diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java index 2164bdded80..3ad9214a29e 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ClientContainer.java @@ -20,14 +20,17 @@ package org.eclipse.jetty.websocket.jsr356; import java.io.IOException; import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; -import java.util.Map; +import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; +import java.util.function.Function; import javax.websocket.ClientEndpoint; import javax.websocket.ClientEndpointConfig; @@ -60,8 +63,6 @@ import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig; import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata; import org.eclipse.jetty.websocket.jsr356.decoders.PrimitiveDecoderMetadataSet; import org.eclipse.jetty.websocket.jsr356.encoders.PrimitiveEncoderMetadataSet; -import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance; -import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEventDriverFactory; import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata; /** @@ -79,11 +80,13 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont private final DecoderFactory decoderFactory; /** Tracking all primitive encoders for the container */ private final EncoderFactory encoderFactory; - /** Tracking for all declared Client endpoints */ - private final Map, EndpointMetadata> endpointClientMetadataCache; + /** Tracking for all open Sessions */ + private Set openSessions = new CopyOnWriteArraySet<>(); /** The jetty websocket client in use for this container */ private WebSocketClient client; - + + private List> annotatedConfigFunctions = new ArrayList<>(); + public ClientContainer() { // This constructor is used with Standalone JSR Client usage. @@ -97,18 +100,18 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont this.scopeDelegate = scope; client = new WebSocketClient(scope, new SslContextFactory(trustAll)); - client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy())); client.setSessionFactory(new JsrSessionFactory(this)); addBean(client); + + annotatedConfigFunctions.add(new ClientEndpointConfigFunction()); - this.endpointClientMetadataCache = new ConcurrentHashMap<>(); this.decoderFactory = new DecoderFactory(this,PrimitiveDecoderMetadataSet.INSTANCE); this.encoderFactory = new EncoderFactory(this,PrimitiveEncoderMetadataSet.INSTANCE); ShutdownThread.register(this); } - - private Session connect(EndpointInstance instance, URI path) throws IOException + + private Session connect(ConfiguredEndpoint instance, URI path) throws IOException { Objects.requireNonNull(instance,"EndpointInstance cannot be null"); Objects.requireNonNull(path,"Path cannot be null"); @@ -161,28 +164,28 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont @Override public Session connectToServer(Class endpointClass, ClientEndpointConfig config, URI path) throws DeploymentException, IOException { - EndpointInstance instance = newClientEndpointInstance(endpointClass,config); + ConfiguredEndpoint instance = newClientEndpointInstance(endpointClass,config); return connect(instance,path); } @Override public Session connectToServer(Class annotatedEndpointClass, URI path) throws DeploymentException, IOException { - EndpointInstance instance = newClientEndpointInstance(annotatedEndpointClass,null); + ConfiguredEndpoint instance = newClientEndpointInstance(annotatedEndpointClass,null); return connect(instance,path); } @Override public Session connectToServer(Endpoint endpoint, ClientEndpointConfig config, URI path) throws DeploymentException, IOException { - EndpointInstance instance = newClientEndpointInstance(endpoint,config); + ConfiguredEndpoint instance = newClientEndpointInstance(endpoint,config); return connect(instance,path); } @Override public Session connectToServer(Object endpoint, URI path) throws DeploymentException, IOException { - EndpointInstance instance = newClientEndpointInstance(endpoint,null); + ConfiguredEndpoint instance = newClientEndpointInstance(endpoint,null); return connect(instance,path); } @@ -339,7 +342,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont return scopeDelegate.getSslContextFactory(); } - private EndpointInstance newClientEndpointInstance(Class endpointClass, ClientEndpointConfig config) + private ConfiguredEndpoint newClientEndpointInstance(Class endpointClass, ClientEndpointConfig config) { try { @@ -351,7 +354,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont } } - public EndpointInstance newClientEndpointInstance(Object endpoint, ClientEndpointConfig config) + public ConfiguredEndpoint newClientEndpointInstance(Object endpoint, ClientEndpointConfig config) { EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass(),config); ClientEndpointConfig cec = config; @@ -366,7 +369,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont cec = new EmptyClientEndpointConfig(); } } - return new EndpointInstance(endpoint,cec,metadata); + return new ConfiguredEndpoint(endpoint,cec,metadata); } @Override diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/EndpointInstance.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ConfiguredEndpoint.java similarity index 70% rename from jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/EndpointInstance.java rename to jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ConfiguredEndpoint.java index 89f683d0b03..2f6ba56bce0 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/EndpointInstance.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/ConfiguredEndpoint.java @@ -16,29 +16,24 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.jsr356.endpoints; +package org.eclipse.jetty.websocket.jsr356; import javax.websocket.EndpointConfig; -import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata; - /** * Associate a JSR Endpoint with its optional {@link EndpointConfig} */ -public class EndpointInstance +public class ConfiguredEndpoint { /** The instance of the Endpoint */ private final Object endpoint; - /** The instance specific configuration for the Endpoint */ + /** The optional instance specific configuration for the Endpoint */ private final EndpointConfig config; - /** The metadata for this endpoint */ - private final EndpointMetadata metadata; - public EndpointInstance(Object endpoint, EndpointConfig config, EndpointMetadata metadata) + public ConfiguredEndpoint(Object endpoint, EndpointConfig config) { this.endpoint = endpoint; this.config = config; - this.metadata = metadata; } public EndpointConfig getConfig() @@ -50,9 +45,4 @@ public class EndpointInstance { return endpoint; } - - public EndpointMetadata getMetadata() - { - return metadata; - } } diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSession.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSession.java index 82db0bd93b2..ec87a96cf79 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSession.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSession.java @@ -19,7 +19,11 @@ package org.eclipse.jetty.websocket.jsr356; import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.lang.reflect.Method; import java.net.URI; +import java.nio.ByteBuffer; import java.security.Principal; import java.util.ArrayList; import java.util.Collections; @@ -30,10 +34,21 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; +import javax.websocket.CloseReason.CloseCode; +import javax.websocket.CloseReason.CloseCodes; +import javax.websocket.Decoder; +import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.Extension; import javax.websocket.MessageHandler; +import javax.websocket.MessageHandler.Partial; +import javax.websocket.MessageHandler.Whole; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; import javax.websocket.RemoteEndpoint.Async; import javax.websocket.RemoteEndpoint.Basic; import javax.websocket.Session; @@ -45,9 +60,19 @@ import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.common.LogicalConnection; import org.eclipse.jetty.websocket.common.WebSocketSession; -import org.eclipse.jetty.websocket.common.events.EventDriver; -import org.eclipse.jetty.websocket.jsr356.endpoints.AbstractJsrEventDriver; +import org.eclipse.jetty.websocket.common.message.MessageSink; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.jsr356.functions.JsrOnCloseFunction; +import org.eclipse.jetty.websocket.jsr356.functions.JsrOnErrorFunction; +import org.eclipse.jetty.websocket.jsr356.functions.JsrOnOpenFunction; +import org.eclipse.jetty.websocket.jsr356.messages.BinaryArrayPartialMessage; +import org.eclipse.jetty.websocket.jsr356.messages.BinaryBufferPartialMessage; +import org.eclipse.jetty.websocket.jsr356.messages.JsrInputStreamMessage; +import org.eclipse.jetty.websocket.jsr356.messages.JsrReaderMessage; +import org.eclipse.jetty.websocket.jsr356.messages.TextPartialMessage; import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata; +import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadataSet; +import org.eclipse.jetty.websocket.jsr356.metadata.EncoderMetadataSet; import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata; import org.eclipse.jetty.websocket.jsr356.metadata.MessageHandlerMetadata; @@ -60,46 +85,252 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess private final ClientContainer container; private final String id; private final EndpointConfig config; - private final EndpointMetadata metadata; private final DecoderFactory decoderFactory; private final EncoderFactory encoderFactory; - /** Factory for MessageHandlers */ - private final MessageHandlerFactory messageHandlerFactory; - /** Array of MessageHandlerWrappers, indexed by {@link MessageType#ordinal()} */ - private final MessageHandlerWrapper wrappers[]; private Set messageHandlerSet; + private List negotiatedExtensions; private Map pathParameters = new HashMap<>(); private JsrAsyncRemote asyncRemote; private JsrBasicRemote basicRemote; - public JsrSession(ClientContainer container, String id, URI requestURI, EventDriver websocket, LogicalConnection connection) + public JsrSession(ClientContainer container, String id, URI requestURI, Object websocket, LogicalConnection connection) { super(container, requestURI, websocket, connection); - if (!(websocket instanceof AbstractJsrEventDriver)) - { - throw new IllegalArgumentException("Cannot use, not a JSR WebSocket: " + websocket); - } - AbstractJsrEventDriver jsr = (AbstractJsrEventDriver)websocket; - this.config = jsr.getConfig(); - this.metadata = jsr.getMetadata(); + this.container = container; - this.id = id; - this.decoderFactory = new DecoderFactory(this,metadata.getDecoders(),container.getDecoderFactory()); - this.encoderFactory = new EncoderFactory(this,metadata.getEncoders(),container.getEncoderFactory()); - this.messageHandlerFactory = new MessageHandlerFactory(); - this.wrappers = new MessageHandlerWrapper[MessageType.values().length]; - this.messageHandlerSet = new HashSet<>(); - } + + ConfiguredEndpoint cendpoint = (ConfiguredEndpoint)websocket; + this.config = cendpoint.getConfig(); + DecoderMetadataSet decoderSet = new DecoderMetadataSet(); + EncoderMetadataSet encoderSet = new EncoderMetadataSet(); + // TODO: figure out how to populare the decoderSet / encoderSet + + this.id = id; + this.decoderFactory = new DecoderFactory(this,decoderSet,container.getDecoderFactory()); + this.encoderFactory = new EncoderFactory(this,encoderSet,container.getEncoderFactory()); + } + + @Override + protected void discoverEndpointFunctions(Object obj) + { + if(obj instanceof ConfiguredEndpoint) + { + throw new IllegalArgumentException("JSR356 Implementation expects a " + ConfiguredEndpoint.class.getName() + " but got: " + obj.getClass().getName()); + } + + ConfiguredEndpoint cendpoint = (ConfiguredEndpoint) obj; + + // Endpoint + Object websocket = cendpoint.getEndpoint(); + + if(websocket instanceof Endpoint) + { + Endpoint endpoint = (Endpoint)websocket; + onOpenFunction = (sess) -> { + endpoint.onOpen(this,config); + return null; + }; + onCloseFunction = (closeinfo) -> { + CloseCode closeCode = CloseCodes.getCloseCode(closeinfo.getStatusCode()); + CloseReason closeReason = new CloseReason(closeCode,closeinfo.getReason()); + endpoint.onClose(this,closeReason); + return null; + }; + onErrorFunction = (cause) -> { + endpoint.onError(this,cause); + return null; + }; + } + + // Annotations + + Class websocketClass = websocket.getClass(); + ClientEndpoint clientEndpoint = websocketClass.getAnnotation(ClientEndpoint.class); + if(clientEndpoint != null) + { + Method onmethod = null; + + // @OnOpen [0..1] + onmethod = ReflectUtils.findAnnotatedMethod(websocketClass,OnOpen.class); + if(onmethod != null) + { + assertNotSet(onOpenFunction,"Open Handler",websocketClass,onmethod); + onOpenFunction = new JsrOnOpenFunction(this,websocket,onmethod); + } + // @OnClose [0..1] + onmethod = ReflectUtils.findAnnotatedMethod(websocketClass,OnClose.class); + if(onmethod != null) + { + assertNotSet(onCloseFunction,"Close Handler",websocketClass,onmethod); + onCloseFunction = new JsrOnCloseFunction(this,websocket,onmethod); + } + // @OnError [0..1] + onmethod = ReflectUtils.findAnnotatedMethod(websocketClass,OnError.class); + if(onmethod != null) + { + assertNotSet(onErrorFunction,"Error Handler",websocketClass,onmethod); + onErrorFunction = new JsrOnErrorFunction(this,websocket,onmethod); + } + // @OnMessage [0..2] + Method onmessages[] = ReflectUtils.findAnnotatedMethods(websocketClass,OnMessage.class); + if(onmessages != null && onmessages.length > 0) + { + for(Method method: onmessages) + { + // Text + // TextStream + // Binary + // BinaryStream + // Pong + } + } + } + } + + @Override + public void addMessageHandler(Class clazz, Partial handler) + { + Objects.requireNonNull(handler, "MessageHandler.Partial cannot be null"); + if (LOG.isDebugEnabled()) + { + LOG.debug("MessageHandler.Partial class: {}",handler.getClass()); + } + + // No decoders for Partial messages per JSR-356 (PFD1 spec) + + if(String.class.isAssignableFrom(clazz)) + { + @SuppressWarnings("unchecked") + Partial strhandler = (Partial)handler; + setMessageAppender(MessageType.TEXT, new TextPartialMessage(strhandler)); + } + else if(ByteBuffer.class.isAssignableFrom(clazz)) + { + @SuppressWarnings("unchecked") + Partial bufhandler = (Partial)handler; + setMessageAppender(MessageType.BINARY, new BinaryBufferPartialMessage(bufhandler)); + } + else if(byte[].class.isAssignableFrom(clazz)) + { + @SuppressWarnings("unchecked") + Partial arrhandler = (Partial)handler; + setMessageAppender(MessageType.BINARY, new BinaryArrayPartialMessage(arrhandler)); + } + else + { + StringBuilder err = new StringBuilder(); + err.append("Unsupported class type for MessageHandler.Partial (only supports , , or ): "); + err.append(clazz.getName()); + throw new IllegalArgumentException(err.toString()); + } + } + + @Override + public void addMessageHandler(Class clazz, Whole handler) + { + Objects.requireNonNull(handler, "MessageHandler.Whole cannot be null"); + if (LOG.isDebugEnabled()) + { + LOG.debug("MessageHandler.Whole class: {}",handler.getClass()); + } + + // Determine Decoder + DecoderFactory.Wrapper decoderWrapper = decoderFactory.getWrapperFor(clazz); + if (decoderWrapper == null) + { + StringBuilder err = new StringBuilder(); + err.append("Unable to find decoder for type <"); + err.append(clazz.getName()); + err.append("> used in <"); + err.append(handler.getClass().getName()); + err.append(">"); + throw new IllegalStateException(err.toString()); + } + + if(decoderWrapper.getMetadata().isStreamed()) + { + // Streaming + if(InputStream.class.isAssignableFrom(clazz)) + { + // Whole Text Streaming + @SuppressWarnings("unchecked") + Whole streamhandler = (Whole)handler; + Decoder.BinaryStream streamdecoder = (Decoder.BinaryStream)decoderWrapper.getDecoder(); + setMessageAppender(MessageType.TEXT,new JsrInputStreamMessage(streamhandler, streamdecoder, websocket, getExecutor())); + } + else if(Reader.class.isAssignableFrom(clazz)) + { + // Whole Reader Streaming + @SuppressWarnings("unchecked") + Whole streamhandler = (Whole)handler; + Decoder.TextStream streamdecoder = (Decoder.TextStream)decoderWrapper.getDecoder(); + setMessageAppender(MessageType.BINARY,new JsrReaderMessage(streamhandler, streamdecoder, websocket, getExecutor())); + } + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void addMessageHandler(MessageHandler handler) throws IllegalStateException + { + Objects.requireNonNull(handler, "MessageHandler cannot be null"); + Class handlerClass = handler.getClass(); + + if (MessageHandler.Whole.class.isAssignableFrom(handlerClass)) + { + Class onMessageClass = ReflectUtils.findGenericClassFor(handlerClass,MessageHandler.Whole.class); + addMessageHandler(onMessageClass,(Whole)handler); + } + + if (MessageHandler.Partial.class.isAssignableFrom(handlerClass)) + { + Class onMessageClass = ReflectUtils.findGenericClassFor(handlerClass,MessageHandler.Partial.class); + addMessageHandler(onMessageClass,(Partial)handler); + } + } + + private void setMessageAppender(MessageType type, MessageSink appender) + { + synchronized(messageAppenders) + { + MessageSink other = messageAppenders[type.ordinal()]; + if (other != null) + { + StringBuilder err = new StringBuilder(); + err.append("Encountered duplicate MessageHandler handling for "); + err.append(type.name()).append(" type messages. "); + err.append(wrapper.getMetadata().getObjectType().getName()); + err.append(">, ").append(metadata.getHandlerClass().getName()); + err.append("<"); + err.append(metadata.getMessageClass().getName()); + err.append("> and "); + err.append(other.getMetadata().getHandlerClass().getName()); + err.append("<"); + err.append(other.getMetadata().getMessageClass().getName()); + err.append("> both implement this message type"); + throw new IllegalStateException(err.toString()); + } + } + } + + + private void addMessageAppender(Class clazz, MessageHandler handler) + { + synchronized(messageAppenders) + { + // TODO Auto-generated method stub + } + } + + private void addMessageHandlerWrapper(Class msgClazz, MessageHandler handler) throws IllegalStateException { Objects.requireNonNull(handler, "MessageHandler cannot be null"); synchronized (wrappers) { - for (MessageHandlerMetadata metadata : messageHandlerFactory.getMetadata(handler.getClass())) + for (MessageHandlerMetadata metadata : messageHandlerFactory.getMetadata(msgClazz)) { DecoderFactory.Wrapper wrapper = decoderFactory.getWrapperFor(metadata.getMessageClass()); if (wrapper == null) @@ -141,7 +372,7 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess updateMessageHandlerSet(); } } - + @Override public void close(CloseReason closeReason) throws IOException { @@ -218,11 +449,6 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess return getPolicy().getMaxTextMessageSize(); } - public MessageHandlerFactory getMessageHandlerFactory() - { - return messageHandlerFactory; - } - @Override public Set getMessageHandlers() { @@ -230,14 +456,6 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess return new HashSet(messageHandlerSet); } - public MessageHandlerWrapper getMessageHandlerWrapper(MessageType type) - { - synchronized (wrappers) - { - return wrappers[type.ordinal()]; - } - } - @Override public List getNegotiatedExtensions() { @@ -329,6 +547,12 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess } } } + + public MessageSink newMessageAppenderFor(MessageType text) + { + // TODO Auto-generated method stub + return null; + } @Override public void setMaxBinaryMessageBufferSize(int length) @@ -380,4 +604,5 @@ public class JsrSession extends WebSocketSession implements javax.websocket.Sess // JSR 356 specification mandates default batch mode to be off. return BatchMode.OFF; } + } diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionFactory.java index 15cde46ebbb..1b79a856798 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionFactory.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionFactory.java @@ -20,36 +20,28 @@ package org.eclipse.jetty.websocket.jsr356; import java.net.URI; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.common.LogicalConnection; import org.eclipse.jetty.websocket.common.SessionFactory; import org.eclipse.jetty.websocket.common.WebSocketSession; -import org.eclipse.jetty.websocket.common.events.EventDriver; -import org.eclipse.jetty.websocket.jsr356.endpoints.AbstractJsrEventDriver; public class JsrSessionFactory implements SessionFactory { - private static final Logger LOG = Log.getLogger(JsrSessionFactory.class); private final ClientContainer container; public JsrSessionFactory(ClientContainer container) { - if(LOG.isDebugEnabled()) { - LOG.debug("Container: {}", container); - } this.container = container; } @Override - public WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection) + public WebSocketSession createSession(URI requestURI, Object websocket, LogicalConnection connection) { return new JsrSession(container,connection.getId(),requestURI,websocket,connection); } @Override - public boolean supports(EventDriver websocket) + public boolean supports(Object websocket) { - return (websocket instanceof AbstractJsrEventDriver); + return (websocket instanceof ConfiguredEndpoint); } } diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactory.java index 08ba2e14684..b56c04190d9 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactory.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/MessageHandlerFactory.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.websocket.MessageHandler; +import javax.websocket.MessageHandler.Whole; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -37,7 +38,7 @@ public class MessageHandlerFactory { private static final Logger LOG = Log.getLogger(MessageHandlerFactory.class); /** Registered MessageHandlers at this level */ - private Map, List> registered; + private Map> registered; public MessageHandlerFactory() { @@ -58,6 +59,21 @@ public class MessageHandlerFactory return register(handler); } + + public List getMetadata(Class clazz, Whole handler) throws IllegalStateException + { + if (LOG.isDebugEnabled()) + { + LOG.debug("getMetadata({},{})",clazz,handler); + } + List ret = registered.get(handler); + if (ret != null) + { + return ret; + } + + return register(handler); + } public List register(Class handler) { diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointScanner.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointScanner.java index 4686cc40739..6d91f38668e 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointScanner.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/AnnotatedEndpointScanner.java @@ -31,8 +31,8 @@ import javax.websocket.OnOpen; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.common.events.annotated.AbstractMethodAnnotationScanner; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; import org.eclipse.jetty.websocket.common.util.ReflectUtils; public class AnnotatedEndpointScanner extends AbstractMethodAnnotationScanner> diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrParamId.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrParamId.java index 674fe785701..967ab32ee92 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrParamId.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/IJsrParamId.java @@ -18,7 +18,7 @@ package org.eclipse.jetty.websocket.jsr356.annotations; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; /** * JSR-356 Parameter Identification processing. diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrCallable.java index 59e0abff4af..e2eab61ff1e 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrCallable.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrCallable.java @@ -26,8 +26,8 @@ import javax.websocket.DecodeException; import javax.websocket.Decoder; import org.eclipse.jetty.websocket.api.InvalidWebSocketException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.common.events.annotated.CallableMethod; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; import org.eclipse.jetty.websocket.common.util.ReflectUtils; import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBase.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBase.java index a1fcdccf917..6f782b7d928 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBase.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBase.java @@ -21,7 +21,7 @@ package org.eclipse.jetty.websocket.jsr356.annotations; import javax.websocket.EndpointConfig; import javax.websocket.Session; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; /** diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.java index 4655f226377..e00d5fd0ed1 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdBinary.java @@ -23,7 +23,7 @@ import java.nio.ByteBuffer; import javax.websocket.OnMessage; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; /** diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoder.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoder.java index 83eea2f4552..a99d5eed4e1 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoder.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdDecoder.java @@ -20,7 +20,7 @@ package org.eclipse.jetty.websocket.jsr356.annotations; import javax.websocket.OnMessage; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; import org.eclipse.jetty.websocket.jsr356.metadata.DecoderMetadata; diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnClose.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnClose.java index 77a1562614a..9a029351544 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnClose.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnClose.java @@ -21,7 +21,7 @@ package org.eclipse.jetty.websocket.jsr356.annotations; import javax.websocket.CloseReason; import javax.websocket.OnClose; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; /** diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnError.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnError.java index 98b5d520fcc..b0a5c14c239 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnError.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnError.java @@ -20,7 +20,7 @@ package org.eclipse.jetty.websocket.jsr356.annotations; import javax.websocket.OnError; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; /** diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnMessage.java index b3673384b33..e69d163d853 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnMessage.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnMessage.java @@ -18,7 +18,7 @@ package org.eclipse.jetty.websocket.jsr356.annotations; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; public abstract class JsrParamIdOnMessage extends JsrParamIdBase implements IJsrParamId { diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnOpen.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnOpen.java index ea1f10b5e17..9b05e17a970 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnOpen.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdOnOpen.java @@ -21,7 +21,7 @@ package org.eclipse.jetty.websocket.jsr356.annotations; import javax.websocket.EndpointConfig; import javax.websocket.OnOpen; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; /** diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdPong.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdPong.java index f1a5dd62e8c..4ad53bc5878 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdPong.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdPong.java @@ -20,7 +20,7 @@ package org.eclipse.jetty.websocket.jsr356.annotations; import javax.websocket.PongMessage; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; public class JsrParamIdPong extends JsrParamIdOnMessage implements IJsrParamId diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdText.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdText.java index 8513c4af1cb..c952be5928b 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdText.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/JsrParamIdText.java @@ -22,7 +22,7 @@ import java.io.Reader; import javax.websocket.OnMessage; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role; /** diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageCallable.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageCallable.java index c077b07eff9..dba7cec315d 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageCallable.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/annotations/OnMessageCallable.java @@ -23,7 +23,7 @@ import java.lang.reflect.Method; import javax.websocket.Decoder; import javax.websocket.Encoder; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.common.util.ReflectUtils; import org.eclipse.jetty.websocket.jsr356.EncoderFactory; import org.eclipse.jetty.websocket.jsr356.JsrSession; diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/JsrClientEndpointImpl.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/JsrClientEndpointImpl.java index 662e5e8f4dd..19c600bb7fe 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/JsrClientEndpointImpl.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/client/JsrClientEndpointImpl.java @@ -26,9 +26,9 @@ import javax.websocket.OnMessage; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.common.events.EventDriverImpl; +import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint; import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents; import org.eclipse.jetty.websocket.jsr356.annotations.OnMessageCallable; -import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance; import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver; /** @@ -39,12 +39,12 @@ public class JsrClientEndpointImpl implements EventDriverImpl @Override public EventDriver create(Object websocket, WebSocketPolicy policy) throws DeploymentException { - if (!(websocket instanceof EndpointInstance)) + if (!(websocket instanceof ConfiguredEndpoint)) { - throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName())); + throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),ConfiguredEndpoint.class.getName())); } - EndpointInstance ei = (EndpointInstance)websocket; + ConfiguredEndpoint ei = (ConfiguredEndpoint)websocket; AnnotatedClientEndpointMetadata metadata = (AnnotatedClientEndpointMetadata)ei.getMetadata(); JsrEvents events = new JsrEvents<>(metadata); @@ -88,12 +88,12 @@ public class JsrClientEndpointImpl implements EventDriverImpl @Override public boolean supports(Object websocket) { - if (!(websocket instanceof EndpointInstance)) + if (!(websocket instanceof ConfiguredEndpoint)) { return false; } - EndpointInstance ei = (EndpointInstance)websocket; + ConfiguredEndpoint ei = (ConfiguredEndpoint)websocket; Object endpoint = ei.getEndpoint(); ClientEndpoint anno = endpoint.getClass().getAnnotation(ClientEndpoint.class); diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java index af5867c03dd..19f8e5d5d4d 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.websocket.jsr356.endpoints; import java.util.Map; +import java.util.concurrent.Executor; import javax.websocket.CloseReason; import javax.websocket.CloseReason.CloseCode; @@ -31,21 +32,24 @@ 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.AbstractEventDriver; +import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint; import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata; public abstract class AbstractJsrEventDriver extends AbstractEventDriver { protected final EndpointMetadata metadata; + protected final Executor executor; protected final EndpointConfig config; protected JsrSession jsrsession; private boolean hasCloseBeenCalled = false; - public AbstractJsrEventDriver(WebSocketPolicy policy, EndpointInstance endpointInstance) + public AbstractJsrEventDriver(WebSocketPolicy policy, ConfiguredEndpoint endpointInstance, Executor executor) { super(policy,endpointInstance.getEndpoint()); this.config = endpointInstance.getConfig(); this.metadata = endpointInstance.getMetadata(); + this.executor = executor; } public EndpointConfig getConfig() @@ -111,4 +115,9 @@ public abstract class AbstractJsrEventDriver extends AbstractEventDriver } public abstract void setPathParameters(Map pathParameters); + + public void dispatch(Runnable runnable) + { + executor.execute(runnable); + } } 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 index 6de3c66b20a..5e4e8fc6cdf 100644 --- 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 @@ -23,6 +23,7 @@ import java.io.InputStream; import java.io.Reader; import java.nio.ByteBuffer; import java.util.Map; +import java.util.concurrent.Executor; import javax.websocket.CloseReason; import javax.websocket.DecodeException; @@ -35,8 +36,9 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.common.message.MessageInputStream; import org.eclipse.jetty.websocket.common.message.MessageReader; -import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage; -import org.eclipse.jetty.websocket.common.message.SimpleTextMessage; +import org.eclipse.jetty.websocket.common.message.ByteArrayMessageSink; +import org.eclipse.jetty.websocket.common.message.StringMessageSink; +import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint; import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents; import org.eclipse.jetty.websocket.jsr356.messages.BinaryPartialOnMessage; @@ -50,9 +52,9 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver private static final Logger LOG = Log.getLogger(JsrAnnotatedEventDriver.class); private final JsrEvents events; - public JsrAnnotatedEventDriver(WebSocketPolicy policy, EndpointInstance endpointInstance, JsrEvents events) + public JsrAnnotatedEventDriver(WebSocketPolicy policy, ConfiguredEndpoint endpointInstance, JsrEvents events, Executor executor) { - super(policy,endpointInstance); + super(policy,endpointInstance,executor); this.events = events; } @@ -97,7 +99,7 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver { LOG.debug("Whole Binary Message"); } - activeMessage = new SimpleBinaryMessage(this); + activeMessage = new ByteArrayMessageSink(this); } } } @@ -174,6 +176,12 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver onFatalError(e); } } + + @Override + public void onObject(Object obj) + { + // TODO Auto-generated method stub + } @Override protected void onClose(CloseReason closereason) @@ -311,7 +319,7 @@ public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver { LOG.debug("Whole Text Message"); } - activeMessage = new SimpleTextMessage(this); + activeMessage = new StringMessageSink(this); } } } 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 index 86ed79da3f9..9216ec2c3f2 100644 --- 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 @@ -23,28 +23,20 @@ import java.io.InputStream; import java.io.Reader; import java.nio.ByteBuffer; import java.util.Map; +import java.util.concurrent.Executor; import javax.websocket.CloseReason; import javax.websocket.Endpoint; -import javax.websocket.MessageHandler; -import javax.websocket.MessageHandler.Whole; -import javax.websocket.PongMessage; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.Frame; -import org.eclipse.jetty.websocket.common.message.MessageInputStream; -import org.eclipse.jetty.websocket.common.message.MessageReader; -import org.eclipse.jetty.websocket.jsr356.JsrPongMessage; +import org.eclipse.jetty.websocket.common.message.MessageSink; +import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint; import org.eclipse.jetty.websocket.jsr356.JsrSession; -import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper; import org.eclipse.jetty.websocket.jsr356.MessageType; -import org.eclipse.jetty.websocket.jsr356.messages.BinaryPartialMessage; -import org.eclipse.jetty.websocket.jsr356.messages.BinaryWholeMessage; -import org.eclipse.jetty.websocket.jsr356.messages.TextPartialMessage; -import org.eclipse.jetty.websocket.jsr356.messages.TextWholeMessage; /** * EventDriver for websocket that extend from {@link javax.websocket.Endpoint} @@ -56,9 +48,9 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver private final Endpoint endpoint; private Map pathParameters; - public JsrEndpointEventDriver(WebSocketPolicy policy, EndpointInstance endpointInstance) + public JsrEndpointEventDriver(WebSocketPolicy policy, Executor executor, ConfiguredEndpoint endpointInstance) { - super(policy,endpointInstance); + super(policy,executor,endpointInstance); this.endpoint = (Endpoint)endpointInstance.getEndpoint(); } @@ -73,8 +65,8 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver { if (activeMessage == null) { - final MessageHandlerWrapper wrapper = jsrsession.getMessageHandlerWrapper(MessageType.BINARY); - if (wrapper == null) + activeMessage = jsrsession.newMessageAppenderFor(MessageType.BINARY); + if (activeMessage == null) { if (LOG.isDebugEnabled()) { @@ -82,6 +74,8 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver } return; } + + /* if (wrapper.wantsPartialMessages()) { activeMessage = new BinaryPartialMessage(wrapper); @@ -105,6 +99,7 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver { activeMessage = new BinaryWholeMessage(this,wrapper); } + */ } activeMessage.appendFrame(buffer,fin); @@ -121,7 +116,13 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver { /* Ignored, handled by BinaryWholeMessage */ } - + + @Override + public void onObject(Object o) + { + // TODO: deliver to message handler + } + @Override protected void onClose(CloseReason closereason) { @@ -176,8 +177,8 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver { if (activeMessage == null) { - final MessageHandlerWrapper wrapper = jsrsession.getMessageHandlerWrapper(MessageType.TEXT); - if (wrapper == null) + activeMessage = jsrsession.newMessageAppenderFor(MessageType.TEXT); + if (activeMessage == null) { if (LOG.isDebugEnabled()) { @@ -185,30 +186,31 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver } return; } - if (wrapper.wantsPartialMessages()) - { - activeMessage = new TextPartialMessage(wrapper); - } - else if (wrapper.wantsStreams()) - { - final MessageReader stream = new MessageReader(new MessageInputStream()); - activeMessage = stream; - - dispatch(new Runnable() - { - @SuppressWarnings("unchecked") - @Override - public void run() - { - MessageHandler.Whole handler = (Whole)wrapper.getHandler(); - handler.onMessage(stream); - } - }); - } - else - { - activeMessage = new TextWholeMessage(this,wrapper); - } + +// if (wrapper.wantsPartialMessages()) +// { +// activeMessage = new TextPartialMessage(wrapper); +// } +// else if (wrapper.wantsStreams()) +// { +// final MessageReader stream = new MessageReader(new MessageInputStream()); +// activeMessage = stream; +// +// dispatch(new Runnable() +// { +// @SuppressWarnings("unchecked") +// @Override +// public void run() +// { +// MessageHandler.Whole handler = (Whole)wrapper.getHandler(); +// handler.onMessage(stream); +// } +// }); +// } +// else +// { +// activeMessage = new TextWholeMessage(this,wrapper); +// } } activeMessage.appendFrame(buffer,fin); @@ -240,8 +242,8 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver private void onPongMessage(ByteBuffer buffer) { - final MessageHandlerWrapper wrapper = jsrsession.getMessageHandlerWrapper(MessageType.PONG); - if (wrapper == null) + MessageSink appender = jsrsession.newMessageAppenderFor(MessageType.PONG); + if (appender == null) { if (LOG.isDebugEnabled()) { @@ -263,9 +265,14 @@ public class JsrEndpointEventDriver extends AbstractJsrEventDriver BufferUtil.flipToFlush(pongBuf,0); } - @SuppressWarnings("unchecked") - Whole pongHandler = (Whole)wrapper.getHandler(); - pongHandler.onMessage(new JsrPongMessage(pongBuf)); + try + { + appender.appendFrame(pongBuf,true); + } + catch (IOException e) + { + LOG.debug(e); + } } @Override 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 index 4a2dc71b9ec..fcfbbe3586c 100644 --- 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 @@ -21,18 +21,19 @@ 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; +import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint; public class JsrEndpointImpl implements EventDriverImpl { @Override public EventDriver create(Object websocket, WebSocketPolicy policy) { - if (!(websocket instanceof EndpointInstance)) + if (!(websocket instanceof ConfiguredEndpoint)) { - throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName())); + throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),ConfiguredEndpoint.class.getName())); } - return new JsrEndpointEventDriver(policy,(EndpointInstance)websocket); + return new JsrEndpointEventDriver(policy,(ConfiguredEndpoint)websocket); } @Override @@ -44,12 +45,12 @@ public class JsrEndpointImpl implements EventDriverImpl @Override public boolean supports(Object websocket) { - if (!(websocket instanceof EndpointInstance)) + if (!(websocket instanceof ConfiguredEndpoint)) { return false; } - EndpointInstance ei = (EndpointInstance)websocket; + ConfiguredEndpoint ei = (ConfiguredEndpoint)websocket; Object endpoint = ei.getEndpoint(); return (endpoint instanceof javax.websocket.Endpoint); diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEventDriverFactory.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEventDriverFactory.java index 0550e7e0051..42e01a24397 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEventDriverFactory.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEventDriverFactory.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.jsr356.endpoints; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.common.events.EventDriverFactory; +import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint; import org.eclipse.jetty.websocket.jsr356.client.JsrClientEndpointImpl; public class JsrEventDriverFactory extends EventDriverFactory @@ -41,9 +42,9 @@ public class JsrEventDriverFactory extends EventDriverFactory @Override protected String getClassName(Object websocket) { - if (websocket instanceof EndpointInstance) + if (websocket instanceof ConfiguredEndpoint) { - EndpointInstance ce = (EndpointInstance)websocket; + ConfiguredEndpoint ce = (ConfiguredEndpoint)websocket; return ce.getEndpoint().getClass().getName(); } diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnByteArrayFunction.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnByteArrayFunction.java new file mode 100644 index 00000000000..0362addd6b2 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnByteArrayFunction.java @@ -0,0 +1,101 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import javax.websocket.OnMessage; +import javax.websocket.Session; + +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * javax.websocket {@link OnMessage} method {@link Function} for BINARY/byte[] types + */ +public class JsrOnByteArrayFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int BUFFER = 2; + private static final int OFFSET = 3; + private static final int LENGTH = 4; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(byte[].class).indexedAs(BUFFER)); + ARGBUILDER.addSignature(new ExactSignature(byte[].class,int.class,int.class).indexedAs(BUFFER,OFFSET,LENGTH)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,byte[].class).indexedAs(SESSION,BUFFER)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,byte[].class,int.class,int.class).indexedAs(SESSION,BUFFER,OFFSET,LENGTH)); + } + + public static DynamicArgs.Builder getDynamicArgsBuilder() + { + return ARGBUILDER; + } + + public static boolean hasMatchingSignature(Method method) + { + return ARGBUILDER.hasMatchingSignature(method); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public JsrOnByteArrayFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnMessage.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnMessage.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,BUFFER,OFFSET,LENGTH); + } + + @Override + public Void apply(byte[] bin) + { + Object args[] = this.callable.toArgs(session,bin,0,bin.length); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnByteBufferFunction.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnByteBufferFunction.java new file mode 100644 index 00000000000..03514eee1e7 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnByteBufferFunction.java @@ -0,0 +1,98 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.function.Function; + +import javax.websocket.OnMessage; +import javax.websocket.Session; + +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * javax.websocket {@link OnMessage} method {@link Function} for BINARY/{@link ByteBuffer} types + */ +public class JsrOnByteBufferFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int BUFFER = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(ByteBuffer.class).indexedAs(BUFFER)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,ByteBuffer.class).indexedAs(SESSION,BUFFER)); + } + + public static DynamicArgs.Builder getDynamicArgsBuilder() + { + return ARGBUILDER; + } + + public static boolean hasMatchingSignature(Method method) + { + return ARGBUILDER.hasMatchingSignature(method); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public JsrOnByteBufferFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnMessage.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnMessage.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,BUFFER); + } + + @Override + public Void apply(ByteBuffer bin) + { + Object args[] = this.callable.toArgs(session,bin); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnCloseFunction.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnCloseFunction.java new file mode 100644 index 00000000000..1b4ea0f0559 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnCloseFunction.java @@ -0,0 +1,91 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import javax.websocket.OnClose; +import javax.websocket.Session; + +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * javax.websocket {@link OnClose} method {@link Function} + */ +public class JsrOnCloseFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int STATUS_CODE = 2; + private static final int REASON = 3; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature().indexedAs()); + ARGBUILDER.addSignature(new ExactSignature(Session.class).indexedAs(SESSION)); + ARGBUILDER.addSignature(new ExactSignature(int.class,String.class).indexedAs(STATUS_CODE,REASON)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,int.class,String.class).indexedAs(SESSION,STATUS_CODE,REASON)); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public JsrOnCloseFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnClose.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnClose.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,STATUS_CODE,REASON); + } + + @Override + public Void apply(CloseInfo closeinfo) + { + Object args[] = this.callable.toArgs(session,closeinfo.getStatusCode(),closeinfo.getReason()); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call close method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnErrorFunction.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnErrorFunction.java new file mode 100644 index 00000000000..3da38fc0080 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnErrorFunction.java @@ -0,0 +1,86 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import javax.websocket.OnError; +import javax.websocket.Session; + +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; + +/** + * javax.websocket {@link OnError} method {@link Function} + */ +public class JsrOnErrorFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int CAUSE = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addParams(Throwable.class).indexedAs(CAUSE); + ARGBUILDER.addParams(Session.class,Throwable.class).indexedAs(SESSION,CAUSE); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public JsrOnErrorFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnError.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnError.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,CAUSE); + } + + @Override + public Void apply(Throwable cause) + { + Object args[] = this.callable.toArgs(session,cause); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call error method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnInputStreamFunction.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnInputStreamFunction.java new file mode 100644 index 00000000000..df936ac27cf --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnInputStreamFunction.java @@ -0,0 +1,99 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import javax.websocket.OnMessage; +import javax.websocket.Session; + +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * javax.websocket {@link OnMessage} method {@link Function} for BINARY/{@link InputStream} streaming + * types + */ +public class JsrOnInputStreamFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int STREAM = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(InputStream.class).indexedAs(STREAM)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,InputStream.class).indexedAs(SESSION,STREAM)); + } + + public static DynamicArgs.Builder getDynamicArgsBuilder() + { + return ARGBUILDER; + } + + public static boolean hasMatchingSignature(Method method) + { + return ARGBUILDER.hasMatchingSignature(method); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public JsrOnInputStreamFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnMessage.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnMessage.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,STREAM); + } + + @Override + public Void apply(InputStream stream) + { + Object args[] = this.callable.toArgs(session,stream); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnOpenFunction.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnOpenFunction.java new file mode 100644 index 00000000000..9253802b2a6 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnOpenFunction.java @@ -0,0 +1,87 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import javax.websocket.OnOpen; +import javax.websocket.Session; + +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * javax.websocket {@link OnOpen} method {@link Function} + */ +public class JsrOnOpenFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature().indexedAs()); + ARGBUILDER.addSignature(new ExactSignature(Session.class).indexedAs(SESSION)); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public JsrOnOpenFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnOpen.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnOpen.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION); + + } + + @Override + public Void apply(org.eclipse.jetty.websocket.api.Session sess) + { + Object args[] = this.callable.toArgs(this.session); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnReaderFunction.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnReaderFunction.java new file mode 100644 index 00000000000..ddc23942b9c --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnReaderFunction.java @@ -0,0 +1,98 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.io.Reader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import javax.websocket.OnMessage; +import javax.websocket.Session; + +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; + +/** + * javax.websocket {@link OnMessage} method {@link Function} for TEXT/{@link Reader} streaming types + */ +public class JsrOnReaderFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int STREAM = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(Reader.class).indexedAs(STREAM)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,Reader.class).indexedAs(SESSION,STREAM)); + } + + public static DynamicArgs.Builder getDynamicArgsBuilder() + { + return ARGBUILDER; + } + + public static boolean hasMatchingSignature(Method method) + { + return ARGBUILDER.hasMatchingSignature(method); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public JsrOnReaderFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnMessage.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnMessage.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,STREAM); + } + + @Override + public Void apply(Reader stream) + { + Object args[] = this.callable.toArgs(session,stream); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnTextFunction.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnTextFunction.java new file mode 100644 index 00000000000..5da8d5d1bdc --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/functions/JsrOnTextFunction.java @@ -0,0 +1,97 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import javax.websocket.OnMessage; +import javax.websocket.Session; + +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * javax.websocket {@link OnMessage} method {@link Function} for TEXT/{@link String} types + */ +public class JsrOnTextFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int TEXT = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(String.class).indexedAs(TEXT)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,String.class).indexedAs(SESSION,TEXT)); + } + + public static DynamicArgs.Builder getDynamicArgsBuilder() + { + return ARGBUILDER; + } + + public static boolean hasMatchingSignature(Method method) + { + return ARGBUILDER.hasMatchingSignature(method); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public JsrOnTextFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnMessage.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnMessage.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,TEXT); + } + + @Override + public Void apply(String text) + { + Object args[] = this.callable.toArgs(session,text); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleBinaryMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryArrayPartialMessage.java similarity index 50% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleBinaryMessage.java rename to jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryArrayPartialMessage.java index 4b00b128acd..146ffdb81f7 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleBinaryMessage.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryArrayPartialMessage.java @@ -16,55 +16,37 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.message; +package org.eclipse.jetty.websocket.jsr356.messages; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import javax.websocket.MessageHandler; + import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.websocket.common.events.EventDriver; +import org.eclipse.jetty.websocket.common.message.MessageSink; -public class SimpleBinaryMessage implements MessageAppender +/** + * Partial BINARY MessageAppender for MessageHandler.Partial<byte[]> interface + */ +public class BinaryArrayPartialMessage implements MessageSink { - private static final int BUFFER_SIZE = 65535; - private final EventDriver onEvent; - protected final ByteArrayOutputStream out; - private int size; - protected boolean finished; + private final MessageHandler.Partial partialHandler; - public SimpleBinaryMessage(EventDriver onEvent) + public BinaryArrayPartialMessage(MessageHandler.Partial handler) { - this.onEvent = onEvent; - this.out = new ByteArrayOutputStream(BUFFER_SIZE); - finished = false; + this.partialHandler = handler; } @Override public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException { - if (finished) - { - throw new IOException("Cannot append to finished buffer"); - } - - if (payload == null) - { - // empty payload is valid - return; - } - - onEvent.getPolicy().assertValidBinaryMessageSize(size + payload.remaining()); - size += payload.remaining(); - - BufferUtil.writeTo(payload,out); + partialHandler.onMessage(BufferUtil.toArray(payload),isLast); } @Override public void messageComplete() { - finished = true; - byte data[] = out.toByteArray(); - onEvent.onBinaryMessage(data); + /* nothing to do here */ } } diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryBufferPartialMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryBufferPartialMessage.java new file mode 100644 index 00000000000..5b3598b3f10 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryBufferPartialMessage.java @@ -0,0 +1,52 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.messages; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import javax.websocket.MessageHandler; + +import org.eclipse.jetty.websocket.common.message.MessageSink; + +/** + * Partial BINARY MessageAppender for MessageHandler.Partial<ByteBuffer> interface + */ +public class BinaryBufferPartialMessage implements MessageSink +{ + private final MessageHandler.Partial partialHandler; + + public BinaryBufferPartialMessage(MessageHandler.Partial handler) + { + this.partialHandler = handler; + } + + @Override + public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException + { + // No decoders for Partial messages per JSR-356 (PFD1 spec) + partialHandler.onMessage(payload.slice(),isLast); + } + + @Override + public void messageComplete() + { + /* nothing to do here */ + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialMessage.java deleted file mode 100644 index 2dd077e5574..00000000000 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialMessage.java +++ /dev/null @@ -1,82 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.messages; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import javax.websocket.MessageHandler; -import javax.websocket.MessageHandler.Partial; - -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.websocket.common.message.MessageAppender; -import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper; - -/** - * Partial BINARY MessageAppender for MessageHandler.Partial interface - */ -public class BinaryPartialMessage implements MessageAppender -{ - private final MessageHandlerWrapper msgWrapper; - private final MessageHandler.Partial partialHandler; - - @SuppressWarnings("unchecked") - public BinaryPartialMessage(MessageHandlerWrapper wrapper) - { - this.msgWrapper = wrapper; - this.partialHandler = (Partial)wrapper.getHandler(); - } - - @Override - public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException - { - // No decoders for Partial messages per JSR-356 (PFD1 spec) - - // Supported Partial<> Type #1: ByteBuffer - if (msgWrapper.isMessageType(ByteBuffer.class)) - { - partialHandler.onMessage(payload==null?BufferUtil.EMPTY_BUFFER: - payload.slice(),isLast); - return; - } - - // Supported Partial<> Type #2: byte[] - if (msgWrapper.isMessageType(byte[].class)) - { - partialHandler.onMessage(payload==null?new byte[0]: - BufferUtil.toArray(payload),isLast); - return; - } - - StringBuilder err = new StringBuilder(); - err.append(msgWrapper.getHandler().getClass()); - err.append(" does not implement an expected "); - err.append(MessageHandler.Partial.class.getName()); - err.append(" of type "); - err.append(ByteBuffer.class.getName()); - err.append(" or byte[]"); - throw new IllegalStateException(err.toString()); - } - - @Override - public void messageComplete() - { - /* nothing to do here */ - } -} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialOnMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialOnMessage.java index ed2d4258193..358d6c08b05 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialOnMessage.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryPartialOnMessage.java @@ -24,13 +24,13 @@ import java.nio.ByteBuffer; import javax.websocket.OnMessage; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.websocket.common.message.MessageAppender; +import org.eclipse.jetty.websocket.common.message.MessageSink; import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver; /** * Partial BINARY MessageAppender for @{@link OnMessage} annotated methods */ -public class BinaryPartialOnMessage implements MessageAppender +public class BinaryPartialOnMessage implements MessageSink { private final JsrAnnotatedEventDriver driver; private boolean finished; diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryWholeMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryWholeMessage.java index 2c5f4f26ece..e3d030b5e97 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryWholeMessage.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/BinaryWholeMessage.java @@ -27,11 +27,11 @@ import javax.websocket.MessageHandler.Whole; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.websocket.api.WebSocketException; import org.eclipse.jetty.websocket.common.events.EventDriver; -import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage; +import org.eclipse.jetty.websocket.common.message.ByteArrayMessageSink; import org.eclipse.jetty.websocket.jsr356.DecoderFactory; import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper; -public class BinaryWholeMessage extends SimpleBinaryMessage +public class BinaryWholeMessage extends ByteArrayMessageSink { private final MessageHandlerWrapper msgWrapper; private final MessageHandler.Whole wholeHandler; diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/JsrInputStreamMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/JsrInputStreamMessage.java new file mode 100644 index 00000000000..255363c7a3c --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/JsrInputStreamMessage.java @@ -0,0 +1,90 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.messages; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.Executor; + +import javax.websocket.Decoder; +import javax.websocket.Decoder.BinaryStream; + +import org.eclipse.jetty.websocket.common.events.EventDriver; +import org.eclipse.jetty.websocket.common.message.MessageSink; +import org.eclipse.jetty.websocket.common.message.MessageInputStream; + +/** + * Handling of InputStreams (Binary messages) via javax.websocket + */ +public class JsrInputStreamMessage implements MessageSink +{ + private final EventDriver events; + private final Decoder.BinaryStream decoder; + private final Executor executor; + private MessageInputStream stream = null; + + public JsrInputStreamMessage(EventDriver events, BinaryStream decoder, Executor executor) + { + this.decoder = decoder; + this.events = events; + this.executor = executor; + } + + @Override + public void appendFrame(ByteBuffer framePayload, boolean fin) throws IOException + { + boolean first = (stream == null); + + stream = new MessageInputStream(); + stream.appendFrame(framePayload,fin); + if (first) + { + executor.execute(new Runnable() + { + @Override + public void run() + { + try + { + if (decoder != null) + { + Object o = decoder.decode(stream); + events.onObject(o); + } + else + { + events.onInputStream(stream); + } + } + catch (Throwable t) + { + events.onError(t); + } + } + }); + } + } + + @Override + public void messageComplete() + { + stream.messageComplete(); + stream = null; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/JsrReaderMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/JsrReaderMessage.java new file mode 100644 index 00000000000..2eb19c8665a --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/JsrReaderMessage.java @@ -0,0 +1,88 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.messages; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.Executor; + +import javax.websocket.Decoder; +import javax.websocket.Decoder.TextStream; + +import org.eclipse.jetty.websocket.common.events.EventDriver; +import org.eclipse.jetty.websocket.common.message.MessageSink; +import org.eclipse.jetty.websocket.common.message.MessageInputStream; +import org.eclipse.jetty.websocket.common.message.MessageReader; + +public class JsrReaderMessage implements MessageSink +{ + private final EventDriver events; + private final Decoder.TextStream decoder; + private final Executor executor; + private MessageReader stream = null; + + public JsrReaderMessage(TextStream decoder, EventDriver events, Executor executor) + { + this.decoder = decoder; + this.events = events; + this.executor = executor; + } + + @Override + public void appendFrame(ByteBuffer framePayload, boolean fin) throws IOException + { + boolean first = (stream == null); + + stream = new MessageReader(new MessageInputStream()); + stream.appendFrame(framePayload,fin); + if (first) + { + executor.execute(new Runnable() + { + @Override + public void run() + { + try + { + if (decoder != null) + { + Object o = decoder.decode(stream); + events.onObject(o); + } + else + { + events.onReader(stream); + } + } + catch (Throwable t) + { + events.onError(t); + } + } + }); + } + } + + @Override + public void messageComplete() + { + stream.messageComplete(); + stream = null; + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialMessage.java index c44b41dce22..c5da4e71e63 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialMessage.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialMessage.java @@ -24,25 +24,20 @@ import java.nio.ByteBuffer; import javax.websocket.MessageHandler; import javax.websocket.MessageHandler.Partial; -import org.eclipse.jetty.websocket.common.message.MessageAppender; +import org.eclipse.jetty.websocket.common.message.MessageSink; import org.eclipse.jetty.websocket.common.util.Utf8PartialBuilder; -import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper; /** * Partial TEXT MessageAppender for MessageHandler.Partial interface */ -public class TextPartialMessage implements MessageAppender +public class TextPartialMessage implements MessageSink { - @SuppressWarnings("unused") - private final MessageHandlerWrapper msgWrapper; private final MessageHandler.Partial partialHandler; private final Utf8PartialBuilder utf8Partial; - @SuppressWarnings("unchecked") - public TextPartialMessage(MessageHandlerWrapper wrapper) + public TextPartialMessage(Partial handler) { - this.msgWrapper = wrapper; - this.partialHandler = (Partial)wrapper.getHandler(); + this.partialHandler = handler; this.utf8Partial = new Utf8PartialBuilder(); } @@ -58,6 +53,6 @@ public class TextPartialMessage implements MessageAppender @Override public void messageComplete() { - /* nothing to do here */ + utf8Partial.reset(); } } diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialOnMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialOnMessage.java index c91ab58f07c..cccc7c77915 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialOnMessage.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextPartialOnMessage.java @@ -23,14 +23,14 @@ import java.nio.ByteBuffer; import javax.websocket.OnMessage; -import org.eclipse.jetty.websocket.common.message.MessageAppender; +import org.eclipse.jetty.websocket.common.message.MessageSink; import org.eclipse.jetty.websocket.common.util.Utf8PartialBuilder; import org.eclipse.jetty.websocket.jsr356.endpoints.JsrAnnotatedEventDriver; /** * Partial TEXT MessageAppender for @{@link OnMessage} annotated methods */ -public class TextPartialOnMessage implements MessageAppender +public class TextPartialOnMessage implements MessageSink { private final JsrAnnotatedEventDriver driver; private final Utf8PartialBuilder utf8Partial; diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextWholeMessage.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextWholeMessage.java index 26aa91c5e19..b3f42beff69 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextWholeMessage.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/messages/TextWholeMessage.java @@ -25,11 +25,11 @@ import javax.websocket.MessageHandler.Whole; import org.eclipse.jetty.websocket.api.WebSocketException; import org.eclipse.jetty.websocket.common.events.EventDriver; -import org.eclipse.jetty.websocket.common.message.SimpleTextMessage; +import org.eclipse.jetty.websocket.common.message.StringMessageSink; import org.eclipse.jetty.websocket.jsr356.DecoderFactory; import org.eclipse.jetty.websocket.jsr356.MessageHandlerWrapper; -public class TextWholeMessage extends SimpleTextMessage +public class TextWholeMessage extends StringMessageSink { private final MessageHandlerWrapper msgWrapper; private final MessageHandler.Whole wholeHandler; diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSet.java index e664b96fad2..6f174d3f2e9 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSet.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/DecoderMetadataSet.java @@ -24,7 +24,7 @@ import java.util.List; import javax.websocket.Decoder; import org.eclipse.jetty.websocket.api.InvalidWebSocketException; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.common.util.ReflectUtils; import org.eclipse.jetty.websocket.jsr356.MessageType; diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSet.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSet.java index d58279d348d..09764639408 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSet.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/metadata/EncoderMetadataSet.java @@ -24,7 +24,7 @@ import java.util.List; import javax.websocket.Encoder; import org.eclipse.jetty.websocket.api.InvalidWebSocketException; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.common.util.ReflectUtils; import org.eclipse.jetty.websocket.jsr356.MessageType; diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTest.java index 0d8de52b362..dc234822cf2 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTest.java +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTest.java @@ -32,7 +32,6 @@ import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.common.test.DummyConnection; import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig; import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata; -import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance; import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEndpointEventDriver; import org.eclipse.jetty.websocket.jsr356.handlers.ByteArrayWholeHandler; import org.eclipse.jetty.websocket.jsr356.handlers.ByteBufferPartialHandler; @@ -60,7 +59,7 @@ public class JsrSessionTest SimpleEndpointMetadata metadata = new SimpleEndpointMetadata(websocket.getClass()); // Executor executor = null; - EndpointInstance ei = new EndpointInstance(websocket,config,metadata); + ConfiguredEndpoint ei = new ConfiguredEndpoint(websocket,config,metadata); EventDriver driver = new JsrEndpointEventDriver(policy,ei); DummyConnection connection = new DummyConnection(); diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_InvalidSignaturesTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_InvalidSignaturesTest.java index c4fee09ae7f..a96d55561e4 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_InvalidSignaturesTest.java +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/ClientAnnotatedEndpointScanner_InvalidSignaturesTest.java @@ -34,7 +34,7 @@ import javax.websocket.OnOpen; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.jsr356.ClientContainer; import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner; import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata; diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/OnCloseTest.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/OnCloseTest.java index f783fe31bb1..300a4e4a0dc 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/OnCloseTest.java +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/endpoints/OnCloseTest.java @@ -33,6 +33,7 @@ import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.jsr356.ClientContainer; +import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint; import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner; import org.eclipse.jetty.websocket.jsr356.annotations.JsrEvents; import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata; @@ -109,7 +110,7 @@ public class OnCloseTest WebSocketPolicy policy = WebSocketPolicy.newClientPolicy(); ClientEndpointConfig config = metadata.getConfig(); TrackingSocket endpoint = (TrackingSocket)testcase.closeClass.newInstance(); - EndpointInstance ei = new EndpointInstance(endpoint,config,metadata); + ConfiguredEndpoint ei = new ConfiguredEndpoint(endpoint,config,metadata); JsrEvents jsrevents = new JsrEvents<>(metadata); EventDriver driver = new JsrAnnotatedEventDriver(policy,ei,jsrevents); diff --git a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferPartialHandler.java b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferPartialHandler.java index 2f83b37570d..06de983864a 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferPartialHandler.java +++ b/jetty-websocket/javax-websocket-client-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/handlers/ByteBufferPartialHandler.java @@ -27,6 +27,5 @@ public class ByteBufferPartialHandler implements MessageHandler.Partial events = new JsrEvents<>(metadata); @@ -96,12 +96,12 @@ public class JsrServerEndpointImpl implements EventDriverImpl @Override public boolean supports(Object websocket) { - if (!(websocket instanceof EndpointInstance)) + if (!(websocket instanceof ConfiguredEndpoint)) { return false; } - EndpointInstance ei = (EndpointInstance)websocket; + ConfiguredEndpoint ei = (ConfiguredEndpoint)websocket; Object endpoint = ei.getEndpoint(); ServerEndpoint anno = endpoint.getClass().getAnnotation(ServerEndpoint.class); diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerExtendsEndpointImpl.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerExtendsEndpointImpl.java index 4a0d3aac632..4379d3188fa 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerExtendsEndpointImpl.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrServerExtendsEndpointImpl.java @@ -23,7 +23,7 @@ import javax.websocket.server.ServerEndpointConfig; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.common.events.EventDriverImpl; -import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance; +import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint; import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEndpointEventDriver; public class JsrServerExtendsEndpointImpl implements EventDriverImpl @@ -31,12 +31,12 @@ public class JsrServerExtendsEndpointImpl implements EventDriverImpl @Override public EventDriver create(Object websocket, WebSocketPolicy policy) { - if (!(websocket instanceof EndpointInstance)) + if (!(websocket instanceof ConfiguredEndpoint)) { - throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),EndpointInstance.class.getName())); + throw new IllegalStateException(String.format("Websocket %s must be an %s",websocket.getClass().getName(),ConfiguredEndpoint.class.getName())); } - EndpointInstance ei = (EndpointInstance)websocket; + ConfiguredEndpoint ei = (ConfiguredEndpoint)websocket; JsrEndpointEventDriver driver = new JsrEndpointEventDriver(policy, ei); ServerEndpointConfig config = (ServerEndpointConfig)ei.getConfig(); @@ -58,12 +58,12 @@ public class JsrServerExtendsEndpointImpl implements EventDriverImpl @Override public boolean supports(Object websocket) { - if (!(websocket instanceof EndpointInstance)) + if (!(websocket instanceof ConfiguredEndpoint)) { return false; } - EndpointInstance ei = (EndpointInstance)websocket; + ConfiguredEndpoint ei = (ConfiguredEndpoint)websocket; Object endpoint = ei.getEndpoint(); return (endpoint instanceof javax.websocket.Endpoint); diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java index c25350366d2..d942c670a55 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/ServerContainer.java @@ -36,9 +36,9 @@ import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.events.EventDriverFactory; import org.eclipse.jetty.websocket.jsr356.ClientContainer; +import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint; import org.eclipse.jetty.websocket.jsr356.JsrSessionFactory; import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner; -import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance; import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata; import org.eclipse.jetty.websocket.server.MappedWebSocketCreator; import org.eclipse.jetty.websocket.server.WebSocketServerFactory; @@ -63,8 +63,8 @@ public class ServerContainer extends ClientContainer implements javax.websocket. this.webSocketServerFactory.addSessionFactory(new JsrSessionFactory(this)); addBean(webSocketServerFactory); } - - public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path) + + public ConfiguredEndpoint newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path) { EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass(),config); ServerEndpointConfig cec = config; @@ -79,7 +79,7 @@ public class ServerContainer extends ClientContainer implements javax.websocket. cec = new BasicServerEndpointConfig(this,endpoint.getClass(),path); } } - return new EndpointInstance(endpoint,cec,metadata); + return new ConfiguredEndpoint(endpoint,cec,metadata); } @Override diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java index 6628a026ad4..44a6375ea5f 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnPartialTest.java @@ -39,9 +39,9 @@ import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; import org.eclipse.jetty.websocket.common.test.DummyConnection; import org.eclipse.jetty.websocket.jsr356.ClientContainer; +import org.eclipse.jetty.websocket.jsr356.ConfiguredEndpoint; import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner; -import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance; import org.eclipse.jetty.websocket.jsr356.server.samples.partial.PartialTrackingSocket; import org.junit.Assert; import org.junit.Rule; @@ -76,7 +76,7 @@ public class OnPartialTest AnnotatedServerEndpointMetadata metadata = new AnnotatedServerEndpointMetadata(containerScope,endpoint,config); AnnotatedEndpointScanner scanner = new AnnotatedEndpointScanner<>(metadata); scanner.scan(); - EndpointInstance ei = new EndpointInstance(websocket,config,metadata); + ConfiguredEndpoint ei = new ConfiguredEndpoint(websocket,config,metadata); EventDriver driver = driverImpl.create(ei,policy); Assert.assertThat("EventDriver",driver,notNullValue()); diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_InvalidSignaturesTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_InvalidSignaturesTest.java index 29a31e15a5b..1516251998c 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_InvalidSignaturesTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ServerAnnotatedEndpointScanner_InvalidSignaturesTest.java @@ -35,7 +35,7 @@ import javax.websocket.server.ServerEndpointConfig; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.WebSocketPolicy; -import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner; diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java index 311f0452808..42574fade93 100644 --- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/WebSocketPolicy.java @@ -18,6 +18,9 @@ package org.eclipse.jetty.websocket.api; +import java.util.ArrayList; +import java.util.List; + /** * Settings for WebSocket operations. */ @@ -34,6 +37,13 @@ public class WebSocketPolicy { return new WebSocketPolicy(WebSocketBehavior.SERVER); } + + /* NOTE TO OTHER DEVELOPERS: + * If you change any of these default values, + * make sure you sync the values with + * org.eclipse.jetty.websocket.api.annotations.WebSocket + * annotation defaults + */ /** * The maximum size of a text message during parsing/generating. @@ -76,14 +86,14 @@ public class WebSocketPolicy *

* Negative values indicate a disabled timeout. */ - private long asyncWriteTimeout = 60000; + private long asyncWriteTimeout = 60_000; /** * The time in ms (milliseconds) that a websocket may be idle before closing. *

* Default: 300000 (ms) */ - private long idleTimeout = 300000; + private long idleTimeout = 300_000; /** * The size of the input (read from network layer) buffer size. @@ -96,6 +106,13 @@ public class WebSocketPolicy * Behavior of the websockets */ private final WebSocketBehavior behavior; + + public static interface PolicyUpdate + { + public void onPolicyUpdate(WebSocketPolicy policy); + } + + private List listeners = new ArrayList<>(); public WebSocketPolicy(WebSocketBehavior behavior) { @@ -152,8 +169,27 @@ public class WebSocketPolicy clone.maxBinaryMessageBufferSize = this.maxBinaryMessageBufferSize; clone.inputBufferSize = this.inputBufferSize; clone.asyncWriteTimeout = this.asyncWriteTimeout; + // clone.listeners.addAll(this.listeners); return clone; } + + public void addListener(PolicyUpdate update) + { + this.listeners.add(update); + } + + public void removeListener(PolicyUpdate update) + { + this.listeners.remove(update); + } + + private void notifyOfUpdate() + { + for(PolicyUpdate update: listeners) + { + update.onPolicyUpdate(this); + } + } /** * The timeout in ms (milliseconds) for async write operations. @@ -248,8 +284,11 @@ public class WebSocketPolicy */ public void setAsyncWriteTimeout(long ms) { + boolean dirty = (this.asyncWriteTimeout != ms); assertLessThan("AsyncWriteTimeout",ms,"IdleTimeout",idleTimeout); this.asyncWriteTimeout = ms; + if (dirty) + notifyOfUpdate(); } /** @@ -260,8 +299,11 @@ public class WebSocketPolicy */ public void setIdleTimeout(long ms) { + boolean dirty = (this.idleTimeout != ms); assertGreaterThan("IdleTimeout",ms,0); this.idleTimeout = ms; + if (dirty) + notifyOfUpdate(); } /** @@ -272,11 +314,14 @@ public class WebSocketPolicy */ public void setInputBufferSize(int size) { + boolean dirty = (this.inputBufferSize != size); assertGreaterThan("InputBufferSize",size,1); assertLessThan("InputBufferSize",size,"MaxTextMessageBufferSize",maxTextMessageBufferSize); assertLessThan("InputBufferSize",size,"MaxBinaryMessageBufferSize",maxBinaryMessageBufferSize); this.inputBufferSize = size; + if(dirty) + notifyOfUpdate(); } /** @@ -289,9 +334,12 @@ public class WebSocketPolicy */ public void setMaxBinaryMessageBufferSize(int size) { + boolean dirty = (this.maxBinaryMessageBufferSize != size); assertGreaterThan("MaxBinaryMessageBufferSize",size,1); this.maxBinaryMessageBufferSize = size; + if(dirty) + notifyOfUpdate(); } /** @@ -304,9 +352,12 @@ public class WebSocketPolicy */ public void setMaxBinaryMessageSize(int size) { + boolean dirty = (this.maxBinaryMessageSize != size); assertGreaterThan("MaxBinaryMessageSize",size,1); this.maxBinaryMessageSize = size; + if(dirty) + notifyOfUpdate(); } /** @@ -319,9 +370,12 @@ public class WebSocketPolicy */ public void setMaxTextMessageBufferSize(int size) { + boolean dirty = (this.maxTextMessageBufferSize != size); assertGreaterThan("MaxTextMessageBufferSize",size,1); this.maxTextMessageBufferSize = size; + if(dirty) + notifyOfUpdate(); } /** @@ -334,9 +388,12 @@ public class WebSocketPolicy */ public void setMaxTextMessageSize(int size) { + boolean dirty = (this.maxTextMessageSize != size); assertGreaterThan("MaxTextMessageSize",size,1); this.maxTextMessageSize = size; + if(dirty) + notifyOfUpdate(); } @Override @@ -344,14 +401,10 @@ public class WebSocketPolicy { StringBuilder builder = new StringBuilder(); builder.append("WebSocketPolicy@").append(Integer.toHexString(hashCode())); - builder.append("[behavior=").append(behavior); - builder.append(",maxTextMessageSize=").append(maxTextMessageSize); - builder.append(",maxTextMessageBufferSize=").append(maxTextMessageBufferSize); - builder.append(",maxBinaryMessageSize=").append(maxBinaryMessageSize); - builder.append(",maxBinaryMessageBufferSize=").append(maxBinaryMessageBufferSize); - builder.append(",asyncWriteTimeout=").append(asyncWriteTimeout); + builder.append("[").append(behavior); + builder.append(",textSize=").append(maxTextMessageSize); + builder.append(",binarySize=").append(maxBinaryMessageSize); builder.append(",idleTimeout=").append(idleTimeout); - builder.append(",inputBufferSize=").append(inputBufferSize); builder.append("]"); return builder.toString(); } diff --git a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java index 8d9aa2cf38d..502167bed52 100644 --- a/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java +++ b/jetty-websocket/websocket-api/src/main/java/org/eclipse/jetty/websocket/api/annotations/WebSocket.java @@ -25,6 +25,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.eclipse.jetty.websocket.api.BatchMode; +import org.eclipse.jetty.websocket.api.StatusCode; /** * Tags a POJO as being a WebSocket class. @@ -35,13 +36,47 @@ import org.eclipse.jetty.websocket.api.BatchMode; { ElementType.TYPE }) public @interface WebSocket { - int inputBufferSize() default -2; - - int maxBinaryMessageSize() default -2; - - int maxIdleTime() default -2; - - int maxTextMessageSize() default -2; + /* NOTE TO OTHER DEVELOPERS: + * If you change any of these default values, + * make sure you sync the values with WebSocketPolicy + */ + /** + * The size of the buffer used to read from the network layer. + *

+ * Default: 4096 (4 K) + */ + int inputBufferSize() default 4 * 1024; + + /** + * The maximum size of a binary message during parsing/generating. + *

+ * Binary messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + *

+ * Default: 65536 (64 K) + */ + int maxBinaryMessageSize() default 64 * 1024; + + /** + * The time in ms (milliseconds) that a websocket may be idle before closing. + *

+ * Default: 300000 (ms) + */ + int maxIdleTime() default 300_000; + + /** + * The maximum size of a text message during parsing/generating. + *

+ * Text messages over this maximum will result in a close code 1009 {@link StatusCode#MESSAGE_TOO_LARGE} + *

+ * Default: 65536 (64 K) + */ + int maxTextMessageSize() default 64 * 1024; + + /** + * The output frame buffering mode. + *

+ * Default: {@link BatchMode#AUTO} + */ BatchMode batchMode() default BatchMode.AUTO; } 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 fd30726e828..f93c9644d78 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 @@ -55,8 +55,6 @@ import org.eclipse.jetty.websocket.client.masks.RandomMasker; import org.eclipse.jetty.websocket.common.SessionFactory; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.WebSocketSessionFactory; -import org.eclipse.jetty.websocket.common.events.EventDriver; -import org.eclipse.jetty.websocket.common.events.EventDriverFactory; import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; @@ -71,7 +69,6 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont private final SslContextFactory sslContextFactory; private final WebSocketExtensionFactory extensionRegistry; private boolean daemon = false; - private EventDriverFactory eventDriverFactory; private SessionFactory sessionFactory; private ByteBufferPool bufferPool; private Executor executor; @@ -137,7 +134,10 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont this.extensionRegistry = new WebSocketExtensionFactory(this); this.masker = new RandomMasker(); - this.eventDriverFactory = new EventDriverFactory(policy); + + addBean(this.executor); + addBean(this.sslContextFactory); + addBean(this.bufferPool); } public Future connect(Object websocket, URI toUri) throws IOException @@ -197,26 +197,8 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont initializeClient(); ConnectionManager manager = getConnectionManager(); - // Setup Driver for user provided websocket - EventDriver driver = null; - if (websocket instanceof EventDriver) - { - // Use the EventDriver as-is - driver = (EventDriver)websocket; - } - else - { - // Wrap websocket with appropriate EventDriver - driver = eventDriverFactory.wrap(websocket); - } - - if (driver == null) - { - throw new IllegalStateException("Unable to identify as websocket object: " + websocket.getClass().getName()); - } - // Create the appropriate (physical vs virtual) connection task - ConnectPromise promise = manager.connect(this,driver,request); + ConnectPromise promise = manager.connect(this,request,websocket); if (upgradeListener != null) { @@ -337,11 +319,6 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont return cookieStore; } - public EventDriverFactory getEventDriverFactory() - { - return eventDriverFactory; - } - public Executor getExecutor() { return executor; @@ -553,11 +530,6 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont this.dispatchIO = dispatchIO; } - public void setEventDriverFactory(EventDriverFactory factory) - { - this.eventDriverFactory = factory; - } - public void setExecutor(Executor executor) { updateBean(this.executor,executor); diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java index 00d3b51693c..dafc4479c1a 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectPromise.java @@ -27,7 +27,6 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeResponse; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.client.masks.Masker; import org.eclipse.jetty.websocket.common.WebSocketSession; -import org.eclipse.jetty.websocket.common.events.EventDriver; /** * Holder for the pending connect information. @@ -36,26 +35,29 @@ public abstract class ConnectPromise extends FuturePromise implements R { private static final Logger LOG = Log.getLogger(ConnectPromise.class); private final WebSocketClient client; - private final EventDriver driver; private final ClientUpgradeRequest request; + private final Object webSocketEndpoint; private final Masker masker; private UpgradeListener upgradeListener; private ClientUpgradeResponse response; private WebSocketSession session; - public ConnectPromise(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request) + public ConnectPromise(WebSocketClient client, ClientUpgradeRequest request, Object websocket) { this.client = client; - this.driver = driver; this.request = request; + this.webSocketEndpoint = websocket; this.masker = client.getMasker(); } @Override public void failed(Throwable cause) { - // Notify websocket of failure to connect - driver.onError(cause); + if (session != null) + { + // Notify websocket of failure to connect + session.notifyError(cause); + } // Notify promise/future of failure to connect super.failed(cause); @@ -65,10 +67,10 @@ public abstract class ConnectPromise extends FuturePromise implements R { return client; } - - public EventDriver getDriver() + + public Object getWebSocketEndpoint() { - return this.driver; + return webSocketEndpoint; } public Masker getMasker() diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java index 06a51b96890..abce808900c 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java @@ -30,7 +30,6 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; -import org.eclipse.jetty.websocket.common.events.EventDriver; /** * Internal Connection/Client Manager used to track active clients, their physical vs virtual connection information, and provide some means to create new @@ -42,9 +41,9 @@ public class ConnectionManager extends ContainerLifeCycle { private SocketAddress bindAddress; - public PhysicalConnect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request) + public PhysicalConnect(WebSocketClient client, ClientUpgradeRequest request, Object websocket) { - super(client,driver,request); + super(client,request,websocket); this.bindAddress = client.getBindAddress(); } @@ -138,9 +137,9 @@ public class ConnectionManager extends ContainerLifeCycle this.client = client; } - public ConnectPromise connect(WebSocketClient client, EventDriver driver, ClientUpgradeRequest request) + public ConnectPromise connect(WebSocketClient client, ClientUpgradeRequest request, Object websocket) { - return new PhysicalConnect(client,driver,request); + return new PhysicalConnect(client,request,websocket); } @Override diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java index 5f7f302a342..25f4efcaa3d 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java @@ -44,7 +44,6 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeResponse; import org.eclipse.jetty.websocket.common.AcceptHash; import org.eclipse.jetty.websocket.common.SessionFactory; import org.eclipse.jetty.websocket.common.WebSocketSession; -import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.common.extensions.ExtensionStack; import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser; import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser.ParseException; @@ -307,8 +306,8 @@ public class UpgradeConnection extends AbstractConnection implements Connection. EndPoint endp = getEndPoint(); Executor executor = getExecutor(); - EventDriver websocket = connectPromise.getDriver(); - WebSocketPolicy policy = websocket.getPolicy(); + Object websocket = connectPromise.getWebSocketEndpoint(); + WebSocketPolicy policy = connectPromise.getClient().getPolicy().clonePolicy(); WebSocketClientConnection connection = new WebSocketClientConnection(endp,executor,connectPromise,policy); diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java index 43a3888cff2..fb2193d2be9 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java @@ -103,7 +103,6 @@ public class WebSocketClientSelectorManager extends SelectorManager else { // Standard "ws://" - endPoint.setIdleTimeout(connectPromise.getDriver().getPolicy().getIdleTimeout()); return newUpgradeConnection(channel,endPoint,connectPromise); } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/DuplicateAnnotationException.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/DuplicateAnnotationException.java new file mode 100644 index 00000000000..2aa7820edab --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/DuplicateAnnotationException.java @@ -0,0 +1,57 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.eclipse.jetty.websocket.api.InvalidWebSocketException; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; + +@SuppressWarnings("serial") +public class DuplicateAnnotationException extends InvalidWebSocketException +{ + public static DuplicateAnnotationException build(Class pojo, Class annoClass, Method... methods) + { + // Build big detailed exception to help the developer + StringBuilder err = new StringBuilder(); + err.append("Duplicate @"); + err.append(annoClass.getSimpleName()); + err.append(" declarations in: "); + err.append(pojo.getName()); + + for (Method method : methods) + { + err.append(System.lineSeparator()); + ReflectUtils.append(err,method); + } + + return new DuplicateAnnotationException(err.toString()); + } + + public DuplicateAnnotationException(String message) + { + super(message); + } + + public DuplicateAnnotationException(String message, Throwable cause) + { + super(message,cause); + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/InvalidSignatureException.java similarity index 69% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/InvalidSignatureException.java index b64855972c4..a27f761df50 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/InvalidSignatureException.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/InvalidSignatureException.java @@ -16,18 +16,18 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.events.annotated; +package org.eclipse.jetty.websocket.common; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import org.eclipse.jetty.websocket.api.InvalidWebSocketException; -import org.eclipse.jetty.websocket.common.events.ParamList; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; @SuppressWarnings("serial") public class InvalidSignatureException extends InvalidWebSocketException { - public static InvalidSignatureException build(Method method, Class annoClass, ParamList... paramlists) + public static InvalidSignatureException build(Method method, Class annoClass, DynamicArgs.Builder ... dynArgsBuilders) { // Build big detailed exception to help the developer StringBuilder err = new StringBuilder(); @@ -38,29 +38,13 @@ public class InvalidSignatureException extends InvalidWebSocketException err.append("Acceptable method declarations for @"); err.append(annoClass.getSimpleName()); err.append(" are:"); - for (ParamList validParams : paramlists) + for (DynamicArgs.Builder argsBuilder : dynArgsBuilders) { - for (Class[] params : validParams) + for (DynamicArgs.Signature signature : argsBuilder.getSignatures()) { err.append(System.lineSeparator()); err.append("public void ").append(method.getName()); - err.append('('); - boolean delim = false; - for (Class type : params) - { - if (delim) - { - err.append(','); - } - err.append(' '); - err.append(type.getName()); - if (type.isArray()) - { - err.append("[]"); - } - delim = true; - } - err.append(')'); + signature.appendDescription(err); } } return new InvalidSignatureException(err.toString()); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionFactory.java index c106c6aefc5..880fc937032 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionFactory.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionFactory.java @@ -20,14 +20,12 @@ package org.eclipse.jetty.websocket.common; import java.net.URI; -import org.eclipse.jetty.websocket.common.events.EventDriver; - /** * Interface for creating jetty {@link WebSocketSession} objects. */ public interface SessionFactory { - public boolean supports(EventDriver websocket); + public boolean supports(Object websocket); - public WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection); + public WebSocketSession createSession(URI requestURI, Object websocket, LogicalConnection connection); } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index f02bb899c2a..c1aaf86d656 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -19,8 +19,10 @@ package org.eclipse.jetty.websocket.common; import java.io.IOException; +import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.URI; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -28,9 +30,12 @@ import java.util.Map; import java.util.Objects; import java.util.ServiceLoader; import java.util.concurrent.Executor; +import java.util.function.Function; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; @@ -41,6 +46,7 @@ import org.eclipse.jetty.util.thread.ThreadClassLoaderScope; import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.CloseException; import org.eclipse.jetty.websocket.api.CloseStatus; +import org.eclipse.jetty.websocket.api.InvalidWebSocketException; import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; @@ -48,17 +54,47 @@ import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeResponse; import org.eclipse.jetty.websocket.api.WebSocketBehavior; +import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.WebSocketFrameListener; +import org.eclipse.jetty.websocket.api.WebSocketListener; +import org.eclipse.jetty.websocket.api.WebSocketPartialListener; +import org.eclipse.jetty.websocket.api.WebSocketPingPongListener; 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.ExtensionFactory; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.IncomingFrames; import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames; -import org.eclipse.jetty.websocket.common.events.EventDriver; +import org.eclipse.jetty.websocket.common.frames.CloseFrame; +import org.eclipse.jetty.websocket.common.frames.ReadOnlyDelegatedFrame; +import org.eclipse.jetty.websocket.common.functions.OnByteArrayFunction; +import org.eclipse.jetty.websocket.common.functions.OnByteBufferFunction; +import org.eclipse.jetty.websocket.common.functions.OnCloseFunction; +import org.eclipse.jetty.websocket.common.functions.OnErrorFunction; +import org.eclipse.jetty.websocket.common.functions.OnFrameFunction; +import org.eclipse.jetty.websocket.common.functions.OnInputStreamFunction; +import org.eclipse.jetty.websocket.common.functions.OnOpenFunction; +import org.eclipse.jetty.websocket.common.functions.OnReaderFunction; +import org.eclipse.jetty.websocket.common.functions.OnTextFunction; import org.eclipse.jetty.websocket.common.io.IOState; import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; +import org.eclipse.jetty.websocket.common.message.ByteArrayMessageSink; +import org.eclipse.jetty.websocket.common.message.ByteBufferMessageSink; +import org.eclipse.jetty.websocket.common.message.InputStreamMessageSink; +import org.eclipse.jetty.websocket.common.message.MessageSink; +import org.eclipse.jetty.websocket.common.message.PartialBinaryMessageSink; +import org.eclipse.jetty.websocket.common.message.PartialTextMessageSink; +import org.eclipse.jetty.websocket.common.message.ReaderMessageSink; +import org.eclipse.jetty.websocket.common.message.StringMessageSink; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; import org.eclipse.jetty.websocket.common.scopes.WebSocketSessionScope; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; @ManagedObject("A Jetty WebSocket Session") public class WebSocketSession extends ContainerLifeCycle implements Session, RemoteEndpointFactory, WebSocketSessionScope, IncomingFrames, Connection.Listener, ConnectionStateListener @@ -68,21 +104,36 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem private final WebSocketContainerScope containerScope; private final URI requestURI; private final LogicalConnection connection; - private final EventDriver websocket; private final Executor executor; + + // The websocket endpoint object itself + private final Object endpoint; + + // The functions for calling into websocket endpoint's declared event handlers + protected Function onOpenFunction; + protected Function onCloseFunction; + protected Function onErrorFunction; + protected Function onPingFunction; + protected Function onPongFunction; + protected Function onFrameFunction; + + // Message Handling sinks + protected MessageSink onTextSink; + protected MessageSink onBinarySink; + protected MessageSink activeMessageSink; + private ClassLoader classLoader; private ExtensionFactory extensionFactory; - private RemoteEndpointFactory remoteEndpointFactory; + private BatchMode batchmode = BatchMode.AUTO; private String protocolVersion; private Map parameterMap = new HashMap<>(); - private RemoteEndpoint remote; - private IncomingFrames incomingHandler; + private WebSocketRemoteEndpoint remote; private OutgoingFrames outgoingHandler; private WebSocketPolicy policy; private UpgradeRequest upgradeRequest; private UpgradeResponse upgradeResponse; - public WebSocketSession(WebSocketContainerScope containerScope, URI requestURI, EventDriver websocket, LogicalConnection connection) + public WebSocketSession(WebSocketContainerScope containerScope, URI requestURI, Object endpoint, LogicalConnection connection) { Objects.requireNonNull(containerScope,"Container Scope cannot be null"); Objects.requireNonNull(requestURI,"Request URI cannot be null"); @@ -90,23 +141,222 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem this.classLoader = Thread.currentThread().getContextClassLoader(); this.containerScope = containerScope; this.requestURI = requestURI; - this.websocket = websocket; + this.endpoint = endpoint; this.connection = connection; this.executor = connection.getExecutor(); this.outgoingHandler = connection; - this.incomingHandler = websocket; this.connection.getIOState().addListener(this); - this.policy = containerScope.getPolicy(); + this.policy = containerScope.getPolicy().clonePolicy(); + + discoverEndpointFunctions(this.endpoint); addBean(this.connection); - addBean(this.websocket); + } + + protected void discoverEndpointFunctions(Object endpoint) + { + // Connection Listener + + if (endpoint instanceof WebSocketConnectionListener) + { + WebSocketConnectionListener wslistener = (WebSocketConnectionListener)endpoint; + onOpenFunction = (sess) -> { + wslistener.onWebSocketConnect(sess); + return null; + }; + onCloseFunction = (closeinfo) -> { + wslistener.onWebSocketClose(closeinfo.getStatusCode(),closeinfo.getReason()); + return null; + }; + onErrorFunction = (cause) -> { + wslistener.onWebSocketError(cause); + return null; + }; + } + + // Simple Data Listener + + if (endpoint instanceof WebSocketListener) + { + WebSocketListener wslistener = (WebSocketListener)endpoint; + onTextSink = new StringMessageSink(policy,(payload) -> { + wslistener.onWebSocketText(payload); + return null; + }); + onBinarySink = new ByteArrayMessageSink(policy,(payload) -> { + wslistener.onWebSocketBinary(payload,0,payload.length); + return null; + }); + } + + // Ping/Pong Listener + + if (endpoint instanceof WebSocketPingPongListener) + { + WebSocketPingPongListener wslistener = (WebSocketPingPongListener)endpoint; + onPongFunction = (pong) -> { + ByteBuffer payload = pong; + if (pong == null) + payload = BufferUtil.EMPTY_BUFFER; + wslistener.onWebSocketPong(payload); + return null; + }; + onPingFunction = (ping) -> { + ByteBuffer payload = ping; + if (ping == null) + payload = BufferUtil.EMPTY_BUFFER; + wslistener.onWebSocketPing(payload); + return null; + }; + } + + // Partial Data / Message Listener + + if (endpoint instanceof WebSocketPartialListener) + { + for(Method method: WebSocketPartialListener.class.getDeclaredMethods()) + { + if(method.getName().equals("onWebSocketPartialText")) + assertNotSet(onTextSink, "TEXT Message Handler", endpoint.getClass(), method); + else if(method.getName().equals("onWebSocketPartialBinary")) + assertNotSet(onBinarySink, "BINARY Message Handler", endpoint.getClass(), method); + } + + WebSocketPartialListener wslistener = (WebSocketPartialListener)endpoint; + onTextSink = new PartialTextMessageSink((partial) -> { + wslistener.onWebSocketPartialText(partial.getPayload(),partial.isFin()); + return null; + }); + onBinarySink = new PartialBinaryMessageSink((partial) -> { + wslistener.onWebSocketPartialBinary(partial.getPayload(),partial.isFin()); + return null; + }); + } + + // Frame Listener + + if (endpoint instanceof WebSocketFrameListener) + { + WebSocketFrameListener wslistener = (WebSocketFrameListener)endpoint; + onFrameFunction = (frame) -> { + wslistener.onWebSocketFrame(new ReadOnlyDelegatedFrame(frame)); + return null; + }; + } + + // Test for annotated websocket endpoint + + Class endpointClass = endpoint.getClass(); + WebSocket websocket = endpointClass.getAnnotation(WebSocket.class); + if (websocket != null) + { + policy.setInputBufferSize(websocket.inputBufferSize()); + policy.setMaxBinaryMessageSize(websocket.maxBinaryMessageSize()); + policy.setMaxTextMessageSize(websocket.maxTextMessageSize()); + policy.setIdleTimeout(websocket.maxIdleTime()); + + this.batchmode = websocket.batchMode(); + + Method onmethod = null; + + // OnWebSocketConnect [0..1] + onmethod = ReflectUtils.findAnnotatedMethod(endpointClass,OnWebSocketConnect.class); + if (onmethod != null) + { + assertNotSet(onOpenFunction, "Open/Connect Handler", endpointClass, onmethod); + onOpenFunction = new OnOpenFunction(endpoint,onmethod); + } + // OnWebSocketClose [0..1] + onmethod = ReflectUtils.findAnnotatedMethod(endpointClass,OnWebSocketClose.class); + if (onmethod != null) + { + assertNotSet(onCloseFunction, "Close Handler", endpointClass, onmethod); + onCloseFunction = new OnCloseFunction(this,endpoint,onmethod); + } + // OnWebSocketError [0..1] + onmethod = ReflectUtils.findAnnotatedMethod(endpointClass,OnWebSocketError.class); + if (onmethod != null) + { + assertNotSet(onErrorFunction, "Error Handler", endpointClass, onmethod); + onErrorFunction = new OnErrorFunction(this,endpoint,onmethod); + } + // OnWebSocketFrame [0..1] + onmethod = ReflectUtils.findAnnotatedMethod(endpointClass,OnWebSocketFrame.class); + if (onmethod != null) + { + assertNotSet(onFrameFunction, "Frame Handler", endpointClass, onmethod); + onFrameFunction = new OnFrameFunction(this,endpoint,onmethod); + } + // OnWebSocketMessage [0..2] + Method onmessages[] = ReflectUtils.findAnnotatedMethods(endpointClass,OnWebSocketMessage.class); + if (onmessages != null && onmessages.length > 0) + { + for (Method onmsg : onmessages) + { + if (OnTextFunction.hasMatchingSignature(onmsg)) + { + assertNotSet(onTextSink, "TEXT Message Handler", endpointClass, onmsg); + // Normal Text Message + onTextSink = new StringMessageSink(policy,new OnTextFunction(this,endpointClass,onmsg)); + } + else if (OnByteBufferFunction.hasMatchingSignature(onmsg)) + { + assertNotSet(onBinarySink, "Binary Message Handler", endpointClass, onmsg); + // ByteBuffer Binary Message + onBinarySink = new ByteBufferMessageSink(policy,new OnByteBufferFunction(this,endpointClass,onmsg)); + } + else if (OnByteArrayFunction.hasMatchingSignature(onmsg)) + { + assertNotSet(onBinarySink, "Binary Message Handler", endpointClass, onmsg); + // byte[] Binary Message + onBinarySink = new ByteArrayMessageSink(policy,new OnByteArrayFunction(this,endpointClass,onmsg)); + } + else if (OnInputStreamFunction.hasMatchingSignature(onmsg)) + { + assertNotSet(onBinarySink, "Binary Message Handler", endpointClass, onmsg); + // InputStream Binary Message + onBinarySink = new InputStreamMessageSink(executor,new OnInputStreamFunction(this,endpointClass,onmsg)); + } + else if (OnReaderFunction.hasMatchingSignature(onmsg)) + { + assertNotSet(onTextSink, "TEXT Message Handler", endpointClass, onmsg); + // Reader Text Message + onTextSink = new ReaderMessageSink(executor,new OnReaderFunction(this,endpointClass,onmsg)); + } + else + { + // Not a valid @OnWebSocketMessage declaration signature + throw InvalidSignatureException.build(onmsg,OnWebSocketMessage.class, + OnTextFunction.getDynamicArgsBuilder(), + OnByteBufferFunction.getDynamicArgsBuilder(), + OnByteArrayFunction.getDynamicArgsBuilder(), + OnInputStreamFunction.getDynamicArgsBuilder(), + OnReaderFunction.getDynamicArgsBuilder()); + } + } + } + } + } + + protected void assertNotSet(Object val, String role, Class pojo, Method method) + { + if(val == null) + return; + + StringBuilder err = new StringBuilder(); + err.append("Cannot replace previously assigned "); + err.append(role); + err.append(" with "); + ReflectUtils.append(err,pojo,method); + + throw new InvalidWebSocketException(err.toString()); } @Override public void close() { /* This is assumed to always be a NORMAL closure, no reason phrase */ - close(StatusCode.NORMAL, null); + connection.close(StatusCode.NORMAL,null); } @Override @@ -141,7 +391,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem @Override protected void doStart() throws Exception { - if(LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) LOG.debug("starting - {}",this); Iterator iter = ServiceLoader.load(RemoteEndpointFactory.class).iterator(); @@ -160,9 +410,10 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem @Override protected void doStop() throws Exception { - if(LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) LOG.debug("stopping - {}",this); - try + + if (getConnection() != null) { close(StatusCode.SHUTDOWN,"Shutdown"); } @@ -177,16 +428,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem public void dump(Appendable out, String indent) throws IOException { dumpThis(out); - out.append(indent).append(" +- incomingHandler : "); - if (incomingHandler instanceof Dumpable) - { - ((Dumpable)incomingHandler).dump(out,indent + " "); - } - else - { - out.append(incomingHandler.toString()).append(System.lineSeparator()); - } - + out.append(indent).append(" +- endpoint : ").append(endpoint.getClass().getName()).append('@').append(Integer.toHexString(endpoint.hashCode())); out.append(indent).append(" +- outgoingHandler : "); if (outgoingHandler instanceof Dumpable) { @@ -249,6 +491,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem return this.containerScope; } + public Executor getExecutor() + { + return executor; + } + public ExtensionFactory getExtensionFactory() { return extensionFactory; @@ -263,12 +510,6 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem return connection.getMaxIdleTimeout(); } - @ManagedAttribute(readonly = true) - public IncomingFrames getIncomingHandler() - { - return incomingHandler; - } - @Override public InetSocketAddress getLocalAddress() { @@ -296,7 +537,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem @Override public RemoteEndpoint getRemote() { - if(LOG_OPEN.isDebugEnabled()) + if (LOG_OPEN.isDebugEnabled()) LOG_OPEN.debug("[{}] {}.getRemote()",policy.getBehavior(),this.getClass().getSimpleName()); ConnectionState state = connection.getIOState().getConnectionState(); @@ -331,7 +572,6 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem return this.upgradeResponse; } - @Override public WebSocketSession getWebSocketSession() { @@ -343,7 +583,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { final int prime = 31; int result = 1; - result = (prime * result) + ((connection == null)?0:connection.hashCode()); + result = (prime * result) + ((connection == null) ? 0 : connection.hashCode()); return result; } @@ -353,11 +593,9 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem @Override public void incomingError(Throwable t) { - if (connection.getIOState().isInputAvailable()) - { - // Forward Errors to User WebSocket Object - websocket.incomingError(t); - } + // Forward Errors to User WebSocket Object + if (onErrorFunction != null) + onErrorFunction.apply(t); } /** @@ -372,12 +610,120 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem Thread.currentThread().setContextClassLoader(classLoader); if (connection.getIOState().isInputAvailable()) { - // Forward Frames Through Extension List - incomingHandler.incomingFrame(frame); + if (onFrameFunction != null) + onFrameFunction.apply(frame); + + byte opcode = frame.getOpCode(); + switch (opcode) + { + case OpCode.CLOSE: + { + boolean validate = true; + CloseFrame closeframe = (CloseFrame)frame; + CloseInfo close = new CloseInfo(closeframe,validate); + + // process handshake + getConnection().getIOState().onCloseRemote(close); + + return; + } + case OpCode.PING: + { + if (LOG.isDebugEnabled()) + LOG.debug("PING: {}",BufferUtil.toDetailString(frame.getPayload())); + + ByteBuffer pongBuf; + if (frame.hasPayload()) + { + pongBuf = ByteBuffer.allocate(frame.getPayload().remaining()); + BufferUtil.put(frame.getPayload().slice(),pongBuf); + BufferUtil.flipToFlush(pongBuf,0); + } + else + { + pongBuf = ByteBuffer.allocate(0); + } + + if (onPingFunction != null) + onPingFunction.apply(frame.getPayload()); + + getRemote().sendPong(pongBuf); + break; + } + case OpCode.PONG: + { + if (LOG.isDebugEnabled()) + LOG.debug("PONG: {}",BufferUtil.toDetailString(frame.getPayload())); + + if (onPongFunction != null) + onPongFunction.apply(frame.getPayload()); + break; + } + case OpCode.BINARY: + { + if (activeMessageSink == null) + activeMessageSink = onBinarySink; + + if (activeMessageSink != null) + activeMessageSink.accept(frame.getPayload(),frame.isFin()); + return; + } + case OpCode.TEXT: + { + if (activeMessageSink == null) + activeMessageSink = onTextSink; + + if (activeMessageSink != null) + activeMessageSink.accept(frame.getPayload(),frame.isFin()); + return; + } + case OpCode.CONTINUATION: + { + if (activeMessageSink != null) + activeMessageSink.accept(frame.getPayload(),frame.isFin()); + + return; + } + default: + { + if (LOG.isDebugEnabled()) + LOG.debug("Unhandled OpCode: {}",opcode); + } + } + } + } + catch (NotUtf8Exception e) + { + notifyError(e); + close(StatusCode.BAD_PAYLOAD,e.getMessage()); + } + catch (CloseException e) + { + close(e.getStatusCode(),e.getMessage()); + } + catch (Throwable t) + { + LOG.warn("Unhandled Error (closing connection)",t); + + notifyError(t); + + // Unhandled Error, close the connection. + switch (policy.getBehavior()) + { + case SERVER: + close(StatusCode.SERVER_ERROR,t.getClass().getSimpleName()); + break; + case CLIENT: + close(StatusCode.POLICY_VIOLATION,t.getClass().getSimpleName()); + break; } } finally { + // Unset active MessageSink if this was a fin frame + if (frame.isFin() && activeMessageSink != null) + activeMessageSink = null; + Thread.currentThread().setContextClassLoader(old); } } @@ -411,7 +757,8 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { LOG.debug("notifyClose({},{})",statusCode,reason); } - websocket.onClose(new CloseInfo(statusCode,reason)); + if (onCloseFunction != null) + onCloseFunction.apply(new CloseInfo(statusCode,reason)); } public void notifyError(Throwable cause) @@ -427,7 +774,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem @Override public void onOpened(Connection connection) { - if(LOG_OPEN.isDebugEnabled()) + if (LOG_OPEN.isDebugEnabled()) LOG_OPEN.debug("[{}] {}.onOpened()",policy.getBehavior(),this.getClass().getSimpleName()); open(); } @@ -470,17 +817,12 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem } } - public WebSocketRemoteEndpoint newRemoteEndpoint(LogicalConnection connection, OutgoingFrames outgoingFrames, BatchMode batchMode) - { - return new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode()); - } - /** * Open/Activate the session */ public void open() { - if(LOG_OPEN.isDebugEnabled()) + if (LOG_OPEN.isDebugEnabled()) LOG_OPEN.debug("[{}] {}.open()",policy.getBehavior(),this.getClass().getSimpleName()); if (remote != null) @@ -489,18 +831,19 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem return; } - try(ThreadClassLoaderScope scope = new ThreadClassLoaderScope(classLoader)) + try (ThreadClassLoaderScope scope = new ThreadClassLoaderScope(classLoader)) { // Upgrade success connection.getIOState().onConnected(); // Connect remote - remote = remoteEndpointFactory.newRemoteEndpoint(connection,outgoingHandler,getBatchMode()); - if(LOG_OPEN.isDebugEnabled()) + remote = new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode()); + if (LOG_OPEN.isDebugEnabled()) LOG_OPEN.debug("[{}] {}.open() remote={}",policy.getBehavior(),this.getClass().getSimpleName(),remote); // Open WebSocket - websocket.openSession(this); + if (onOpenFunction != null) + onOpenFunction.apply(this); // Open connection connection.getIOState().onOpened(); @@ -521,7 +864,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem // Exception on end-user WS-Endpoint. // Fast-fail & close connection with reason. int statusCode = StatusCode.SERVER_ERROR; - if(policy.getBehavior() == WebSocketBehavior.CLIENT) + if (policy.getBehavior() == WebSocketBehavior.CLIENT) { statusCode = StatusCode.POLICY_VIOLATION; } @@ -591,7 +934,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem */ public BatchMode getBatchMode() { - return BatchMode.AUTO; + return this.batchmode; } @Override @@ -599,11 +942,10 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Rem { StringBuilder builder = new StringBuilder(); builder.append("WebSocketSession["); - builder.append("websocket=").append(websocket); + builder.append("websocket=").append(endpoint.getClass().getName()); builder.append(",behavior=").append(policy.getBehavior()); builder.append(",connection=").append(connection); builder.append(",remote=").append(remote); - builder.append(",incoming=").append(incomingHandler); builder.append(",outgoing=").append(outgoingHandler); builder.append("]"); return builder.toString(); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionFactory.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionFactory.java index e46a2313256..18cd0452344 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionFactory.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSessionFactory.java @@ -20,9 +20,8 @@ package org.eclipse.jetty.websocket.common; import java.net.URI; -import org.eclipse.jetty.websocket.common.events.EventDriver; -import org.eclipse.jetty.websocket.common.events.JettyAnnotatedEventDriver; -import org.eclipse.jetty.websocket.common.events.JettyListenerEventDriver; +import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; /** @@ -38,13 +37,13 @@ public class WebSocketSessionFactory implements SessionFactory } @Override - public boolean supports(EventDriver websocket) + public boolean supports(Object websocket) { - return (websocket instanceof JettyAnnotatedEventDriver) || (websocket instanceof JettyListenerEventDriver); + return (websocket instanceof WebSocketConnectionListener) || (websocket.getClass().getAnnotation(WebSocket.class) != null); } @Override - public WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection) + public WebSocketSession createSession(URI requestURI, Object websocket, LogicalConnection connection) { return new WebSocketSession(containerScope, requestURI,websocket,connection); } 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 deleted file mode 100644 index afb8b4871ff..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java +++ /dev/null @@ -1,267 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.Utf8Appendable.NotUtf8Exception; -import org.eclipse.jetty.util.component.AbstractLifeCycle; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.api.BatchMode; -import org.eclipse.jetty.websocket.api.CloseException; -import org.eclipse.jetty.websocket.api.StatusCode; -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.WebSocketSession; -import org.eclipse.jetty.websocket.common.frames.CloseFrame; -import org.eclipse.jetty.websocket.common.message.MessageAppender; - -/** - * EventDriver is the main interface between the User's WebSocket POJO and the internal jetty implementation of WebSocket. - */ -public abstract class AbstractEventDriver extends AbstractLifeCycle implements IncomingFrames, EventDriver -{ - private static final Logger LOG = Log.getLogger(AbstractEventDriver.class); - protected final Logger TARGET_LOG; - protected WebSocketPolicy policy; - protected final Object websocket; - protected WebSocketSession session; - protected MessageAppender activeMessage; - - public AbstractEventDriver(WebSocketPolicy policy, Object websocket) - { - this.policy = policy; - this.websocket = websocket; - this.TARGET_LOG = Log.getLogger(websocket.getClass()); - } - - protected void appendMessage(ByteBuffer buffer, boolean fin) throws IOException - { - activeMessage.appendFrame(buffer,fin); - - if (fin) - { - activeMessage.messageComplete(); - activeMessage = null; - } - } - - protected void dispatch(Runnable runnable) - { - session.dispatch(runnable); - } - - @Override - public WebSocketPolicy getPolicy() - { - return policy; - } - - @Override - public WebSocketSession getSession() - { - return session; - } - - @Override - public final void incomingError(Throwable e) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("incomingError(" + e.getClass().getName() + ")",e); - } - - onError(e); - } - - @Override - public void incomingFrame(Frame frame) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("incomingFrame({})",frame); - } - - try - { - onFrame(frame); - - byte opcode = frame.getOpCode(); - switch (opcode) - { - case OpCode.CLOSE: - { - boolean validate = true; - CloseFrame closeframe = (CloseFrame)frame; - CloseInfo close = new CloseInfo(closeframe,validate); - - // process handshake - session.getConnection().getIOState().onCloseRemote(close); - - return; - } - case OpCode.PING: - { - if (LOG.isDebugEnabled()) - { - LOG.debug("PING: {}",BufferUtil.toDetailString(frame.getPayload())); - } - ByteBuffer pongBuf; - if (frame.hasPayload()) - { - pongBuf = ByteBuffer.allocate(frame.getPayload().remaining()); - BufferUtil.put(frame.getPayload().slice(),pongBuf); - BufferUtil.flipToFlush(pongBuf,0); - } - else - { - pongBuf = ByteBuffer.allocate(0); - } - onPing(frame.getPayload()); - session.getRemote().sendPong(pongBuf); - break; - } - case OpCode.PONG: - { - if (LOG.isDebugEnabled()) - { - LOG.debug("PONG: {}",BufferUtil.toDetailString(frame.getPayload())); - } - onPong(frame.getPayload()); - break; - } - case OpCode.BINARY: - { - onBinaryFrame(frame.getPayload(),frame.isFin()); - return; - } - case OpCode.TEXT: - { - onTextFrame(frame.getPayload(),frame.isFin()); - return; - } - case OpCode.CONTINUATION: - { - onContinuationFrame(frame.getPayload(),frame.isFin()); - return; - } - default: - { - if (LOG.isDebugEnabled()) - LOG.debug("Unhandled OpCode: {}",opcode); - } - } - } - 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 onContinuationFrame(ByteBuffer buffer, boolean fin) throws IOException - { - if (activeMessage == null) - { - throw new IOException("Out of order Continuation frame encountered"); - } - - appendMessage(buffer,fin); - } - - @Override - public void onPong(ByteBuffer buffer) - { - /* TODO: provide annotation in future */ - } - - @Override - public void onPing(ByteBuffer buffer) - { - /* TODO: provide annotation in future */ - } - - @Override - public BatchMode getBatchMode() - { - return null; - } - - @Override - public void openSession(WebSocketSession session) - { - if (LOG.isDebugEnabled()) - LOG.debug("openSession({})",session); - this.session = session; - this.session.getContainerScope().getObjectFactory().decorate(this.websocket); - try - { - this.onConnect(); - } - catch (Throwable t) - { - unhandled(t); - throw t; - } - } - - protected void terminateConnection(int statusCode, String rawreason) - { - if (LOG.isDebugEnabled()) - LOG.debug("terminateConnection({},{})",statusCode,rawreason); - session.close(statusCode,CloseFrame.truncate(rawreason)); - } - - private void unhandled(Throwable t) - { - TARGET_LOG.warn("Unhandled Error (closing connection)",t); - onError(t); - - if (t instanceof CloseException) - { - terminateConnection(((CloseException)t).getStatusCode(),t.getClass().getSimpleName()); - return; - } - - // 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 deleted file mode 100644 index 173e695e71a..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriver.java +++ /dev/null @@ -1,68 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.io.InputStream; -import java.io.Reader; -import java.nio.ByteBuffer; - -import org.eclipse.jetty.websocket.api.BatchMode; -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.WebSocketSession; - -public interface EventDriver extends IncomingFrames -{ - public WebSocketPolicy getPolicy(); - - public WebSocketSession getSession(); - - public BatchMode getBatchMode(); - - public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException; - - public void onBinaryMessage(byte[] data); - - public void onClose(CloseInfo close); - - public void onConnect(); - - public void onContinuationFrame(ByteBuffer buffer, boolean fin) throws IOException; - - public void onError(Throwable t); - - public void onFrame(Frame frame); - - public void onInputStream(InputStream stream) throws IOException; - - public void onPing(ByteBuffer buffer); - - public void onPong(ByteBuffer buffer); - - public void onReader(Reader reader) throws IOException; - - public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException; - - public void onTextMessage(String message); - - public void openSession(WebSocketSession session); -} 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 deleted file mode 100644 index 3f5620d32f4..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverFactory.java +++ /dev/null @@ -1,149 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.ArrayList; -import java.util.List; - -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.WebSocketListener; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; -import org.eclipse.jetty.websocket.api.annotations.WebSocket; - -/** - * Create EventDriver implementations. - */ -public class EventDriverFactory -{ - 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.implementations = new ArrayList<>(); - - addImplementation(new JettyListenerImpl()); - addImplementation(new JettyAnnotatedImpl()); - } - - public void addImplementation(EventDriverImpl impl) - { - if (implementations.contains(impl)) - { - LOG.warn("Ignoring attempt to add duplicate EventDriverImpl: " + impl); - return; - } - - implementations.add(impl); - } - - public void clearImplementations() - { - this.implementations.clear(); - } - - protected String getClassName(Object websocket) - { - return websocket.getClass().getName(); - } - - public List getImplementations() - { - return implementations; - } - - public boolean removeImplementation(EventDriverImpl impl) - { - return this.implementations.remove(impl); - } - - @Override - public String toString() - { - 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(); - } - - /** - * Wrap the given WebSocket object instance in a suitable EventDriver - * - * @param websocket - * the websocket instance to wrap. Must either implement {@link WebSocketListener} or be annotated with {@link WebSocket @WebSocket} - * @return appropriate EventDriver for this websocket instance. - */ - public EventDriver wrap(Object websocket) - { - if (websocket == null) - { - throw new InvalidWebSocketException("null websocket object"); - } - - for (EventDriverImpl impl : implementations) - { - if (impl.supports(websocket)) - { - try - { - return impl.create(websocket,policy.clonePolicy()); - } - catch (Throwable e) - { - throw new InvalidWebSocketException("Unable to create websocket",e); - } - } - } - - // Create a clear error message for the developer - StringBuilder err = new StringBuilder(); - err.append(getClassName(websocket)); - 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++) - { - EventDriverImpl impl = implementations.get(i); - if (i > 0) - { - err.append(" or "); - } - err.append("\n(").append(i + 1).append(") "); - err.append(impl.describeRule()); - } - - 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 deleted file mode 100644 index 651626ed6a4..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/EventDriverImpl.java +++ /dev/null @@ -1,58 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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 - * @throws Throwable - * if unable to create the EventDriver - */ - EventDriver create(Object websocket, WebSocketPolicy policy) throws Throwable; - - /** - * 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/JettyAnnotatedEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java deleted file mode 100644 index a95f4296773..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedEventDriver.java +++ /dev/null @@ -1,243 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.io.InputStream; -import java.io.Reader; -import java.nio.ByteBuffer; - -import org.eclipse.jetty.websocket.api.BatchMode; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; -import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.api.extensions.Frame; -import org.eclipse.jetty.websocket.common.CloseInfo; -import org.eclipse.jetty.websocket.common.message.MessageAppender; -import org.eclipse.jetty.websocket.common.message.MessageInputStream; -import org.eclipse.jetty.websocket.common.message.MessageReader; -import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage; -import org.eclipse.jetty.websocket.common.message.SimpleTextMessage; - -/** - * Handler for Annotated User WebSocket objects. - */ -public class JettyAnnotatedEventDriver extends AbstractEventDriver -{ - private final JettyAnnotatedMetadata events; - private boolean hasCloseBeenCalled = false; - private BatchMode batchMode; - - public JettyAnnotatedEventDriver(WebSocketPolicy policy, Object websocket, JettyAnnotatedMetadata events) - { - super(policy,websocket); - this.events = events; - - WebSocket anno = websocket.getClass().getAnnotation(WebSocket.class); - // Setup the policy - if (anno.maxTextMessageSize() > 0) - { - this.policy.setMaxTextMessageSize(anno.maxTextMessageSize()); - } - if (anno.maxBinaryMessageSize() > 0) - { - this.policy.setMaxBinaryMessageSize(anno.maxBinaryMessageSize()); - } - if (anno.inputBufferSize() > 0) - { - this.policy.setInputBufferSize(anno.inputBufferSize()); - } - if (anno.maxIdleTime() > 0) - { - this.policy.setIdleTimeout(anno.maxIdleTime()); - } - this.batchMode = anno.batchMode(); - } - - @Override - public BatchMode getBatchMode() - { - return this.batchMode; - } - - @Override - public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException - { - if (events.onBinary == null) - { - // not interested in binary events - return; - } - - if (activeMessage == null) - { - if (events.onBinary.isStreaming()) - { - activeMessage = new MessageInputStream(); - final MessageAppender msg = activeMessage; - dispatch(new Runnable() - { - @Override - public void run() - { - try - { - events.onBinary.call(websocket,session,msg); - } - catch (Throwable t) - { - // dispatched calls need to be reported - onError(t); - } - } - }); - } - else - { - activeMessage = new SimpleBinaryMessage(this); - } - } - - appendMessage(buffer,fin); - } - - @Override - public void onBinaryMessage(byte[] data) - { - if (events.onBinary != null) - { - events.onBinary.call(websocket,session,data,0,data.length); - } - } - - @Override - public void onClose(CloseInfo close) - { - if (hasCloseBeenCalled) - { - // avoid duplicate close events (possible when using harsh Session.disconnect()) - return; - } - hasCloseBeenCalled = true; - if (events.onClose != null) - { - events.onClose.call(websocket,session,close.getStatusCode(),close.getReason()); - } - } - - @Override - public void onConnect() - { - if (events.onConnect != null) - { - events.onConnect.call(websocket,session); - } - } - - @Override - public void onError(Throwable cause) - { - if (events.onError != null) - { - events.onError.call(websocket,session,cause); - } - } - - @Override - public void onFrame(Frame frame) - { - if (events.onFrame != null) - { - events.onFrame.call(websocket,session,frame); - } - } - - @Override - public void onInputStream(InputStream stream) - { - if (events.onBinary != null) - { - events.onBinary.call(websocket,session,stream); - } - } - - @Override - public void onReader(Reader reader) - { - if (events.onText != null) - { - events.onText.call(websocket,session,reader); - } - } - - @Override - public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException - { - if (events.onText == null) - { - // not interested in text events - return; - } - - if (activeMessage == null) - { - if (events.onText.isStreaming()) - { - activeMessage = new MessageReader(new MessageInputStream()); - final MessageAppender msg = activeMessage; - dispatch(new Runnable() - { - @Override - public void run() - { - try - { - events.onText.call(websocket,session,msg); - } - catch (Throwable t) - { - // dispatched calls need to be reported - onError(t); - } - } - }); - } - else - { - activeMessage = new SimpleTextMessage(this); - } - } - - appendMessage(buffer,fin); - } - - @Override - public void onTextMessage(String message) - { - if (events.onText != null) - { - events.onText.call(websocket,session,message); - } - } - - @Override - public String toString() - { - return String.format("%s[%s]", this.getClass().getSimpleName(), websocket); - } -} 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 deleted file mode 100644 index 4b84097e5a1..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedImpl.java +++ /dev/null @@ -1,65 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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(); - synchronized (this) - { - 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("%s [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 deleted file mode 100644 index c0b2383a359..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedMetadata.java +++ /dev/null @@ -1,53 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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 deleted file mode 100644 index 39440b5739b..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScanner.java +++ /dev/null @@ -1,171 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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) - { - if (LOG.isDebugEnabled()) - 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/JettyListenerEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java deleted file mode 100644 index cad31b0267c..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerEventDriver.java +++ /dev/null @@ -1,198 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.io.InputStream; -import java.io.Reader; -import java.nio.ByteBuffer; - -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; -import org.eclipse.jetty.websocket.api.WebSocketFrameListener; -import org.eclipse.jetty.websocket.api.WebSocketListener; -import org.eclipse.jetty.websocket.api.WebSocketPartialListener; -import org.eclipse.jetty.websocket.api.WebSocketPingPongListener; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; -import org.eclipse.jetty.websocket.api.extensions.Frame; -import org.eclipse.jetty.websocket.api.extensions.Frame.Type; -import org.eclipse.jetty.websocket.common.CloseInfo; -import org.eclipse.jetty.websocket.common.frames.ReadOnlyDelegatedFrame; -import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage; -import org.eclipse.jetty.websocket.common.message.SimpleTextMessage; -import org.eclipse.jetty.websocket.common.util.Utf8PartialBuilder; - -/** - * Handler for {@link WebSocketListener} based User WebSocket implementations. - */ -public class JettyListenerEventDriver extends AbstractEventDriver -{ - private static final Logger LOG = Log.getLogger(JettyListenerEventDriver.class); - private final WebSocketConnectionListener listener; - private Utf8PartialBuilder utf8Partial; - private boolean hasCloseBeenCalled = false; - - public JettyListenerEventDriver(WebSocketPolicy policy, WebSocketConnectionListener listener) - { - super(policy,listener); - this.listener = listener; - } - - @Override - public void onBinaryFrame(ByteBuffer buffer, boolean fin) throws IOException - { - if (listener instanceof WebSocketListener) - { - if (activeMessage == null) - { - activeMessage = new SimpleBinaryMessage(this); - } - - appendMessage(buffer,fin); - } - - if (listener instanceof WebSocketPartialListener) - { - ((WebSocketPartialListener)listener).onWebSocketPartialBinary(buffer.slice().asReadOnlyBuffer(),fin); - } - } - - @Override - public void onBinaryMessage(byte[] data) - { - if (listener instanceof WebSocketListener) - { - ((WebSocketListener)listener).onWebSocketBinary(data,0,data.length); - } - } - - @Override - public void onClose(CloseInfo close) - { - if (hasCloseBeenCalled) - { - // avoid duplicate close events (possible when using harsh Session.disconnect()) - return; - } - hasCloseBeenCalled = true; - - int statusCode = close.getStatusCode(); - String reason = close.getReason(); - listener.onWebSocketClose(statusCode,reason); - } - - @Override - public void onConnect() - { - if (LOG.isDebugEnabled()) - LOG.debug("onConnect()"); - listener.onWebSocketConnect(session); - } - - @Override - public void onError(Throwable cause) - { - listener.onWebSocketError(cause); - } - - @Override - public void onFrame(Frame frame) - { - if (listener instanceof WebSocketFrameListener) - { - ((WebSocketFrameListener)listener).onWebSocketFrame(new ReadOnlyDelegatedFrame(frame)); - } - - if (listener instanceof WebSocketPingPongListener) - { - if (frame.getType() == Type.PING) - { - ((WebSocketPingPongListener)listener).onWebSocketPing(frame.getPayload().asReadOnlyBuffer()); - } - else if (frame.getType() == Type.PONG) - { - ((WebSocketPingPongListener)listener).onWebSocketPong(frame.getPayload().asReadOnlyBuffer()); - } - } - } - - @Override - public void onInputStream(InputStream stream) - { - /* not supported in Listener mode (yet) */ - } - - @Override - public void onReader(Reader reader) - { - /* not supported in Listener mode (yet) */ - } - - @Override - public void onTextFrame(ByteBuffer buffer, boolean fin) throws IOException - { - if (listener instanceof WebSocketListener) - { - if (activeMessage == null) - { - activeMessage = new SimpleTextMessage(this); - } - - appendMessage(buffer,fin); - } - - if (listener instanceof WebSocketPartialListener) - { - if (utf8Partial == null) - { - utf8Partial = new Utf8PartialBuilder(); - } - - String partial = utf8Partial.toPartialString(buffer); - - ((WebSocketPartialListener)listener).onWebSocketPartialText(partial,fin); - - if (fin) - { - partial = null; - } - } - } - - /** - * Whole Message event. - * - * @param message the whole message - */ - @Override - public void onTextMessage(String message) - { - if (listener instanceof WebSocketListener) - { - ((WebSocketListener)listener).onWebSocketText(message); - } - } - - @Override - public String toString() - { - return String.format("%s[%s]",JettyListenerEventDriver.class.getSimpleName(),listener.getClass().getName()); - } -} 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 deleted file mode 100644 index bd0011ae2b9..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/AbstractMethodAnnotationScanner.java +++ /dev/null @@ -1,195 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.websocket.api.InvalidWebSocketException; -import org.eclipse.jetty.websocket.common.events.ParamList; - -/** - * Basic scanner for Annotated Methods - * @param The type of metadata - */ -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(System.lineSeparator()); - - 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(System.lineSeparator()); - - 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(System.lineSeparator()); - - 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(System.lineSeparator()); - - 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 deleted file mode 100644 index ede7a20ccef..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/CallableMethod.java +++ /dev/null @@ -1,145 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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 java.util.Objects; - -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.common.util.ReflectUtils; - -/** - * 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) - { - Objects.requireNonNull(pojo, "Pojo cannot be null"); - Objects.requireNonNull(method, "Method cannot be null"); - this.pojo = pojo; - this.method = method; - this.paramTypes = method.getParameterTypes(); - } - - public Object call(Object obj, Object... args) - { - if ((this.pojo == null) || (this.method == null)) - { - LOG.warn("Cannot execute call: pojo={}, method={}",pojo,method); - return null; // no call event method determined - } - - if (obj == null) - { - String err = String.format("Cannot call %s on null object", this.method); - LOG.warn(new RuntimeException(err)); - return null; - } - - 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 - { - return this.method.invoke(obj,args); - } - catch (Throwable t) - { - String err = formatMethodCallError(args); - throw unwrapRuntimeException(err,t); - } - } - - private RuntimeException unwrapRuntimeException(String err, final Throwable t) - { - Throwable ret = t; - - while (ret instanceof InvocationTargetException) - { - ret = ((InvocationTargetException)ret).getCause(); - } - - if (ret instanceof RuntimeException) - { - return (RuntimeException)ret; - } - - return new RuntimeException(err,ret); - } - - public String formatMethodCallError(Object... args) - { - StringBuilder err = new StringBuilder(); - err.append("Cannot call method "); - err.append(ReflectUtils.toString(pojo,method)); - err.append(" with args: ["); - - boolean delim = false; - for (Object arg : args) - { - if (delim) - { - err.append(", "); - } - if (arg == null) - { - err.append(""); - } - else - { - err.append(arg.getClass().getName()); - } - delim = true; - } - err.append("]"); - return err.toString(); - } - - 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()); - } -} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java deleted file mode 100644 index 957ac01425b..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethod.java +++ /dev/null @@ -1,154 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.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.Session; -import org.eclipse.jetty.websocket.api.WebSocketException; -import org.eclipse.jetty.websocket.api.util.QuoteUtil; - -public class EventMethod -{ - private static final Logger LOG = Log.getLogger(EventMethod.class); - - private static Object[] dropFirstArg(Object[] args) - { - if (args.length == 1) - { - return new Object[0]; - } - Object ret[] = new Object[args.length - 1]; - System.arraycopy(args,1,ret,0,ret.length); - return ret; - } - - protected Class pojo; - protected Method method; - private boolean hasSession = false; - private boolean isStreaming = false; - private Class[] paramTypes; - - public EventMethod(Class pojo, Method method) - { - this.pojo = pojo; - this.paramTypes = method.getParameterTypes(); - this.method = method; - identifyPresentParamTypes(); - } - - public EventMethod(Class pojo, String methodName, Class... paramTypes) - { - try - { - this.pojo = pojo; - this.paramTypes = paramTypes; - this.method = pojo.getMethod(methodName,paramTypes); - identifyPresentParamTypes(); - } - catch (NoSuchMethodException | SecurityException e) - { - LOG.warn("Cannot use method {}({}): {}",methodName,paramTypes,e.getMessage()); - this.method = null; - } - } - - 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) - { - Object trimArgs[] = dropFirstArg(args); - call(obj,trimArgs); - 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, QuoteUtil.join(args,",")); - throw new WebSocketException(err,e); - } - } - - public Method getMethod() - { - return method; - } - - protected Class[] getParamTypes() - { - return this.paramTypes; - } - - private void identifyPresentParamTypes() - { - this.hasSession = false; - this.isStreaming = false; - - if (paramTypes == null) - { - return; - } - - for (Class paramType : paramTypes) - { - if (Session.class.isAssignableFrom(paramType)) - { - this.hasSession = true; - } - if (Reader.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType)) - { - this.isStreaming = true; - } - } - } - - public boolean isHasSession() - { - return hasSession; - } - - public boolean isStreaming() - { - return isStreaming; - } -} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java deleted file mode 100644 index 217b061cf39..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/EventMethods.java +++ /dev/null @@ -1,105 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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; - -/** - * A representation of the methods available to call for a particular class. - */ -public class EventMethods -{ - private Class pojoClass; - public EventMethod onConnect = null; - public EventMethod onClose = null; - public EventMethod onBinary = null; - public EventMethod onText = null; - public EventMethod onError = null; - public EventMethod onFrame = null; - - public EventMethods(Class pojoClass) - { - this.pojoClass = pojoClass; - } - - @Override - public boolean equals(Object obj) - { - if (this == obj) - { - return true; - } - if (obj == null) - { - return false; - } - if (getClass() != obj.getClass()) - { - return false; - } - EventMethods other = (EventMethods)obj; - if (pojoClass == null) - { - if (other.pojoClass != null) - { - return false; - } - } - else if (!pojoClass.getName().equals(other.pojoClass.getName())) - { - return false; - } - return true; - } - - public Class getPojoClass() - { - return pojoClass; - } - - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = (prime * result) + ((pojoClass == null)?0:pojoClass.getName().hashCode()); - return result; - } - - @Override - public String toString() - { - StringBuilder builder = new StringBuilder(); - builder.append("EventMethods [pojoClass="); - builder.append(pojoClass); - builder.append(", onConnect="); - builder.append(onConnect); - builder.append(", onClose="); - builder.append(onClose); - builder.append(", onBinary="); - builder.append(onBinary); - builder.append(", onText="); - builder.append(onText); - builder.append(", onException="); - builder.append(onError); - builder.append(", onFrame="); - builder.append(onFrame); - builder.append("]"); - return builder.toString(); - } - -} 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 deleted file mode 100644 index 9f352d700ce..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/annotated/OptionalSessionCallableMethod.java +++ /dev/null @@ -1,91 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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/functions/OnByteArrayFunction.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnByteArrayFunction.java new file mode 100644 index 00000000000..cbc0a822f36 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnByteArrayFunction.java @@ -0,0 +1,101 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; + +/** + * Jetty {@link WebSocket} {@link OnWebSocketMessage} method {@link Function} for BINARY/byte[] types + */ +public class OnByteArrayFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int BUFFER = 2; + private static final int OFFSET = 3; + private static final int LENGTH = 4; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(byte[].class).indexedAs(BUFFER)); + ARGBUILDER.addSignature(new ExactSignature(byte[].class,int.class,int.class).indexedAs(BUFFER,OFFSET,LENGTH)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,byte[].class).indexedAs(SESSION,BUFFER)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,byte[].class,int.class,int.class).indexedAs(SESSION,BUFFER,OFFSET,LENGTH)); + } + + public static DynamicArgs.Builder getDynamicArgsBuilder() + { + return ARGBUILDER; + } + + public static boolean hasMatchingSignature(Method method) + { + return ARGBUILDER.hasMatchingSignature(method); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public OnByteArrayFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnWebSocketMessage.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnWebSocketMessage.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,BUFFER,OFFSET,LENGTH); + } + + @Override + public Void apply(byte[] bin) + { + Object args[] = this.callable.toArgs(session,bin,0,bin.length); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnByteBufferFunction.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnByteBufferFunction.java new file mode 100644 index 00000000000..d669db06604 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnByteBufferFunction.java @@ -0,0 +1,98 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.function.Function; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * Jetty {@link WebSocket} {@link OnWebSocketMessage} method {@link Function} for BINARY/{@link ByteBuffer} types + */ +public class OnByteBufferFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int BUFFER = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(ByteBuffer.class).indexedAs(BUFFER)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,ByteBuffer.class).indexedAs(SESSION,BUFFER)); + } + + public static DynamicArgs.Builder getDynamicArgsBuilder() + { + return ARGBUILDER; + } + + public static boolean hasMatchingSignature(Method method) + { + return ARGBUILDER.hasMatchingSignature(method); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public OnByteBufferFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnWebSocketMessage.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnWebSocketMessage.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,BUFFER); + } + + @Override + public Void apply(ByteBuffer bin) + { + Object args[] = this.callable.toArgs(session,bin); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnCloseFunction.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnCloseFunction.java new file mode 100644 index 00000000000..15dbd7b4225 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnCloseFunction.java @@ -0,0 +1,91 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * Jetty {@link WebSocket} {@link OnWebSocketClose} method {@link Function} + */ +public class OnCloseFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int STATUS_CODE = 2; + private static final int REASON = 3; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature().indexedAs()); + ARGBUILDER.addSignature(new ExactSignature(Session.class).indexedAs(SESSION)); + ARGBUILDER.addSignature(new ExactSignature(int.class,String.class).indexedAs(STATUS_CODE,REASON)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,int.class,String.class).indexedAs(SESSION,STATUS_CODE,REASON)); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public OnCloseFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnWebSocketClose.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnWebSocketClose.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,STATUS_CODE,REASON); + } + + @Override + public Void apply(CloseInfo closeinfo) + { + Object args[] = this.callable.toArgs(session,closeinfo.getStatusCode(),closeinfo.getReason()); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call close method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnErrorFunction.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnErrorFunction.java new file mode 100644 index 00000000000..7d4b363a1c7 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnErrorFunction.java @@ -0,0 +1,87 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * Jetty {@link WebSocket} {@link OnWebSocketError} method {@link Function} + */ +public class OnErrorFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int CAUSE = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(Throwable.class).indexedAs(CAUSE)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,Throwable.class).indexedAs(SESSION,CAUSE)); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public OnErrorFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnWebSocketError.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnWebSocketError.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,CAUSE); + } + + @Override + public Void apply(Throwable cause) + { + Object args[] = this.callable.toArgs(session,cause); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call error method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnFrameFunction.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnFrameFunction.java new file mode 100644 index 00000000000..09381883540 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnFrameFunction.java @@ -0,0 +1,90 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.api.extensions.Frame; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * Jetty {@link WebSocket} {@link OnWebSocketFrame} method {@link Function} + */ +public class OnFrameFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int FRAME = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(Frame.class).indexedAs(FRAME)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,Frame.class).indexedAs(SESSION,FRAME)); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public OnFrameFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnWebSocketFrame.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnWebSocketFrame.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,FRAME); + } + + @Override + public Void apply(Frame frame) + { + WebSocketFrame copy = WebSocketFrame.copy(frame); + Object args[] = this.callable.toArgs(session,copy); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call frame method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnInputStreamFunction.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnInputStreamFunction.java new file mode 100644 index 00000000000..caa86c1d2be --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnInputStreamFunction.java @@ -0,0 +1,99 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * Jetty {@link WebSocket} {@link OnWebSocketMessage} method {@link Function} for BINARY/{@link InputStream} streaming + * types + */ +public class OnInputStreamFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int STREAM = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(InputStream.class).indexedAs(STREAM)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,InputStream.class).indexedAs(SESSION,STREAM)); + } + + public static DynamicArgs.Builder getDynamicArgsBuilder() + { + return ARGBUILDER; + } + + public static boolean hasMatchingSignature(Method method) + { + return ARGBUILDER.hasMatchingSignature(method); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public OnInputStreamFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnWebSocketMessage.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnWebSocketMessage.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,STREAM); + } + + @Override + public Void apply(InputStream stream) + { + Object args[] = this.callable.toArgs(session,stream); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnOpenFunction.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnOpenFunction.java new file mode 100644 index 00000000000..dd48a527a07 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnOpenFunction.java @@ -0,0 +1,85 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * Jetty {@link WebSocket} {@link OnWebSocketConnect} method {@link Function} + */ +public class OnOpenFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature().indexedAs()); + ARGBUILDER.addSignature(new ExactSignature(Session.class).indexedAs(SESSION)); + } + + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public OnOpenFunction(Object endpoint, Method method) + { + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnWebSocketConnect.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnWebSocketConnect.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION); + + } + + @Override + public Void apply(Session session) + { + Object args[] = this.callable.toArgs(session); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnReaderFunction.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnReaderFunction.java new file mode 100644 index 00000000000..801dd08ca28 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnReaderFunction.java @@ -0,0 +1,98 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.io.Reader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * Jetty {@link WebSocket} {@link OnWebSocketMessage} method {@link Function} for TEXT/{@link Reader} streaming types + */ +public class OnReaderFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int STREAM = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(Reader.class).indexedAs(STREAM)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,Reader.class).indexedAs(SESSION,STREAM)); + } + + public static DynamicArgs.Builder getDynamicArgsBuilder() + { + return ARGBUILDER; + } + + public static boolean hasMatchingSignature(Method method) + { + return ARGBUILDER.hasMatchingSignature(method); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public OnReaderFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnWebSocketMessage.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnWebSocketMessage.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,STREAM); + } + + @Override + public Void apply(Reader stream) + { + Object args[] = this.callable.toArgs(session,stream); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnTextFunction.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnTextFunction.java new file mode 100644 index 00000000000..2bed5bdd72c --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/functions/OnTextFunction.java @@ -0,0 +1,97 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.functions; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketException; +import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.common.InvalidSignatureException; +import org.eclipse.jetty.websocket.common.util.DynamicArgs; +import org.eclipse.jetty.websocket.common.util.ReflectUtils; +import org.eclipse.jetty.websocket.common.util.DynamicArgs.ExactSignature; + +/** + * Jetty {@link WebSocket} {@link OnWebSocketMessage} method {@link Function} for TEXT/{@link String} types + */ +public class OnTextFunction implements Function +{ + private static final DynamicArgs.Builder ARGBUILDER; + private static final int SESSION = 1; + private static final int TEXT = 2; + + static + { + ARGBUILDER = new DynamicArgs.Builder(); + ARGBUILDER.addSignature(new ExactSignature(String.class).indexedAs(TEXT)); + ARGBUILDER.addSignature(new ExactSignature(Session.class,String.class).indexedAs(SESSION,TEXT)); + } + + public static DynamicArgs.Builder getDynamicArgsBuilder() + { + return ARGBUILDER; + } + + public static boolean hasMatchingSignature(Method method) + { + return ARGBUILDER.hasMatchingSignature(method); + } + + private final Session session; + private final Object endpoint; + private final Method method; + private final DynamicArgs callable; + + public OnTextFunction(Session session, Object endpoint, Method method) + { + this.session = session; + this.endpoint = endpoint; + this.method = method; + + ReflectUtils.assertIsAnnotated(method,OnWebSocketMessage.class); + ReflectUtils.assertIsPublicNonStatic(method); + ReflectUtils.assertIsReturn(method,Void.TYPE); + + this.callable = ARGBUILDER.build(method); + if (this.callable == null) + { + throw InvalidSignatureException.build(method,OnWebSocketMessage.class,ARGBUILDER); + } + this.callable.setArgReferences(SESSION,TEXT); + } + + @Override + public Void apply(String text) + { + Object args[] = this.callable.toArgs(session,text); + try + { + method.invoke(endpoint,args); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) + { + throw new WebSocketException("Unable to call text message method " + ReflectUtils.toString(endpoint.getClass(),method),e); + } + return null; + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index 5a4d309c9c5..3dd1b6afc2c 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -45,6 +45,7 @@ import org.eclipse.jetty.websocket.api.CloseException; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.api.WebSocketPolicy.PolicyUpdate; import org.eclipse.jetty.websocket.api.WriteCallback; import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.api.extensions.Frame; @@ -58,7 +59,7 @@ import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; /** * Provides the implementation of {@link LogicalConnection} within the framework of the new {@link org.eclipse.jetty.io.Connection} framework of {@code jetty-io}. */ -public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, Connection.UpgradeTo, ConnectionStateListener, Dumpable +public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, Connection.UpgradeTo, ConnectionStateListener, PolicyUpdate, Dumpable { private final AtomicBoolean closed = new AtomicBoolean(); @@ -244,8 +245,16 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp this.flusher = new Flusher(bufferPool,generator,endp); this.setInputBufferSize(policy.getInputBufferSize()); this.setMaxIdleTimeout(policy.getIdleTimeout()); + this.policy.addListener(this); } - + + @Override + public void onPolicyUpdate(WebSocketPolicy policy) + { + this.setInputBufferSize(policy.getInputBufferSize()); + this.setMaxIdleTimeout(policy.getIdleTimeout()); + } + @Override public Executor getExecutor() { @@ -316,7 +325,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp endPoint.close(); } } - + protected void execute(Runnable task) { try @@ -434,6 +443,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp if (LOG.isDebugEnabled()) LOG.debug("{} onClose()",policy.getBehavior()); super.onClose(); + policy.removeListener(this); ioState.onDisconnected(); flusher.close(); } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/ByteArrayMessageSink.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/ByteArrayMessageSink.java new file mode 100644 index 00000000000..216358e4543 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/ByteArrayMessageSink.java @@ -0,0 +1,74 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.message; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.function.Function; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; + +public class ByteArrayMessageSink implements MessageSink +{ + private static final int BUFFER_SIZE = 65535; + private final WebSocketPolicy policy; + private final Function onMessageFunction; + private ByteArrayOutputStream out; + private int size; + + public ByteArrayMessageSink(WebSocketPolicy policy, Function onMessageFunction) + { + this.policy = policy; + this.onMessageFunction = onMessageFunction; + } + + @Override + public void accept(ByteBuffer payload, Boolean fin) + { + try + { + if (payload != null) + { + policy.assertValidBinaryMessageSize(size + payload.remaining()); + size += payload.remaining(); + + if (out == null) + out = new ByteArrayOutputStream(BUFFER_SIZE); + + BufferUtil.writeTo(payload,out); + } + } + catch (IOException e) + { + throw new RuntimeException("Unable to append Binary Message", e); + } + finally + { + if (fin) + { + onMessageFunction.apply(out.toByteArray()); + // reset + out = null; + size = 0; + } + } + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/ByteBufferMessageSink.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/ByteBufferMessageSink.java new file mode 100644 index 00000000000..2203d7c293e --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/ByteBufferMessageSink.java @@ -0,0 +1,75 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.message; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.function.Function; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; + +public class ByteBufferMessageSink implements MessageSink +{ + private static final int BUFFER_SIZE = 65535; + private final WebSocketPolicy policy; + private final Function onMessageFunction; + private ByteArrayOutputStream out; + private int size; + + public ByteBufferMessageSink(WebSocketPolicy policy, Function onMessageFunction) + { + this.policy = policy; + this.onMessageFunction = onMessageFunction; + } + + @Override + public void accept(ByteBuffer payload, Boolean fin) + { + try + { + if (payload != null) + { + policy.assertValidBinaryMessageSize(size + payload.remaining()); + size += payload.remaining(); + + if (out == null) + out = new ByteArrayOutputStream(BUFFER_SIZE); + + BufferUtil.writeTo(payload,out); + } + } + catch (IOException e) + { + throw new RuntimeException("Unable to append Binary Message", e); + } + finally + { + if (fin) + { + ByteBuffer bbuf = ByteBuffer.wrap(out.toByteArray()); + onMessageFunction.apply(bbuf); + // reset + out = null; + size = 0; + } + } + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/InputStreamMessageSink.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/InputStreamMessageSink.java new file mode 100644 index 00000000000..7891a450b42 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/InputStreamMessageSink.java @@ -0,0 +1,74 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.message; + +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.concurrent.Executor; +import java.util.function.Function; + +public class InputStreamMessageSink implements MessageSink +{ + private final Function onStreamFunction; + private final Executor executor; + private MessageInputStream stream; + + public InputStreamMessageSink(Executor executor, Function function) + { + this.executor = executor; + this.onStreamFunction = function; + } + + @Override + public void accept(ByteBuffer payload, Boolean fin) + { + try + { + boolean first = false; + + if (stream == null) + { + stream = new MessageInputStream(); + first = true; + } + + stream.accept(payload,fin); + if (first) + { + executor.execute(new Runnable() + { + @Override + public void run() + { + // processing of errors is the responsibility + // of the stream function + onStreamFunction.apply(stream); + } + }); + } + } + finally + { + if (fin) + { + stream = null; + } + } + } +} 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 c1f0438a21e..6ed26047f04 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 @@ -35,7 +35,7 @@ import org.eclipse.jetty.util.log.Logger; *

* An InputStream that can access a queue of ByteBuffer payloads, along with expected InputStream blocking behavior. */ -public class MessageInputStream extends InputStream implements MessageAppender +public class MessageInputStream extends InputStream implements MessageSink { private static final Logger LOG = Log.getLogger(MessageInputStream.class); private static final ByteBuffer EOF = ByteBuffer.allocate(0).asReadOnlyBuffer(); @@ -54,13 +54,13 @@ public class MessageInputStream extends InputStream implements MessageAppender { this.timeoutMs = timeoutMs; } - + @Override - public void appendFrame(ByteBuffer framePayload, boolean fin) throws IOException + public void accept(ByteBuffer payload, Boolean fin) { if (LOG.isDebugEnabled()) { - LOG.debug("Appending {} chunk: {}",fin?"final":"non-final",BufferUtil.toDetailString(framePayload)); + LOG.debug("Appending {} chunk: {}",fin?"final":"non-final",BufferUtil.toDetailString(payload)); } // If closed, we should just toss incoming payloads into the bit bucket. @@ -74,26 +74,26 @@ public class MessageInputStream extends InputStream implements MessageAppender // be processed after this method returns. try { - if (framePayload == null) + if (payload == null) { // skip if no payload return; } - int capacity = framePayload.remaining(); + int capacity = payload.remaining(); if (capacity <= 0) { // skip if no payload data to copy return; } // TODO: the copy buffer should be pooled too, but no buffer pool available from here. - ByteBuffer copy = framePayload.isDirect()?ByteBuffer.allocateDirect(capacity):ByteBuffer.allocate(capacity); - copy.put(framePayload).flip(); + ByteBuffer copy = payload.isDirect()?ByteBuffer.allocateDirect(capacity):ByteBuffer.allocate(capacity); + copy.put(payload).flip(); buffers.put(copy); } catch (InterruptedException e) { - throw new IOException(e); + throw new RuntimeException(e); } finally { @@ -126,14 +126,6 @@ public class MessageInputStream extends InputStream implements MessageAppender return false; } - @Override - public void messageComplete() - { - if (LOG.isDebugEnabled()) - LOG.debug("Message completed"); - buffers.offer(EOF); - } - @Override public int read() throws IOException { 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 a46b1d7a356..2e63f8a158a 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 @@ -18,18 +18,16 @@ package org.eclipse.jetty.websocket.common.message; -import java.io.IOException; import java.io.InputStreamReader; import java.nio.ByteBuffer; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** * Support class for reading a (single) WebSocket TEXT message via a Reader. *

- * In compliance to the WebSocket spec, this reader always uses the UTF8 {@link Charset}. + * In compliance to the WebSocket spec, this reader always uses the {@link StandardCharsets#UTF_8}. */ -public class MessageReader extends InputStreamReader implements MessageAppender +public class MessageReader extends InputStreamReader implements MessageSink { private final MessageInputStream stream; @@ -38,16 +36,10 @@ public class MessageReader extends InputStreamReader implements MessageAppender super(stream, StandardCharsets.UTF_8); this.stream = stream; } - + @Override - public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException + public void accept(ByteBuffer payload, Boolean fin) { - this.stream.appendFrame(payload, isLast); - } - - @Override - public void messageComplete() - { - this.stream.messageComplete(); + this.stream.accept(payload, fin); } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageAppender.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageSink.java similarity index 57% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageAppender.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageSink.java index 5beeeac92ec..4a9a493c64e 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageAppender.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/MessageSink.java @@ -18,30 +18,23 @@ package org.eclipse.jetty.websocket.common.message; -import java.io.IOException; import java.nio.ByteBuffer; +import java.util.function.BiConsumer; /** - * Appender for messages (used for multiple frames with continuations, and also to allow for streaming APIs) + * Sink consumer for messages (used for multiple frames with continuations, + * and also to allow for streaming APIs) */ -public interface MessageAppender +public interface MessageSink extends BiConsumer { /** - * Append the frame payload to the message. + * Consume the frame payload to the message. * - * @param framePayload + * @param payload * the frame payload to append. - * @param isLast - * flag indicating if this is the last part of the message or not. - * @throws IOException - * if unable to append the frame payload + * @param fin + * flag indicating if this is the final part of the message or not. */ - abstract void appendFrame(ByteBuffer framePayload, boolean isLast) throws IOException; - - /** - * Notification that message is to be considered complete. - *

- * Any cleanup or final actions should be taken here. - */ - abstract void messageComplete(); + @Override + void accept(ByteBuffer payload, Boolean fin); } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/package-info.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/PartialBinaryMessage.java similarity index 59% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/package-info.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/PartialBinaryMessage.java index ef22f34ddc4..d43a33124eb 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/package-info.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/PartialBinaryMessage.java @@ -16,8 +16,32 @@ // ======================================================================== // -/** - * Jetty WebSocket Common : Event Driver for WebSocket Object - */ -package org.eclipse.jetty.websocket.common.events; +package org.eclipse.jetty.websocket.common.message; +import java.nio.ByteBuffer; +import java.util.function.Function; + +/** + * {@link Function} argument for Partial Binary Messages + */ +public class PartialBinaryMessage +{ + private final ByteBuffer payload; + private final boolean fin; + + public PartialBinaryMessage(ByteBuffer payload, boolean fin) + { + this.payload = payload; + this.fin = fin; + } + + public ByteBuffer getPayload() + { + return payload; + } + + public boolean isFin() + { + return fin; + } +} 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/message/PartialBinaryMessageSink.java similarity index 52% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/JettyListenerImpl.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/PartialBinaryMessageSink.java index d841daf8e6d..5ffe8feb33c 100644 --- 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/message/PartialBinaryMessageSink.java @@ -16,30 +16,23 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.events; +package org.eclipse.jetty.websocket.common.message; -import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; -import org.eclipse.jetty.websocket.api.WebSocketListener; -import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import java.nio.ByteBuffer; +import java.util.function.Function; -public class JettyListenerImpl implements EventDriverImpl +public class PartialBinaryMessageSink implements MessageSink { - @Override - public EventDriver create(Object websocket, WebSocketPolicy policy) - { - WebSocketConnectionListener listener = (WebSocketConnectionListener)websocket; - return new JettyListenerEventDriver(policy,listener); - } + private final Function onBinaryFunction; - @Override - public String describeRule() + public PartialBinaryMessageSink(Function function) { - return "class implements " + WebSocketListener.class.getName(); + this.onBinaryFunction = function; } - + @Override - public boolean supports(Object websocket) + public void accept(ByteBuffer payload, Boolean fin) { - return (websocket instanceof WebSocketConnectionListener); + onBinaryFunction.apply(new PartialBinaryMessage(payload,fin)); } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ParamList.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/PartialTextMessage.java similarity index 62% rename from jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ParamList.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/PartialTextMessage.java index 7434d0758af..dc24b2da0b0 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/ParamList.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/PartialTextMessage.java @@ -16,18 +16,31 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.events; +package org.eclipse.jetty.websocket.common.message; -import java.util.ArrayList; +import java.util.function.Function; /** - * Simple class for representing a list of class arrays. + * {@link Function} argument for Partial Text Messages */ -@SuppressWarnings("serial") -public class ParamList extends ArrayList[]> +public class PartialTextMessage { - public void addParams(Class... paramTypes) + private final String payload; + private final boolean fin; + + public PartialTextMessage(String payload, boolean fin) { - this.add(paramTypes); + this.payload = payload; + this.fin = fin; + } + + public String getPayload() + { + return payload; + } + + public boolean isFin() + { + return fin; } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/PartialTextMessageSink.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/PartialTextMessageSink.java new file mode 100644 index 00000000000..fe3e4d05e0f --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/PartialTextMessageSink.java @@ -0,0 +1,51 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.message; + +import java.nio.ByteBuffer; +import java.util.function.Function; + +import org.eclipse.jetty.websocket.common.util.Utf8PartialBuilder; + +public class PartialTextMessageSink implements MessageSink +{ + private final Function onTextFunction; + private final Utf8PartialBuilder utf8Partial; + + public PartialTextMessageSink(Function function) + { + this.onTextFunction = function; + this.utf8Partial = new Utf8PartialBuilder(); + } + + @Override + public void accept(ByteBuffer payload, Boolean fin) + { + String partialText = utf8Partial.toPartialString(payload); + try + { + onTextFunction.apply(new PartialTextMessage(partialText,fin)); + } + finally + { + if (fin) + utf8Partial.reset(); + } + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/ReaderMessageSink.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/ReaderMessageSink.java new file mode 100644 index 00000000000..e1948b424df --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/ReaderMessageSink.java @@ -0,0 +1,74 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.message; + +import java.io.Reader; +import java.nio.ByteBuffer; +import java.util.concurrent.Executor; +import java.util.function.Function; + +public class ReaderMessageSink implements MessageSink +{ + private final Executor executor; + private final Function onStreamFunction; + private MessageReader stream; + + public ReaderMessageSink(Executor executor, Function function) + { + this.executor = executor; + this.onStreamFunction = function; + } + + @Override + public void accept(ByteBuffer payload, Boolean fin) + { + try + { + boolean first = false; + + if (stream == null) + { + stream = new MessageReader(new MessageInputStream()); + first = true; + } + + stream.accept(payload,fin); + if (first) + { + executor.execute(new Runnable() + { + @Override + public void run() + { + // processing of errors is the responsibility + // of the stream function + onStreamFunction.apply(stream); + } + }); + } + } + finally + { + if (fin) + { + stream = null; + } + } + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleTextMessage.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleTextMessage.java deleted file mode 100644 index c2895d2e4af..00000000000 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/SimpleTextMessage.java +++ /dev/null @@ -1,71 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.message; - -import java.io.IOException; -import java.nio.ByteBuffer; - -import org.eclipse.jetty.util.Utf8StringBuilder; -import org.eclipse.jetty.websocket.common.events.EventDriver; - -public class SimpleTextMessage implements MessageAppender -{ - private final EventDriver onEvent; - protected final Utf8StringBuilder utf; - private int size = 0; - protected boolean finished; - - public SimpleTextMessage(EventDriver onEvent) - { - this.onEvent = onEvent; - this.utf = new Utf8StringBuilder(1024); - size = 0; - finished = false; - } - - @Override - public void appendFrame(ByteBuffer payload, boolean isLast) throws IOException - { - if (finished) - { - throw new IOException("Cannot append to finished buffer"); - } - - if (payload == null) - { - // empty payload is valid - return; - } - - onEvent.getPolicy().assertValidTextMessageSize(size + payload.remaining()); - size += payload.remaining(); - - // allow for fast fail of BAD utf (incomplete utf will trigger on messageComplete) - this.utf.append(payload); - } - - @Override - public void messageComplete() - { - finished = true; - - // notify event - onEvent.onTextMessage(utf.toString()); - } -} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/StringMessageSink.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/StringMessageSink.java new file mode 100644 index 00000000000..2288b8b970b --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/message/StringMessageSink.java @@ -0,0 +1,70 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.message; + +import java.nio.ByteBuffer; +import java.util.function.Function; + +import org.eclipse.jetty.util.Utf8StringBuilder; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; + +public class StringMessageSink implements MessageSink +{ + private final WebSocketPolicy policy; + private final Function onMessageFunction; + private Utf8StringBuilder utf; + private int size = 0; + + public StringMessageSink(WebSocketPolicy policy, Function onMessageFunction) + { + this.policy = policy; + this.onMessageFunction = onMessageFunction; + size = 0; + } + + @Override + public void accept(ByteBuffer payload, Boolean fin) + { + try + { + if (payload != null) + { + policy.assertValidTextMessageSize(size + payload.remaining()); + size += payload.remaining(); + + if (utf == null) + utf = new Utf8StringBuilder(1024); + + // allow for fast fail of BAD utf (incomplete utf will trigger on messageComplete) + utf.append(payload); + } + } + finally + { + if (fin) + { + // notify event + onMessageFunction.apply(utf.toString()); + // reset + size = 0; + utf = null; + } + } + } +} diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/DynamicArgs.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/DynamicArgs.java new file mode 100644 index 00000000000..9bae7acfcb9 --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/DynamicArgs.java @@ -0,0 +1,273 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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.util; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * Provide argument utilities for working with methods that + * have a dynamic assortment of arguments. + *

    + *
  1. Can identify a set of parameters as matching the Builder
  2. + *
  3. Can create a DynamicArgs for the matched signature
  4. + *
  5. Can create an argument array for the provided potential arguments, + * suitable to be used with {@link Method#invoke(Object, Object...)}
  6. + *
+ */ +public class DynamicArgs +{ + public static interface Signature + { + public boolean matches(Class[] types); + public void appendDescription(StringBuilder str); + public Object[] toArgs(Object[] potentialArgs, int[] argReferences); + } + + public static class UnorderedSignature implements Signature + { + private final Class[] validParams; + private int[] validParamsIndex; + + public UnorderedSignature(Class ... classes) + { + this.validParams = classes; + } + + public UnorderedSignature indexedAs(int ... index) + { + this.validParamsIndex = index; + return this; + } + + public Class[] getParams() + { + return validParams; + } + + public int[] getIndex() + { + return validParamsIndex; + } + + public int size() + { + return validParams.length; + } + + public boolean matches(Class[] types) + { + // Matches if the provided types + // match the valid params in any order + + if (types.length != validParams.length) + return false; + int len = validParams.length; + for (int i = 0; i < len; i++) + { + if (!validParams[i].equals(types[i])) + return false; + } + return true; + } + + public void appendDescription(StringBuilder str) + { + str.append('('); + boolean delim = false; + for (Class type : validParams) + { + if (delim) + { + str.append(','); + } + str.append(' '); + str.append(type.getName()); + if (type.isArray()) + { + str.append("[]"); + } + delim = true; + } + str.append(')'); + } + + public Object[] toArgs(Object[] potentialArgs, int[] argReferences) + { + int slen = size(); + int plen = potentialArgs.length; + Object args[] = new Object[slen]; + for (int sidx = 0; sidx < slen; sidx++) + { + int wantIdx = validParamsIndex[sidx]; + for (int argIdx = 0; argIdx < plen; argIdx++) + { + if (argReferences[argIdx] == wantIdx) + args[sidx] = potentialArgs[argIdx]; + } + } + return args; + } + } + + public static class ExactSignature implements Signature + { + private final Class[] params; + private int[] index; + + public ExactSignature(Class ... classes) + { + this.params = classes; + } + + public ExactSignature indexedAs(int ... index) + { + this.index = index; + return this; + } + + public Class[] getParams() + { + return params; + } + + public int[] getIndex() + { + return index; + } + + public int size() + { + return params.length; + } + + public boolean matches(Class[] types) + { + if (types.length != params.length) + return false; + int len = params.length; + for (int i = 0; i < len; i++) + { + if (!params[i].equals(types[i])) + return false; + } + return true; + } + + public void appendDescription(StringBuilder str) + { + str.append('('); + boolean delim = false; + for (Class type : params) + { + if (delim) + { + str.append(','); + } + str.append(' '); + str.append(type.getName()); + if (type.isArray()) + { + str.append("[]"); + } + delim = true; + } + str.append(')'); + } + + public Object[] toArgs(Object[] potentialArgs, int[] argReferences) + { + int slen = size(); + int plen = potentialArgs.length; + Object args[] = new Object[slen]; + for (int sidx = 0; sidx < slen; sidx++) + { + int wantIdx = index[sidx]; + for (int argIdx = 0; argIdx < plen; argIdx++) + { + if (argReferences[argIdx] == wantIdx) + args[sidx] = potentialArgs[argIdx]; + } + } + return args; + } + } + + public static class Builder + { + private List signatures = new ArrayList<>(); + + public DynamicArgs build(Method method) + { + Class paramTypes[] = method.getParameterTypes(); + for (Signature sig : signatures) + { + if (sig.matches(paramTypes)) + { + return new DynamicArgs(sig); + } + } + + return null; + } + + public boolean hasMatchingSignature(Method method) + { + Class paramTypes[] = method.getParameterTypes(); + for (Signature sig : signatures) + { + if (sig.matches(paramTypes)) + { + return true; + } + } + return false; + } + + public Builder addSignature(Signature sig) + { + signatures.add(sig); + return this; + } + + public List getSignatures() + { + return this.signatures; + } + } + + private final Signature signature; + private int argReferences[]; + + public DynamicArgs(Signature sig) + { + this.signature = sig; + } + + public void setArgReferences(int... argIndex) + { + this.argReferences = argIndex; + } + + public Object[] toArgs(Object... potentialArgs) + { + return signature.toArgs(potentialArgs,argReferences); + } +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/utils/Primitives.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/Primitives.java similarity index 98% rename from jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/utils/Primitives.java rename to jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/Primitives.java index ea8694fe0dd..04e8d0aef20 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/utils/Primitives.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/Primitives.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.jsr356.utils; +package org.eclipse.jetty.websocket.common.util; import java.util.Collections; import java.util.HashMap; diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java index f7f686f128f..c31dfba0b6b 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java @@ -18,12 +18,18 @@ package org.eclipse.jetty.websocket.common.util; +import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.websocket.api.InvalidWebSocketException; +import org.eclipse.jetty.websocket.common.DuplicateAnnotationException; public class ReflectUtils { @@ -125,6 +131,103 @@ public class ReflectUtils return sb; } + public static void assertIsAnnotated(Method method, Class annoClass) + { + if (method.getAnnotation(annoClass) == null) + { + StringBuilder err = new StringBuilder(); + err.append("Method does not declare required @"); + err.append(annoClass.getName()); + err.append(" annotation: "); + err.append(method); + + throw new InvalidWebSocketException(err.toString()); + } + } + + public static 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(System.lineSeparator()); + + 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(System.lineSeparator()); + + err.append("Method modifier must not be static"); + + throw new InvalidWebSocketException(err.toString()); + } + } + + public static 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(System.lineSeparator()); + + err.append("Return type must be ").append(type); + + throw new InvalidWebSocketException(err.toString()); + } + } + + public static Method findAnnotatedMethod(Class pojo, Class anno) + { + Method methods[] = findAnnotatedMethods(pojo,anno); + if(methods == null) { + return null; + } + + if(methods.length > 1) + { + throw DuplicateAnnotationException.build(pojo,anno,methods); + } + + return methods[0]; + } + + public static Method[] findAnnotatedMethods(Class pojo, Class anno) + { + List methods = null; + Class clazz = pojo; + + while ((clazz != null) && Object.class.isAssignableFrom(clazz)) + { + for (Method method : clazz.getDeclaredMethods()) + { + if (method.getAnnotation(anno) != null) + { + if (methods == null) + methods = new ArrayList<>(); + methods.add(method); + } + } + clazz = clazz.getSuperclass(); + } + + if (methods == null) + return null; + int len = methods.size(); + return methods.toArray(new Method[len]); + } + /** * Given a Base (concrete) Class, find the interface specified, and return its concrete Generic class declaration. * @@ -186,6 +289,26 @@ public class ReflectUtils } } + public static 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 static boolean resolveGenericRef(GenericRef ref, Class clazz, Type type) { if (type instanceof Class) @@ -344,7 +467,26 @@ public class ReflectUtils public static String toString(Class pojo, Method method) { StringBuilder str = new StringBuilder(); + + append(str,pojo,method); + return str.toString(); + } + + public static String trimClassName(String name) + { + int idx = name.lastIndexOf('.'); + name = name.substring(idx + 1); + idx = name.lastIndexOf('$'); + if (idx >= 0) + { + name = name.substring(idx + 1); + } + return name; + } + + public static void append(StringBuilder str, Class pojo, Method method) + { // method modifiers int mod = method.getModifiers() & Modifier.methodModifiers(); if (mod != 0) @@ -356,9 +498,12 @@ public class ReflectUtils Type retType = method.getGenericReturnType(); appendTypeName(str,retType,false).append(' '); - // class name - str.append(pojo.getName()); - str.append("#"); + if (pojo != null) + { + // class name + str.append(pojo.getName()); + str.append("#"); + } // method name str.append(method.getName()); @@ -376,20 +521,12 @@ public class ReflectUtils } } str.append(')'); - + // TODO: show exceptions? - return str.toString(); } - public static String trimClassName(String name) + public static void append(StringBuilder str, Method method) { - int idx = name.lastIndexOf('.'); - name = name.substring(idx + 1); - idx = name.lastIndexOf('$'); - if (idx >= 0) - { - name = name.substring(idx + 1); - } - return name; + append(str, null, method); } } \ No newline at end of file diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/Utf8PartialBuilder.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/Utf8PartialBuilder.java index 9b0e0e08a94..cc13e475951 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/Utf8PartialBuilder.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/Utf8PartialBuilder.java @@ -32,20 +32,12 @@ import org.eclipse.jetty.util.Utf8StringBuilder; */ public class Utf8PartialBuilder { - private final StringBuilder str; - private final Utf8Appendable utf8; + private StringBuilder str; + private Utf8Appendable utf8; public Utf8PartialBuilder() { - this.str = new StringBuilder(); - this.utf8 = new Utf8Appendable(str) - { - @Override - public int length() - { - return str.length(); - } - }; + reset(); } public String toPartialString(ByteBuffer buf) @@ -60,4 +52,17 @@ public class Utf8PartialBuilder str.setLength(0); return ret; } + + public void reset() + { + this.str = new StringBuilder(); + this.utf8 = new Utf8Appendable(str) + { + @Override + public int length() + { + return str.length(); + } + }; + } } diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AdapterConnectCloseSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AdapterConnectCloseSocket.java index ca08bf07d01..e810d5687a5 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/AdapterConnectCloseSocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/AdapterConnectCloseSocket.java @@ -20,7 +20,7 @@ package examples; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketAdapter; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; public class AdapterConnectCloseSocket extends WebSocketAdapter { diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryArraySocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryArraySocket.java index 73b758898e4..74b455685d8 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryArraySocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryArraySocket.java @@ -23,7 +23,7 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; @WebSocket public class AnnotatedBinaryArraySocket diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java index c33deed2191..fae57ca5e20 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedBinaryStreamSocket.java @@ -25,7 +25,7 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; @WebSocket public class AnnotatedBinaryStreamSocket diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedFramesSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedFramesSocket.java index ad0184a2684..9c8cfc6cf28 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedFramesSocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedFramesSocket.java @@ -24,7 +24,7 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame; import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.api.extensions.Frame; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; @WebSocket public class AnnotatedFramesSocket diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedStreamingSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedStreamingSocket.java index 9cb8cb57021..6c4ca5d7aa3 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedStreamingSocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedStreamingSocket.java @@ -28,7 +28,7 @@ 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; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; @WebSocket public class AnnotatedStreamingSocket diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextSocket.java index 8f01fd597f1..7100f2ee467 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextSocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextSocket.java @@ -24,7 +24,7 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; @WebSocket public class AnnotatedTextSocket diff --git a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextStreamSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextStreamSocket.java index ff821ce3ed4..8a28826d59b 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextStreamSocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/AnnotatedTextStreamSocket.java @@ -25,7 +25,7 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; @WebSocket public class AnnotatedTextStreamSocket diff --git a/jetty-websocket/websocket-common/src/test/java/examples/ListenerBasicSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/ListenerBasicSocket.java index 40429f285b3..1544a222c1e 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/ListenerBasicSocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/ListenerBasicSocket.java @@ -20,7 +20,7 @@ package examples; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketListener; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; public class ListenerBasicSocket implements WebSocketListener { diff --git a/jetty-websocket/websocket-common/src/test/java/examples/ListenerFrameSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/ListenerFrameSocket.java index 1c0c71f6adc..6376d0af4ca 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/ListenerFrameSocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/ListenerFrameSocket.java @@ -21,7 +21,7 @@ package examples; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketFrameListener; import org.eclipse.jetty.websocket.api.extensions.Frame; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; public class ListenerFrameSocket implements WebSocketFrameListener { diff --git a/jetty-websocket/websocket-common/src/test/java/examples/ListenerPartialSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/ListenerPartialSocket.java index c343f5ef621..145d10aceac 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/ListenerPartialSocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/ListenerPartialSocket.java @@ -22,7 +22,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketPartialListener; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; public class ListenerPartialSocket implements WebSocketPartialListener { diff --git a/jetty-websocket/websocket-common/src/test/java/examples/ListenerPingPongSocket.java b/jetty-websocket/websocket-common/src/test/java/examples/ListenerPingPongSocket.java index b0cd5ff0ba6..85a1546b077 100644 --- a/jetty-websocket/websocket-common/src/test/java/examples/ListenerPingPongSocket.java +++ b/jetty-websocket/websocket-common/src/test/java/examples/ListenerPingPongSocket.java @@ -22,7 +22,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketPingPongListener; -import org.eclipse.jetty.websocket.common.events.EventCapture; +import org.eclipse.jetty.websocket.common.test.EventCapture; public class ListenerPingPongSocket implements WebSocketPingPongListener { diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/AnnotatedEndpointDiscoverTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/AnnotatedEndpointDiscoverTest.java new file mode 100644 index 00000000000..8a86bce8ccd --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/AnnotatedEndpointDiscoverTest.java @@ -0,0 +1,276 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 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; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.eclipse.jetty.websocket.api.InvalidWebSocketException; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +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.io.LocalWebSocketSession; +import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope; +import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TestName; + +import examples.AnnotatedBinaryArraySocket; +import examples.AnnotatedBinaryStreamSocket; +import examples.AnnotatedTextSocket; +import examples.AnnotatedTextStreamSocket; + +public class AnnotatedEndpointDiscoverTest +{ + private WebSocketContainerScope containerScope = new SimpleContainerScope(WebSocketPolicy.newServerPolicy()); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TestName testname = new TestName(); + + public LocalWebSocketSession createSession(Object endpoint) + { + return new LocalWebSocketSession(containerScope,testname,endpoint); + } + + /** + * Test Case for bad declaration (duplicate OnWebSocketBinary declarations) + */ + @Test + public void testAnnotatedBadDuplicateBinarySocket() + { + // Should toss exception + thrown.expect(InvalidWebSocketException.class); + thrown.expectMessage(containsString("Cannot replace previously assigned Binary Message Handler with ")); + createSession(new BadDuplicateBinarySocket()); + } + + /** + * Test Case for bad declaration (duplicate frame type methods) + */ + @Test + public void testAnnotatedBadDuplicateFrameSocket() + { + // Should toss exception + thrown.expect(InvalidWebSocketException.class); + thrown.expectMessage(containsString("Duplicate @OnWebSocketFrame")); + createSession(new BadDuplicateFrameSocket()); + } + + /** + * Test Case for bad declaration a method with a non-void return type + */ + @Test + public void testAnnotatedBadSignature_NonVoidReturn() + { + // Should toss exception + thrown.expect(InvalidWebSocketException.class); + thrown.expectMessage(containsString("must be void")); + createSession(new BadBinarySignatureSocket()); + } + + /** + * Test Case for bad declaration a method with a public static method + */ + @Test + public void testAnnotatedBadSignature_Static() + { + // Should toss exception + thrown.expect(InvalidWebSocketException.class); + thrown.expectMessage(containsString("must not be static")); + createSession(new BadTextSignatureSocket()); + } + + /** + * Test Case for socket for binary array messages + */ + @Test + public void testAnnotatedBinaryArraySocket() + { + LocalWebSocketSession session = createSession(new AnnotatedBinaryArraySocket()); + + String classId = AnnotatedBinaryArraySocket.class.getSimpleName(); + + assertThat(classId + ".onBinary",session.getOnBinarySink(),notNullValue()); + assertThat(classId + ".onClose",session.getOnCloseFunction(),notNullValue()); + assertThat(classId + ".onConnect",session.getOnOpenFunction(),notNullValue()); + + assertThat(classId + ".onException",session.getOnErrorFunction(),nullValue()); + assertThat(classId + ".onText",session.getOnTextSink(),nullValue()); + assertThat(classId + ".onFrame",session.getOnFrameFunction(),nullValue()); + } + + /** + * Test Case for socket for binary stream messages + */ + @Test + public void testAnnotatedBinaryStreamSocket() + { + LocalWebSocketSession session = createSession(new AnnotatedBinaryStreamSocket()); + + String classId = AnnotatedBinaryStreamSocket.class.getSimpleName(); + + assertThat(classId + ".onBinary",session.getOnBinarySink(),notNullValue()); + assertThat(classId + ".onClose",session.getOnCloseFunction(),notNullValue()); + assertThat(classId + ".onConnect",session.getOnOpenFunction(),notNullValue()); + + assertThat(classId + ".onException",session.getOnErrorFunction(),nullValue()); + assertThat(classId + ".onText",session.getOnTextSink(),nullValue()); + assertThat(classId + ".onFrame",session.getOnFrameFunction(),nullValue()); + } + + /** + * Test Case for no exceptions and 4 methods (3 methods from parent) + */ + @Test + public void testAnnotatedMyEchoBinarySocket() + { + LocalWebSocketSession session = createSession(new MyEchoBinarySocket()); + + String classId = MyEchoBinarySocket.class.getSimpleName(); + + assertThat(classId + ".onBinary",session.getOnBinarySink(),notNullValue()); + assertThat(classId + ".onClose",session.getOnCloseFunction(),notNullValue()); + assertThat(classId + ".onConnect",session.getOnOpenFunction(),notNullValue()); + assertThat(classId + ".onException",session.getOnErrorFunction(),nullValue()); + assertThat(classId + ".onText",session.getOnTextSink(),notNullValue()); + assertThat(classId + ".onFrame",session.getOnFrameFunction(),nullValue()); + } + + /** + * Test Case for no exceptions and 3 methods + */ + @Test + public void testAnnotatedMyEchoSocket() + { + LocalWebSocketSession session = createSession(new MyEchoSocket()); + + String classId = MyEchoSocket.class.getSimpleName(); + + assertThat(classId + ".onBinary",session.getOnBinarySink(),nullValue()); + assertThat(classId + ".onClose",session.getOnCloseFunction(),notNullValue()); + assertThat(classId + ".onConnect",session.getOnOpenFunction(),notNullValue()); + assertThat(classId + ".onException",session.getOnErrorFunction(),nullValue()); + assertThat(classId + ".onText",session.getOnTextSink(),notNullValue()); + assertThat(classId + ".onFrame",session.getOnFrameFunction(),nullValue()); + } + + /** + * Test Case for annotated for text messages w/connection param + */ + @Test + public void testAnnotatedMyStatelessEchoSocket() + { + LocalWebSocketSession session = createSession(new MyStatelessEchoSocket()); + + String classId = MyStatelessEchoSocket.class.getSimpleName(); + + assertThat(classId + ".onBinary",session.getOnBinarySink(),nullValue()); + assertThat(classId + ".onClose",session.getOnCloseFunction(),nullValue()); + assertThat(classId + ".onConnect",session.getOnOpenFunction(),nullValue()); + assertThat(classId + ".onException",session.getOnErrorFunction(),nullValue()); + assertThat(classId + ".onText",session.getOnTextSink(),notNullValue()); + assertThat(classId + ".onFrame",session.getOnFrameFunction(),nullValue()); + } + + /** + * Test Case for no exceptions and no methods + */ + @Test + public void testAnnotatedNoop() + { + LocalWebSocketSession session = createSession(new NoopSocket()); + + String classId = NoopSocket.class.getSimpleName(); + + assertThat(classId + ".onBinary",session.getOnBinarySink(),nullValue()); + assertThat(classId + ".onClose",session.getOnCloseFunction(),nullValue()); + assertThat(classId + ".onConnect",session.getOnOpenFunction(),nullValue()); + assertThat(classId + ".onException",session.getOnErrorFunction(),nullValue()); + assertThat(classId + ".onText",session.getOnTextSink(),nullValue()); + assertThat(classId + ".onFrame",session.getOnFrameFunction(),nullValue()); + } + + /** + * Test Case for no exceptions and 1 methods + */ + @Test + public void testAnnotatedOnFrame() + { + LocalWebSocketSession session = createSession(new FrameSocket()); + + String classId = FrameSocket.class.getSimpleName(); + + assertThat(classId + ".onBinary",session.getOnBinarySink(),nullValue()); + assertThat(classId + ".onClose",session.getOnCloseFunction(),nullValue()); + assertThat(classId + ".onConnect",session.getOnOpenFunction(),nullValue()); + assertThat(classId + ".onException",session.getOnErrorFunction(),nullValue()); + assertThat(classId + ".onText",session.getOnTextSink(),nullValue()); + assertThat(classId + ".onFrame",session.getOnFrameFunction(),notNullValue()); + } + + /** + * Test Case for socket for simple text messages + */ + @Test + public void testAnnotatedTextSocket() + { + LocalWebSocketSession session = createSession(new AnnotatedTextSocket()); + + String classId = AnnotatedTextSocket.class.getSimpleName(); + + assertThat(classId + ".onBinary",session.getOnBinarySink(),nullValue()); + assertThat(classId + ".onClose",session.getOnCloseFunction(),notNullValue()); + assertThat(classId + ".onConnect",session.getOnOpenFunction(),notNullValue()); + assertThat(classId + ".onException",session.getOnErrorFunction(),notNullValue()); + assertThat(classId + ".onText",session.getOnTextSink(),notNullValue()); + assertThat(classId + ".onFrame",session.getOnFrameFunction(),nullValue()); + } + + /** + * Test Case for socket for text stream messages + */ + @Test + public void testAnnotatedTextStreamSocket() + { + LocalWebSocketSession session = createSession(new AnnotatedTextStreamSocket()); + + String classId = AnnotatedTextStreamSocket.class.getSimpleName(); + + assertThat(classId + ".onBinary",session.getOnBinarySink(),nullValue()); + assertThat(classId + ".onClose",session.getOnCloseFunction(),notNullValue()); + assertThat(classId + ".onConnect",session.getOnOpenFunction(),notNullValue()); + assertThat(classId + ".onException",session.getOnErrorFunction(),nullValue()); + assertThat(classId + ".onText",session.getOnTextSink(),notNullValue()); + assertThat(classId + ".onFrame",session.getOnFrameFunction(),nullValue()); + } +} diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadDuplicateBinarySocket.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadDuplicateBinarySocket.java index 85959711656..a6f52b3ffd5 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadDuplicateBinarySocket.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/annotations/BadDuplicateBinarySocket.java @@ -42,7 +42,7 @@ public class BadDuplicateBinarySocket } /** - * Second method + * Second method (also binary) * @param stream the input stream */ @OnWebSocketMessage 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 deleted file mode 100644 index 2c951825cb8..00000000000 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverFactoryTest.java +++ /dev/null @@ -1,85 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.allOf; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.instanceOf; - -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.NotASocket; -import org.junit.Assert; -import org.junit.Test; - -import examples.AdapterConnectCloseSocket; -import examples.ListenerBasicSocket; - -public class EventDriverFactoryTest -{ - /** - * Test Case for no exceptions and 5 methods (extends WebSocketAdapter) - */ - @Test - public void testAdapterConnectCloseSocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - AdapterConnectCloseSocket socket = new AdapterConnectCloseSocket(); - EventDriver driver = factory.wrap(socket); - - String classId = AdapterConnectCloseSocket.class.getSimpleName(); - Assert.assertThat("EventDriver for " + classId,driver,instanceOf(JettyListenerEventDriver.class)); - } - - /** - * Test Case for bad declaration (duplicate OnWebSocketBinary declarations) - */ - @Test - public void testBadNotASocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - try - { - NotASocket bad = new NotASocket(); - // Should toss exception - factory.wrap(bad); - } - catch (InvalidWebSocketException e) - { - // Validate that we have clear error message to the developer - Assert.assertThat(e.getMessage(),allOf(containsString(WebSocketListener.class.getSimpleName()),containsString(WebSocket.class.getSimpleName()))); - } - } - - /** - * Test Case for no exceptions and 5 methods (implement WebSocketListener) - */ - @Test - public void testListenerBasicSocket() - { - EventDriverFactory factory = new EventDriverFactory(WebSocketPolicy.newClientPolicy()); - ListenerBasicSocket socket = new ListenerBasicSocket(); - EventDriver driver = factory.wrap(socket); - - String classId = ListenerBasicSocket.class.getSimpleName(); - 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/EventDriverTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java deleted file mode 100644 index 9df0fbb24c0..00000000000 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventDriverTest.java +++ /dev/null @@ -1,194 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.util.concurrent.TimeoutException; - -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.common.CloseInfo; -import org.eclipse.jetty.websocket.common.frames.BinaryFrame; -import org.eclipse.jetty.websocket.common.frames.PingFrame; -import org.eclipse.jetty.websocket.common.frames.TextFrame; -import org.eclipse.jetty.websocket.common.io.CloseableLocalWebSocketSession; -import org.eclipse.jetty.websocket.common.io.LocalWebSocketSession; -import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope; -import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; -import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; - -import examples.AdapterConnectCloseSocket; -import examples.AnnotatedBinaryArraySocket; -import examples.AnnotatedBinaryStreamSocket; -import examples.AnnotatedFramesSocket; -import examples.AnnotatedTextSocket; -import examples.ListenerBasicSocket; - -public class EventDriverTest -{ - @Rule - public TestName testname = new TestName(); - - @Rule - public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test"); - - private WebSocketContainerScope container; - - @Before - public void initContainer() - { - this.container = new SimpleContainerScope(WebSocketPolicy.newClientPolicy()); - } - - private Frame makeBinaryFrame(String content, boolean fin) - { - return new BinaryFrame().setPayload(content).setFin(fin); - } - - @Test - public void testAdapter_ConnectClose() throws Exception - { - AdapterConnectCloseSocket socket = new AdapterConnectCloseSocket(); - EventDriver driver = wrap(socket); - - try (LocalWebSocketSession conn = new CloseableLocalWebSocketSession(container,testname,driver)) - { - conn.open(); - driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame()); - - socket.capture.assertEventCount(2); - socket.capture.pop().assertEventStartsWith("onWebSocketConnect"); - socket.capture.pop().assertEventStartsWith("onWebSocketClose"); - } - } - - @Test - public void testAnnotated_ByteArray() throws Exception - { - AnnotatedBinaryArraySocket socket = new AnnotatedBinaryArraySocket(); - EventDriver driver = wrap(socket); - - try (LocalWebSocketSession conn = new CloseableLocalWebSocketSession(container,testname,driver)) - { - conn.open(); - driver.incomingFrame(makeBinaryFrame("Hello World",true)); - driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame()); - - socket.capture.assertEventCount(3); - socket.capture.pop().assertEventStartsWith("onConnect"); - socket.capture.pop().assertEvent("onBinary([11],0,11)"); - socket.capture.pop().assertEventStartsWith("onClose(1000,"); - } - } - - @Test - public void testAnnotated_Error() throws Exception - { - AnnotatedTextSocket socket = new AnnotatedTextSocket(); - EventDriver driver = wrap(socket); - - try (LocalWebSocketSession conn = new CloseableLocalWebSocketSession(container,testname,driver)) - { - conn.open(); - driver.incomingError(new WebSocketException("oof")); - driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame()); - - socket.capture.assertEventCount(3); - socket.capture.pop().assertEventStartsWith("onConnect"); - socket.capture.pop().assertEventStartsWith("onError(WebSocketException: oof)"); - socket.capture.pop().assertEventStartsWith("onClose(1000,"); - } - } - - @Test - public void testAnnotated_Frames() throws Exception - { - AnnotatedFramesSocket socket = new AnnotatedFramesSocket(); - EventDriver driver = wrap(socket); - - try (LocalWebSocketSession conn = new CloseableLocalWebSocketSession(container,testname,driver)) - { - conn.open(); - driver.incomingFrame(new PingFrame().setPayload("PING")); - driver.incomingFrame(new TextFrame().setPayload("Text Me")); - driver.incomingFrame(new BinaryFrame().setPayload("Hello Bin")); - driver.incomingFrame(new CloseInfo(StatusCode.SHUTDOWN,"testcase").asFrame()); - - socket.capture.assertEventCount(6); - socket.capture.pop().assertEventStartsWith("onConnect("); - socket.capture.pop().assertEventStartsWith("onFrame(PING["); - socket.capture.pop().assertEventStartsWith("onFrame(TEXT["); - socket.capture.pop().assertEventStartsWith("onFrame(BINARY["); - socket.capture.pop().assertEventStartsWith("onFrame(CLOSE["); - socket.capture.pop().assertEventStartsWith("onClose(1001,"); - } - } - - @Test - public void testAnnotated_InputStream() throws IOException, TimeoutException, InterruptedException - { - AnnotatedBinaryStreamSocket socket = new AnnotatedBinaryStreamSocket(); - EventDriver driver = wrap(socket); - - try (LocalWebSocketSession conn = new CloseableLocalWebSocketSession(container,testname,driver)) - { - conn.open(); - driver.incomingFrame(makeBinaryFrame("Hello World",true)); - driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame()); - - socket.capture.assertEventCount(3); - socket.capture.pop().assertEventStartsWith("onConnect"); - socket.capture.pop().assertEventRegex("^onBinary\\(.*InputStream.*"); - socket.capture.pop().assertEventStartsWith("onClose(1000,"); - } - } - - @Test - public void testListenerBasic_Text() throws Exception - { - ListenerBasicSocket socket = new ListenerBasicSocket(); - EventDriver driver = wrap(socket); - - try (LocalWebSocketSession conn = new CloseableLocalWebSocketSession(container,testname,driver)) - { - conn.start(); - conn.open(); - driver.incomingFrame(new TextFrame().setPayload("Hello World")); - driver.incomingFrame(new CloseInfo(StatusCode.NORMAL).asFrame()); - - socket.capture.assertEventCount(3); - socket.capture.pop().assertEventStartsWith("onWebSocketConnect"); - socket.capture.pop().assertEventStartsWith("onWebSocketText(\"Hello World\")"); - socket.capture.pop().assertEventStartsWith("onWebSocketClose(1000,"); - } - } - - private EventDriver wrap(Object websocket) - { - WebSocketPolicy policy = WebSocketPolicy.newServerPolicy(); - EventDriverFactory factory = new EventDriverFactory(policy); - return factory.wrap(websocket); - } -} 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 deleted file mode 100644 index 8d6b1de3501..00000000000 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/JettyAnnotatedScannerTest.java +++ /dev/null @@ -1,342 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2016 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.containsString; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; - -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-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java index 9f84f19b372..b0d3a9a0e3e 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/io/LocalWebSocketSession.java @@ -19,9 +19,14 @@ package org.eclipse.jetty.websocket.common.io; import java.net.URI; +import java.nio.ByteBuffer; +import java.util.function.Function; +import org.eclipse.jetty.websocket.api.Session; +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; +import org.eclipse.jetty.websocket.common.message.MessageSink; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; import org.eclipse.jetty.websocket.common.test.OutgoingFramesCapture; import org.junit.rules.TestName; @@ -31,9 +36,9 @@ public class LocalWebSocketSession extends WebSocketSession private String id; private OutgoingFramesCapture outgoingCapture; - public LocalWebSocketSession(WebSocketContainerScope containerScope, TestName testname, EventDriver driver) + public LocalWebSocketSession(WebSocketContainerScope containerScope, TestName testname, Object websocket) { - super(containerScope,URI.create("ws://localhost/LocalWebSocketSesssion/" + testname.getMethodName()),driver, + super(containerScope,URI.create("ws://localhost/LocalWebSocketSesssion/" + testname.getMethodName()),websocket, new LocalWebSocketConnection(testname,containerScope.getBufferPool())); this.id = testname.getMethodName(); outgoingCapture = new OutgoingFramesCapture(); @@ -50,6 +55,46 @@ public class LocalWebSocketSession extends WebSocketSession { return outgoingCapture; } + + public Function getOnOpenFunction() + { + return onOpenFunction; + } + + public Function getOnCloseFunction() + { + return onCloseFunction; + } + + public Function getOnErrorFunction() + { + return onErrorFunction; + } + + public Function getOnPingFunction() + { + return onPingFunction; + } + + public Function getOnPongFunction() + { + return onPongFunction; + } + + public Function getOnFrameFunction() + { + return onFrameFunction; + } + + public MessageSink getOnTextSink() + { + return onTextSink; + } + + public MessageSink getOnBinarySink() + { + return onBinarySink; + } @Override public String toString() diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java index 8975c5a5cf6..d123afe338d 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageInputStreamTest.java @@ -50,7 +50,7 @@ public class MessageInputStreamTest // Append a single message (simple, short) ByteBuffer payload = BufferUtil.toBuffer("Hello World",StandardCharsets.UTF_8); boolean fin = true; - stream.appendFrame(payload,fin); + stream.accept(payload,fin); // Read entire message it from the stream. byte buf[] = new byte[32]; @@ -82,14 +82,14 @@ public class MessageInputStreamTest startLatch.countDown(); boolean fin = false; TimeUnit.MILLISECONDS.sleep(200); - stream.appendFrame(BufferUtil.toBuffer("Saved",StandardCharsets.UTF_8),fin); + stream.accept(BufferUtil.toBuffer("Saved",StandardCharsets.UTF_8),fin); TimeUnit.MILLISECONDS.sleep(200); - stream.appendFrame(BufferUtil.toBuffer(" by ",StandardCharsets.UTF_8),fin); + stream.accept(BufferUtil.toBuffer(" by ",StandardCharsets.UTF_8),fin); fin = true; TimeUnit.MILLISECONDS.sleep(200); - stream.appendFrame(BufferUtil.toBuffer("Zero",StandardCharsets.UTF_8),fin); + stream.accept(BufferUtil.toBuffer("Zero",StandardCharsets.UTF_8),fin); } - catch (IOException | InterruptedException e) + catch (InterruptedException e) { hadError.set(true); e.printStackTrace(System.err); @@ -128,9 +128,9 @@ public class MessageInputStreamTest boolean fin = true; // wait for a little bit before populating buffers TimeUnit.MILLISECONDS.sleep(400); - stream.appendFrame(BufferUtil.toBuffer("I will conquer",StandardCharsets.UTF_8),fin); + stream.accept(BufferUtil.toBuffer("I will conquer",StandardCharsets.UTF_8),fin); } - catch (IOException | InterruptedException e) + catch (InterruptedException e) { hadError.set(true); e.printStackTrace(System.err); @@ -164,7 +164,7 @@ public class MessageInputStreamTest { // wait for a little bit before sending input closed TimeUnit.MILLISECONDS.sleep(400); - stream.messageComplete(); + // TODO: stream.messageComplete(); } catch (InterruptedException e) { @@ -194,9 +194,9 @@ public class MessageInputStreamTest ByteBuffer msg2 = ByteBuffer.allocate(0); // what is being tested ByteBuffer msg3 = BufferUtil.toBuffer("World",StandardCharsets.UTF_8); - stream.appendFrame(msg1,false); - stream.appendFrame(msg2,false); - stream.appendFrame(msg3,true); + stream.accept(msg1,false); + stream.accept(msg2,false); + stream.accept(msg3,true); // Read entire message it from the stream. byte buf[] = new byte[32]; @@ -218,9 +218,9 @@ public class MessageInputStreamTest ByteBuffer msg2 = null; // what is being tested ByteBuffer msg3 = BufferUtil.toBuffer("World",StandardCharsets.UTF_8); - stream.appendFrame(msg1,false); - stream.appendFrame(msg2,false); - stream.appendFrame(msg3,true); + stream.accept(msg1,false); + stream.accept(msg2,false); + stream.accept(msg3,true); // Read entire message it from the stream. byte buf[] = new byte[32]; diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java index f364cb97930..5e16134191d 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageOutputStreamTest.java @@ -22,6 +22,8 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import java.net.URI; +import java.net.URISyntaxException; import java.util.Arrays; import org.eclipse.jetty.toolchain.test.TestTracker; @@ -29,9 +31,9 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames; -import org.eclipse.jetty.websocket.common.events.EventDriver; -import org.eclipse.jetty.websocket.common.events.EventDriverFactory; +import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.io.FramePipes; +import org.eclipse.jetty.websocket.common.io.LocalWebSocketConnection; import org.eclipse.jetty.websocket.common.io.LocalWebSocketSession; import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; @@ -57,7 +59,7 @@ public class MessageOutputStreamTest public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test"); private WebSocketPolicy policy; - private TrackingSocket socket; + private TrackingSocket remoteSocket; private LocalWebSocketSession session; @After @@ -68,26 +70,23 @@ public class MessageOutputStreamTest } @Before - public void setupSession() throws Exception + public void setupSession() throws URISyntaxException { policy = WebSocketPolicy.newServerPolicy(); policy.setInputBufferSize(1024); policy.setMaxBinaryMessageBufferSize(1024); - // Event Driver factory - EventDriverFactory factory = new EventDriverFactory(policy); - // Container WebSocketContainerScope containerScope = new SimpleContainerScope(policy,bufferPool); - // local socket - EventDriver driver = factory.wrap(new TrackingSocket("local")); - // remote socket - socket = new TrackingSocket("remote"); - OutgoingFrames socketPipe = FramePipes.to(factory.wrap(socket)); + remoteSocket = new TrackingSocket("remote"); + WebSocketSession remoteSession = new LocalWebSocketSession(containerScope,testname,remoteSocket); + OutgoingFrames socketPipe = FramePipes.to(remoteSession); - session = new LocalWebSocketSession(containerScope,testname,driver); + // Local Session + TrackingSocket localSocket = new TrackingSocket("local"); + session = new LocalWebSocketSession(containerScope,testname,localSocket); session.setPolicy(policy); // talk to our remote socket @@ -108,8 +107,8 @@ public class MessageOutputStreamTest stream.write("World".getBytes("UTF-8")); } - Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1)); - String msg = socket.messageQueue.poll(); + Assert.assertThat("Socket.messageQueue.size",remoteSocket.messageQueue.size(),is(1)); + String msg = remoteSocket.messageQueue.poll(); Assert.assertThat("Message",msg,allOf(containsString("byte[11]"),containsString("Hello World"))); } @@ -121,8 +120,8 @@ public class MessageOutputStreamTest stream.write("Hello World".getBytes("UTF-8")); } - Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1)); - String msg = socket.messageQueue.poll(); + Assert.assertThat("Socket.messageQueue.size",remoteSocket.messageQueue.size(),is(1)); + String msg = remoteSocket.messageQueue.poll(); Assert.assertThat("Message",msg,allOf(containsString("byte[11]"),containsString("Hello World"))); } @@ -140,8 +139,8 @@ public class MessageOutputStreamTest stream.write(buf); } - Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1)); - String msg = socket.messageQueue.poll(); + Assert.assertThat("Socket.messageQueue.size",remoteSocket.messageQueue.size(),is(1)); + String msg = remoteSocket.messageQueue.poll(); Assert.assertThat("Message",msg,allOf(containsString("byte[" + bufsize + "]"),containsString("xxxo>>>"))); } } diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageWriterTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageWriterTest.java index 3d58434b5a8..ab29b760b95 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageWriterTest.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/message/MessageWriterTest.java @@ -20,6 +20,8 @@ package org.eclipse.jetty.websocket.common.message; import static org.hamcrest.Matchers.is; +import java.net.URI; +import java.net.URISyntaxException; import java.util.Arrays; import org.eclipse.jetty.toolchain.test.TestTracker; @@ -27,10 +29,9 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames; -import org.eclipse.jetty.websocket.common.events.EventDriver; -import org.eclipse.jetty.websocket.common.events.EventDriverFactory; +import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.io.FramePipes; -import org.eclipse.jetty.websocket.common.io.LocalWebSocketSession; +import org.eclipse.jetty.websocket.common.io.LocalWebSocketConnection; import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope; import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope; import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule; @@ -55,8 +56,8 @@ public class MessageWriterTest public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test"); private WebSocketPolicy policy; - private TrackingSocket socket; - private LocalWebSocketSession session; + private TrackingSocket remoteSocket; + private WebSocketSession session; @After public void closeSession() throws Exception @@ -66,26 +67,27 @@ public class MessageWriterTest } @Before - public void setupSession() throws Exception + public void setupSession() throws URISyntaxException { policy = WebSocketPolicy.newServerPolicy(); policy.setInputBufferSize(1024); policy.setMaxTextMessageBufferSize(1024); - // Event Driver factory - EventDriverFactory factory = new EventDriverFactory(policy); - // Container WebSocketContainerScope containerScope = new SimpleContainerScope(policy,bufferPool); - // local socket - EventDriver driver = factory.wrap(new TrackingSocket("local")); - // remote socket - socket = new TrackingSocket("remote"); - OutgoingFrames socketPipe = FramePipes.to(factory.wrap(socket)); + remoteSocket = new TrackingSocket("remote"); + URI remoteURI = new URI("ws://localhost/remote"); + LocalWebSocketConnection remoteConnection = new LocalWebSocketConnection(bufferPool); + WebSocketSession remoteSession = new WebSocketSession(containerScope,remoteURI,remoteSocket,remoteConnection); + OutgoingFrames socketPipe = FramePipes.to(remoteSession); - session = new LocalWebSocketSession(containerScope,testname,driver); + // Local Session + TrackingSocket localSocket = new TrackingSocket("local"); + URI localURI = new URI("ws://localhost/local"); + LocalWebSocketConnection localConnection = new LocalWebSocketConnection(bufferPool); + session = new WebSocketSession(containerScope,localURI,localSocket,localConnection); session.setPolicy(policy); // talk to our remote socket @@ -106,8 +108,8 @@ public class MessageWriterTest stream.write("World"); } - Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1)); - String msg = socket.messageQueue.poll(); + Assert.assertThat("Socket.messageQueue.size",remoteSocket.messageQueue.size(),is(1)); + String msg = remoteSocket.messageQueue.poll(); Assert.assertThat("Message",msg,is("Hello World")); } @@ -119,8 +121,8 @@ public class MessageWriterTest stream.append("Hello World"); } - Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1)); - String msg = socket.messageQueue.poll(); + Assert.assertThat("Socket.messageQueue.size",remoteSocket.messageQueue.size(),is(1)); + String msg = remoteSocket.messageQueue.poll(); Assert.assertThat("Message",msg,is("Hello World")); } @@ -139,8 +141,8 @@ public class MessageWriterTest stream.write(buf); } - Assert.assertThat("Socket.messageQueue.size",socket.messageQueue.size(),is(1)); - String msg = socket.messageQueue.poll(); + Assert.assertThat("Socket.messageQueue.size",remoteSocket.messageQueue.size(),is(1)); + String msg = remoteSocket.messageQueue.poll(); String expected = new String(buf); Assert.assertThat("Message",msg,is(expected)); } diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventCapture.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/EventCapture.java similarity index 98% rename from jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventCapture.java rename to jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/EventCapture.java index 9a5cad21d90..683393f3e90 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/events/EventCapture.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/EventCapture.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.websocket.common.events; +package org.eclipse.jetty.websocket.common.test; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java index baebd44b8d7..9f929f25b74 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerConnection.java @@ -34,10 +34,6 @@ public class WebSocketServerConnection extends AbstractWebSocketConnection imple public WebSocketServerConnection(EndPoint endp, Executor executor, Scheduler scheduler, WebSocketPolicy policy, ByteBufferPool bufferPool) { super(endp,executor,scheduler,policy,bufferPool); - if (policy.getIdleTimeout() > 0) - { - endp.setIdleTimeout(policy.getIdleTimeout()); - } } @Override 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 d0e1f20fa16..6c740635d43 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 @@ -66,8 +66,6 @@ import org.eclipse.jetty.websocket.common.LogicalConnection; import org.eclipse.jetty.websocket.common.SessionFactory; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.WebSocketSessionFactory; -import org.eclipse.jetty.websocket.common.events.EventDriver; -import org.eclipse.jetty.websocket.common.events.EventDriverFactory; import org.eclipse.jetty.websocket.common.extensions.ExtensionStack; import org.eclipse.jetty.websocket.common.extensions.WebSocketExtensionFactory; import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection; @@ -93,7 +91,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc private final List listeners = new CopyOnWriteArrayList<>(); private final String supportedVersions; private final WebSocketPolicy defaultPolicy; - private final EventDriverFactory eventDriverFactory; private final ByteBufferPool bufferPool; private final WebSocketExtensionFactory extensionFactory; private Executor executor; @@ -129,7 +126,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc this.registeredSocketClasses = new ArrayList<>(); this.defaultPolicy = policy; - this.eventDriverFactory = new EventDriverFactory(defaultPolicy); this.bufferPool = bufferPool; this.extensionFactory = new WebSocketExtensionFactory(this); @@ -206,8 +202,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc HttpConnection connection = (HttpConnection)request.getAttribute("org.eclipse.jetty.server.HttpConnection"); // Send the upgrade - EventDriver driver = eventDriverFactory.wrap(websocketPojo); - return upgrade(connection, sockreq, sockresp, driver); + return upgrade(connection, sockreq, sockresp, websocketPojo); } catch (URISyntaxException e) { @@ -247,7 +242,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc return new WebSocketServerFactory(policy, bufferPool); } - private WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection) + private WebSocketSession createSession(URI requestURI, Object websocket, LogicalConnection connection) { if (websocket == null) { @@ -333,11 +328,6 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc return objectFactory; } - public EventDriverFactory getEventDriverFactory() - { - return eventDriverFactory; - } - @Override public ExtensionFactory getExtensionFactory() { @@ -513,13 +503,13 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc * This method will not normally return, but will instead throw a UpgradeConnectionException, to exit HTTP handling and initiate WebSocket handling of the * connection. * - * @param http the raw http connection - * @param request The request to upgrade - * @param response The response to upgrade - * @param driver The websocket handler implementation to use + * @param http the raw http connection + * @param request the request to upgrade + * @param response the response to upgrade + * @param websocket the websocket endpoint instance * @throws IOException */ - private boolean upgrade(HttpConnection http, ServletUpgradeRequest request, ServletUpgradeResponse response, EventDriver driver) throws IOException + private boolean upgrade(HttpConnection http, ServletUpgradeRequest request, ServletUpgradeResponse response, Object websocket) throws IOException { if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade"))) { @@ -583,6 +573,9 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc // Use raw extension list from request extensionStack.negotiate(request.getExtensions()); } + + // Policy + WebSocketPolicy policy = getPolicy().clonePolicy(); // Get original HTTP connection EndPoint endp = http.getEndPoint(); @@ -591,9 +584,9 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc ByteBufferPool bufferPool = connector.getByteBufferPool(); // Setup websocket connection - AbstractWebSocketConnection wsConnection = new WebSocketServerConnection(endp, executor, scheduler, driver.getPolicy(), bufferPool); + AbstractWebSocketConnection wsConnection = new WebSocketServerConnection(endp, executor, scheduler, policy, bufferPool); - extensionStack.setPolicy(driver.getPolicy()); + extensionStack.setPolicy(policy); extensionStack.configure(wsConnection.getParser()); extensionStack.configure(wsConnection.getGenerator()); @@ -604,8 +597,8 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc } // Setup Session - WebSocketSession session = createSession(request.getRequestURI(), driver, wsConnection); - session.setPolicy(driver.getPolicy()); + WebSocketSession session = createSession(request.getRequestURI(), websocket, wsConnection); + session.setPolicy(policy); session.setUpgradeRequest(request); // set true negotiated extension list back to response response.setExtensions(extensionStack.getNegotiatedExtensions()); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java index f9dfe45e126..22d50cbfa98 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java @@ -257,7 +257,7 @@ public class WebSocketCloseTest { client.setProtocols("fastfail"); client.setTimeout(1,TimeUnit.SECONDS); - try (StacklessLogging scope = new StacklessLogging(FastFailSocket.class, WebSocketSession.class)) + try (StacklessLogging scope = new StacklessLogging(CloseServlet.class)) { client.connect(); client.sendStandardRequest(); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java index c185a14c251..2002447ede9 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java @@ -37,7 +37,6 @@ import org.eclipse.jetty.websocket.common.Generator; import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.Parser; import org.eclipse.jetty.websocket.common.WebSocketFrame; -import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.common.frames.BinaryFrame; import org.eclipse.jetty.websocket.common.frames.ContinuationFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; @@ -45,6 +44,7 @@ import org.eclipse.jetty.websocket.common.test.BlockheadClient; import org.eclipse.jetty.websocket.common.test.UnitGenerator; import org.eclipse.jetty.websocket.common.util.Hex; import org.eclipse.jetty.websocket.server.helper.RFCServlet; +import org.eclipse.jetty.websocket.server.helper.RFCSocket; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.junit.AfterClass; import org.junit.Assert; @@ -209,14 +209,14 @@ public class WebSocketServletRFCTest @Test public void testInternalError() throws Exception { - try (BlockheadClient client = new BlockheadClient(server.getServerUri()); - StacklessLogging stackless=new StacklessLogging(EventDriver.class)) + BlockheadClient client = new BlockheadClient(server.getServerUri()); + try { client.connect(); client.sendStandardRequest(); client.expectUpgradeResponse(); - try (StacklessLogging context = new StacklessLogging(EventDriver.class)) + try (StacklessLogging context = new StacklessLogging(RFCSocket.class)) { // Generate text frame client.write(new TextFrame().setPayload("CRASH")); @@ -228,6 +228,10 @@ public class WebSocketServletRFCTest Assert.assertThat("Close Frame.status code",close.getStatusCode(),is(StatusCode.SERVER_ERROR)); } } + finally + { + client.close(); + } } /** diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/MisbehavingClassTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/MisbehavingClassTest.java index e31036a9ab9..4eb7fd8f1a1 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/MisbehavingClassTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/misbehaving/MisbehavingClassTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.server.misbehaving; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; + import java.util.concurrent.TimeUnit; import org.eclipse.jetty.toolchain.test.EventQueue; @@ -29,7 +30,6 @@ import org.eclipse.jetty.websocket.api.StatusCode; 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; import org.eclipse.jetty.websocket.common.test.BlockheadClient; import org.eclipse.jetty.websocket.common.test.IBlockheadClient; import org.eclipse.jetty.websocket.server.SimpleServletServer; @@ -72,30 +72,32 @@ public class MisbehavingClassTest { client.setProtocols("listener-runtime-connect"); client.setTimeout(1,TimeUnit.SECONDS); + try (StacklessLogging scope = new StacklessLogging(BadSocketsServlet.class)) + { + ListenerRuntimeOnConnectSocket socket = badSocketsServlet.listenerRuntimeConnect; + socket.reset(); - ListenerRuntimeOnConnectSocket socket = badSocketsServlet.listenerRuntimeConnect; - socket.reset(); + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); - client.connect(); - client.sendStandardRequest(); - client.expectUpgradeResponse(); + EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); + WebSocketFrame frame = frames.poll(); + assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); + CloseInfo close = new CloseInfo(frame); + assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR)); - EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); - WebSocketFrame frame = frames.poll(); - assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); - CloseInfo close = new CloseInfo(frame); - assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR)); + client.write(close.asFrame()); // respond with close - client.write(close.asFrame()); // respond with close + // ensure server socket got close event + assertThat("Close Latch",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true)); + assertThat("closeStatusCode",socket.closeStatusCode,is(StatusCode.SERVER_ERROR)); - // ensure server socket got close event - assertThat("Close Latch",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true)); - assertThat("closeStatusCode",socket.closeStatusCode,is(StatusCode.SERVER_ERROR)); - - // Validate errors - assertThat("socket.onErrors",socket.errors.size(),is(1)); - Throwable cause = socket.errors.pop(); - assertThat("Error type",cause,instanceOf(RuntimeException.class)); + // Validate errors + assertThat("socket.onErrors",socket.errors.size(),is(1)); + Throwable cause = socket.errors.pop(); + assertThat("Error type",cause,instanceOf(ArrayIndexOutOfBoundsException.class)); + } } } @@ -107,30 +109,32 @@ public class MisbehavingClassTest { client.setProtocols("annotated-runtime-connect"); client.setTimeout(1,TimeUnit.SECONDS); + try (StacklessLogging scope = new StacklessLogging(BadSocketsServlet.class)) + { + AnnotatedRuntimeOnConnectSocket socket = badSocketsServlet.annotatedRuntimeConnect; + socket.reset(); - AnnotatedRuntimeOnConnectSocket socket = badSocketsServlet.annotatedRuntimeConnect; - socket.reset(); + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); - client.connect(); - client.sendStandardRequest(); - client.expectUpgradeResponse(); + EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); + WebSocketFrame frame = frames.poll(); + assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); + CloseInfo close = new CloseInfo(frame); + assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR)); - EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); - WebSocketFrame frame = frames.poll(); - assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); - CloseInfo close = new CloseInfo(frame); - assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR)); + client.write(close.asFrame()); // respond with close - client.write(close.asFrame()); // respond with close + // ensure server socket got close event + assertThat("Close Latch",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true)); + assertThat("closeStatusCode",socket.closeStatusCode,is(StatusCode.SERVER_ERROR)); - // ensure server socket got close event - assertThat("Close Latch",socket.closeLatch.await(1,TimeUnit.SECONDS),is(true)); - assertThat("closeStatusCode",socket.closeStatusCode,is(StatusCode.SERVER_ERROR)); - - // Validate errors - assertThat("socket.onErrors",socket.errors.size(),is(1)); - Throwable cause = socket.errors.pop(); - assertThat("Error type",cause,instanceOf(RuntimeException.class)); + // Validate errors + assertThat("socket.onErrors",socket.errors.size(),is(1)); + Throwable cause = socket.errors.pop(); + assertThat("Error type",cause,instanceOf(ArrayIndexOutOfBoundsException.class)); + } } } } diff --git a/pom.xml b/pom.xml index a44ab4139b0..4551370784a 100644 --- a/pom.xml +++ b/pom.xml @@ -794,7 +794,7 @@ javax.websocket javax.websocket-api - 1.0 + 1.1 javax.annotation