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

davsclaus pushed a commit to branch fix/CAMEL-25057
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 455dd8da08fa16f483fd833c81fdc05a78a3a3f2
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Sep 27 17:28:19 2026 +0200

    CAMEL-25057: camel-console - Dev consoles: fix bugs found in a deep review
    
    - the consumer console shows the details of scheduled poll consumers
    - the processor console JSON limit no longer returns one too many
    - the processor console does not fail without camel-management
    - the trace console only dumps with dump=true
    - the browse console with freshSize=true honours limit
    - camel-management as test dependency so management driven consoles can be 
tested
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 core/camel-console/pom.xml                         |  6 ++
 .../camel/impl/console/BrowseDevConsole.java       |  8 +++
 .../camel/impl/console/ConsumerDevConsole.java     | 20 ++++--
 .../camel/impl/console/ProcessorDevConsole.java    | 14 +++-
 .../apache/camel/impl/console/TraceDevConsole.java |  4 +-
 .../camel/impl/console/ConsoleEdgeCasesTest.java   | 78 ++++++++++++++++++++++
 .../impl/console/ManagedConsoleEdgeCasesTest.java  | 75 +++++++++++++++++++++
 7 files changed, 198 insertions(+), 7 deletions(-)

diff --git a/core/camel-console/pom.xml b/core/camel-console/pom.xml
index 8c79ab89f431..aded5064b66f 100644
--- a/core/camel-console/pom.xml
+++ b/core/camel-console/pom.xml
@@ -68,6 +68,12 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+        <!-- many consoles are driven by the managed MBeans (JMX) -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-management</artifactId>
+            <scope>test</scope>
+        </dependency>
        <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter</artifactId>
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/BrowseDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/BrowseDevConsole.java
index b678fb301745..f6ff67937a37 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/BrowseDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/BrowseDevConsole.java
@@ -130,6 +130,10 @@ public class BrowseDevConsole extends AbstractDevConsole {
                         begin = Math.max(0, list.size() - pos);
                         list = list.subList(begin, list.size());
                     }
+                    if (list != null && list.size() > max) {
+                        // fresh size gets all the messages (to know the size) 
but only limit messages are dumped
+                        list = list.subList(0, max);
+                    }
                     if (list != null) {
                         sb.append("\n");
                         sb.append(String.format("Browse: %s (size: %d limit: 
%d position: %d)%n", endpoint.getEndpointUri(),
@@ -178,6 +182,10 @@ public class BrowseDevConsole extends AbstractDevConsole {
                         begin = Math.max(0, list.size() - pos);
                         list = list.subList(begin, list.size());
                     }
+                    if (list != null && list.size() > max) {
+                        // fresh size gets all the messages (to know the size) 
but only limit messages are dumped
+                        list = list.subList(0, max);
+                    }
                     if (list != null) {
                         Long firstTimestamp = null;
                         Long lastTimestamp = null;
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ConsumerDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ConsumerDevConsole.java
index d63c937e18ce..c010760518ea 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ConsumerDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ConsumerDevConsole.java
@@ -32,6 +32,7 @@ import 
org.apache.camel.api.management.mbean.ManagedRouteMBean;
 import org.apache.camel.api.management.mbean.ManagedSchedulePollConsumerMBean;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.ScheduledPollConsumer;
 import org.apache.camel.support.console.AbstractDevConsole;
 import org.apache.camel.util.json.JsonRecordSupport;
 
@@ -122,7 +123,8 @@ public class ConsumerDevConsole extends AbstractDevConsole {
                     sb.append(String.format("%n    Remote: %b", 
mc.isRemoteEndpoint()));
                     sb.append(String.format("%n    Hosted: %b", 
mc.isHostedService()));
                     sb.append(String.format("%n    Inflight: %d", inflight));
-                    if (mcc instanceof ManagedSchedulePollConsumerMBean mpc) {
+                    ManagedSchedulePollConsumerMBean mpc = 
scheduledPollConsumer(mcc, route);
+                    if (mpc != null) {
                         sb.append(String.format("%n    Polling: %s", 
mpc.isPolling()));
                         sb.append(String.format("%n    First Poll Done: %s", 
mpc.isFirstPollDone()));
                         sb.append(String.format("%n    Scheduler Started: %s", 
mpc.isSchedulerStarted()));
@@ -185,6 +187,17 @@ public class ConsumerDevConsole extends AbstractDevConsole 
{
         return sb.toString();
     }
 
+    /**
+     * The managed scheduled poll consumer of the route, or null if it is not 
a scheduled poll consumer. The managed
+     * consumer is a proxy of the interface it is asked for, so it is not an 
instance of the scheduled poll interface.
+     */
+    private static ManagedSchedulePollConsumerMBean 
scheduledPollConsumer(ManagedCamelContext mcc, Route route) {
+        if (route.getConsumer() instanceof ScheduledPollConsumer) {
+            return mcc.getManagedConsumer(route.getId(), 
ManagedSchedulePollConsumerMBean.class);
+        }
+        return null;
+    }
+
     @Override
     protected Map<String, Object> doCallJson(Map<String, Object> options) {
         final List<ConsumerEntry> list = new ArrayList<>();
@@ -222,9 +235,8 @@ public class ConsumerDevConsole extends AbstractDevConsole {
                     Boolean fixedRate = null;
                     Long period = null;
 
-                    // NOTE: this checks mcc (the ManagedCamelContext), not mc 
(the consumer) - so this branch is
-                    // effectively dead code, but that pre-existing behavior 
is preserved as-is here
-                    if (mcc instanceof ManagedSchedulePollConsumerMBean mpc) {
+                    ManagedSchedulePollConsumerMBean mpc = 
scheduledPollConsumer(mcc, route);
+                    if (mpc != null) {
                         scheduled = true;
                         polling = mpc.isPolling();
                         firstPollDone = mpc.isFirstPollDone();
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDevConsole.java
index 932fd6ee3333..9d815205df4a 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ProcessorDevConsole.java
@@ -123,6 +123,10 @@ public class ProcessorDevConsole extends 
AbstractDevConsole {
 
         ManagedCamelContext mcc = 
getCamelContext().getCamelContextExtension().getContextPlugin(ManagedCamelContext.class);
         final StringBuilder sb = new StringBuilder();
+        if (mcc == null) {
+            // camel-management is not in use
+            return sb.toString();
+        }
         final AtomicInteger counter = new AtomicInteger();
         for (Route r : getCamelContext().getRoutes()) {
             ManagedRouteMBean mrb = mcc.getManagedRoute(r.getRouteId());
@@ -245,6 +249,10 @@ public class ProcessorDevConsole extends 
AbstractDevConsole {
 
         List<ProcessorEntry> list = new ArrayList<>();
         ManagedCamelContext mcc = 
getCamelContext().getCamelContextExtension().getContextPlugin(ManagedCamelContext.class);
+        if (mcc == null) {
+            // camel-management is not in use
+            return JsonRecordSupport.toJsonObject(new Response(list));
+        }
         for (Route r : getCamelContext().getRoutes()) {
             ManagedRouteMBean mrb = mcc.getManagedRoute(r.getRouteId());
             includeProcessorsJson(mrb, list, filter, max);
@@ -284,7 +292,7 @@ public class ProcessorDevConsole extends AbstractDevConsole 
{
             CamelContext camelContext, List<ProcessorEntry> list, int max, 
List<ManagedProcessorMBean> mps) {
         for (int i = 0; i < mps.size(); i++) {
             ManagedProcessorMBean mp = mps.get(i);
-            if (list.size() > max) {
+            if (list.size() >= max) {
                 return;
             }
 
@@ -409,6 +417,10 @@ public class ProcessorDevConsole extends 
AbstractDevConsole {
 
         List<ManagedProcessorMBean> mps = new ArrayList<>();
         ManagedCamelContext mcc = 
getCamelContext().getCamelContextExtension().getContextPlugin(ManagedCamelContext.class);
+        if (mcc == null) {
+            // camel-management is not in use
+            return;
+        }
         for (Route r : getCamelContext().getRoutes()) {
             ManagedRouteMBean mrb = mcc.getManagedRoute(r.getRouteId());
             try {
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/TraceDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/TraceDevConsole.java
index 81881de640d5..a8fdf375fe34 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/TraceDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/TraceDevConsole.java
@@ -96,7 +96,7 @@ public class TraceDevConsole extends AbstractDevConsole {
 
         BacklogTracer tracer = 
getCamelContext().getCamelContextExtension().getContextPlugin(BacklogTracer.class);
         if (tracer != null) {
-            if (dump != null) {
+            if ("true".equalsIgnoreCase(dump)) {
                 for (BacklogTracerEventMessage t : 
tracer.dumpAllTracedMessages()) {
                     addMessage(t);
                 }
@@ -156,7 +156,7 @@ public class TraceDevConsole extends AbstractDevConsole {
 
         BacklogTracer tracer = 
getCamelContext().getCamelContextExtension().getContextPlugin(BacklogTracer.class);
         if (tracer != null) {
-            if (dump != null) {
+            if ("true".equalsIgnoreCase(dump)) {
                 for (BacklogTracerEventMessage t : 
tracer.dumpAllTracedMessages()) {
                     addMessage(t);
                 }
diff --git 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/ConsoleEdgeCasesTest.java
 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/ConsoleEdgeCasesTest.java
new file mode 100644
index 000000000000..0feadd4e1963
--- /dev/null
+++ 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/ConsoleEdgeCasesTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.impl.console;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.console.DevConsole;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Consoles used without JMX (not enabled by ContextTestSupport).
+ */
+public class ConsoleEdgeCasesTest extends ContextTestSupport {
+
+    private DevConsole console(String id) {
+        return 
PluginHelper.getDevConsoleResolver(context).resolveDevConsole(id);
+    }
+
+    @Test
+    public void testTraceConsoleDumpFalse() {
+        context.setBacklogTracing(true);
+        JsonObject out = (JsonObject) 
console("trace").call(DevConsole.MediaType.JSON, Map.of("dump", "false"));
+        // not dumping, so the status of the tracer
+        assertThat(out.get("traces")).isNull();
+    }
+
+    @Test
+    public void testBrowseConsoleFreshSizeWithLimit() {
+        for (int i = 0; i < 20; i++) {
+            template.sendBody("direct:start", "Message " + i);
+        }
+        JsonObject out = (JsonObject) 
console("browse").call(DevConsole.MediaType.JSON,
+                Map.of("freshSize", "true", "limit", "5", "filter", "seda*"));
+        JsonArray browse = out.getCollection("browse");
+        JsonObject entry = (JsonObject) browse.get(0);
+        assertThat(entry.getInteger("queueSize")).isEqualTo(20);
+        List<?> messages = entry.getCollection("messages");
+        assertThat(messages).hasSize(5);
+
+        String text = (String) 
console("browse").call(DevConsole.MediaType.TEXT,
+                Map.of("freshSize", "true", "limit", "5", "filter", "seda*"));
+        assertThat(text).contains("Message 4").doesNotContain("Message 5");
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                context.setBacklogTracingStandby(true);
+
+                from("direct:start").to("seda:queue1");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/ManagedConsoleEdgeCasesTest.java
 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/ManagedConsoleEdgeCasesTest.java
new file mode 100644
index 000000000000..f661e104d384
--- /dev/null
+++ 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/ManagedConsoleEdgeCasesTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.impl.console;
+
+import java.util.Map;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.console.DevConsole;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Consoles driven by camel-management (JMX enabled).
+ */
+public class ManagedConsoleEdgeCasesTest extends ContextTestSupport {
+
+    @Override
+    protected boolean useJmx() {
+        return true;
+    }
+
+    private DevConsole console(String id) {
+        return 
PluginHelper.getDevConsoleResolver(context).resolveDevConsole(id);
+    }
+
+    @Test
+    public void testConsumerConsoleScheduledPollConsumer() {
+        JsonObject out = (JsonObject) 
console("consumer").call(DevConsole.MediaType.JSON);
+        JsonArray consumers = out.getCollection("consumers");
+        JsonObject file = (JsonObject) consumers.stream()
+                .filter(c -> ((JsonObject) 
c).getString("uri").startsWith("file:")).findFirst().orElseThrow();
+        assertThat(file.getBoolean("scheduled")).isTrue();
+        assertThat(file.getLong("delay")).isEqualTo(5000L);
+
+        String text = (String) 
console("consumer").call(DevConsole.MediaType.TEXT);
+        assertThat(text).contains("Polling:");
+    }
+
+    @Test
+    public void testProcessorConsoleLimit() {
+        JsonObject out = (JsonObject) 
console("processor").call(DevConsole.MediaType.JSON, Map.of("limit", "1"));
+        JsonArray processors = out.getCollection("processors");
+        assertThat(processors).hasSize(1);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("file:target/data/console-edge?delay=5000&initialDelay=600000").routeId("files")
+                        .to("log:a").to("log:b").to("log:c");
+            }
+        };
+    }
+}

Reply via email to