You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/10/26 14:46:38 UTC

(juneau) branch master updated: Make RestChildren.destroyChild skip a child whose resource bean the container already removed, so the remaining children are still destroyed, with a regression test; have seven tests capture and assert on their expected stack traces instead of printing them to the build log.

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new ec6d5b1ce7 Make RestChildren.destroyChild skip a child whose resource bean the container already removed, so the remaining children are still destroyed, with a regression test; have seven tests capture and assert on their expected stack traces instead of printing them to the build log.
ec6d5b1ce7 is described below

commit ec6d5b1ce790e26b43dac1a16df83ff132a10db4
Author: James Bognar <ja...@apache.org>
AuthorDate: Mon Oct 26 10:46:32 2026 -0400

    Make RestChildren.destroyChild skip a child whose resource bean the container already removed, so the remaining children are still destroyed, with a regression test; have seven tests capture and assert on their expected stack traces instead of printing them to the build log.
---
 .../commons/logging/LogRecordCapture_Test.java     |  2 +-
 .../juneau/commons/logging/RichLogger_Test.java    |  4 +-
 .../juneau/commons/utils/ThrowableUtils_Test.java  | 10 +++-
 .../server/DebugMarshalling_Behavior_Test.java     |  4 +-
 .../server/Rest_MethodOverrideAgreement_Test.java  |  9 ++-
 .../rest/mock/RequestIdDebugCapture_Test.java      |  2 +-
 .../juneau/rest/mock/RestDebugCapture_Test.java    |  4 +-
 .../apache/juneau/rest/server/RestChildren.java    | 10 +++-
 .../rest/server/RestChildren_Destroy_Test.java     | 70 ++++++++++++++++++++++
 9 files changed, 102 insertions(+), 13 deletions(-)

diff --git a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/LogRecordCapture_Test.java b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/LogRecordCapture_Test.java
index 548a25b884..9d34738354 100644
--- a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/LogRecordCapture_Test.java
+++ b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/LogRecordCapture_Test.java
@@ -383,7 +383,7 @@ class LogRecordCapture_Test extends TestBase {
 	@Test void j03_lastAndAssertHelpers_returnRawValues() {
 		var logger = RichLogger.getLogger("j03");
 		var ex = new RuntimeException("x");
-		try (var capture = logger.captureEvents()) {
+		try (var quiet = LogRecordCapture.quiet("j03"); var capture = logger.captureEvents()) {
 			logger.info("a");
 			logger.warning(ex, "b");
 
diff --git a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/RichLogger_Test.java b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/RichLogger_Test.java
index f01c9e035e..c14b7bc961 100644
--- a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/RichLogger_Test.java
+++ b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/logging/RichLogger_Test.java
@@ -900,7 +900,9 @@ class RichLogger_Test extends TestBase {
 			rec.setLoggerName(name);
 			logger.log(rec);
 			logger.info("commons");
-			logger.log(Level.WARNING, new RuntimeException("ignored"), () -> "supplier");
+			var ignored = new RuntimeException("ignored");
+			ignored.setStackTrace(new StackTraceElement[0]);  // Keep the console handler from printing a trace.
+			logger.log(Level.WARNING, ignored, () -> "supplier");
 
 			assertSize(3, capture.getRecords());
 			assertEquals(3, published.get());
diff --git a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/utils/ThrowableUtils_Test.java b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/utils/ThrowableUtils_Test.java
index b2de0bb1f4..97f3cbb188 100644
--- a/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/utils/ThrowableUtils_Test.java
+++ b/juneau-core/juneau-commons/src/test/java/org/apache/juneau/commons/utils/ThrowableUtils_Test.java
@@ -25,6 +25,7 @@ import java.sql.*;
 import java.util.*;
 
 import org.apache.juneau.commons.*;
+import org.apache.juneau.commons.logging.*;
 import org.apache.juneau.commons.reflect.*;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.api.parallel.*;
@@ -538,10 +539,13 @@ class ThrowableUtils_Test extends TestBase {
 	//====================================================================================================
 	@Test
 	void a032_printStackTrace_integer() {
-		// Redirects to System.err — just verify it doesn't throw
+		// Logged as a warning on the ThrowableUtils logger; capture it so the trace stays off the console.
 		var ex = new RuntimeException("test error");
-		assertDoesNotThrow(() -> printStackTrace(ex, (Integer)null));
-		assertDoesNotThrow(() -> printStackTrace(ex, 2));
+		var records = LogRecordCapture.quietly(ThrowableUtils.class, () -> {
+			assertDoesNotThrow(() -> printStackTrace(ex, (Integer)null));
+			assertDoesNotThrow(() -> printStackTrace(ex, 2));
+		});
+		assertEquals(2, records.size());
 	}
 
 	//====================================================================================================
diff --git a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/DebugMarshalling_Behavior_Test.java b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/DebugMarshalling_Behavior_Test.java
index 9be5604206..b5cfc73e71 100644
--- a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/DebugMarshalling_Behavior_Test.java
+++ b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/DebugMarshalling_Behavior_Test.java
@@ -21,6 +21,7 @@ import static org.apache.juneau.http.classic.header.ContentType.*;
 import java.util.logging.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.commons.logging.*;
 import org.apache.juneau.http.*;
 import org.apache.juneau.marshall.json.*;
 import org.apache.juneau.rest.mock.classic.*;
@@ -75,7 +76,8 @@ class DebugMarshalling_Behavior_Test extends TestBase {
 		var logger = Logger.getLogger(resourceClass.getName());
 		var prev = logger.getLevel();
 		logger.setLevel(level);
-		try {
+		// At FINE the server logs the failed request with its stack trace; keep that off the console.
+		try (var quiet = LogRecordCapture.quiet(resourceClass)) {
 			action.run();
 		} finally {
 			logger.setLevel(prev);
diff --git a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/Rest_MethodOverrideAgreement_Test.java b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/Rest_MethodOverrideAgreement_Test.java
index 01af088103..3ee0cf6a8e 100644
--- a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/Rest_MethodOverrideAgreement_Test.java
+++ b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/Rest_MethodOverrideAgreement_Test.java
@@ -24,6 +24,7 @@ import java.util.concurrent.*;
 import java.util.stream.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.commons.logging.*;
 import org.apache.juneau.rest.mock.classic.*;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.params.*;
@@ -282,9 +283,11 @@ class Rest_MethodOverrideAgreement_Test extends TestBase {
 	}
 
 	@Test void b13_conflict_withCallLoggerAndDebug_stays400() throws Exception {
-		var c = MockRestClient.create(Star.class).ignoreErrors().debug().build();
-		var r = c.get("/x").header("X-Method", "DELETE").header("X-HTTP-Method", "PUT").run();
-		assertEquals(400, r.getStatusCode());
+		try (var quiet = LogRecordCapture.quiet(Star.class)) {  // The debug logger would otherwise print the 400's stack trace.
+			var c = MockRestClient.create(Star.class).ignoreErrors().debug().build();
+			var r = c.get("/x").header("X-Method", "DELETE").header("X-HTTP-Method", "PUT").run();
+			assertEquals(400, r.getStatusCode());
+		}
 	}
 
 	@Test void b14_nonConflictingHeaders_stillDispatchWithTrailingSpace405() throws Exception {
diff --git a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RequestIdDebugCapture_Test.java b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RequestIdDebugCapture_Test.java
index 32382ec9cb..38584f6a45 100644
--- a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RequestIdDebugCapture_Test.java
+++ b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RequestIdDebugCapture_Test.java
@@ -106,7 +106,7 @@ class RequestIdDebugCapture_Test {
 	//------------------------------------------------------------------------------------------------------------------
 
 	@Test void c01_early404RecordCarriesRequestId() throws Exception {
-		try (var c = RichLogger.getLogger(A_Resource.class).captureEvents(Level.FINEST)) {
+		try (var quiet = LogRecordCapture.quiet(A_Resource.class); var c = RichLogger.getLogger(A_Resource.class).captureEvents(Level.FINEST)) {
 			var client = org.apache.juneau.rest.mock.classic.MockRestClient.create(A_Resource.class).ignoreErrors().debug().build();
 			var res = client.get("/does-not-exist").run().assertStatus(404);
 			var echoed = res.getHeader("X-Request-Id").asString().orElseThrow();
diff --git a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RestDebugCapture_Test.java b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RestDebugCapture_Test.java
index 2b02a08e19..c05d5f6b9f 100644
--- a/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RestDebugCapture_Test.java
+++ b/juneau-rest/juneau-rest-mock/src/test/java/org/apache/juneau/rest/mock/RestDebugCapture_Test.java
@@ -437,7 +437,7 @@ class RestDebugCapture_Test {
 	 * headers and bodies are never rendered when no operation was resolved.
 	 */
 	@Test void g01_noOpPath_atFinestTier_neverRendersHeadersOrBody() throws Exception {
-		try (var c = RichLogger.getLogger(A_Resource.class).captureEvents(Level.FINEST)) {
+		try (var quiet = LogRecordCapture.quiet(A_Resource.class); var c = RichLogger.getLogger(A_Resource.class).captureEvents(Level.FINEST)) {
 			var client = org.apache.juneau.rest.mock.classic.MockRestClient.create(A_Resource.class).debug().build();
 
 			client.get("/does-not-exist").header("X-Secret-Header", "leak-test-value").ignoreErrors().run()
@@ -527,7 +527,7 @@ class RestDebugCapture_Test {
 
 	@Test void a05_captureByHostName_observesOpAndNoOpLoggerPaths() throws Exception {
 		var hostName = A05_HostResource.class.getName();
-		try (var c = RichLogger.getLogger(A05_HostResource.class).captureEvents(Level.FINEST)) {
+		try (var quiet = LogRecordCapture.quiet(A05_HostResource.class); var c = RichLogger.getLogger(A05_HostResource.class).captureEvents(Level.FINEST)) {
 			var client = org.apache.juneau.rest.mock.classic.MockRestClient
 				.create(A05_HostResource.class)
 				.debug()
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestChildren.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestChildren.java
index 61ec4a4155..982946c5a4 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestChildren.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/RestChildren.java
@@ -656,7 +656,15 @@ public class RestChildren {
 		// Servlet-backed resources delegate Servlet.destroy() through to RestContext.destroy() (see RestServlet.destroy),
 		// so we must avoid double-destruction in that case — call Servlet.destroy() and let it transitively call
 		// RestContext.destroy() exactly once.
-		if (child.getResource() instanceof Servlet s)
+		Object resource;
+		try {
+			resource = child.getResource();
+		} catch (NoSuchElementException e) {
+			// The resource bean was already removed by the owning container (e.g. a closed Spring context),
+			// so there is no instance left to destroy.
+			return;
+		}
+		if (resource instanceof Servlet s)
 			s.destroy();
 		else
 			child.destroy();
diff --git a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/RestChildren_Destroy_Test.java b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/RestChildren_Destroy_Test.java
new file mode 100644
index 0000000000..1b9d616ecb
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/RestChildren_Destroy_Test.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+import java.util.concurrent.atomic.*;
+import java.util.function.*;
+
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests {@link RestChildren#destroy()} when a child's resource is no longer available from its supplier.
+ *
+ * @since 10.0.0
+ */
+@SuppressWarnings({
+	"resource" // RestContext instances are owned and destroyed by the test via RestChildren.destroy().
+})
+class RestChildren_Destroy_Test extends org.apache.juneau.TestBase {
+
+	static final AtomicInteger DESTROYED = new AtomicInteger();
+
+	@Rest(path="/gone")
+	public static class Gone {
+		@RestDestroy public void destroy() { DESTROYED.addAndGet(100); }
+	}
+
+	@Rest(path="/alive")
+	public static class Alive {
+		@RestDestroy public void destroy() { DESTROYED.incrementAndGet(); }
+	}
+
+	private static RestContext ctx(Class<?> type, Supplier<?> supplier) throws Exception {
+		return new RestContext(new RestContext.Args(type, null, null, supplier, null, null, null, null, null, null));
+	}
+
+	// A child whose bean was removed by its container (e.g. a closed Spring context) must be skipped, not abort the parent's destroy.
+	@Test void a01_destroy_childResourceGone_otherChildrenStillDestroyed() throws Exception {
+		DESTROYED.set(0);
+		var present = new AtomicBoolean(true);
+		var gone = ctx(Gone.class, () -> {
+			if (! present.get())
+				throw new NoSuchElementException("bean removed");
+			return new Gone();
+		});
+		var alive = ctx(Alive.class, Alive::new);
+		var children = RestChildren.create(null, null, null).add(gone).add(alive).build();
+
+		present.set(false);
+		assertDoesNotThrow(children::destroy);
+
+		assertEquals(1, DESTROYED.get());
+	}
+}