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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 38865d3fdc23 CAMEL-25237: camel-core - Switch EIP should count the 
messages per case (#27229)
38865d3fdc23 is described below

commit 38865d3fdc23f765d4be1db5027e1c1dc3688f38
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 1 20:07:34 2026 +0200

    CAMEL-25237: camel-core - Switch EIP should count the messages per case 
(#27229)
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../java/org/apache/camel/impl/DefaultModel.java   | 28 ++++++---
 .../apache/camel/model/SwitchCaseDefinition.java   |  6 +-
 .../org/apache/camel/model/SwitchDefinition.java   |  3 +-
 .../apache/camel/model/SwitchDefinitionTest.java   |  8 ++-
 .../org/apache/camel/processor/SwitchTest.java     | 20 +++++++
 .../apache/camel/management/ManagedSwitchTest.java | 66 ++++++++++++++++++++++
 6 files changed, 116 insertions(+), 15 deletions(-)

diff --git 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
index 48107f9cb2f4..0bf095fdb152 100644
--- 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
+++ 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
@@ -51,6 +51,8 @@ import org.apache.camel.model.RouteFilters;
 import org.apache.camel.model.RouteTemplateDefinition;
 import org.apache.camel.model.RouteTemplateParameterDefinition;
 import org.apache.camel.model.RoutesDefinition;
+import org.apache.camel.model.SwitchCaseDefinition;
+import org.apache.camel.model.SwitchDefinition;
 import org.apache.camel.model.TemplatedRouteDefinition;
 import org.apache.camel.model.TemplatedRouteParameterDefinition;
 import org.apache.camel.model.ToDefinition;
@@ -849,16 +851,16 @@ public class DefaultModel implements Model {
             Collection<ProcessorDefinition> col = 
ProcessorDefinitionHelper.filterTypeInOutputs(route.getOutputs(),
                     ProcessorDefinition.class);
             for (ProcessorDefinition proc : col) {
-                String pid = proc.getId();
-                // match direct by ids
-                if (id.equals(pid)) {
+                if (matchesId(proc, id)) {
                     return proc;
                 }
-                // try to match via node prefix id
-                if (proc.getNodePrefixId() != null) {
-                    pid = proc.getNodePrefixId() + pid;
-                    if (id.equals(pid)) {
-                        return proc;
+                if (proc instanceof SwitchDefinition sw) {
+                    // a case of a Switch is not a processor in the tree: its 
send is, under the id of the case
+                    for (SwitchCaseDefinition c : sw.getCases()) {
+                        ToDefinition send = c.getToDefinition();
+                        if (send != null && matchesId(send, id)) {
+                            return send;
+                        }
                     }
                 }
             }
@@ -866,6 +868,16 @@ public class DefaultModel implements Model {
         return null;
     }
 
+    private static boolean matchesId(ProcessorDefinition<?> proc, String id) {
+        String pid = proc.getId();
+        // match direct by ids
+        if (id.equals(pid)) {
+            return true;
+        }
+        // try to match via node prefix id
+        return proc.getNodePrefixId() != null && 
id.equals(proc.getNodePrefixId() + pid);
+    }
+
     @Override
     public <T extends ProcessorDefinition<T>> T getProcessorDefinition(String 
id, Class<T> type) {
         ProcessorDefinition<?> answer = getProcessorDefinition(id);
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java
index 46786eae7989..6a4a93f6d589 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchCaseDefinition.java
@@ -42,8 +42,9 @@ public class SwitchCaseDefinition extends 
OptionalIdentifiedDefinition<SwitchCas
     private EndpointProducerBuilder endpointProducerBuilder;
     @XmlTransient
     private SwitchDefinition parent;
+    // created with the case (as the otherwise node is), so the processor 
index follows the order of the cases
     @XmlTransient
-    private ToDefinition toDefinition;
+    private ToDefinition toDefinition = new ToDefinition();
 
     public SwitchCaseDefinition() {
     }
@@ -100,9 +101,6 @@ public class SwitchCaseDefinition extends 
OptionalIdentifiedDefinition<SwitchCas
 
     /** Prepare the destination node before processor creation and management 
instrumentation. */
     public void prepareToDefinition() {
-        if (toDefinition == null) {
-            toDefinition = new ToDefinition();
-        }
         if (endpointProducerBuilder != null) {
             toDefinition.setEndpointProducerBuilder(endpointProducerBuilder);
         } else {
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchDefinition.java
index 0e79be39ae5c..5bdf48a1d431 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/SwitchDefinition.java
@@ -63,12 +63,13 @@ public class SwitchDefinition extends 
NoOutputDefinition<SwitchDefinition> {
     protected SwitchDefinition(SwitchDefinition source) {
         super(source);
         this.selector = source.selector != null ? 
source.selector.copyDefinition() : null;
-        setOtherwise(source.otherwise != null ? 
source.otherwise.copyDefinition() : null);
         for (SwitchCaseDefinition c : source.cases) {
             SwitchCaseDefinition copy = c.copyDefinition();
             copy.setParent(this);
             cases.add(copy);
         }
+        // copied after the cases, so the otherwise node keeps coming last in 
the processor index
+        setOtherwise(source.otherwise != null ? 
source.otherwise.copyDefinition() : null);
     }
 
     @Override
diff --git 
a/core/camel-core-model/src/test/java/org/apache/camel/model/SwitchDefinitionTest.java
 
b/core/camel-core-model/src/test/java/org/apache/camel/model/SwitchDefinitionTest.java
index 451d30b5a215..99c2622bfccf 100644
--- 
a/core/camel-core-model/src/test/java/org/apache/camel/model/SwitchDefinitionTest.java
+++ 
b/core/camel-core-model/src/test/java/org/apache/camel/model/SwitchDefinitionTest.java
@@ -32,9 +32,13 @@ class SwitchDefinitionTest {
                 .doCase("billing", 
"{{case.uri}}").otherwise("{{fallback.uri}}");
         SwitchCaseDefinition c = sw.getCases().get(0);
         c.setId("billing");
-        assertNull(c.getToDefinition());
+        // the send node exists from the start (so its processor index follows 
the order of the cases), but reading
+        // the model does not prepare it: that happens when the route is 
reified
+        ToDefinition unprepared = c.getToDefinition();
         sw.getChildren();
-        assertNull(c.getToDefinition());
+        assertSame(unprepared, c.getToDefinition());
+        assertNull(unprepared.getUri());
+        assertNull(unprepared.getId());
 
         c.prepareToDefinition();
         sw.prepareOtherwiseDefinition();
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchTest.java 
b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchTest.java
index 1abc11b39954..6c93cbf1a654 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/SwitchTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/SwitchTest.java
@@ -27,12 +27,18 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.Exchange;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.model.Model;
+import org.apache.camel.model.ProcessorDefinition;
+import org.apache.camel.model.SwitchCaseDefinition;
+import org.apache.camel.model.SwitchDefinition;
+import org.apache.camel.model.ToDefinition;
 import org.apache.camel.support.ExpressionAdapter;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class SwitchTest extends ContextTestSupport {
@@ -53,6 +59,20 @@ class SwitchTest extends ContextTestSupport {
         assertMockEndpointsSatisfied();
     }
 
+    @Test
+    void theSendOfACaseIsFoundByTheIdOfTheCase() {
+        // the management and the dev consoles look a processor up by its id: 
a case is found as its send
+        // (CAMEL-25237), as the otherwise is
+        Model model = 
context.getCamelContextExtension().getContextPlugin(Model.class);
+        SwitchDefinition sw = (SwitchDefinition) 
model.getRouteDefinitions().get(0).getOutputs().get(0);
+        for (SwitchCaseDefinition c : sw.getCases()) {
+            ProcessorDefinition<?> found = 
model.getProcessorDefinition(c.getId());
+            assertSame(c.getToDefinition(), found, c.getId());
+        }
+        assertSame(sw.getOtherwiseDefinition(), 
model.getProcessorDefinition(sw.getId() + "-otherwise"));
+        assertEquals("mock:billing", ((ToDefinition) 
model.getProcessorDefinition(sw.getCases().get(0).getId())).getUri());
+    }
+
     @Test
     void structuredSelectorResultsFailWithoutUsingFallback() throws Exception {
         getMockEndpoint("mock:review").expectedMessageCount(0);
diff --git 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedSwitchTest.java
 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedSwitchTest.java
index d8f6520adfe0..ff1e622861ca 100644
--- 
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedSwitchTest.java
+++ 
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedSwitchTest.java
@@ -16,10 +16,15 @@
  */
 package org.apache.camel.management;
 
+import java.util.Collection;
+
 import javax.management.ObjectName;
 import javax.management.openmbean.TabularData;
 
+import org.apache.camel.api.management.ManagedCamelContext;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.model.Model;
+import org.apache.camel.model.SwitchDefinition;
 import org.apache.camel.processor.SendProcessor;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.DisabledOnOs;
@@ -60,6 +65,62 @@ class ManagedSwitchTest extends ManagementTestSupport {
         assertEquals(0L, getMBeanServer().getAttribute(name, 
"UnmatchedCount"));
     }
 
+    @Test
+    void countsEveryCaseWithAGeneratedId() throws Exception {
+        // a Switch as YAML routes write it: no ids on the cases, so they are 
generated (CAMEL-25237); the route is
+        // one of those the context starts with (createRouteBuilder)
+        template.sendBodyAndHeader("direct:generated", "a", "specialist", 
"weather");
+        template.sendBodyAndHeader("direct:generated", "b", "specialist", 
"weather");
+        template.sendBodyAndHeader("direct:generated", "c", "specialist", 
"reservation");
+        template.sendBodyAndHeader("direct:generated", "d", "specialist", 
"cost");
+
+        // the ids the cases got: generated, as the route structure and the 
diagrams show them
+        SwitchDefinition sw = (SwitchDefinition) 
context.getCamelContextExtension()
+                
.getContextPlugin(Model.class).getRouteDefinition("generatedRoute").getOutputs().get(0);
+        String reservation = sw.getCases().get(0).getId();
+        String weather = sw.getCases().get(1).getId();
+        assertNotNull(reservation);
+        assertNotNull(weather);
+        String all = getMBeanServer().queryNames(new 
ObjectName("org.apache.camel:type=processors,*"), null).toString();
+        assertEquals(1L, 
getMBeanServer().getAttribute(getCamelObjectName(TYPE_PROCESSOR, reservation), 
"ExchangesTotal"),
+                all);
+        assertEquals(2L, 
getMBeanServer().getAttribute(getCamelObjectName(TYPE_PROCESSOR, weather), 
"ExchangesTotal"));
+        assertEquals(1L, getMBeanServer().getAttribute(
+                getCamelObjectName(TYPE_PROCESSOR, sw.getId() + "-otherwise"), 
"ExchangesTotal"));
+        // the route lists them among its processors, as camel get processor 
and the dev consoles show them
+        assertEquals("generatedRoute",
+                
getMBeanServer().getAttribute(getCamelObjectName(TYPE_PROCESSOR, weather), 
"RouteId"));
+        @SuppressWarnings("unchecked")
+        Collection<String> ids = (Collection<String>) getMBeanServer().invoke(
+                
getCamelObjectName(DefaultManagementObjectNameStrategy.TYPE_ROUTE, 
"generatedRoute"), "processorIds", null,
+                null);
+        assertTrue(ids.contains(reservation) && ids.contains(weather), 
ids.toString());
+        // and are found by their id, as the dev consoles (camel get 
processor, route structure) look them up
+        ManagedCamelContext mcc = 
context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class);
+        assertNotNull(mcc.getManagedProcessor(reservation), "the reservation 
case is a managed processor");
+        assertEquals(2L, mcc.getManagedProcessor(weather).getExchangesTotal());
+        assertNotNull(mcc.getManagedProcessor(sw.getId() + "-otherwise"));
+    }
+
+    @Test
+    void listsTheOtherwiseAfterTheCases() {
+        // the dev consoles (camel get processor) list the processors by their 
index: the cases in their order, then
+        // the otherwise
+        SwitchDefinition sw = (SwitchDefinition) 
context.getCamelContextExtension()
+                
.getContextPlugin(Model.class).getRouteDefinition("generatedRoute").getOutputs().get(0);
+        ManagedCamelContext mcc = 
context.getCamelContextExtension().getContextPlugin(ManagedCamelContext.class);
+        int reservation = 
mcc.getManagedProcessor(sw.getCases().get(0).getId()).getIndex();
+        int weather = 
mcc.getManagedProcessor(sw.getCases().get(1).getId()).getIndex();
+        int otherwise = mcc.getManagedProcessor(sw.getId() + 
"-otherwise").getIndex();
+        assertTrue(mcc.getManagedProcessor(sw.getId()).getIndex() < 
reservation);
+        assertTrue(reservation < weather, reservation + " < " + weather);
+        assertTrue(weather < otherwise, weather + " < " + otherwise);
+
+        // and so does a copy (route templates)
+        SwitchDefinition copy = sw.copyDefinition();
+        assertTrue(copy.getCases().get(1).getToDefinition().getIndex() < 
copy.getOtherwiseDefinition().getIndex());
+    }
+
     @ParameterizedTest
     @NullSource
     @ValueSource(booleans = { true, false })
@@ -95,6 +156,11 @@ class ManagedSwitchTest extends ManagementTestSupport {
                         .doSwitch(header("decision")).id("dispatch")
                         .doCase("billing").id("urgentCase").to("mock:urgent")
                         .otherwise("mock:review");
+                from("direct:generated").routeId("generatedRoute")
+                        .doSwitch(header("specialist"))
+                        .doCase("reservation", "mock:reservation")
+                        .doCase("weather", "mock:weather")
+                        .otherwise("mock:other");
             }
         };
     }

Reply via email to