From b13f8d2718d4493b97c64d1f15de9429516149e8 Mon Sep 17 00:00:00 2001 From: Romain Manni-Bucau Date: Wed, 13 Jan 2021 15:03:23 +0100 Subject: [PATCH] adding exitOnFirstListenerFailure flag in standardcontext to enable to stop at first listener error --- .../apache/catalina/core/StandardContext.java | 24 +++++++ .../catalina/core/TestStandardContext.java | 63 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java index abb123f1c6f8..be996bdfbdf5 100644 --- a/java/org/apache/catalina/core/StandardContext.java +++ b/java/org/apache/catalina/core/StandardContext.java @@ -171,6 +171,12 @@ public StandardContext() { // ----------------------------------------------------- Instance Variables + /** + * Allow to fail as soon as the first context listener failed. + * Note that it does not impact stop phase since some listeners can just cleanup some state. + */ + protected boolean exitOnFirstListenerFailure = false; + /** * Allow multipart/form-data requests to be parsed even when the * target servlet doesn't specify @MultipartConfig or have a @@ -4671,6 +4677,9 @@ public boolean listenerStart() { getLogger().error(sm.getString("standardContext.listenerStart", instance.getClass().getName()), t); ok = false; + if (exitOnFirstListenerFailure) { + break; + } } } return ok; @@ -6359,6 +6368,21 @@ public long getStartTime() { return startTime; } + /** + * Toggle to enable to stop deployment on first listener and skip next listeners. + * @return true if the first context listener will stop the context deployment, false otherwise. + */ + public boolean isExitOnFirstListenerFailure() { + return exitOnFirstListenerFailure; + } + + /** + * Enables to customize if the first context listener failure stops the deployment or not. + * @param value true to stop the deployment at first context listener error. + */ + public void setExitOnFirstListenerFailure(final boolean value) { + this.exitOnFirstListenerFailure = value; + } private static class NoPluggabilityServletContext implements ServletContext { diff --git a/test/org/apache/catalina/core/TestStandardContext.java b/test/org/apache/catalina/core/TestStandardContext.java index b28905a05f22..06d4dd78a4d7 100644 --- a/test/org/apache/catalina/core/TestStandardContext.java +++ b/test/org/apache/catalina/core/TestStandardContext.java @@ -19,7 +19,9 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -31,6 +33,8 @@ import jakarta.servlet.Servlet; import jakarta.servlet.ServletContainerInitializer; import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRegistration; import jakarta.servlet.ServletRequest; @@ -72,6 +76,11 @@ import org.apache.tomcat.util.descriptor.web.FilterMap; import org.apache.tomcat.util.descriptor.web.LoginConfig; +import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singletonList; +import static org.junit.Assert.*; + public class TestStandardContext extends TomcatBaseTest { @@ -81,6 +90,60 @@ public class TestStandardContext extends TomcatBaseTest { "Connection: close\r\n" + "\r\n"; + @Test + public void testExitOnFirstListenerFailure() throws Exception { + final Collection visited = new ArrayList<>(); + final ServletContainerInitializer initializer = (c, ctx) -> { + ctx.addListener(new ServletContextListener() { + @Override + public void contextInitialized(final ServletContextEvent sce) { + visited.add("first:init"); + throw new IllegalStateException("test failure"); + } + }); + ctx.addListener(new ServletContextListener() { + @Override + public void contextInitialized(final ServletContextEvent sce) { + visited.add("second:init"); + } + }); + }; + { // first ensure by default we go through all listeners + final Tomcat tomcat = getTomcatInstance(); + final File docBase = new File(tomcat.getHost().getAppBaseFile(), "ROOT"); + if (!docBase.mkdirs() && !docBase.isDirectory()) { + Assert.fail("Unable to create docBase"); + } + + final Context root = tomcat.addContext("", "ROOT"); + root.addServletContainerInitializer(initializer, emptySet()); + try { + tomcat.start(); + assertEquals(LifecycleState.STOPPED, root.getState()); + } finally { + tearDown(); + } + assertEquals(asList("first:init", "second:init"), visited); + } + { // now do the same but with the flag to stop ASAP + setUp(); + visited.clear(); + final Tomcat tomcat = getTomcatInstance(); + final File docBase = new File(tomcat.getHost().getAppBaseFile(), "ROOT"); + if (!docBase.mkdirs() && !docBase.isDirectory()) { + Assert.fail("Unable to create docBase"); + } + + final StandardContext root = (StandardContext) + tomcat.addContext("", "ROOT"); + root.setExitOnFirstListenerFailure(true); + root.addServletContainerInitializer(initializer, emptySet()); + tomcat.start(); + assertEquals(LifecycleState.STOPPED, root.getState()); + assertEquals(singletonList("first:init"), visited); + } + } + @Test public void testBug46243() throws Exception { // This tests that if a Filter init() fails then the web application