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");
}
};
}