diff --git a/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java index 5ba4d7355b6..f0b139ac0da 100644 --- a/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java +++ b/jetty-websocket/websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.websocket.core.Behavior; @@ -291,7 +292,12 @@ public abstract class CoreClientUpgradeRequest extends HttpRequest implements Re headers(headers -> headers.add(HttpHeader.SEC_WEBSOCKET_EXTENSIONS, extensionString)); // Notify the listener which may change the headers directly. - notifyUpgradeListeners((listener) -> listener.onHandshakeRequest(this)); + Exception listenerError = notifyUpgradeListeners((listener) -> listener.onHandshakeRequest(this)); + if (listenerError != null) + { + abort(listenerError); + return; + } // Check if extensions were set in the headers from the upgrade listener. String extsAfterListener = String.join(",", getHeaders().getCSV(HttpHeader.SEC_WEBSOCKET_EXTENSIONS, true)); @@ -306,8 +312,9 @@ public abstract class CoreClientUpgradeRequest extends HttpRequest implements Re } } - private void notifyUpgradeListeners(Consumer action) + private Exception notifyUpgradeListeners(Consumer action) { + MultiException multiException = null; for (UpgradeListener listener : upgradeListeners) { try @@ -317,8 +324,13 @@ public abstract class CoreClientUpgradeRequest extends HttpRequest implements Re catch (Throwable t) { LOG.info("Exception while invoking listener {}", listener, t); + if (multiException == null) + multiException = new MultiException(); + multiException.add(t); } } + + return multiException; } public void upgrade(HttpResponse response, EndPoint endPoint) @@ -437,7 +449,9 @@ public abstract class CoreClientUpgradeRequest extends HttpRequest implements Re WebSocketConnection wsConnection = new WebSocketConnection(endPoint, httpClient.getExecutor(), httpClient.getScheduler(), bufferPool, coreSession); wsClient.getEventListeners().forEach(wsConnection::addEventListener); coreSession.setWebSocketConnection(wsConnection); - notifyUpgradeListeners((listener) -> listener.onHandshakeResponse(this, response)); + Exception listenerError = notifyUpgradeListeners((listener) -> listener.onHandshakeResponse(this, response)); + if (listenerError != null) + throw new WebSocketException("onHandshakeResponse error", listenerError); // Now swap out the connection try diff --git a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java index c8717bacb16..c5c8bf77a6c 100644 --- a/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java +++ b/jetty-websocket/websocket-core-server/src/main/java/org/eclipse/jetty/websocket/core/server/WebSocketServerComponents.java @@ -40,9 +40,9 @@ public class WebSocketServerComponents extends WebSocketComponents public static final String WEBSOCKET_DEFLATER_POOL_ATTRIBUTE = "jetty.websocket.deflater"; public static final String WEBSOCKET_BUFFER_POOL_ATTRIBUTE = "jetty.websocket.bufferPool"; - WebSocketServerComponents(InflaterPool inflaterPool, DeflaterPool deflaterPool, ByteBufferPool bufferPool) + WebSocketServerComponents(InflaterPool inflaterPool, DeflaterPool deflaterPool, ByteBufferPool bufferPool, DecoratedObjectFactory objectFactory) { - super(null, null, bufferPool, inflaterPool, deflaterPool); + super(null, objectFactory, bufferPool, inflaterPool, deflaterPool); } /** @@ -79,7 +79,10 @@ public class WebSocketServerComponents extends WebSocketComponents if (bufferPool == null) bufferPool = server.getBean(ByteBufferPool.class); - WebSocketComponents serverComponents = new WebSocketServerComponents(inflaterPool, deflaterPool, bufferPool); + DecoratedObjectFactory objectFactory = (DecoratedObjectFactory)servletContext.getAttribute(DecoratedObjectFactory.ATTR); + WebSocketComponents serverComponents = new WebSocketServerComponents(inflaterPool, deflaterPool, bufferPool, objectFactory); + if (objectFactory != null) + serverComponents.unmanage(objectFactory); // These components may be managed by the server but not yet started. // In this case we don't want them to be managed by the components as well. diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/AnnotatedClientEndpointConfig.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/AnnotatedClientEndpointConfig.java index df3c677cd86..dca6437e73d 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/AnnotatedClientEndpointConfig.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/AnnotatedClientEndpointConfig.java @@ -18,17 +18,18 @@ import java.util.List; import javax.websocket.ClientEndpoint; import javax.websocket.ClientEndpointConfig; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException; import org.eclipse.jetty.websocket.javax.common.ClientEndpointConfigWrapper; public class AnnotatedClientEndpointConfig extends ClientEndpointConfigWrapper { - public AnnotatedClientEndpointConfig(ClientEndpoint anno) + public AnnotatedClientEndpointConfig(ClientEndpoint anno, WebSocketComponents components) { Configurator configurator; try { - configurator = anno.configurator().getDeclaredConstructor().newInstance(); + configurator = components.getObjectFactory().createInstance(anno.configurator()); } catch (Exception e) { diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientContainer.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientContainer.java index 1776aae14f8..52cda26afd2 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientContainer.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientContainer.java @@ -229,9 +229,16 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple @Override public Session connectToServer(final Endpoint endpoint, final ClientEndpointConfig providedConfig, final URI path) throws DeploymentException, IOException { - ClientEndpointConfig config = providedConfig; - if (config == null) + ClientEndpointConfig config; + if (providedConfig == null) + { config = new BasicClientEndpointConfig(); + } + else + { + config = providedConfig; + components.getObjectFactory().decorate(providedConfig.getConfigurator()); + } ConfiguredEndpoint instance = new ConfiguredEndpoint(endpoint, config); return connect(instance, path); @@ -240,6 +247,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple @Override public Session connectToServer(Object endpoint, URI path) throws DeploymentException, IOException { + // The Configurator will be decorated when it is created in the getAnnotatedConfig method. ClientEndpointConfig config = getAnnotatedConfig(endpoint); ConfiguredEndpoint instance = new ConfiguredEndpoint(endpoint, config); return connect(instance, path); @@ -275,7 +283,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple if (anno == null) throw new DeploymentException("Could not get ClientEndpoint annotation for " + endpoint.getClass().getName()); - return new AnnotatedClientEndpointConfig(anno); + return new AnnotatedClientEndpointConfig(anno, components); } @Override diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientFrameHandlerFactory.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientFrameHandlerFactory.java index e2ccc8bbf9a..7efad63a8b7 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientFrameHandlerFactory.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/internal/JavaxWebSocketClientFrameHandlerFactory.java @@ -48,7 +48,7 @@ public class JavaxWebSocketClientFrameHandlerFactory extends JavaxWebSocketFrame if (endpointClass.getAnnotation(ClientEndpoint.class) == null) return null; - JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig); + JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig, components); return discoverJavaxFrameHandlerMetadata(endpointClass, metadata); } } diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketContainer.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketContainer.java index 04da5c5a4c9..225d5eec8bd 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketContainer.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketContainer.java @@ -66,6 +66,11 @@ public abstract class JavaxWebSocketContainer extends ContainerLifeCycle impleme return components.getObjectFactory(); } + public WebSocketComponents getWebSocketComponents() + { + return components; + } + public long getDefaultAsyncSendTimeout() { return defaultCustomizer.getWriteTimeout().toMillis(); diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java index 66d49e0ac42..36d40b30f64 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerFactory.java @@ -38,6 +38,7 @@ import javax.websocket.Session; import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; import org.eclipse.jetty.websocket.core.CoreSession; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException; import org.eclipse.jetty.websocket.core.internal.messages.MessageSink; @@ -102,10 +103,12 @@ public abstract class JavaxWebSocketFrameHandlerFactory protected final JavaxWebSocketContainer container; protected final InvokerUtils.ParamIdentifier paramIdentifier; + protected final WebSocketComponents components; public JavaxWebSocketFrameHandlerFactory(JavaxWebSocketContainer container, InvokerUtils.ParamIdentifier paramIdentifier) { this.container = container; + this.components = container.getWebSocketComponents(); this.paramIdentifier = paramIdentifier == null ? InvokerUtils.PARAM_IDENTITY : paramIdentifier; } @@ -165,6 +168,9 @@ public abstract class JavaxWebSocketFrameHandlerFactory errorHandle = InvokerUtils.bindTo(errorHandle, endpoint); pongHandle = InvokerUtils.bindTo(pongHandle, endpoint); + // Decorate the endpointInstance while we are still upgrading for access to things like HttpSession. + components.getObjectFactory().decorate(endpoint); + return new JavaxWebSocketFrameHandler( container, upgradeRequest, @@ -248,7 +254,7 @@ public abstract class JavaxWebSocketFrameHandlerFactory protected JavaxWebSocketFrameHandlerMetadata createEndpointMetadata(EndpointConfig endpointConfig) { - JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig); + JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig, container.getWebSocketComponents()); MethodHandles.Lookup lookup = getServerMethodHandleLookup(); Method openMethod = ReflectUtils.findMethod(Endpoint.class, "onOpen", Session.class, EndpointConfig.class); diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerMetadata.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerMetadata.java index 6671a876dfc..48fece1e46c 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerMetadata.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandlerMetadata.java @@ -18,6 +18,7 @@ import javax.websocket.Encoder; import javax.websocket.EndpointConfig; import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException; import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders; import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders; @@ -64,10 +65,10 @@ public class JavaxWebSocketFrameHandlerMetadata */ private UriTemplatePathSpec uriTemplatePathSpec; - public JavaxWebSocketFrameHandlerMetadata(EndpointConfig endpointConfig) + public JavaxWebSocketFrameHandlerMetadata(EndpointConfig endpointConfig, WebSocketComponents components) { - this.availableDecoders = new AvailableDecoders(endpointConfig); - this.availableEncoders = new AvailableEncoders(endpointConfig); + this.availableDecoders = new AvailableDecoders(endpointConfig, components); + this.availableEncoders = new AvailableEncoders(endpointConfig, components); } public AvailableDecoders getAvailableDecoders() diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java index 541d4d9fc20..81757f5b685 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketSession.java @@ -74,8 +74,8 @@ public class JavaxWebSocketSession implements javax.websocket.Session this.coreSession = coreSession; this.frameHandler = frameHandler; this.sessionId = UUID.randomUUID().toString(); - this.availableDecoders = new AvailableDecoders(endpointConfig); - this.availableEncoders = new AvailableEncoders(endpointConfig); + this.availableDecoders = new AvailableDecoders(endpointConfig, container.getWebSocketComponents()); + this.availableEncoders = new AvailableEncoders(endpointConfig, container.getWebSocketComponents()); if (endpointConfig instanceof PathParamProvider) { diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/decoders/AvailableDecoders.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/decoders/AvailableDecoders.java index 57ebb056319..3aa44344cc2 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/decoders/AvailableDecoders.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/decoders/AvailableDecoders.java @@ -26,6 +26,7 @@ import java.util.stream.Stream; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException; import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils; @@ -34,9 +35,12 @@ public class AvailableDecoders implements Iterable, Closeable { private final List registeredDecoders = new ArrayList<>(); private final EndpointConfig config; + private final WebSocketComponents components; - public AvailableDecoders(EndpointConfig config) + public AvailableDecoders(EndpointConfig config, WebSocketComponents components) { + this.components = Objects.requireNonNull(components); + // Register the Config Based Decoders. this.config = Objects.requireNonNull(config); registerAll(config.getDecoders()); @@ -73,7 +77,7 @@ public class AvailableDecoders implements Iterable, Closeable private void registerPrimitive(Class decoderClass, Class interfaceType, Class type) { - registeredDecoders.add(new RegisteredDecoder(decoderClass, interfaceType, type, config, true)); + registeredDecoders.add(new RegisteredDecoder(decoderClass, interfaceType, type, config, components, true)); } private void register(Class decoder) @@ -152,7 +156,7 @@ public class AvailableDecoders implements Iterable, Closeable return; } - registeredDecoders.add(new RegisteredDecoder(decoder, interfaceClass, objectType, config)); + registeredDecoders.add(new RegisteredDecoder(decoder, interfaceClass, objectType, config, components)); } public RegisteredDecoder getFirstRegisteredDecoder(Class type) diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/decoders/RegisteredDecoder.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/decoders/RegisteredDecoder.java index bc2b00b7249..b2ba440a86c 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/decoders/RegisteredDecoder.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/decoders/RegisteredDecoder.java @@ -17,6 +17,7 @@ import java.lang.reflect.InvocationTargetException; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.javax.common.InitException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,21 +33,23 @@ public class RegisteredDecoder public final Class objectType; public final boolean primitive; public final EndpointConfig config; + private final WebSocketComponents components; private Decoder instance; - public RegisteredDecoder(Class decoder, Class interfaceType, Class objectType, EndpointConfig endpointConfig) + public RegisteredDecoder(Class decoder, Class interfaceType, Class objectType, EndpointConfig endpointConfig, WebSocketComponents components) { - this(decoder, interfaceType, objectType, endpointConfig, false); + this(decoder, interfaceType, objectType, endpointConfig, components, false); } - public RegisteredDecoder(Class decoder, Class interfaceType, Class objectType, EndpointConfig endpointConfig, boolean primitive) + public RegisteredDecoder(Class decoder, Class interfaceType, Class objectType, EndpointConfig endpointConfig, WebSocketComponents components, boolean primitive) { this.decoder = decoder; this.interfaceType = interfaceType; this.objectType = objectType; this.primitive = primitive; this.config = endpointConfig; + this.components = components; } public boolean implementsInterface(Class type) @@ -65,7 +68,7 @@ public class RegisteredDecoder { try { - instance = decoder.getConstructor().newInstance(); + instance = components.getObjectFactory().createInstance(decoder); instance.init(config); return (T)instance; } diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/encoders/AvailableEncoders.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/encoders/AvailableEncoders.java index 4e4211bf776..96b4fc10b20 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/encoders/AvailableEncoders.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/encoders/AvailableEncoders.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException; import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils; @@ -36,79 +37,15 @@ public class AvailableEncoders implements Predicate>, Closeable { private static final Logger LOG = LoggerFactory.getLogger(AvailableEncoders.class); - public static class RegisteredEncoder - { - public final Class encoder; - public final Class interfaceType; - public final Class objectType; - public final boolean primitive; - public Encoder instance; - - public RegisteredEncoder(Class encoder, Class interfaceType, Class objectType) - { - this(encoder, interfaceType, objectType, false); - } - - public RegisteredEncoder(Class encoder, Class interfaceType, Class objectType, boolean primitive) - { - this.encoder = encoder; - this.interfaceType = interfaceType; - this.objectType = objectType; - this.primitive = primitive; - } - - public boolean implementsInterface(Class type) - { - return interfaceType.isAssignableFrom(type); - } - - public boolean isType(Class type) - { - return objectType.isAssignableFrom(type); - } - - public void destroyInstance() - { - if (instance != null) - { - try - { - instance.destroy(); - } - catch (Throwable t) - { - LOG.warn("Error destroying Decoder", t); - } - - instance = null; - } - } - - @Override - public String toString() - { - StringBuilder str = new StringBuilder(); - str.append(AvailableEncoders.RegisteredEncoder.class.getSimpleName()); - str.append('[').append(encoder.getName()); - str.append(',').append(interfaceType.getName()); - str.append(',').append(objectType.getName()); - if (primitive) - { - str.append(",PRIMITIVE"); - } - str.append(']'); - return str.toString(); - } - } - private final EndpointConfig config; - private LinkedList registeredEncoders; + private final WebSocketComponents components; + private final LinkedList registeredEncoders; - public AvailableEncoders(EndpointConfig config) + public AvailableEncoders(EndpointConfig config, WebSocketComponents components) { - Objects.requireNonNull(config); - this.config = config; - registeredEncoders = new LinkedList<>(); + this.config = Objects.requireNonNull(config); + this.components = Objects.requireNonNull(components); + this.registeredEncoders = new LinkedList<>(); // TEXT based [via Class reference] registerPrimitive(BooleanEncoder.class, Encoder.Text.class, Boolean.class); @@ -289,7 +226,7 @@ public class AvailableEncoders implements Predicate>, Closeable return registeredEncoder.instance; } - registeredEncoder.instance = registeredEncoder.encoder.getConstructor().newInstance(); + registeredEncoder.instance = components.getObjectFactory().createInstance(registeredEncoder.encoder); registeredEncoder.instance.init(this.config); return registeredEncoder.instance; } diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/encoders/RegisteredEncoder.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/encoders/RegisteredEncoder.java new file mode 100644 index 00000000000..c2c108c6e6b --- /dev/null +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/encoders/RegisteredEncoder.java @@ -0,0 +1,86 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.javax.common.encoders; + +import javax.websocket.Encoder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RegisteredEncoder +{ + private static final Logger LOG = LoggerFactory.getLogger(RegisteredEncoder.class); + + public final Class encoder; + public final Class interfaceType; + public final Class objectType; + public final boolean primitive; + public Encoder instance; + + public RegisteredEncoder(Class encoder, Class interfaceType, Class objectType) + { + this(encoder, interfaceType, objectType, false); + } + + public RegisteredEncoder(Class encoder, Class interfaceType, Class objectType, boolean primitive) + { + this.encoder = encoder; + this.interfaceType = interfaceType; + this.objectType = objectType; + this.primitive = primitive; + } + + public boolean implementsInterface(Class type) + { + return interfaceType.isAssignableFrom(type); + } + + public boolean isType(Class type) + { + return objectType.isAssignableFrom(type); + } + + public void destroyInstance() + { + if (instance != null) + { + try + { + instance.destroy(); + } + catch (Throwable t) + { + LOG.warn("Error destroying Decoder", t); + } + + instance = null; + } + } + + @Override + public String toString() + { + StringBuilder str = new StringBuilder(); + str.append(RegisteredEncoder.class.getSimpleName()); + str.append('[').append(encoder.getName()); + str.append(',').append(interfaceType.getName()); + str.append(',').append(objectType.getName()); + if (primitive) + { + str.append(",PRIMITIVE"); + } + str.append(']'); + return str.toString(); + } +} diff --git a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java index 31c6aef0e17..b9b2de4c608 100644 --- a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java +++ b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/AbstractJavaxWebSocketFrameHandlerTest.java @@ -19,6 +19,7 @@ import javax.websocket.ClientEndpointConfig; import javax.websocket.EndpointConfig; import org.eclipse.jetty.websocket.core.CoreSession; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders; import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders; import org.junit.jupiter.api.AfterAll; @@ -45,13 +46,22 @@ public abstract class AbstractJavaxWebSocketFrameHandlerTest protected AvailableDecoders decoders; protected Map uriParams; protected EndpointConfig endpointConfig; - protected CoreSession coreSession = new CoreSession.Empty(); + protected CoreSession coreSession = new CoreSession.Empty() + { + private final WebSocketComponents components = new WebSocketComponents(); + + @Override + public WebSocketComponents getWebSocketComponents() + { + return components; + } + }; public AbstractJavaxWebSocketFrameHandlerTest() { endpointConfig = ClientEndpointConfig.Builder.create().build(); - encoders = new AvailableEncoders(endpointConfig); - decoders = new AvailableDecoders(endpointConfig); + encoders = new AvailableEncoders(endpointConfig, coreSession.getWebSocketComponents()); + decoders = new AvailableDecoders(endpointConfig, coreSession.getWebSocketComponents()); uriParams = new HashMap<>(); } diff --git a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/DummyFrameHandlerFactory.java b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/DummyFrameHandlerFactory.java index b8757660033..7175e37fe58 100644 --- a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/DummyFrameHandlerFactory.java +++ b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/DummyFrameHandlerFactory.java @@ -45,7 +45,7 @@ public class DummyFrameHandlerFactory extends JavaxWebSocketFrameHandlerFactory return null; } - JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig); + JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig, components); return discoverJavaxFrameHandlerMetadata(endpointClass, metadata); } } diff --git a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/messages/AbstractMessageSinkTest.java b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/messages/AbstractMessageSinkTest.java index 839440f2429..83682c9a413 100644 --- a/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/messages/AbstractMessageSinkTest.java +++ b/jetty-websocket/websocket-javax-common/src/test/java/org/eclipse/jetty/websocket/javax/common/messages/AbstractMessageSinkTest.java @@ -20,12 +20,15 @@ import java.util.function.Consumer; import javax.websocket.ClientEndpointConfig; import javax.websocket.Decoder; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.javax.common.AbstractSessionTest; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder; public abstract class AbstractMessageSinkTest extends AbstractSessionTest { + private final WebSocketComponents _components = new WebSocketComponents(); + public List toRegisteredDecoderList(Class clazz, Class objectType) { Class interfaceType; @@ -40,7 +43,7 @@ public abstract class AbstractMessageSinkTest extends AbstractSessionTest else throw new IllegalStateException(); - return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build())); + return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build(), _components)); } public MethodHandle getAcceptHandle(Consumer copy, Class type) diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/AnnotatedServerEndpointConfig.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/AnnotatedServerEndpointConfig.java index 4d960c4a25b..4424722f7d7 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/AnnotatedServerEndpointConfig.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/AnnotatedServerEndpointConfig.java @@ -70,9 +70,7 @@ public class AnnotatedServerEndpointConfig extends ServerEndpointConfigWrapper else path = anno.value(); - // Make sure all Configurators obtained are decorated. - ServerEndpointConfig.Configurator rawConfigurator = getConfigurator(baseServerConfig, anno); - ServerEndpointConfig.Configurator configurator = containerScope.getObjectFactory().decorate(rawConfigurator); + ServerEndpointConfig.Configurator configurator = getConfigurator(baseServerConfig, anno, containerScope); // Build a ServerEndpointConfig with the Javax API builder to wrap. ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder.create(endpointClass, path) @@ -90,7 +88,7 @@ public class AnnotatedServerEndpointConfig extends ServerEndpointConfigWrapper init(endpointConfig); } - private static Configurator getConfigurator(ServerEndpointConfig baseServerConfig, ServerEndpoint anno) throws DeploymentException + private static Configurator getConfigurator(ServerEndpointConfig baseServerConfig, ServerEndpoint anno, JavaxWebSocketContainer containerScope) throws DeploymentException { Configurator ret = null; @@ -115,7 +113,7 @@ public class AnnotatedServerEndpointConfig extends ServerEndpointConfigWrapper // Instantiate the provided configurator try { - return anno.configurator().getConstructor().newInstance(); + return containerScope.getObjectFactory().createInstance(anno.configurator()); } catch (Exception e) { diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java index 83249769551..347238eb702 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketCreator.java @@ -159,8 +159,6 @@ public class JavaxWebSocketCreator implements WebSocketCreator // [JSR] Step 6: create endpoint class Class endpointClass = config.getEndpointClass(); Object endpoint = config.getConfigurator().getEndpointInstance(endpointClass); - // Do not decorate here (let the Connection and Session start first) - // This will allow CDI to see Session for injection into Endpoint classes. return new ConfiguredEndpoint(endpoint, config); } catch (InstantiationException e) diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java index f7f67507e56..b7b2e029bed 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerContainer.java @@ -232,6 +232,9 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer if (isStarted() || isStarting()) { + // Decorate the provided Configurator. + components.getObjectFactory().decorate(providedConfig.getConfigurator()); + // If we have annotations merge the annotated ServerEndpointConfig with the provided one. Class endpointClass = providedConfig.getEndpointClass(); ServerEndpoint anno = endpointClass.getAnnotation(ServerEndpoint.class); diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java index 1a5ff15b9be..17e2d651d6e 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/internal/JavaxWebSocketServerFrameHandlerFactory.java @@ -43,7 +43,7 @@ public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketClien return super.getMetadata(endpointClass, endpointConfig); UriTemplatePathSpec templatePathSpec = new UriTemplatePathSpec(anno.value()); - JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig); + JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig, components); metadata.setUriTemplatePathSpec(templatePathSpec); return discoverJavaxFrameHandlerMetadata(endpointClass, metadata); } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/AvailableDecodersTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/AvailableDecodersTest.java index 6da6ffa0bc2..461fd1c0792 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/AvailableDecodersTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/AvailableDecodersTest.java @@ -26,6 +26,7 @@ import javax.websocket.Decoder; import javax.websocket.EndpointConfig; import org.eclipse.jetty.toolchain.test.Hex; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException; import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders; import org.eclipse.jetty.websocket.javax.common.decoders.IntegerDecoder; @@ -42,6 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class AvailableDecodersTest { private AvailableDecoders availableDecoders; + private WebSocketComponents components = new WebSocketComponents(); @SafeVarargs public final void init(Class... decoder) @@ -49,7 +51,7 @@ public class AvailableDecodersTest EndpointConfig testConfig = ClientEndpointConfig.Builder.create() .decoders(Arrays.asList(decoder)) .build(); - this.availableDecoders = new AvailableDecoders(testConfig); + this.availableDecoders = new AvailableDecoders(testConfig, components); } public T getInstanceFor(Class type) diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/AvailableEncodersTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/AvailableEncodersTest.java index 1905dd3ede3..57692790fa9 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/AvailableEncodersTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/AvailableEncodersTest.java @@ -25,6 +25,7 @@ import javax.websocket.Encoder; import javax.websocket.EndpointConfig; import org.eclipse.jetty.toolchain.test.Hex; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException; import org.eclipse.jetty.websocket.javax.client.internal.BasicClientEndpointConfig; import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders; @@ -41,6 +42,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class AvailableEncodersTest { private static EndpointConfig testConfig; + private final WebSocketComponents components = new WebSocketComponents(); @BeforeAll public static void initConfig() @@ -48,7 +50,7 @@ public class AvailableEncodersTest testConfig = new BasicClientEndpointConfig(); } - private AvailableEncoders encoders = new AvailableEncoders(testConfig); + private final AvailableEncoders encoders = new AvailableEncoders(testConfig, components); public void assertTextEncoder(Class type, T value, String expectedEncoded) throws IllegalAccessException, InstantiationException, EncodeException { diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/DecoderTextStreamTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/DecoderTextStreamTest.java index ae8850901d6..c5401959478 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/DecoderTextStreamTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/coders/DecoderTextStreamTest.java @@ -28,6 +28,7 @@ import javax.websocket.Decoder; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.websocket.core.Frame; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.javax.common.decoders.RegisteredDecoder; import org.eclipse.jetty.websocket.javax.common.messages.DecodedTextStreamMessageSink; import org.eclipse.jetty.websocket.javax.tests.FunctionMethod; @@ -43,6 +44,8 @@ import static org.hamcrest.Matchers.notNullValue; */ public class DecoderTextStreamTest extends AbstractClientSessionTest { + private final WebSocketComponents _components = new WebSocketComponents(); + @Test public void testQuotesDecoderDirect() throws Exception { @@ -119,6 +122,6 @@ public class DecoderTextStreamTest extends AbstractClientSessionTest else throw new IllegalStateException(); - return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build())); + return List.of(new RegisteredDecoder(clazz, interfaceType, objectType, ClientEndpointConfig.Builder.create().build(), _components)); } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AbstractJavaxWebSocketServerFrameHandlerTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AbstractJavaxWebSocketServerFrameHandlerTest.java index d52e7c7b0c2..d51177fd52f 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AbstractJavaxWebSocketServerFrameHandlerTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/server/AbstractJavaxWebSocketServerFrameHandlerTest.java @@ -19,6 +19,7 @@ import javax.websocket.EndpointConfig; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.javax.client.internal.BasicClientEndpointConfig; import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders; import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders; @@ -54,12 +55,13 @@ public abstract class AbstractJavaxWebSocketServerFrameHandlerTest protected AvailableDecoders decoders; protected Map uriParams; protected EndpointConfig endpointConfig; + private WebSocketComponents components = new WebSocketComponents(); public AbstractJavaxWebSocketServerFrameHandlerTest() { endpointConfig = new BasicClientEndpointConfig(); - encoders = new AvailableEncoders(endpointConfig); - decoders = new AvailableDecoders(endpointConfig); + encoders = new AvailableEncoders(endpointConfig, components); + decoders = new AvailableDecoders(endpointConfig, components); uriParams = new HashMap<>(); } } diff --git a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java index bc7593dba70..5e184a3f488 100644 --- a/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java +++ b/jetty-websocket/websocket-jetty-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java @@ -89,7 +89,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli if (httpClient == null) coreClient.getHttpClient().setName("Jetty-WebSocketClient@" + hashCode()); - frameHandlerFactory = new JettyWebSocketFrameHandlerFactory(this); + frameHandlerFactory = new JettyWebSocketFrameHandlerFactory(this, components); sessionListeners.add(sessionTracker); addBean(sessionTracker); } diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java index 4746ebd4f4f..b16eba885ef 100644 --- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java +++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerFactory.java @@ -46,6 +46,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.core.CoreSession; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException; import org.eclipse.jetty.websocket.core.internal.messages.ByteArrayMessageSink; @@ -78,11 +79,18 @@ import org.eclipse.jetty.websocket.core.internal.util.ReflectUtils; public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle { private final WebSocketContainer container; - private Map, JettyWebSocketFrameHandlerMetadata> metadataMap = new ConcurrentHashMap<>(); + private final WebSocketComponents components; + private final Map, JettyWebSocketFrameHandlerMetadata> metadataMap = new ConcurrentHashMap<>(); - public JettyWebSocketFrameHandlerFactory(WebSocketContainer container) + public JettyWebSocketFrameHandlerFactory(WebSocketContainer container, WebSocketComponents components) { this.container = container; + this.components = components; + } + + public WebSocketComponents getWebSocketComponents() + { + return components; } public JettyWebSocketFrameHandlerMetadata getMetadata(Class endpointClass) @@ -130,6 +138,9 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle final MethodHandle pongHandle = InvokerUtils.bindTo(metadata.getPongHandle(), endpointInstance); BatchMode batchMode = metadata.getBatchMode(); + // Decorate the endpointInstance while we are still upgrading for access to things like HttpSession. + components.getObjectFactory().decorate(endpointInstance); + return new JettyWebSocketFrameHandler( container, endpointInstance, diff --git a/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java b/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java index 31f041e7f3d..1fd9853c81e 100644 --- a/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java +++ b/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandlerTest.java @@ -32,6 +32,7 @@ import org.eclipse.jetty.websocket.core.CloseStatus; import org.eclipse.jetty.websocket.core.CoreSession; import org.eclipse.jetty.websocket.core.Frame; import org.eclipse.jetty.websocket.core.OpCode; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -39,6 +40,7 @@ import org.junit.jupiter.api.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.notNullValue; import static org.junit.jupiter.api.Assertions.assertTimeout; +import static org.junit.jupiter.api.Assertions.assertTrue; public class JettyWebSocketFrameHandlerTest { @@ -57,20 +59,26 @@ public class JettyWebSocketFrameHandlerTest container.stop(); } - private JettyWebSocketFrameHandlerFactory endpointFactory = new JettyWebSocketFrameHandlerFactory(container); - private CoreSession coreSession = new CoreSession.Empty() + private final WebSocketComponents components = new WebSocketComponents(); + private final JettyWebSocketFrameHandlerFactory endpointFactory = new JettyWebSocketFrameHandlerFactory(container, components); + private final CoreSession coreSession = new CoreSession.Empty() { @Override public Behavior getBehavior() { return Behavior.CLIENT; } + + @Override + public WebSocketComponents getWebSocketComponents() + { + return components; + } }; private JettyWebSocketFrameHandler newLocalFrameHandler(Object wsEndpoint) { - JettyWebSocketFrameHandler localEndpoint = endpointFactory.newJettyFrameHandler(wsEndpoint); - return localEndpoint; + return endpointFactory.newJettyFrameHandler(wsEndpoint); } public static class ConnectionOnly implements WebSocketConnectionListener @@ -97,7 +105,7 @@ public class JettyWebSocketFrameHandlerTest } @Test - public void testConnectionListener() throws Exception + public void testConnectionListener() { ConnectionOnly socket = new ConnectionOnly(); JettyWebSocketFrameHandler localEndpoint = newLocalFrameHandler(socket); @@ -138,7 +146,7 @@ public class JettyWebSocketFrameHandlerTest } @Test - public void testAnnotatedStreamedTextSingle() throws Exception + public void testAnnotatedStreamedTextSingle() { assertTimeout(Duration.ofMillis(1000), () -> { @@ -160,7 +168,7 @@ public class JettyWebSocketFrameHandlerTest } @Test - public void testAnnotatedStreamedTextMultipleParts() throws Exception + public void testAnnotatedStreamedTextMultipleParts() { assertTimeout(Duration.ofMillis(1000), () -> { @@ -177,7 +185,7 @@ public class JettyWebSocketFrameHandlerTest localEndpoint.onFrame(CloseStatus.toFrame(StatusCode.NORMAL, "Normal"), Callback.NOOP); // Await completion (of threads) - socket.streamLatch.await(2, TimeUnit.SECONDS); + assertTrue(socket.streamLatch.await(2, TimeUnit.SECONDS)); // Validate Events socket.events.assertEvents("onTextStream\\(Hello World\\)"); @@ -185,7 +193,7 @@ public class JettyWebSocketFrameHandlerTest } @Test - public void testListenerPartialSocket() throws Exception + public void testListenerPartialSocket() { // Setup EndPoints.ListenerPartialSocket socket = new EndPoints.ListenerPartialSocket(); @@ -216,7 +224,7 @@ public class JettyWebSocketFrameHandlerTest } @Test - public void testListenerBasicSocket() throws Exception + public void testListenerBasicSocket() { // Setup EndPoints.ListenerBasicSocket socket = new EndPoints.ListenerBasicSocket(); @@ -242,7 +250,7 @@ public class JettyWebSocketFrameHandlerTest } @Test - public void testListenerBasicSocketError() throws Exception + public void testListenerBasicSocketError() { // Setup EndPoints.ListenerBasicSocket socket = new EndPoints.ListenerBasicSocket(); @@ -262,7 +270,7 @@ public class JettyWebSocketFrameHandlerTest } @Test - public void testListenerFrameSocket() throws Exception + public void testListenerFrameSocket() { // Setup EndPoints.ListenerFrameSocket socket = new EndPoints.ListenerFrameSocket(); @@ -292,7 +300,7 @@ public class JettyWebSocketFrameHandlerTest } @Test - public void testListenerPingPongSocket() throws Exception + public void testListenerPingPongSocket() { // Setup EndPoints.ListenerPingPongSocket socket = new EndPoints.ListenerPingPongSocket(); diff --git a/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/LocalEndpointMetadataTest.java b/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/LocalEndpointMetadataTest.java index 2a3e64b5ea0..de14ec98537 100644 --- a/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/LocalEndpointMetadataTest.java +++ b/jetty-websocket/websocket-jetty-common/src/test/java/org/eclipse/jetty/websocket/common/LocalEndpointMetadataTest.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.websocket.common; import org.eclipse.jetty.websocket.api.exceptions.InvalidWebSocketException; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.exception.DuplicateAnnotationException; import org.eclipse.jetty.websocket.core.exception.InvalidSignatureException; import org.eclipse.jetty.websocket.core.internal.messages.ByteArrayMessageSink; @@ -51,7 +52,8 @@ public class LocalEndpointMetadataTest container.stop(); } - private JettyWebSocketFrameHandlerFactory endpointFactory = new JettyWebSocketFrameHandlerFactory(container); + private final WebSocketComponents components = new WebSocketComponents(); + private final JettyWebSocketFrameHandlerFactory endpointFactory = new JettyWebSocketFrameHandlerFactory(container, components); private JettyWebSocketFrameHandlerMetadata createMetadata(Class endpointClass) { diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java index ce5094845ad..808939ec9b9 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/JettyWebSocketServerContainer.java @@ -110,7 +110,7 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements this.webSocketMappings = webSocketMappings; this.components = components; this.executor = executor; - this.frameHandlerFactory = new JettyServerFrameHandlerFactory(this); + this.frameHandlerFactory = new JettyServerFrameHandlerFactory(this, components); addBean(frameHandlerFactory); addSessionListener(sessionTracker); diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java index d1ce713e73f..fad26c8d0f3 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/internal/JettyServerFrameHandlerFactory.java @@ -15,10 +15,10 @@ package org.eclipse.jetty.websocket.server.internal; import javax.servlet.ServletContext; -import org.eclipse.jetty.websocket.api.WebSocketContainer; import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler; import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory; import org.eclipse.jetty.websocket.core.FrameHandler; +import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.server.FrameHandlerFactory; import org.eclipse.jetty.websocket.core.server.ServerUpgradeRequest; import org.eclipse.jetty.websocket.core.server.ServerUpgradeResponse; @@ -32,9 +32,9 @@ public class JettyServerFrameHandlerFactory extends JettyWebSocketFrameHandlerFa return (container == null) ? null : container.getBean(JettyServerFrameHandlerFactory.class); } - public JettyServerFrameHandlerFactory(WebSocketContainer container) + public JettyServerFrameHandlerFactory(JettyWebSocketServerContainer container, WebSocketComponents components) { - super(container); + super(container, components); } @Override diff --git a/tests/test-cdi/pom.xml b/tests/test-cdi/pom.xml index e973750bab2..505c36f1b8a 100644 --- a/tests/test-cdi/pom.xml +++ b/tests/test-cdi/pom.xml @@ -48,6 +48,24 @@ jetty-slf4j-impl test + + org.eclipse.jetty.websocket + websocket-javax-server + ${project.version} + test + + + org.eclipse.jetty.websocket + websocket-jetty-server + ${project.version} + test + + + org.eclipse.jetty.websocket + websocket-jetty-client + ${project.version} + test + org.eclipse.jetty.toolchain jetty-test-helper diff --git a/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/websocket/JavaxWebSocketCdiTest.java b/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/websocket/JavaxWebSocketCdiTest.java new file mode 100644 index 00000000000..c32fbfafd60 --- /dev/null +++ b/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/websocket/JavaxWebSocketCdiTest.java @@ -0,0 +1,404 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.cdi.tests.websocket; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; +import javax.inject.Inject; +import javax.websocket.ClientEndpoint; +import javax.websocket.ClientEndpointConfig; +import javax.websocket.CloseReason; +import javax.websocket.Decoder; +import javax.websocket.Encoder; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.HandshakeResponse; +import javax.websocket.MessageHandler; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.WebSocketContainer; +import javax.websocket.server.HandshakeRequest; +import javax.websocket.server.ServerEndpoint; +import javax.websocket.server.ServerEndpointConfig; + +import org.eclipse.jetty.cdi.CdiDecoratingListener; +import org.eclipse.jetty.cdi.CdiServletContainerInitializer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.BlockingArrayQueue; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.core.WebSocketComponents; +import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; +import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientContainerProvider; +import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer.Configurator; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class JavaxWebSocketCdiTest +{ + private Server _server; + private WebSocketContainer _client; + private ServerConnector _connector; + private ServletContextHandler context; + + @BeforeEach + public void before() + { + _server = new Server(); + _connector = new ServerConnector(_server); + _server.addConnector(_connector); + + context = new ServletContextHandler(); + context.setContextPath("/"); + + // Enable Weld + CDI + context.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE); + context.addServletContainerInitializer(new CdiServletContainerInitializer()); + context.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener()); + + // Add to Server + _server.setHandler(context); + } + + public void start(Configurator configurator) throws Exception + { + // Add WebSocket endpoints + JavaxWebSocketServletContainerInitializer.configure(context, configurator); + + // Start Server + _server.start(); + + // Configure the Client with the same DecoratedObjectFactory from the server. + WebSocketComponents components = WebSocketServerComponents.getWebSocketComponents(context.getServletContext()); + _client = new JavaxWebSocketClientContainer(components); + LifeCycle.start(_client); + } + + @AfterEach + public void after() throws Exception + { + JavaxWebSocketClientContainerProvider.stop(_client); + _server.stop(); + } + + @Test + public void testAnnotatedEndpoint() throws Exception + { + start((servletContext, wsContainer) -> wsContainer.addEndpoint(AnnotatedCdiEchoSocket.class)); + + // If we can get an echo from the websocket endpoint we know that CDI injection of the logger worked as there was no NPE. + AnnotatedCdiClientSocket clientEndpoint = new AnnotatedCdiClientSocket(); + URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo"); + Session session = _client.connectToServer(clientEndpoint, uri); + session.getBasicRemote().sendText("hello world"); + assertThat(clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS), is("hello world")); + session.close(); + assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testConfiguredEndpoint() throws Exception + { + ServerEndpointConfig serverEndpointConfig = ServerEndpointConfig.Builder.create(ConfiguredCdiEchoSocket.class, "/echo") + .configurator(new ServerConfigurator()) + .build(); + start((servletContext, wsContainer) -> wsContainer.addEndpoint(serverEndpointConfig)); + + // If we can get an echo from the websocket endpoint we know that CDI injection of the logger worked as there was no NPE. + ConfiguredCdiClientSocket clientEndpoint = new ConfiguredCdiClientSocket(); + ClientEndpointConfig clientEndpointConfig = ClientEndpointConfig.Builder.create() + .configurator(new ClientConfigurator()) + .build(); + + URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo"); + Session session = _client.connectToServer(clientEndpoint, clientEndpointConfig, uri); + session.getBasicRemote().sendText("hello world"); + assertThat(clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS), is("hello world")); + session.close(); + assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testEncoderDecoder() throws Exception + { + start((servletContext, wsContainer) -> wsContainer.addEndpoint(AnnotatedCdiEchoSocket.class)); + + // If we can get an echo from the websocket endpoint we know that CDI injection of the logger worked as there was no NPE. + AnnotatedEncoderDecoderClientSocket clientEndpoint = new AnnotatedEncoderDecoderClientSocket(); + URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo"); + Session session = _client.connectToServer(clientEndpoint, uri); + session.getBasicRemote().sendObject("hello world"); + assertThat(clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS), is("decoded(encoded(hello world))")); + session.close(); + assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + @Disabled("See issue https://github.com/eclipse/jetty.project/issues/6174") + public void testHttpSessionInjection() throws Exception + { + start((servletContext, wsContainer) -> wsContainer.addEndpoint(CdiHttpSessionSocket.class)); + + // If we can get an echo from the websocket endpoint we know that CDI injection of the logger worked as there was no NPE. + AnnotatedCdiClientSocket clientEndpoint = new AnnotatedCdiClientSocket(); + URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo"); + Session session = _client.connectToServer(clientEndpoint, uri); + session.getBasicRemote().sendObject("hello world"); + String rcvMessage = clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS); + assertThat(rcvMessage, containsString("hello world, SessionID:")); + session.close(); + assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS)); + } + + public static class ClientConfigurator extends ClientEndpointConfig.Configurator + { + @Inject + public Logger logger; + + @Override + public void beforeRequest(Map> headers) + { + logger.info("beforeRequest"); + } + } + + public static class ServerConfigurator extends ServerEndpointConfig.Configurator + { + @Inject + public Logger logger; + + @Override + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) + { + logger.info("modifyHandshake"); + } + } + + @ClientEndpoint(configurator = ClientConfigurator.class) + public static class AnnotatedCdiClientSocket + { + @Inject + public Logger logger; + + BlockingArrayQueue _textMessages = new BlockingArrayQueue<>(); + CountDownLatch _closeLatch = new CountDownLatch(1); + + @OnOpen + public void onOpen(Session session) + { + logger.info("onOpen: " + session); + } + + @OnMessage + public void onMessage(String message) + { + _textMessages.add(message); + } + + @OnError + public void onError(Throwable t) + { + t.printStackTrace(); + } + + @OnClose + public void onClose() + { + _closeLatch.countDown(); + } + } + + @ServerEndpoint(value = "/echo", configurator = ServerConfigurator.class) + public static class AnnotatedCdiEchoSocket + { + @Inject + protected Logger logger; + protected Session session; + + @OnOpen + public void onOpen(Session session) + { + logger.info("onOpen() session:" + session); + this.session = session; + } + + @OnMessage + public void onMessage(String message) throws IOException + { + this.session.getBasicRemote().sendText(message); + } + + @OnError + public void onError(Throwable t) + { + t.printStackTrace(); + } + } + + public static class ConfiguredCdiClientSocket extends Endpoint implements MessageHandler.Whole + { + @Inject + public Logger logger; + + BlockingArrayQueue _textMessages = new BlockingArrayQueue<>(); + CountDownLatch _closeLatch = new CountDownLatch(1); + + @Override + public void onMessage(String message) + { + _textMessages.add(message); + } + + @Override + public void onOpen(Session session, EndpointConfig config) + { + logger.info("onOpen: " + session); + session.addMessageHandler(this); + } + + @Override + public void onError(Session session, Throwable thr) + { + thr.printStackTrace(); + } + + @Override + public void onClose(Session session, CloseReason closeReason) + { + _closeLatch.countDown(); + } + } + + public static class ConfiguredCdiEchoSocket extends Endpoint implements MessageHandler.Whole + { + @Inject + public Logger logger; + private Session session; + + @Override + public void onMessage(String message) + { + try + { + session.getBasicRemote().sendText(message); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Override + public void onOpen(Session session, EndpointConfig config) + { + logger.info("onOpen() session:" + session); + this.session = session; + session.addMessageHandler(this); + } + + @Override + public void onError(Session session, Throwable thr) + { + thr.printStackTrace(); + } + } + + public static class CustomEncoder implements Encoder.Text + { + @Inject + public Logger logger; + + @Override + public String encode(String s) + { + return "encoded(" + s + ")"; + } + + @Override + public void init(EndpointConfig config) + { + logger.info("init: " + config); + } + + @Override + public void destroy() + { + } + } + + public static class CustomDecoder implements Decoder.Text + { + @Inject + public Logger logger; + + @Override + public String decode(String s) + { + return "decoded(" + s + ")"; + } + + @Override + public void init(EndpointConfig config) + { + logger.info("init: " + config); + } + + @Override + public void destroy() + { + } + + @Override + public boolean willDecode(String s) + { + return true; + } + } + + @ClientEndpoint(encoders = {CustomEncoder.class}, decoders = {CustomDecoder.class}) + public static class AnnotatedEncoderDecoderClientSocket extends AnnotatedCdiClientSocket + { + } + + @ServerEndpoint("/echo") + public static class CdiHttpSessionSocket extends AnnotatedCdiEchoSocket + { + @Inject + private javax.servlet.http.HttpSession httpSession; + + @Override + public void onMessage(String message) throws IOException + { + session.getBasicRemote().sendText(message + ", SessionID:" + httpSession.getId()); + } + } +} diff --git a/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/websocket/JettyWebSocketCdiTest.java b/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/websocket/JettyWebSocketCdiTest.java new file mode 100644 index 00000000000..794950e85d5 --- /dev/null +++ b/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/websocket/JettyWebSocketCdiTest.java @@ -0,0 +1,153 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.cdi.tests.websocket; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; +import javax.inject.Inject; + +import org.eclipse.jetty.cdi.CdiDecoratingListener; +import org.eclipse.jetty.cdi.CdiServletContainerInitializer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.BlockingArrayQueue; +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.OnWebSocketMessage; +import org.eclipse.jetty.websocket.api.annotations.WebSocket; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class JettyWebSocketCdiTest +{ + private Server _server; + private WebSocketClient _client; + private ServerConnector _connector; + + @BeforeEach + public void before() throws Exception + { + _server = new Server(); + _connector = new ServerConnector(_server); + _server.addConnector(_connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + + // Enable Weld + CDI + context.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE); + context.addServletContainerInitializer(new CdiServletContainerInitializer()); + context.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener()); + + // Add WebSocket endpoints + JettyWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) -> + wsContainer.addMapping("/echo", CdiEchoSocket.class)); + + // Add to Server + _server.setHandler(context); + + // Start Server + _server.start(); + + _client = new WebSocketClient(); + _client.start(); + } + + @AfterEach + public void after() throws Exception + { + _client.stop(); + _server.stop(); + } + + @WebSocket + public static class TestClientEndpoint + { + BlockingArrayQueue _textMessages = new BlockingArrayQueue<>(); + CountDownLatch _closeLatch = new CountDownLatch(1); + + @OnWebSocketMessage + public void onMessage(String message) + { + _textMessages.add(message); + } + + @OnWebSocketClose + public void onClose() + { + _closeLatch.countDown(); + } + } + + @Test + public void testBasicEcho() throws Exception + { + // If we can get an echo from the websocket endpoint we know that CDI injection of the logger worked as there was no NPE. + TestClientEndpoint clientEndpoint = new TestClientEndpoint(); + URI uri = URI.create("ws://localhost:" + _connector.getLocalPort() + "/echo"); + Session session = _client.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS); + session.getRemote().sendString("hello world"); + assertThat(clientEndpoint._textMessages.poll(5, TimeUnit.SECONDS), is("hello world")); + session.close(); + assertTrue(clientEndpoint._closeLatch.await(5, TimeUnit.SECONDS)); + } + + @WebSocket() + public static class CdiEchoSocket + { + @Inject + public Logger logger; + + private Session session; + + @OnWebSocketConnect + public void onOpen(Session session) + { + logger.info("onOpen() session:" + session); + this.session = session; + } + + @OnWebSocketMessage + public void onMessage(String message) throws IOException + { + this.session.getRemote().sendString(message); + } + + @OnWebSocketError + public void onError(Throwable t) + { + t.printStackTrace(); + } + + @OnWebSocketClose + public void onClose(int statusCode, String reason) + { + logger.info("onClose() close: " + statusCode + ":" + reason); + this.session = null; + } + } +} diff --git a/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/websocket/LogFactory.java b/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/websocket/LogFactory.java new file mode 100644 index 00000000000..1e683c874ec --- /dev/null +++ b/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/websocket/LogFactory.java @@ -0,0 +1,29 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.cdi.tests.websocket; + +import java.util.logging.Logger; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; + +public class LogFactory +{ + @Produces + @Default + public Logger createLogger(InjectionPoint injectionPoint) + { + return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); + } +}