From 97bbec7ac0695780ccad3575d3e6a55bc78ceb6b Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 22 Dec 2020 11:25:37 +1100 Subject: [PATCH 01/15] Issue #5832 - deregister ShutdownThread for WebSocketClientContainer Signed-off-by: Lachlan Roberts --- ...JavaxWebSocketClientContainerProvider.java | 21 ++++++------------- .../JavaxWebSocketClientContainer.java | 9 ++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java index 3f00b215773..6ac2a48d47c 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java @@ -17,7 +17,6 @@ import javax.websocket.ContainerProvider; import javax.websocket.WebSocketContainer; import org.eclipse.jetty.util.component.LifeCycle; -import org.eclipse.jetty.util.thread.ShutdownThread; import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; /** @@ -59,22 +58,14 @@ public class JavaxWebSocketClientContainerProvider extends ContainerProvider // TODO: do we want to provide a non-standard way to configure to always return the same clientContainer based on a config somewhere? (system.property?) JavaxWebSocketClientContainer clientContainer = new JavaxWebSocketClientContainer(); - - // Register as JVM runtime shutdown hook? - ShutdownThread.register(clientContainer); - - if (!clientContainer.isStarted()) + try { - try - { - clientContainer.start(); - } - catch (Exception e) - { - throw new RuntimeException("Unable to start Client Container", e); - } + clientContainer.start(); + } + catch (Exception e) + { + throw new RuntimeException("Unable to start Client Container", e); } - return clientContainer; } } 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 c22f991e167..f96863b2268 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 @@ -33,6 +33,7 @@ import javax.websocket.Session; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.thread.ShutdownThread; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; import org.eclipse.jetty.websocket.core.exception.InvalidWebSocketException; @@ -59,6 +60,14 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple public JavaxWebSocketClientContainer() { this(new WebSocketComponents()); + ShutdownThread.register(this); + } + + @Override + protected void doStop() throws Exception + { + super.doStop(); + ShutdownThread.deregister(this); } /** From 374e02cbc3990622f3679d98a71aa27b011cc372 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 22 Dec 2020 15:53:10 +1100 Subject: [PATCH 02/15] register ShutdownThread in doStart Signed-off-by: Lachlan Roberts --- .../client/internal/JavaxWebSocketClientContainer.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 f96863b2268..d6e521dc487 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 @@ -60,7 +60,13 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple public JavaxWebSocketClientContainer() { this(new WebSocketComponents()); + } + + @Override + protected void doStart() throws Exception + { ShutdownThread.register(this); + super.doStart(); } @Override From 25f8c65539e3349e10f9b62ff9d4cee365e5afdb Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 23 Dec 2020 11:36:18 +1100 Subject: [PATCH 03/15] Issue #5832 - shutdown WSClientContainer with ContextHandler if possible Signed-off-by: Lachlan Roberts --- .../JavaxWebSocketClientContainer.java | 67 +++++++++--- .../JavaxWebSocketServerContainer.java | 12 +++ .../javax/tests/ClientInWebappTest.java | 101 ++++++++++++++++++ 3 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java 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 d6e521dc487..201d5ea1eec 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 @@ -15,6 +15,7 @@ package org.eclipse.jetty.websocket.javax.client.internal; import java.io.IOException; import java.net.URI; +import java.util.EventListener; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -33,6 +34,7 @@ import javax.websocket.Session; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.thread.ShutdownThread; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient; @@ -62,20 +64,6 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple this(new WebSocketComponents()); } - @Override - protected void doStart() throws Exception - { - ShutdownThread.register(this); - super.doStart(); - } - - @Override - protected void doStop() throws Exception - { - super.doStop(); - ShutdownThread.deregister(this); - } - /** * Create a {@link javax.websocket.WebSocketContainer} using the supplied * {@link HttpClient} for environments where you want to configure @@ -286,4 +274,55 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple return new AnnotatedClientEndpointConfig(anno); } + + @Override + protected void doStart() throws Exception + { + doClientStart(); + super.doStart(); + } + + protected void doClientStart() + { + try + { + Object context = getClass().getClassLoader() + .loadClass("org.eclipse.jetty.server.handler.ContextHandler") + .getMethod("getCurrentContext") + .invoke(null); + + Object contextHandler = context.getClass() + .getMethod("getContextHandler") + .invoke(context); + + AbstractLifeCycleListener shutdownListener = new AbstractLifeCycleListener() + { + @Override + public void lifeCycleStopped(LifeCycle event) + { + LifeCycle.stop(this); + } + }; + + contextHandler.getClass() + .getMethod("addEventListener", EventListener.class) + .invoke(contextHandler, shutdownListener); + } + catch (Throwable ignored) + { + ShutdownThread.register(this); + } + } + + @Override + protected void doStop() throws Exception + { + super.doStop(); + doClientStop(); + } + + protected void doClientStop() + { + ShutdownThread.deregister(this); + } } 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 0d4ca23f9a3..005179d917d 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 @@ -296,4 +296,16 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer deferredEndpointConfigs.clear(); } } + + @Override + protected void doClientStart() + { + // Do nothing to avoid registration with the ShutdownThread. + } + + @Override + protected void doClientStop() + { + // Do nothing to avoid de-registration with the ShutdownThread. + } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java new file mode 100644 index 00000000000..d349b18dc65 --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java @@ -0,0 +1,101 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 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.tests; + +import java.io.IOException; +import java.net.URI; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.websocket.ContainerProvider; +import javax.websocket.WebSocketContainer; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; +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.empty; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class ClientInWebappTest +{ + private Server server; + private URI serverUri; + private HttpClient httpClient; + private volatile WebSocketContainer container; + + public class ServerSocket extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + container = ContainerProvider.getWebSocketContainer(); + } + } + + @BeforeEach + public void before() throws Exception + { + server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(8080); + server.addConnector(connector); + + ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler.setContextPath("/"); + contextHandler.addServlet(new ServletHolder(new ServerSocket()), "/"); + server.setHandler(contextHandler); + server.start(); + serverUri = WSURI.toWebsocket(server.getURI()); + + httpClient = new HttpClient(); + httpClient.start(); + } + + @AfterEach + public void after() throws Exception + { + httpClient.stop(); + server.stop(); + } + + @Test + public void testWebSocketClientContainerInWebapp() throws Exception + { + ContentResponse response = httpClient.GET(serverUri); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + + assertNotNull(container); + assertThat(container, instanceOf(JavaxWebSocketClientContainer.class)); + JavaxWebSocketClientContainer clientContainer = (JavaxWebSocketClientContainer)container; + assertThat(clientContainer.isRunning(), is(true)); + + // The client should be attached to the servers LifeCycle and should stop with it. + server.stop(); + assertThat(clientContainer.isRunning(), is(false)); + assertThat(server.getContainedBeans(WebSocketContainer.class), empty()); + } +} From dd1d4bc1ca783f07b6542038f47cfa329f44b12f Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 23 Dec 2020 11:45:09 +1100 Subject: [PATCH 04/15] Issue #5832 - use lifeCycleStopping as lifeCycleStopped is never called Signed-off-by: Lachlan Roberts --- .../javax/client/internal/JavaxWebSocketClientContainer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 201d5ea1eec..28d22373794 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 @@ -298,9 +298,9 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple AbstractLifeCycleListener shutdownListener = new AbstractLifeCycleListener() { @Override - public void lifeCycleStopped(LifeCycle event) + public void lifeCycleStopping(LifeCycle event) { - LifeCycle.stop(this); + LifeCycle.stop(JavaxWebSocketClientContainer.this); } }; From 3286f9a4546e705da60a7f99130bc6cf52e8f3b8 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Thu, 24 Dec 2020 13:59:45 +1100 Subject: [PATCH 05/15] Issue #5832 - changes from review Signed-off-by: Lachlan Roberts --- .../src/main/java/module-info.java | 1 + ...JavaxWebSocketClientContainerProvider.java | 9 +-------- .../JavaxWebSocketClientContainer.java | 19 ++++++++++++++++++- .../javax/tests/ClientInWebappTest.java | 13 ++++++++++--- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/jetty-websocket/websocket-javax-client/src/main/java/module-info.java b/jetty-websocket/websocket-javax-client/src/main/java/module-info.java index 54323eb068b..c827261eb6f 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/module-info.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/module-info.java @@ -20,6 +20,7 @@ module org.eclipse.jetty.websocket.javax.client exports org.eclipse.jetty.websocket.javax.client; exports org.eclipse.jetty.websocket.javax.client.internal to org.eclipse.jetty.websocket.javax.server; + requires org.slf4j; requires org.eclipse.jetty.client; requires org.eclipse.jetty.websocket.core.client; requires org.eclipse.jetty.websocket.javax.common; diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java index 6ac2a48d47c..c1974dceb55 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java @@ -58,14 +58,7 @@ public class JavaxWebSocketClientContainerProvider extends ContainerProvider // TODO: do we want to provide a non-standard way to configure to always return the same clientContainer based on a config somewhere? (system.property?) JavaxWebSocketClientContainer clientContainer = new JavaxWebSocketClientContainer(); - try - { - clientContainer.start(); - } - catch (Exception e) - { - throw new RuntimeException("Unable to start Client Container", e); - } + LifeCycle.start(clientContainer); return clientContainer; } } 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 28d22373794..e54f02df27a 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 @@ -46,6 +46,8 @@ import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketExtensionConfig; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandler; import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Container for Client use of the javax.websocket API. @@ -55,6 +57,8 @@ import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactor @ManagedObject("JSR356 Client Container") public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer implements javax.websocket.WebSocketContainer { + private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketClientContainer.class); + protected WebSocketCoreClient coreClient; protected Function coreClientFactory; private final JavaxWebSocketClientFrameHandlerFactory frameHandlerFactory; @@ -295,12 +299,25 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple .getMethod("getContextHandler") .invoke(context); + contextHandler.getClass() + .getMethod("addManaged", LifeCycle.class) + .invoke(contextHandler, this); + AbstractLifeCycleListener shutdownListener = new AbstractLifeCycleListener() { @Override public void lifeCycleStopping(LifeCycle event) { - LifeCycle.stop(JavaxWebSocketClientContainer.this); + try + { + contextHandler.getClass() + .getMethod("removeBean", Object.class) + .invoke(contextHandler, JavaxWebSocketClientContainer.this); + } + catch (Throwable t) + { + LOG.warn("could not remove client WebSocketContainer bean from {}", contextHandler); + } } }; diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java index d349b18dc65..14fb5a7099f 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java @@ -15,6 +15,7 @@ package org.eclipse.jetty.websocket.javax.tests; import java.io.IOException; import java.net.URI; +import java.util.Collection; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -43,11 +44,12 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; public class ClientInWebappTest { private Server server; + private ServletContextHandler contextHandler; private URI serverUri; private HttpClient httpClient; private volatile WebSocketContainer container; - public class ServerSocket extends HttpServlet + public class WebSocketClientInServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException @@ -64,9 +66,9 @@ public class ClientInWebappTest connector.setPort(8080); server.addConnector(connector); - ServletContextHandler contextHandler = new ServletContextHandler(); + contextHandler = new ServletContextHandler(); contextHandler.setContextPath("/"); - contextHandler.addServlet(new ServletHolder(new ServerSocket()), "/"); + contextHandler.addServlet(new ServletHolder(new WebSocketClientInServlet()), "/"); server.setHandler(contextHandler); server.start(); serverUri = WSURI.toWebsocket(server.getURI()); @@ -93,6 +95,11 @@ public class ClientInWebappTest JavaxWebSocketClientContainer clientContainer = (JavaxWebSocketClientContainer)container; assertThat(clientContainer.isRunning(), is(true)); + // The container should be a bean on the ContextHandler. + Collection containedBeans = contextHandler.getBeans(WebSocketContainer.class); + assertThat(containedBeans.size(), is(1)); + assertThat(containedBeans.toArray()[0], is(container)); + // The client should be attached to the servers LifeCycle and should stop with it. server.stop(); assertThat(clientContainer.isRunning(), is(false)); From 774dac66a2364b42c43bd8ef1e39c2fde78369b6 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 8 Jan 2021 13:24:45 +1100 Subject: [PATCH 06/15] Allow shutdown of Javax WS Client Container though a SCI Signed-off-by: Lachlan Roberts --- .../websocket-javax-client/pom.xml | 5 ++ .../src/main/java/module-info.java | 1 + .../client/JavaxWebSocketClientShutdown.java | 48 +++++++++++++++++++ .../JavaxWebSocketClientContainer.java | 40 ++++++++++------ .../javax.servlet.ServletContainerInitializer | 1 + .../JavaxWebSocketServerContainer.java | 6 --- .../javax/tests/ClientInWebappTest.java | 8 +++- 7 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java create mode 100644 jetty-websocket/websocket-javax-client/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer diff --git a/jetty-websocket/websocket-javax-client/pom.xml b/jetty-websocket/websocket-javax-client/pom.xml index c9f5585a308..a7e9654819e 100644 --- a/jetty-websocket/websocket-javax-client/pom.xml +++ b/jetty-websocket/websocket-javax-client/pom.xml @@ -34,6 +34,11 @@ jetty-client ${project.version} + + org.eclipse.jetty.toolchain + jetty-servlet-api + true + org.eclipse.jetty jetty-xml diff --git a/jetty-websocket/websocket-javax-client/src/main/java/module-info.java b/jetty-websocket/websocket-javax-client/src/main/java/module-info.java index c827261eb6f..7e16f2a0b99 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/module-info.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/module-info.java @@ -20,6 +20,7 @@ module org.eclipse.jetty.websocket.javax.client exports org.eclipse.jetty.websocket.javax.client; exports org.eclipse.jetty.websocket.javax.client.internal to org.eclipse.jetty.websocket.javax.server; + requires static jetty.servlet.api; requires org.slf4j; requires org.eclipse.jetty.client; requires org.eclipse.jetty.websocket.core.client; diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java new file mode 100644 index 00000000000..ed73c307642 --- /dev/null +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java @@ -0,0 +1,48 @@ +// +// ======================================================================== +// Copyright (c) 1995-2020 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.client; + +import java.util.Set; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; + +import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; + +public class JavaxWebSocketClientShutdown extends ContainerLifeCycle implements ServletContainerInitializer, ServletContextListener +{ + @Override + public void onStartup(Set> c, ServletContext ctx) throws ServletException + { + JavaxWebSocketClientContainer.SHUTDOWN_CONTAINER.compareAndSet(null, this); + ctx.addListener(this); + } + + @Override + public void contextInitialized(ServletContextEvent sce) + { + LifeCycle.start(this); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) + { + LifeCycle.stop(this); + removeBeans(); + } +} 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 e54f02df27a..196cff6de83 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 @@ -23,6 +23,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import javax.websocket.ClientEndpoint; import javax.websocket.ClientEndpointConfig; @@ -34,6 +35,7 @@ import javax.websocket.Session; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.thread.ShutdownThread; import org.eclipse.jetty.websocket.core.WebSocketComponents; @@ -58,6 +60,7 @@ import org.slf4j.LoggerFactory; public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer implements javax.websocket.WebSocketContainer { private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketClientContainer.class); + public static final AtomicReference SHUTDOWN_CONTAINER = new AtomicReference<>(); protected WebSocketCoreClient coreClient; protected Function coreClientFactory; @@ -287,6 +290,24 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple } protected void doClientStart() + { + // If we are running in Jetty register shutdown with the ContextHandler. + // TODO: add test mode to disable this. + if (shutdownWithContextHandler(this)) + return; + + // If we are running inside a different ServletContainer we can register with the SHUTDOWN_CONTAINER static. + ContainerLifeCycle shutdownContainer = SHUTDOWN_CONTAINER.get(); + if (shutdownContainer != null) + { + shutdownContainer.addManaged(this); + return; + } + + ShutdownThread.register(this); + } + + private boolean shutdownWithContextHandler(LifeCycle lifeCycle) { try { @@ -301,7 +322,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple contextHandler.getClass() .getMethod("addManaged", LifeCycle.class) - .invoke(contextHandler, this); + .invoke(contextHandler, lifeCycle); AbstractLifeCycleListener shutdownListener = new AbstractLifeCycleListener() { @@ -324,22 +345,11 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple contextHandler.getClass() .getMethod("addEventListener", EventListener.class) .invoke(contextHandler, shutdownListener); + return true; } - catch (Throwable ignored) + catch (Throwable throwable) { - ShutdownThread.register(this); + return false; } } - - @Override - protected void doStop() throws Exception - { - super.doStop(); - doClientStop(); - } - - protected void doClientStop() - { - ShutdownThread.deregister(this); - } } diff --git a/jetty-websocket/websocket-javax-client/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/jetty-websocket/websocket-javax-client/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer new file mode 100644 index 00000000000..b9a2f2d58b9 --- /dev/null +++ b/jetty-websocket/websocket-javax-client/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer @@ -0,0 +1 @@ +org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientShutdown \ No newline at end of file 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 005179d917d..f928baf444a 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 @@ -302,10 +302,4 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer { // Do nothing to avoid registration with the ShutdownThread. } - - @Override - protected void doClientStop() - { - // Do nothing to avoid de-registration with the ShutdownThread. - } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java index 14fb5a7099f..a85793ea76a 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java @@ -30,6 +30,8 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.servlet.listener.ContainerInitializer; +import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientShutdown; import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -63,13 +65,17 @@ public class ClientInWebappTest { server = new Server(); ServerConnector connector = new ServerConnector(server); - connector.setPort(8080); server.addConnector(connector); contextHandler = new ServletContextHandler(); contextHandler.setContextPath("/"); contextHandler.addServlet(new ServletHolder(new WebSocketClientInServlet()), "/"); server.setHandler(contextHandler); + + // Because we are using embedded we must manually add the Javax WS Client Shutdown SCI. + // TODO: fix to not use ContainerInitializer.asContextListener + contextHandler.addEventListener(ContainerInitializer.asContextListener(new JavaxWebSocketClientShutdown())); + server.start(); serverUri = WSURI.toWebsocket(server.getURI()); From 9e19e875f526ef2b766accf73b28c0dd568770a1 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Tue, 12 Jan 2021 17:08:53 +1100 Subject: [PATCH 07/15] Issue #5832 - deprecate and remove usage of the ContainerInitializer utility Signed-off-by: Lachlan Roberts --- .../jetty/servlet/ServletContextHandler.java | 21 +++++++++++ .../listener/ContainerInitializer.java | 2 + ...xWebSocketServletContainerInitializer.java | 22 ++++++++++- .../javax/tests/ClientInWebappTest.java | 4 +- ...yWebSocketServletContainerInitializer.java | 37 +++++++++++++------ 5 files changed, 70 insertions(+), 16 deletions(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index 8a1bf1c3bec..d81c7f9af21 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -696,6 +696,27 @@ public class ServletContextHandler extends ContextHandler relinkHandlers(); } + /** + * Utility Method to allow for manual execution of {@link javax.servlet.ServletContainerInitializer} when using Embedded Jetty. + * @param containerInitializer the ServletContainerInitializer to register. + * @see Initializer + */ + public void addServletContainerInitializer(ServletContainerInitializer containerInitializer) + { + addManaged(new Initializer(this, containerInitializer)); + } + + /** + * Utility Method to allow for manual execution of {@link javax.servlet.ServletContainerInitializer} when using Embedded Jetty. + * @param containerInitializer the ServletContainerInitializer to register. + * @param classes the Set of application classes. + * @see Initializer + */ + public void addServletContainerInitializer(ServletContainerInitializer containerInitializer, Set> classes) + { + addManaged(new Initializer(this, containerInitializer, classes)); + } + /** * The DecoratedObjectFactory for use by IoC containers (weld / spring / etc) * diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java index 474f9b3bfcf..a58ad05abd0 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java @@ -25,7 +25,9 @@ import javax.servlet.ServletContextListener; /** * Utility Methods for manual execution of {@link javax.servlet.ServletContainerInitializer} when * using Embedded Jetty. + * @deprecated use {@link org.eclipse.jetty.servlet.ServletContextHandler#addServletContainerInitializer(ServletContainerInitializer)}. */ +@Deprecated public final class ContainerInitializer { /** diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java index f0e27b3cd16..6043082d389 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java @@ -15,6 +15,7 @@ package org.eclipse.jetty.websocket.javax.server.config; import java.util.HashSet; import java.util.Set; +import java.util.function.Consumer; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -28,7 +29,6 @@ import javax.websocket.server.ServerEndpointConfig; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.listener.ContainerInitializer; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.thread.ThreadClassLoaderScope; import org.eclipse.jetty.websocket.core.WebSocketComponents; @@ -52,6 +52,8 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain public static final String HTTPCLIENT_ATTRIBUTE = "org.eclipse.jetty.websocket.javax.HttpClient"; private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketServletContainerInitializer.class); + private Consumer afterStartupConsumer; + /** * Test a ServletContext for {@code init-param} or {@code attribute} at {@code keyName} for * true or false setting that determines if the specified feature is enabled (or not). @@ -102,7 +104,7 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain // the initialization phase is over. (important for this SCI to function) context.getServletContext().setExtendedListenerTypes(true); - context.addEventListener(ContainerInitializer.asContextListener(new JavaxWebSocketServletContainerInitializer()) + context.addServletContainerInitializer(new JavaxWebSocketServletContainerInitializer() .afterStartup((servletContext) -> { JavaxWebSocketServerContainer serverContainer = JavaxWebSocketServerContainer.getContainer(servletContext); @@ -270,6 +272,9 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain } } } + + if (afterStartupConsumer != null) + afterStartupConsumer.accept(context); } @SuppressWarnings("unchecked") @@ -296,4 +301,17 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain } } } + + /** + * Add a optional consumer to execute once the {@link ServletContainerInitializer#onStartup(Set, ServletContext)} has + * been called successfully. + * + * @param consumer the consumer to execute after the SCI has executed + * @return this configured {@link JavaxWebSocketServletContainerInitializer} instance. + */ + public JavaxWebSocketServletContainerInitializer afterStartup(Consumer consumer) + { + this.afterStartupConsumer = consumer; + return this; + } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java index a85793ea76a..5973ee59456 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java @@ -30,7 +30,6 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.servlet.listener.ContainerInitializer; import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientShutdown; import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; import org.junit.jupiter.api.AfterEach; @@ -73,8 +72,7 @@ public class ClientInWebappTest server.setHandler(contextHandler); // Because we are using embedded we must manually add the Javax WS Client Shutdown SCI. - // TODO: fix to not use ContainerInitializer.asContextListener - contextHandler.addEventListener(ContainerInitializer.asContextListener(new JavaxWebSocketClientShutdown())); + contextHandler.addServletContainerInitializer(new JavaxWebSocketClientShutdown()); server.start(); serverUri = WSURI.toWebsocket(server.getURI()); diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java index 37a86b31809..d81763b3765 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java @@ -14,11 +14,11 @@ package org.eclipse.jetty.websocket.server.config; import java.util.Set; +import java.util.function.Consumer; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.listener.ContainerInitializer; import org.eclipse.jetty.websocket.core.WebSocketComponents; import org.eclipse.jetty.websocket.core.server.WebSocketMappings; import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents; @@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory; public class JettyWebSocketServletContainerInitializer implements ServletContainerInitializer { private static final Logger LOG = LoggerFactory.getLogger(JettyWebSocketServletContainerInitializer.class); + private Consumer afterStartupConsumer; public interface Configurator { @@ -51,17 +52,15 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain if (!context.isStopped()) throw new IllegalStateException("configure should be called before starting"); - context.addEventListener( - ContainerInitializer - .asContextListener(new JettyWebSocketServletContainerInitializer()) - .afterStartup((servletContext) -> + context.addServletContainerInitializer(new JettyWebSocketServletContainerInitializer() + .afterStartup((servletContext) -> + { + if (configurator != null) { - if (configurator != null) - { - JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(servletContext); - configurator.accept(servletContext, container); - } - })); + JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(servletContext); + configurator.accept(servletContext, container); + } + })); } /** @@ -101,5 +100,21 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain JettyWebSocketServerContainer container = JettyWebSocketServletContainerInitializer.initialize(contextHandler); if (LOG.isDebugEnabled()) LOG.debug("onStartup {}", container); + + if (afterStartupConsumer != null) + afterStartupConsumer.accept(context); + } + + /** + * Add a optional consumer to execute once the {@link ServletContainerInitializer#onStartup(Set, ServletContext)} has + * been called successfully. + * + * @param consumer the consumer to execute after the SCI has executed + * @return this configured {@link JettyWebSocketServletContainerInitializer} instance. + */ + public JettyWebSocketServletContainerInitializer afterStartup(Consumer consumer) + { + this.afterStartupConsumer = consumer; + return this; } } From 35051dfde51a53442384e5eb3bf85758c765ac7b Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 13 Jan 2021 17:54:30 +1100 Subject: [PATCH 08/15] Issue #5832 - changes from review Signed-off-by: Lachlan Roberts --- .../jetty/servlet/ServletContextHandler.java | 2 +- .../client/JavaxWebSocketClientShutdown.java | 3 +- .../JavaxWebSocketClientContainer.java | 13 +++- ...xWebSocketServletContainerInitializer.java | 63 +++++++------------ ...yWebSocketServletContainerInitializer.java | 43 +++++-------- .../jetty/cdi/tests/EmbeddedWeldTest.java | 16 ++--- 6 files changed, 63 insertions(+), 77 deletions(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index d81c7f9af21..f7b18da4018 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -703,7 +703,7 @@ public class ServletContextHandler extends ContextHandler */ public void addServletContainerInitializer(ServletContainerInitializer containerInitializer) { - addManaged(new Initializer(this, containerInitializer)); + addServletContainerInitializer(containerInitializer, Collections.emptySet()); } /** diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java index ed73c307642..481b2b701fc 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java @@ -29,7 +29,7 @@ public class JavaxWebSocketClientShutdown extends ContainerLifeCycle implements @Override public void onStartup(Set> c, ServletContext ctx) throws ServletException { - JavaxWebSocketClientContainer.SHUTDOWN_CONTAINER.compareAndSet(null, this); + JavaxWebSocketClientContainer.initialize(this); ctx.addListener(this); } @@ -44,5 +44,6 @@ public class JavaxWebSocketClientShutdown extends ContainerLifeCycle implements { LifeCycle.stop(this); removeBeans(); + JavaxWebSocketClientContainer.initialize(null); } } 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 196cff6de83..bc3bd9508bc 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 @@ -60,7 +60,12 @@ import org.slf4j.LoggerFactory; public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer implements javax.websocket.WebSocketContainer { private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketClientContainer.class); - public static final AtomicReference SHUTDOWN_CONTAINER = new AtomicReference<>(); + private static final AtomicReference SHUTDOWN_CONTAINER = new AtomicReference<>(); + + public static void initialize(ContainerLifeCycle container) + { + SHUTDOWN_CONTAINER.set(container); + } protected WebSocketCoreClient coreClient; protected Function coreClientFactory; @@ -282,6 +287,12 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple return new AnnotatedClientEndpointConfig(anno); } + @Override + protected void doStop() throws Exception + { + super.doStop(); + } + @Override protected void doStart() throws Exception { diff --git a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java index 6043082d389..476b333ca6d 100644 --- a/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java +++ b/jetty-websocket/websocket-javax-server/src/main/java/org/eclipse/jetty/websocket/javax/server/config/JavaxWebSocketServletContainerInitializer.java @@ -15,7 +15,6 @@ package org.eclipse.jetty.websocket.javax.server.config; import java.util.HashSet; import java.util.Set; -import java.util.function.Consumer; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -52,7 +51,17 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain public static final String HTTPCLIENT_ATTRIBUTE = "org.eclipse.jetty.websocket.javax.HttpClient"; private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketServletContainerInitializer.class); - private Consumer afterStartupConsumer; + private final Configurator configurator; + + public JavaxWebSocketServletContainerInitializer() + { + this(null); + } + + public JavaxWebSocketServletContainerInitializer(Configurator configurator) + { + this.configurator = configurator; + } /** * Test a ServletContext for {@code init-param} or {@code attribute} at {@code keyName} for @@ -98,28 +107,7 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain { if (!context.isStopped()) throw new IllegalStateException("configure should be called before starting"); - - // In this embedded-jetty usage, allow ServletContext.addListener() to - // add other ServletContextListeners (such as the ContextDestroyListener) after - // the initialization phase is over. (important for this SCI to function) - context.getServletContext().setExtendedListenerTypes(true); - - context.addServletContainerInitializer(new JavaxWebSocketServletContainerInitializer() - .afterStartup((servletContext) -> - { - JavaxWebSocketServerContainer serverContainer = JavaxWebSocketServerContainer.getContainer(servletContext); - if (configurator != null) - { - try - { - configurator.accept(servletContext, serverContainer); - } - catch (DeploymentException e) - { - throw new RuntimeException("Failed to deploy WebSocket Endpoint", e); - } - } - })); + context.addServletContainerInitializer(new JavaxWebSocketServletContainerInitializer(configurator)); } /** @@ -273,8 +261,18 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain } } - if (afterStartupConsumer != null) - afterStartupConsumer.accept(context); + // Call the configurator after startup. + if (configurator != null) + { + try + { + configurator.accept(context, container); + } + catch (DeploymentException e) + { + throw new RuntimeException("Failed to deploy WebSocket Endpoint", e); + } + } } @SuppressWarnings("unchecked") @@ -301,17 +299,4 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain } } } - - /** - * Add a optional consumer to execute once the {@link ServletContainerInitializer#onStartup(Set, ServletContext)} has - * been called successfully. - * - * @param consumer the consumer to execute after the SCI has executed - * @return this configured {@link JavaxWebSocketServletContainerInitializer} instance. - */ - public JavaxWebSocketServletContainerInitializer afterStartup(Consumer consumer) - { - this.afterStartupConsumer = consumer; - return this; - } } diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java index d81763b3765..d843b2c4edb 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.websocket.server.config; import java.util.Set; -import java.util.function.Consumer; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; @@ -32,7 +31,17 @@ import org.slf4j.LoggerFactory; public class JettyWebSocketServletContainerInitializer implements ServletContainerInitializer { private static final Logger LOG = LoggerFactory.getLogger(JettyWebSocketServletContainerInitializer.class); - private Consumer afterStartupConsumer; + private final Configurator configurator; + + public JettyWebSocketServletContainerInitializer() + { + this(null); + } + + public JettyWebSocketServletContainerInitializer(Configurator configurator) + { + this.configurator = configurator; + } public interface Configurator { @@ -51,16 +60,7 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain { if (!context.isStopped()) throw new IllegalStateException("configure should be called before starting"); - - context.addServletContainerInitializer(new JettyWebSocketServletContainerInitializer() - .afterStartup((servletContext) -> - { - if (configurator != null) - { - JettyWebSocketServerContainer container = JettyWebSocketServerContainer.getContainer(servletContext); - configurator.accept(servletContext, container); - } - })); + context.addServletContainerInitializer(new JettyWebSocketServletContainerInitializer()); } /** @@ -101,20 +101,9 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain if (LOG.isDebugEnabled()) LOG.debug("onStartup {}", container); - if (afterStartupConsumer != null) - afterStartupConsumer.accept(context); - } - - /** - * Add a optional consumer to execute once the {@link ServletContainerInitializer#onStartup(Set, ServletContext)} has - * been called successfully. - * - * @param consumer the consumer to execute after the SCI has executed - * @return this configured {@link JettyWebSocketServletContainerInitializer} instance. - */ - public JettyWebSocketServletContainerInitializer afterStartup(Consumer consumer) - { - this.afterStartupConsumer = consumer; - return this; + if (configurator != null) + { + configurator.accept(context, container); + } } } diff --git a/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/EmbeddedWeldTest.java b/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/EmbeddedWeldTest.java index f0d6d5271f7..d04c062228a 100644 --- a/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/EmbeddedWeldTest.java +++ b/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/EmbeddedWeldTest.java @@ -103,29 +103,29 @@ public class EmbeddedWeldTest case "CdiServletContainerInitializer+Listener": // Expect:INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters. - context.addBean(new ServletContextHandler.Initializer(context, new CdiServletContainerInitializer())); + context.addServletContainerInitializer(new CdiServletContainerInitializer()); context.addEventListener(new org.jboss.weld.environment.servlet.Listener()); break; case "CdiServletContainerInitializer(CdiDecoratingListener)+Listener": // Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters context.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE); - context.addBean(new ServletContextHandler.Initializer(context, new CdiServletContainerInitializer())); + context.addServletContainerInitializer(new CdiServletContainerInitializer()); context.addEventListener(new org.jboss.weld.environment.servlet.Listener()); break; case "CdiServletContainerInitializer+EnhancedListener": // Expect:INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters. - context.addBean(new ServletContextHandler.Initializer(context, new CdiServletContainerInitializer())); - context.addBean(new ServletContextHandler.Initializer(context, new org.jboss.weld.environment.servlet.EnhancedListener())); + context.addServletContainerInitializer(new CdiServletContainerInitializer()); + context.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener()); break; // NOTE: This is the preferred mode from the Weld team. case "CdiServletContainerInitializer(CdiDecoratingListener)+EnhancedListener": // Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters context.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE); - context.addBean(new ServletContextHandler.Initializer(context, new CdiServletContainerInitializer())); - context.addBean(new ServletContextHandler.Initializer(context, new org.jboss.weld.environment.servlet.EnhancedListener())); + context.addServletContainerInitializer(new CdiServletContainerInitializer()); + context.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener()); break; } @@ -190,8 +190,8 @@ public class EmbeddedWeldTest server.setHandler(webapp); webapp.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE); - webapp.addBean(new ServletContextHandler.Initializer(webapp, new CdiServletContainerInitializer())); - webapp.addBean(new ServletContextHandler.Initializer(webapp, new org.jboss.weld.environment.servlet.EnhancedListener())); + webapp.addServletContainerInitializer(new CdiServletContainerInitializer()); + webapp.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener()); String pkg = EmbeddedWeldTest.class.getPackage().getName(); webapp.getServerClassMatcher().add("-" + pkg + "."); From 2f6176661a824b153eb6997177ccc594a9ac876c Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Thu, 14 Jan 2021 14:38:12 +1100 Subject: [PATCH 09/15] improve testing for JavaxWebSocketClientContainer shutdown Signed-off-by: Lachlan Roberts --- .../JavaxWebSocketClientContainer.java | 90 ++++++++++++------- .../JavaxWebSocketServerContainer.java | 8 +- ...=> JavaxClientShutdownWithServerTest.java} | 51 +++++++++-- 3 files changed, 112 insertions(+), 37 deletions(-) rename jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/{ClientInWebappTest.java => JavaxClientShutdownWithServerTest.java} (61%) 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 bc3bd9508bc..654a23d6c7a 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 @@ -15,7 +15,6 @@ package org.eclipse.jetty.websocket.javax.client.internal; import java.io.IOException; import java.net.URI; -import java.util.EventListener; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -70,6 +69,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple protected WebSocketCoreClient coreClient; protected Function coreClientFactory; private final JavaxWebSocketClientFrameHandlerFactory frameHandlerFactory; + private boolean allowContextHandlerRegistration = true; public JavaxWebSocketClientContainer() { @@ -110,6 +110,11 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple this.frameHandlerFactory = new JavaxWebSocketClientFrameHandlerFactory(this); } + public void allowContextHandlerRegistration(boolean allowContextHandlerRegistration) + { + this.allowContextHandlerRegistration = allowContextHandlerRegistration; + } + protected HttpClient getHttpClient() { return getWebSocketCoreClient().getHttpClient(); @@ -287,12 +292,6 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple return new AnnotatedClientEndpointConfig(anno); } - @Override - protected void doStop() throws Exception - { - super.doStop(); - } - @Override protected void doStart() throws Exception { @@ -300,11 +299,17 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple super.doStart(); } + @Override + protected void doStop() throws Exception + { + doClientStop(); + super.doStop(); + } + protected void doClientStart() { // If we are running in Jetty register shutdown with the ContextHandler. - // TODO: add test mode to disable this. - if (shutdownWithContextHandler(this)) + if (allowContextHandlerRegistration && addToContextHandler(this)) return; // If we are running inside a different ServletContainer we can register with the SHUTDOWN_CONTAINER static. @@ -318,7 +323,25 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple ShutdownThread.register(this); } - private boolean shutdownWithContextHandler(LifeCycle lifeCycle) + protected void doClientStop() + { + // Remove from context handler if running in Jetty server. + removeFromContextHandler(this); + + // Remove from the Shutdown Container. + ContainerLifeCycle shutdownContainer = SHUTDOWN_CONTAINER.get(); + if (shutdownContainer != null && shutdownContainer.contains(this)) + { + // Un-manage first as removeBean() will stop this, but this is already in STOPPING state. + shutdownContainer.unmanage(this); + shutdownContainer.removeBean(this); + } + + // If not running in a server we need to de-register with the shutdown thread. + ShutdownThread.deregister(this); + } + + private boolean addToContextHandler(LifeCycle lifeCycle) { try { @@ -335,32 +358,37 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple .getMethod("addManaged", LifeCycle.class) .invoke(contextHandler, lifeCycle); - AbstractLifeCycleListener shutdownListener = new AbstractLifeCycleListener() - { - @Override - public void lifeCycleStopping(LifeCycle event) - { - try - { - contextHandler.getClass() - .getMethod("removeBean", Object.class) - .invoke(contextHandler, JavaxWebSocketClientContainer.this); - } - catch (Throwable t) - { - LOG.warn("could not remove client WebSocketContainer bean from {}", contextHandler); - } - } - }; - - contextHandler.getClass() - .getMethod("addEventListener", EventListener.class) - .invoke(contextHandler, shutdownListener); return true; } catch (Throwable throwable) { + if (LOG.isDebugEnabled()) + LOG.debug("error from addToContextHandler({})", lifeCycle, throwable); return false; } } + + private void removeFromContextHandler(LifeCycle lifeCycle) + { + try + { + Object context = getClass().getClassLoader() + .loadClass("org.eclipse.jetty.server.handler.ContextHandler") + .getMethod("getCurrentContext") + .invoke(null); + + Object contextHandler = context.getClass() + .getMethod("getContextHandler") + .invoke(context); + + contextHandler.getClass() + .getMethod("removeBean", Object.class) + .invoke(contextHandler, JavaxWebSocketClientContainer.this); + } + catch (Throwable throwable) + { + if (LOG.isDebugEnabled()) + LOG.debug("error from removeFromContextHandler({})", lifeCycle, throwable); + } + } } 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 f928baf444a..24928dbb693 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 @@ -300,6 +300,12 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer @Override protected void doClientStart() { - // Do nothing to avoid registration with the ShutdownThread. + // Do nothing. + } + + @Override + protected void doClientStop() + { + // Do nothing. } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java similarity index 61% rename from jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java rename to jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java index 5973ee59456..4e9f37224af 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/ClientInWebappTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java @@ -30,6 +30,8 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientShutdown; import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; import org.junit.jupiter.api.AfterEach; @@ -42,15 +44,17 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertNotNull; -public class ClientInWebappTest +public class JavaxClientShutdownWithServerTest { private Server server; private ServletContextHandler contextHandler; private URI serverUri; private HttpClient httpClient; private volatile WebSocketContainer container; + private ContainerLifeCycle shutdownContainer; - public class WebSocketClientInServlet extends HttpServlet + + public class ContextHandlerShutdownServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException @@ -59,6 +63,18 @@ public class ClientInWebappTest } } + public class ServletContainerInitializerShutdownServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + JavaxWebSocketClientContainer clientContainer = new JavaxWebSocketClientContainer(); + clientContainer.allowContextHandlerRegistration(false); + LifeCycle.start(clientContainer); + container = clientContainer; + } + } + @BeforeEach public void before() throws Exception { @@ -68,11 +84,14 @@ public class ClientInWebappTest contextHandler = new ServletContextHandler(); contextHandler.setContextPath("/"); - contextHandler.addServlet(new ServletHolder(new WebSocketClientInServlet()), "/"); + contextHandler.addServlet(new ServletHolder(new ContextHandlerShutdownServlet()), "/contextHandlerShutdown"); + contextHandler.addServlet(new ServletHolder(new ServletContainerInitializerShutdownServlet()), "/servletContainerInitializerShutdown"); server.setHandler(contextHandler); // Because we are using embedded we must manually add the Javax WS Client Shutdown SCI. - contextHandler.addServletContainerInitializer(new JavaxWebSocketClientShutdown()); + JavaxWebSocketClientShutdown javaxWebSocketClientShutdown = new JavaxWebSocketClientShutdown(); + shutdownContainer = javaxWebSocketClientShutdown; + contextHandler.addServletContainerInitializer(javaxWebSocketClientShutdown); server.start(); serverUri = WSURI.toWebsocket(server.getURI()); @@ -91,7 +110,7 @@ public class ClientInWebappTest @Test public void testWebSocketClientContainerInWebapp() throws Exception { - ContentResponse response = httpClient.GET(serverUri); + ContentResponse response = httpClient.GET(serverUri.resolve("/contextHandlerShutdown")); assertThat(response.getStatus(), is(HttpStatus.OK_200)); assertNotNull(container); @@ -109,4 +128,26 @@ public class ClientInWebappTest assertThat(clientContainer.isRunning(), is(false)); assertThat(server.getContainedBeans(WebSocketContainer.class), empty()); } + + @Test + public void testWebSocketClientContainerInWebapp2() throws Exception + { + ContentResponse response = httpClient.GET(serverUri.resolve("/servletContainerInitializerShutdown")); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + + assertNotNull(container); + assertThat(container, instanceOf(JavaxWebSocketClientContainer.class)); + JavaxWebSocketClientContainer clientContainer = (JavaxWebSocketClientContainer)container; + assertThat(clientContainer.isRunning(), is(true)); + + // The container should be a bean on the ContextHandler. + Collection containedBeans = shutdownContainer.getBeans(WebSocketContainer.class); + assertThat(containedBeans.size(), is(1)); + assertThat(containedBeans.toArray()[0], is(container)); + + // The client should be attached to the servers LifeCycle and should stop with it. + server.stop(); + assertThat(clientContainer.isRunning(), is(false)); + assertThat(server.getContainedBeans(WebSocketContainer.class), empty()); + } } From a60ecfa4cc86490db8496f3f6117d28e8c28ab50 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 18 Jan 2021 23:23:35 +1100 Subject: [PATCH 10/15] Issue #5832 - fix bugs when stopping the JavaxWebSocketClientContainer Signed-off-by: Lachlan Roberts --- .../client/JavaxWebSocketClientShutdown.java | 9 ++++ .../JavaxWebSocketClientContainer.java | 47 ++++++++++++++----- .../JavaxClientShutdownWithServerTest.java | 2 +- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java index 481b2b701fc..3d189cb70ef 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java @@ -23,9 +23,13 @@ import javax.servlet.ServletException; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class JavaxWebSocketClientShutdown extends ContainerLifeCycle implements ServletContainerInitializer, ServletContextListener { + private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketClientShutdown.class); + @Override public void onStartup(Set> c, ServletContext ctx) throws ServletException { @@ -36,12 +40,17 @@ public class JavaxWebSocketClientShutdown extends ContainerLifeCycle implements @Override public void contextInitialized(ServletContextEvent sce) { + if (LOG.isDebugEnabled()) + LOG.debug("contextInitialized({}) {}", sce, this); LifeCycle.start(this); } @Override public void contextDestroyed(ServletContextEvent sce) { + if (LOG.isDebugEnabled()) + LOG.debug("contextDestroyed({}) {}", sce, this); + LifeCycle.stop(this); removeBeans(); JavaxWebSocketClientContainer.initialize(null); 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 654a23d6c7a..a535b9c0ede 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 @@ -64,12 +64,14 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple public static void initialize(ContainerLifeCycle container) { SHUTDOWN_CONTAINER.set(container); + if (LOG.isDebugEnabled()) + LOG.debug("initialized {} to {}", String.format("%s@%x", SHUTDOWN_CONTAINER.getClass().getSimpleName(), SHUTDOWN_CONTAINER.hashCode()), container); } protected WebSocketCoreClient coreClient; protected Function coreClientFactory; private final JavaxWebSocketClientFrameHandlerFactory frameHandlerFactory; - private boolean allowContextHandlerRegistration = true; + private boolean allowShutdownWithContextHandler = true; public JavaxWebSocketClientContainer() { @@ -110,9 +112,9 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple this.frameHandlerFactory = new JavaxWebSocketClientFrameHandlerFactory(this); } - public void allowContextHandlerRegistration(boolean allowContextHandlerRegistration) + public void allowShutdownWithContextHandler(boolean allowShutdownWithContextHandler) { - this.allowContextHandlerRegistration = allowContextHandlerRegistration; + this.allowShutdownWithContextHandler = allowShutdownWithContextHandler; } protected HttpClient getHttpClient() @@ -302,37 +304,51 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple @Override protected void doStop() throws Exception { - doClientStop(); super.doStop(); + doClientStop(); } protected void doClientStart() { + if (LOG.isDebugEnabled()) + LOG.debug("doClientStart() {}", this); + // If we are running in Jetty register shutdown with the ContextHandler. - if (allowContextHandlerRegistration && addToContextHandler(this)) + if (allowShutdownWithContextHandler && addToContextHandler()) + { + if (LOG.isDebugEnabled()) + LOG.debug("Shutdown registered with ContextHandler"); return; + } // If we are running inside a different ServletContainer we can register with the SHUTDOWN_CONTAINER static. ContainerLifeCycle shutdownContainer = SHUTDOWN_CONTAINER.get(); if (shutdownContainer != null) { shutdownContainer.addManaged(this); + if (LOG.isDebugEnabled()) + LOG.debug("Shutdown registered with ShutdownContainer {}", shutdownContainer); return; } ShutdownThread.register(this); + if (LOG.isDebugEnabled()) + LOG.debug("Shutdown registered with ShutdownThread"); } protected void doClientStop() { + if (LOG.isDebugEnabled()) + LOG.debug("doClientStop() {}", this); + // Remove from context handler if running in Jetty server. - removeFromContextHandler(this); + removeFromContextHandler(); // Remove from the Shutdown Container. ContainerLifeCycle shutdownContainer = SHUTDOWN_CONTAINER.get(); if (shutdownContainer != null && shutdownContainer.contains(this)) { - // Un-manage first as removeBean() will stop this, but this is already in STOPPING state. + // Un-manage first as we don't want to call stop again while in STOPPING state. shutdownContainer.unmanage(this); shutdownContainer.removeBean(this); } @@ -341,7 +357,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple ShutdownThread.deregister(this); } - private boolean addToContextHandler(LifeCycle lifeCycle) + private boolean addToContextHandler() { try { @@ -356,19 +372,19 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple contextHandler.getClass() .getMethod("addManaged", LifeCycle.class) - .invoke(contextHandler, lifeCycle); + .invoke(contextHandler, this); return true; } catch (Throwable throwable) { if (LOG.isDebugEnabled()) - LOG.debug("error from addToContextHandler({})", lifeCycle, throwable); + LOG.debug("error from addToContextHandler() for {}", this, throwable); return false; } } - private void removeFromContextHandler(LifeCycle lifeCycle) + private void removeFromContextHandler() { try { @@ -381,14 +397,19 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple .getMethod("getContextHandler") .invoke(context); + // Un-manage first as we don't want to call stop again while in STOPPING state. + contextHandler.getClass() + .getMethod("unmanage", Object.class) + .invoke(contextHandler, this); + contextHandler.getClass() .getMethod("removeBean", Object.class) - .invoke(contextHandler, JavaxWebSocketClientContainer.this); + .invoke(contextHandler, this); } catch (Throwable throwable) { if (LOG.isDebugEnabled()) - LOG.debug("error from removeFromContextHandler({})", lifeCycle, throwable); + LOG.debug("error from removeFromContextHandler() for {}", this, throwable); } } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java index 4e9f37224af..f3b0eaae79f 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java @@ -69,7 +69,7 @@ public class JavaxClientShutdownWithServerTest protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { JavaxWebSocketClientContainer clientContainer = new JavaxWebSocketClientContainer(); - clientContainer.allowContextHandlerRegistration(false); + clientContainer.allowShutdownWithContextHandler(false); LifeCycle.start(clientContainer); container = clientContainer; } From 99aef1a2c0d8721b69cb027ff33d048c609c73f1 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 18 Jan 2021 23:27:00 +1100 Subject: [PATCH 11/15] Issue #5832 - fix bugs when stopping the JavaxWebSocketClientContainer Signed-off-by: Lachlan Roberts --- ...a => JavaxWebSocketShutdownContainer.java} | 4 ++-- .../javax.servlet.ServletContainerInitializer | 2 +- .../JavaxClientShutdownWithServerTest.java | 23 ++++++++----------- ...yWebSocketServletContainerInitializer.java | 2 +- 4 files changed, 14 insertions(+), 17 deletions(-) rename jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/{JavaxWebSocketClientShutdown.java => JavaxWebSocketShutdownContainer.java} (91%) diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java similarity index 91% rename from jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java rename to jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java index 3d189cb70ef..5a49e05137b 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientShutdown.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java @@ -26,9 +26,9 @@ import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientCon import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class JavaxWebSocketClientShutdown extends ContainerLifeCycle implements ServletContainerInitializer, ServletContextListener +public class JavaxWebSocketShutdownContainer extends ContainerLifeCycle implements ServletContainerInitializer, ServletContextListener { - private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketClientShutdown.class); + private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketShutdownContainer.class); @Override public void onStartup(Set> c, ServletContext ctx) throws ServletException diff --git a/jetty-websocket/websocket-javax-client/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/jetty-websocket/websocket-javax-client/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer index b9a2f2d58b9..59e5f4b4055 100644 --- a/jetty-websocket/websocket-javax-client/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer +++ b/jetty-websocket/websocket-javax-client/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer @@ -1 +1 @@ -org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientShutdown \ No newline at end of file +org.eclipse.jetty.websocket.javax.client.JavaxWebSocketShutdownContainer \ No newline at end of file diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java index f3b0eaae79f..6f7959d1b46 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java @@ -13,10 +13,8 @@ package org.eclipse.jetty.websocket.javax.tests; -import java.io.IOException; import java.net.URI; import java.util.Collection; -import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -32,7 +30,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.LifeCycle; -import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientShutdown; +import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketShutdownContainer; import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -53,11 +51,10 @@ public class JavaxClientShutdownWithServerTest private volatile WebSocketContainer container; private ContainerLifeCycle shutdownContainer; - public class ContextHandlerShutdownServlet extends HttpServlet { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { container = ContainerProvider.getWebSocketContainer(); } @@ -66,7 +63,7 @@ public class JavaxClientShutdownWithServerTest public class ServletContainerInitializerShutdownServlet extends HttpServlet { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { JavaxWebSocketClientContainer clientContainer = new JavaxWebSocketClientContainer(); clientContainer.allowShutdownWithContextHandler(false); @@ -84,12 +81,12 @@ public class JavaxClientShutdownWithServerTest contextHandler = new ServletContextHandler(); contextHandler.setContextPath("/"); - contextHandler.addServlet(new ServletHolder(new ContextHandlerShutdownServlet()), "/contextHandlerShutdown"); - contextHandler.addServlet(new ServletHolder(new ServletContainerInitializerShutdownServlet()), "/servletContainerInitializerShutdown"); + contextHandler.addServlet(new ServletHolder(new ContextHandlerShutdownServlet()), "/contextHandler"); + contextHandler.addServlet(new ServletHolder(new ServletContainerInitializerShutdownServlet()), "/shutdownContainer"); server.setHandler(contextHandler); // Because we are using embedded we must manually add the Javax WS Client Shutdown SCI. - JavaxWebSocketClientShutdown javaxWebSocketClientShutdown = new JavaxWebSocketClientShutdown(); + JavaxWebSocketShutdownContainer javaxWebSocketClientShutdown = new JavaxWebSocketShutdownContainer(); shutdownContainer = javaxWebSocketClientShutdown; contextHandler.addServletContainerInitializer(javaxWebSocketClientShutdown); @@ -108,9 +105,9 @@ public class JavaxClientShutdownWithServerTest } @Test - public void testWebSocketClientContainerInWebapp() throws Exception + public void testShutdownWithContextHandler() throws Exception { - ContentResponse response = httpClient.GET(serverUri.resolve("/contextHandlerShutdown")); + ContentResponse response = httpClient.GET(serverUri.resolve("/contextHandler")); assertThat(response.getStatus(), is(HttpStatus.OK_200)); assertNotNull(container); @@ -130,9 +127,9 @@ public class JavaxClientShutdownWithServerTest } @Test - public void testWebSocketClientContainerInWebapp2() throws Exception + public void testShutdownWithShutdownContainer() throws Exception { - ContentResponse response = httpClient.GET(serverUri.resolve("/servletContainerInitializerShutdown")); + ContentResponse response = httpClient.GET(serverUri.resolve("/shutdownContainer")); assertThat(response.getStatus(), is(HttpStatus.OK_200)); assertNotNull(container); diff --git a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java index d843b2c4edb..7f225a812a4 100644 --- a/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java +++ b/jetty-websocket/websocket-jetty-server/src/main/java/org/eclipse/jetty/websocket/server/config/JettyWebSocketServletContainerInitializer.java @@ -60,7 +60,7 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain { if (!context.isStopped()) throw new IllegalStateException("configure should be called before starting"); - context.addServletContainerInitializer(new JettyWebSocketServletContainerInitializer()); + context.addServletContainerInitializer(new JettyWebSocketServletContainerInitializer(configurator)); } /** From c6c1ccfdd08b3494ab0a215e1591da721630af7c Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Wed, 20 Jan 2021 17:00:33 +1100 Subject: [PATCH 12/15] Rename JavaxWebSocketClientContainer.initialize to setShutdownContainer. Signed-off-by: Lachlan Roberts --- .../javax/client/JavaxWebSocketShutdownContainer.java | 4 ++-- .../javax/client/internal/JavaxWebSocketClientContainer.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java index 5a49e05137b..020cb85a8e9 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java @@ -33,7 +33,7 @@ public class JavaxWebSocketShutdownContainer extends ContainerLifeCycle implemen @Override public void onStartup(Set> c, ServletContext ctx) throws ServletException { - JavaxWebSocketClientContainer.initialize(this); + JavaxWebSocketClientContainer.setShutdownContainer(this); ctx.addListener(this); } @@ -53,6 +53,6 @@ public class JavaxWebSocketShutdownContainer extends ContainerLifeCycle implemen LifeCycle.stop(this); removeBeans(); - JavaxWebSocketClientContainer.initialize(null); + JavaxWebSocketClientContainer.setShutdownContainer(null); } } 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 a535b9c0ede..06fbed510f4 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 @@ -61,7 +61,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketClientContainer.class); private static final AtomicReference SHUTDOWN_CONTAINER = new AtomicReference<>(); - public static void initialize(ContainerLifeCycle container) + public static void setShutdownContainer(ContainerLifeCycle container) { SHUTDOWN_CONTAINER.set(container); if (LOG.isDebugEnabled()) From ff4f2ef28f4f83036f5c409116ab457dfd7f5348 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Thu, 21 Jan 2021 15:27:15 +1100 Subject: [PATCH 13/15] only allow adding ServletContainerInitializers before starting Signed-off-by: Lachlan Roberts --- .../org/eclipse/jetty/servlet/ServletContextHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index f7b18da4018..d2012a901e4 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -703,6 +703,9 @@ public class ServletContextHandler extends ContextHandler */ public void addServletContainerInitializer(ServletContainerInitializer containerInitializer) { + if (!isStopped()) + throw new IllegalStateException("ServletContainerInitializers should be added before starting"); + addServletContainerInitializer(containerInitializer, Collections.emptySet()); } @@ -714,6 +717,9 @@ public class ServletContextHandler extends ContextHandler */ public void addServletContainerInitializer(ServletContainerInitializer containerInitializer, Set> classes) { + if (!isStopped()) + throw new IllegalStateException("ServletContainerInitializers should be added before starting"); + addManaged(new Initializer(this, containerInitializer, classes)); } From ed863615819e9c4a98abf6d405b2846b1f1d86e7 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 22 Feb 2021 10:03:43 +1100 Subject: [PATCH 14/15] Revert changes to ServletContainerInitializer Signed-off-by: Lachlan Roberts --- .../jetty/servlet/ServletContextHandler.java | 27 ------------------- .../listener/ContainerInitializer.java | 2 -- .../JavaxWebSocketShutdownContainer.java | 2 +- .../JavaxClientShutdownWithServerTest.java | 2 +- .../jetty/cdi/tests/EmbeddedWeldTest.java | 16 +++++------ 5 files changed, 10 insertions(+), 39 deletions(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java index 39bd9b49e8c..d420675241b 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java @@ -747,33 +747,6 @@ public class ServletContextHandler extends ContextHandler relinkHandlers(); } - /** - * Utility Method to allow for manual execution of {@link javax.servlet.ServletContainerInitializer} when using Embedded Jetty. - * @param containerInitializer the ServletContainerInitializer to register. - * @see Initializer - */ - public void addServletContainerInitializer(ServletContainerInitializer containerInitializer) - { - if (!isStopped()) - throw new IllegalStateException("ServletContainerInitializers should be added before starting"); - - addServletContainerInitializer(containerInitializer, Collections.emptySet()); - } - - /** - * Utility Method to allow for manual execution of {@link javax.servlet.ServletContainerInitializer} when using Embedded Jetty. - * @param containerInitializer the ServletContainerInitializer to register. - * @param classes the Set of application classes. - * @see Initializer - */ - public void addServletContainerInitializer(ServletContainerInitializer containerInitializer, Set> classes) - { - if (!isStopped()) - throw new IllegalStateException("ServletContainerInitializers should be added before starting"); - - addManaged(new Initializer(this, containerInitializer, classes)); - } - /** * The DecoratedObjectFactory for use by IoC containers (weld / spring / etc) * diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java index 927c7de8fb0..8478fd62ae2 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ContainerInitializer.java @@ -25,9 +25,7 @@ import javax.servlet.ServletContextListener; /** * Utility Methods for manual execution of {@link javax.servlet.ServletContainerInitializer} when * using Embedded Jetty. - * @deprecated use {@link org.eclipse.jetty.servlet.ServletContextHandler#addServletContainerInitializer(ServletContainerInitializer)}. */ -@Deprecated public final class ContainerInitializer { /** diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java index 020cb85a8e9..b4781791d4a 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java @@ -1,6 +1,6 @@ // // ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// 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 diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java index 6f7959d1b46..59f6fc76850 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java @@ -1,6 +1,6 @@ // // ======================================================================== -// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. +// 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 diff --git a/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/EmbeddedWeldTest.java b/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/EmbeddedWeldTest.java index 06d63fc4467..3738462013b 100644 --- a/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/EmbeddedWeldTest.java +++ b/tests/test-cdi/src/test/java/org/eclipse/jetty/cdi/tests/EmbeddedWeldTest.java @@ -103,29 +103,29 @@ public class EmbeddedWeldTest case "CdiServletContainerInitializer+Listener": // Expect:INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters. - context.addServletContainerInitializer(new CdiServletContainerInitializer()); + context.addBean(new ServletContextHandler.Initializer(context, new CdiServletContainerInitializer())); context.addEventListener(new org.jboss.weld.environment.servlet.Listener()); break; case "CdiServletContainerInitializer(CdiDecoratingListener)+Listener": // Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters context.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE); - context.addServletContainerInitializer(new CdiServletContainerInitializer()); + context.addBean(new ServletContextHandler.Initializer(context, new CdiServletContainerInitializer())); context.addEventListener(new org.jboss.weld.environment.servlet.Listener()); break; case "CdiServletContainerInitializer+EnhancedListener": // Expect:INFO: WELD-ENV-001213: Jetty CDI SPI support detected, CDI injection will be available in Listeners, Servlets and Filters. - context.addServletContainerInitializer(new CdiServletContainerInitializer()); - context.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener()); + context.addBean(new ServletContextHandler.Initializer(context, new CdiServletContainerInitializer())); + context.addBean(new ServletContextHandler.Initializer(context, new org.jboss.weld.environment.servlet.EnhancedListener())); break; // NOTE: This is the preferred mode from the Weld team. case "CdiServletContainerInitializer(CdiDecoratingListener)+EnhancedListener": // Expect:INFO: WELD-ENV-001212: Jetty CdiDecoratingListener support detected, CDI injection will be available in Listeners, Servlets and Filters context.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE); - context.addServletContainerInitializer(new CdiServletContainerInitializer()); - context.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener()); + context.addBean(new ServletContextHandler.Initializer(context, new CdiServletContainerInitializer())); + context.addBean(new ServletContextHandler.Initializer(context, new org.jboss.weld.environment.servlet.EnhancedListener())); break; } @@ -190,8 +190,8 @@ public class EmbeddedWeldTest server.setHandler(webapp); webapp.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE); - webapp.addServletContainerInitializer(new CdiServletContainerInitializer()); - webapp.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener()); + webapp.addBean(new ServletContextHandler.Initializer(webapp, new CdiServletContainerInitializer())); + webapp.addBean(new ServletContextHandler.Initializer(webapp, new org.jboss.weld.environment.servlet.EnhancedListener())); String pkg = EmbeddedWeldTest.class.getPackage().getName(); webapp.getServerClassMatcher().add("-" + pkg + "."); From 47f24db0bea94b2057acaaca7d901584cf1cbd02 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Mon, 22 Feb 2021 21:09:22 +1100 Subject: [PATCH 15/15] Issue #5832 - Improve testing for WebSocket client shutdown. Signed-off-by: Lachlan Roberts --- ...JavaxWebSocketClientContainerProvider.java | 1 - .../JavaxWebSocketShutdownContainer.java | 15 ++ .../JavaxWebSocketClientContainer.java | 8 +- .../jetty/websocket/javax/tests/WSServer.java | 13 +- ...ClientShutdownWithServerEmbeddedTest.java} | 45 +---- ...vaxClientShutdownWithServerWebAppTest.java | 191 ++++++++++++++++++ 6 files changed, 222 insertions(+), 51 deletions(-) rename jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/{JavaxClientShutdownWithServerTest.java => JavaxClientShutdownWithServerEmbeddedTest.java} (66%) create mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerWebAppTest.java diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java index f747d44b7c1..d5d6fb99494 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketClientContainerProvider.java @@ -67,5 +67,4 @@ public class JavaxWebSocketClientContainerProvider extends ContainerProvider LifeCycle.start(clientContainer); return clientContainer; } - } diff --git a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java index b4781791d4a..6448e724506 100644 --- a/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java +++ b/jetty-websocket/websocket-javax-client/src/main/java/org/eclipse/jetty/websocket/javax/client/JavaxWebSocketShutdownContainer.java @@ -26,6 +26,15 @@ import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientCon import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + *

This manages the LifeCycle of {@link javax.websocket.WebSocketContainer} instances that are created with + * {@link javax.websocket.ContainerProvider}, if this code is being run from another ServletContainer, or if run inside a + * Jetty Server with the WebSocket client classes provided by the webapp.

+ * + *

This mechanism will not work if run with embedded Jetty or if the WebSocket client classes are provided by the server. + * In this case then the client {@link javax.websocket.WebSocketContainer} will register itself to be automatically shutdown + * with the Jetty {@code ContextHandler}.

+ */ public class JavaxWebSocketShutdownContainer extends ContainerLifeCycle implements ServletContainerInitializer, ServletContextListener { private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketShutdownContainer.class); @@ -55,4 +64,10 @@ public class JavaxWebSocketShutdownContainer extends ContainerLifeCycle implemen removeBeans(); JavaxWebSocketClientContainer.setShutdownContainer(null); } + + @Override + public String toString() + { + return String.format("%s@%x{%s, size=%s}", getClass().getSimpleName(), hashCode(), getState(), getBeans().size()); + } } 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 d7f0ce5b606..1776aae14f8 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 @@ -71,7 +71,6 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple protected WebSocketCoreClient coreClient; protected Function coreClientFactory; private final JavaxWebSocketClientFrameHandlerFactory frameHandlerFactory; - private boolean allowShutdownWithContextHandler = true; public JavaxWebSocketClientContainer() { @@ -102,11 +101,6 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple this.frameHandlerFactory = new JavaxWebSocketClientFrameHandlerFactory(this); } - public void allowShutdownWithContextHandler(boolean allowShutdownWithContextHandler) - { - this.allowShutdownWithContextHandler = allowShutdownWithContextHandler; - } - protected HttpClient getHttpClient() { return getWebSocketCoreClient().getHttpClient(); @@ -304,7 +298,7 @@ public class JavaxWebSocketClientContainer extends JavaxWebSocketContainer imple LOG.debug("doClientStart() {}", this); // If we are running in Jetty register shutdown with the ContextHandler. - if (allowShutdownWithContextHandler && addToContextHandler()) + if (addToContextHandler()) { if (LOG.isDebugEnabled()) LOG.debug("Shutdown registered with ContextHandler"); diff --git a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/WSServer.java b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/WSServer.java index 3a27ec71607..77813d64460 100644 --- a/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/WSServer.java +++ b/jetty-websocket/websocket-javax-tests/src/main/java/org/eclipse/jetty/websocket/javax/tests/WSServer.java @@ -17,7 +17,9 @@ import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.Random; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; @@ -46,7 +48,16 @@ public class WSServer extends LocalServer implements LocalFuzzer.Provider { private static final Logger LOG = LoggerFactory.getLogger(WSServer.class); private final Path testDir; - private ContextHandlerCollection contexts = new ContextHandlerCollection(); + private final ContextHandlerCollection contexts = new ContextHandlerCollection(); + + public WSServer() + { + String baseDirName = Long.toString(Math.abs(new Random().nextLong())); + this.testDir = MavenTestingUtils.getTargetTestingPath(baseDirName); + if (Files.exists(testDir)) + throw new IllegalStateException("TestDir already exists."); + FS.ensureDirExists(testDir); + } public WSServer(Path testDir) { diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerEmbeddedTest.java similarity index 66% rename from jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java rename to jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerEmbeddedTest.java index 59f6fc76850..6a6fcc788ea 100644 --- a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerTest.java +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerEmbeddedTest.java @@ -28,8 +28,6 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketShutdownContainer; import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; import org.junit.jupiter.api.AfterEach; @@ -42,14 +40,13 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertNotNull; -public class JavaxClientShutdownWithServerTest +public class JavaxClientShutdownWithServerEmbeddedTest { private Server server; private ServletContextHandler contextHandler; private URI serverUri; private HttpClient httpClient; private volatile WebSocketContainer container; - private ContainerLifeCycle shutdownContainer; public class ContextHandlerShutdownServlet extends HttpServlet { @@ -60,18 +57,6 @@ public class JavaxClientShutdownWithServerTest } } - public class ServletContainerInitializerShutdownServlet extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) - { - JavaxWebSocketClientContainer clientContainer = new JavaxWebSocketClientContainer(); - clientContainer.allowShutdownWithContextHandler(false); - LifeCycle.start(clientContainer); - container = clientContainer; - } - } - @BeforeEach public void before() throws Exception { @@ -81,13 +66,11 @@ public class JavaxClientShutdownWithServerTest contextHandler = new ServletContextHandler(); contextHandler.setContextPath("/"); - contextHandler.addServlet(new ServletHolder(new ContextHandlerShutdownServlet()), "/contextHandler"); - contextHandler.addServlet(new ServletHolder(new ServletContainerInitializerShutdownServlet()), "/shutdownContainer"); + contextHandler.addServlet(new ServletHolder(new ContextHandlerShutdownServlet()), "/"); server.setHandler(contextHandler); // Because we are using embedded we must manually add the Javax WS Client Shutdown SCI. JavaxWebSocketShutdownContainer javaxWebSocketClientShutdown = new JavaxWebSocketShutdownContainer(); - shutdownContainer = javaxWebSocketClientShutdown; contextHandler.addServletContainerInitializer(javaxWebSocketClientShutdown); server.start(); @@ -107,7 +90,7 @@ public class JavaxClientShutdownWithServerTest @Test public void testShutdownWithContextHandler() throws Exception { - ContentResponse response = httpClient.GET(serverUri.resolve("/contextHandler")); + ContentResponse response = httpClient.GET(serverUri); assertThat(response.getStatus(), is(HttpStatus.OK_200)); assertNotNull(container); @@ -125,26 +108,4 @@ public class JavaxClientShutdownWithServerTest assertThat(clientContainer.isRunning(), is(false)); assertThat(server.getContainedBeans(WebSocketContainer.class), empty()); } - - @Test - public void testShutdownWithShutdownContainer() throws Exception - { - ContentResponse response = httpClient.GET(serverUri.resolve("/shutdownContainer")); - assertThat(response.getStatus(), is(HttpStatus.OK_200)); - - assertNotNull(container); - assertThat(container, instanceOf(JavaxWebSocketClientContainer.class)); - JavaxWebSocketClientContainer clientContainer = (JavaxWebSocketClientContainer)container; - assertThat(clientContainer.isRunning(), is(true)); - - // The container should be a bean on the ContextHandler. - Collection containedBeans = shutdownContainer.getBeans(WebSocketContainer.class); - assertThat(containedBeans.size(), is(1)); - assertThat(containedBeans.toArray()[0], is(container)); - - // The client should be attached to the servers LifeCycle and should stop with it. - server.stop(); - assertThat(clientContainer.isRunning(), is(false)); - assertThat(server.getContainedBeans(WebSocketContainer.class), empty()); - } } diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerWebAppTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerWebAppTest.java new file mode 100644 index 00000000000..1f1562652f0 --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/JavaxClientShutdownWithServerWebAppTest.java @@ -0,0 +1,191 @@ +// +// ======================================================================== +// 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.tests; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.websocket.ContainerProvider; +import javax.websocket.WebSocketContainer; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.webapp.Configuration; +import org.eclipse.jetty.webapp.Configurations; +import org.eclipse.jetty.websocket.core.WebSocketComponents; +import org.eclipse.jetty.websocket.core.client.CoreClientUpgradeRequest; +import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientContainerProvider; +import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketShutdownContainer; +import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketConfiguration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +public class JavaxClientShutdownWithServerWebAppTest +{ + private WSServer server; + private HttpClient httpClient; + + @FunctionalInterface + interface ThrowingRunnable + { + void run() throws Exception; + } + + public void start(ThrowingRunnable configuration) throws Exception + { + server = new WSServer(); + configuration.run(); + server.start(); + httpClient = new HttpClient(); + httpClient.start(); + } + + @AfterEach + public void after() throws Exception + { + httpClient.stop(); + server.stop(); + } + + @WebServlet("/") + public static class ContextHandlerShutdownServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + { + ContainerProvider.getWebSocketContainer(); + } + } + + public WSServer.WebApp createWebSocketWebapp(String contextName) throws Exception + { + WSServer.WebApp app = server.createWebApp(contextName); + + // Exclude the Javax WebSocket configuration from the webapp. + Configuration[] configurations = Configurations.getKnown().stream() + .filter(configuration -> !(configuration instanceof JavaxWebSocketConfiguration)) + .toArray(Configuration[]::new); + app.getWebAppContext().setConfigurations(configurations); + + // Copy over the individual jars required for Javax WebSocket. + app.createWebInf(); + app.copyLib(JavaxWebSocketClientContainerProvider.class, "websocket-javax-client.jar"); + app.copyLib(JavaxWebSocketContainer.class, "websocket-javax-common.jar"); + app.copyLib(ContainerLifeCycle.class, "jetty-util.jar"); + app.copyLib(CoreClientUpgradeRequest.class, "websocket-core-client.jar"); + app.copyLib(WebSocketComponents.class, "websocket-core-common.jar"); + app.copyLib(Response.class, "jetty-client.jar"); + app.copyLib(ByteBufferPool.class, "jetty-io.jar"); + app.copyLib(BadMessageException.class, "jetty-http.jar"); + + return app; + } + + @Test + public void websocketProvidedByServer() throws Exception + { + start(() -> + { + WSServer.WebApp app1 = server.createWebApp("app1"); + app1.createWebInf(); + app1.copyClass(ContextHandlerShutdownServlet.class); + app1.deploy(); + + WSServer.WebApp app2 = server.createWebApp("app2"); + app2.createWebInf(); + app2.copyClass(ContextHandlerShutdownServlet.class); + app2.deploy(); + + WSServer.WebApp app3 = server.createWebApp("app3"); + app3.createWebInf(); + app3.copyClass(ContextHandlerShutdownServlet.class); + app3.deploy(); + }); + + // Before connecting to the server there is only the containers created for the server component of each WebApp. + assertThat(server.isRunning(), is(true)); + assertThat(server.getContainedBeans(WebSocketContainer.class).size(), is(3)); + + // After hitting each WebApp with a request we now have an additional 3 client containers. + ContentResponse response = httpClient.GET(server.getServerUri().resolve("/app1")); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + response = httpClient.GET(server.getServerUri().resolve("/app2")); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + response = httpClient.GET(server.getServerUri().resolve("/app3")); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + assertThat(server.getContainedBeans(WebSocketContainer.class).size(), is(6)); + + // All the websocket containers are removed on stopping of the server. + server.stop(); + assertThat(server.isRunning(), is(false)); + assertThat(server.getContainedBeans(WebSocketContainer.class).size(), is(0)); + } + + @Test + public void websocketProvidedByWebApp() throws Exception + { + start(() -> + { + WSServer.WebApp app1 = createWebSocketWebapp("app1"); + app1.copyClass(ContextHandlerShutdownServlet.class); + app1.deploy(); + + WSServer.WebApp app2 = createWebSocketWebapp("app2"); + app2.copyClass(ContextHandlerShutdownServlet.class); + app2.deploy(); + + WSServer.WebApp app3 = createWebSocketWebapp("app3"); + app3.copyClass(ContextHandlerShutdownServlet.class); + app3.deploy(); + }); + + // Before connecting to the server there is only the containers created for the server component of each WebApp. + assertThat(server.isRunning(), is(true)); + assertThat(server.getContainedBeans(WebSocketContainer.class).size(), is(0)); + + // After hitting each WebApp with a request we now have an additional 3 client containers. + ContentResponse response = httpClient.GET(server.getServerUri().resolve("/app1")); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + response = httpClient.GET(server.getServerUri().resolve("/app2")); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + response = httpClient.GET(server.getServerUri().resolve("/app3")); + assertThat(response.getStatus(), is(HttpStatus.OK_200)); + + // Collect the toString result of the ShutdownContainers from the dump. + List results = Arrays.stream(server.getServer().dump().split("\n")) + .filter(line -> line.contains("+> " + JavaxWebSocketShutdownContainer.class.getSimpleName())) + .collect(Collectors.toList()); + + // We only have 3 Shutdown Containers and they all contain only 1 item to be shutdown. + assertThat(results.size(), is(3)); + for (String result : results) + { + assertThat(result, containsString("size=1")); + } + } +}