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

davsclaus pushed a commit to branch camel-spring-boot-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git


The following commit(s) were added to refs/heads/camel-spring-boot-4.18.x by 
this push:
     new 271a7f301c0 CAMEL-24499: camel-spring-boot - route detail view 
bypasses the start-exception filter (#1909)
271a7f301c0 is described below

commit 271a7f301c049d48951962f69914918802a6d6b1
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Aug 28 13:08:56 2026 +0200

    CAMEL-24499: camel-spring-boot - route detail view bypasses the 
start-exception filter (#1909)
    
    RouteEndpointInfo annotates its properties map with
    @JsonIgnoreProperties({"route.start.exception"}), but 
RouteDetailsEndpointInfo
    re-declared the same field without the annotation plus a getProperties()
    override, so the actuator detail operation serialized the property the base 
view
    filters. That property holds the Throwable itself, so Jackson emitted the 
whole
    object graph - nested causes, every stack frame with class, file and line,
    classloader names and the JDK version.
    
    Both the annotation and the shadowing field were introduced together under
    CAMEL-20993, so the filter was intended and the subclass field simply 
escaped
    it. The shadow and its getter are removed and the annotated base-class 
property
    is used instead. getProperties() stays available on the subclass by
    inheritance, so this is source and binary compatible.
    
    The regression test uses Awaitility to wait until the supervising route
    controller has actually recorded route.start.exception before building 
either
    view, so the assertions cannot pass vacuously.
    
    Closes #1898
---
 .../boot/actuate/endpoint/CamelRoutesEndpoint.java |  12 +--
 .../CamelRoutesEndpointStartExceptionTest.java     | 110 +++++++++++++++++++++
 2 files changed, 112 insertions(+), 10 deletions(-)

diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpoint.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpoint.java
index 8658bdf10e4..f736e75fa04 100644
--- 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpoint.java
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpoint.java
@@ -276,16 +276,12 @@ public class CamelRoutesEndpoint {
 
         @JsonProperty("details")
         private RouteDetails routeDetails;
-        private final Map<String, Object> properties;
 
         public RouteDetailsEndpointInfo(final CamelContext camelContext, final 
Route route) {
             super(route);
 
-            if (route.getProperties() != null) {
-                this.properties = new HashMap<>(route.getProperties());
-            } else {
-                this.properties = Collections.emptyMap();
-            }
+            // route properties are carried by the base class, whose field 
holds the
+            // @JsonIgnoreProperties filter - do not shadow it here, or the 
filter is bypassed
             if (camelContext.getManagementStrategy().getManagementAgent() != 
null) {
                 ManagedCamelContext mcc = 
camelContext.getCamelContextExtension()
                         .getContextPlugin(ManagedCamelContext.class);
@@ -293,10 +289,6 @@ public class CamelRoutesEndpoint {
             }
         }
 
-        public Map<String, Object> getProperties() {
-            return properties;
-        }
-
         @JsonInclude(JsonInclude.Include.NON_EMPTY)
         static class RouteDetails {
 
diff --git 
a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpointStartExceptionTest.java
 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpointStartExceptionTest.java
new file mode 100644
index 00000000000..f6eff86ee6b
--- /dev/null
+++ 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpointStartExceptionTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.camel.spring.boot.actuate.endpoint;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Route;
+import org.apache.camel.spring.boot.CamelAutoConfiguration;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The {@code route.start.exception} route property is filtered out of the 
actuator route views. The base
+ * {@link CamelRoutesEndpoint.RouteEndpointInfo} carries the {@code 
@JsonIgnoreProperties} filter; the detail view must
+ * not bypass it.
+ */
+@EnableAutoConfiguration
+@SpringBootTest(classes = { CamelAutoConfiguration.class, 
CamelRoutesEndpointAutoConfiguration.class,
+        ActuatorTestControlledRoutes.class }, properties = {
+                "management.endpoints.web.exposure.include=*",
+                "camel.routecontroller.enabled=true",
+                "camel.routecontroller.initial-delay=100",
+                "camel.routecontroller.back-off-delay=100",
+                "camel.routecontroller.back-off-max-attempts=3",
+                "camel.main.routes-exclude-pattern=*",
+                "camel.main.routes-collector-enabled=true" })
+public class CamelRoutesEndpointStartExceptionTest {
+
+    private static final String FAILING_ROUTE_ID = "controlled-bar";
+    private static final String START_EXCEPTION = "route.start.exception";
+
+    @Autowired
+    CamelRoutesEndpoint endpoint;
+
+    @Autowired
+    CamelContext camelContext;
+
+    private final ObjectMapper mapper = new ObjectMapper();
+
+    private Route failingRoute() {
+        return camelContext.getRouteController().getControlledRoutes().stream()
+                .filter(route -> FAILING_ROUTE_ID.equals(route.getId()))
+                .findAny()
+                .orElse(null);
+    }
+
+    /**
+     * The supervising route controller starts routes asynchronously, so the 
start failure is not recorded when the
+     * context finishes refreshing. Without waiting for it the assertions 
below would pass vacuously - there would be
+     * no property to leak in the first place.
+     */
+    @BeforeEach
+    public void waitUntilTheStartFailureIsRecorded() {
+        await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
+            Route route = failingRoute();
+            assertNotNull(route, "the supervised route should be known to the 
route controller");
+            assertInstanceOf(Throwable.class, 
route.getProperties().get(START_EXCEPTION),
+                    "the route start failure should be recorded before the 
endpoint views are checked");
+        });
+    }
+
+    @Test
+    public void infoViewDoesNotExposeStartException() throws Exception {
+        Object info = endpoint.doReadAction(FAILING_ROUTE_ID, 
CamelRoutesEndpoint.ReadAction.INFO);
+        assertNotNull(info);
+
+        String json = mapper.writeValueAsString(info);
+        assertFalse(json.contains(START_EXCEPTION),
+                START_EXCEPTION + " must not be serialized in the info view, 
but was: " + json);
+        assertTrue(json.contains("customId"),
+                "the remaining route properties should still be serialized, 
but were not: " + json);
+    }
+
+    @Test
+    public void detailViewDoesNotExposeStartException() throws Exception {
+        Object details = endpoint.doReadAction(FAILING_ROUTE_ID, 
CamelRoutesEndpoint.ReadAction.DETAIL);
+        assertNotNull(details);
+
+        String json = mapper.writeValueAsString(details);
+        assertFalse(json.contains(START_EXCEPTION),
+                START_EXCEPTION + " must not be serialized in the detail view, 
but was: " + json);
+        assertTrue(json.contains("customId"),
+                "the remaining route properties should still be serialized, 
but were not: " + json);
+    }
+}

Reply via email to