You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/06/11 11:29:22 UTC
(camel) branch main updated: CAMEL-23735: Restore documented default aggregation strategy in dynamic-router
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 ec7354b3d3f7 CAMEL-23735: Restore documented default aggregation strategy in dynamic-router
ec7354b3d3f7 is described below
commit ec7354b3d3f7ff4b1097212cba16c184bb0e557a
Author: Tholgue <th...@protonmail.com>
AuthorDate: Thu Jun 11 13:29:15 2026 +0200
CAMEL-23735: Restore documented default aggregation strategy in dynamic-router
The CAMEL-20302 refactoring (4.4.0) unintentionally changed the default
aggregation strategy from UseLatestAggregationStrategy to a
NoopAggregationStrategy that keeps the first reply, contradicting the
documented behavior ("Camel will use the last reply as the outgoing
message"). This restores UseLatestAggregationStrategy as the default,
deprecates NoopAggregationStrategy for binary compatibility, and adds
an integration test reproducing the issue.
Closes #23949
---
.../routing/DynamicRouterRecipientListHelper.java | 11 +-
.../DynamicRouterDefaultAggregationIT.java | 121 +++++++++++++++++++++
.../DynamicRouterRecipientListHelperTest.java | 5 +-
3 files changed, 134 insertions(+), 3 deletions(-)
diff --git a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
index 4d1df1c681f6..414a16814e07 100644
--- a/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
+++ b/components/camel-dynamic-router/src/main/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelper.java
@@ -152,7 +152,7 @@ public final class DynamicRouterRecipientListHelper {
.orElseThrow(() -> new IllegalArgumentException(
"Cannot find AggregationStrategy in Registry with name: " +
cfg.getAggregationStrategy()))))
- .orElse(new NoopAggregationStrategy());
+ .orElse(new UseLatestAggregationStrategy());
CamelContextAware.trySetCamelContext(strategy, camelContext);
return cfg.isShareUnitOfWork() ? new ShareUnitOfWorkAggregationStrategy(strategy) : strategy;
}
@@ -256,6 +256,15 @@ public final class DynamicRouterRecipientListHelper {
.orElse(null);
}
+ /**
+ * Keeps the first result and discards later ones. This was unintentionally used as the default aggregation
+ * strategy, contradicting the documented behavior of using the last reply as the outgoing message.
+ *
+ * @deprecated no longer used as the default aggregation strategy: {@link UseLatestAggregationStrategy} is used
+ * instead, matching the documentation. Configure an explicit {@code aggregationStrategy} if the
+ * first-result behavior is desired.
+ */
+ @Deprecated
public static class NoopAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
diff --git a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java
new file mode 100644
index 000000000000..bdf67dfc9ff5
--- /dev/null
+++ b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/integration/DynamicRouterDefaultAggregationIT.java
@@ -0,0 +1,121 @@
+/*
+ * 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.component.dynamicrouter.integration;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.dynamicrouter.control.DynamicRouterControlMessage;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.infra.core.CamelContextExtension;
+import org.apache.camel.test.infra.core.DefaultCamelContextExtension;
+import org.apache.camel.test.infra.core.annotations.RouteFixture;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+/**
+ * Verifies the default aggregation behavior of the Dynamic Router: as documented, when no aggregationStrategy is
+ * configured, "Camel will use the last reply as the outgoing message", so modifications made by the last matching
+ * recipient (message body, exchange properties) must be visible in the calling route after the dynamic-router call.
+ */
+public class DynamicRouterDefaultAggregationIT {
+
+ @RegisterExtension
+ protected static CamelContextExtension contextExtension = new DefaultCamelContextExtension();
+
+ CamelContext context;
+
+ ProducerTemplate template;
+
+ MockEndpoint resultMock;
+
+ @RouteFixture
+ public void createRouteBuilder(CamelContext context) throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:start")
+ .routeId("directToDynamicRouter")
+ .to("dynamic-router://test?recipientMode=allMatch")
+ .to("mock:result");
+
+ // First recipient (priority 1): leaves its copy of the exchange untouched
+ from("direct:first")
+ .routeId("firstRecipient")
+ .log("first recipient received '${body}'");
+
+ // Second recipient (priority 2): modifies the body and sets a property on its copy
+ from("direct:second")
+ .routeId("secondRecipient")
+ .setProperty("enrichedBy", constant("second"))
+ .setBody(constant("modified by second"));
+ }
+ });
+ }
+
+ @BeforeEach
+ void setUp() {
+ this.context = contextExtension.getContext();
+ this.template = context.createProducerTemplate();
+ this.resultMock = contextExtension.getMockEndpoint("mock:result", true);
+ }
+
+ @AfterEach
+ void tearDown() {
+ context.stop();
+ }
+
+ /**
+ * With two matching recipients in allMatch mode, the result that comes back to the calling route must be the last
+ * reply: the body and the exchange property set by the second recipient.
+ *
+ * @throws Exception if interrupted while waiting for mocks to be satisfied
+ */
+ @Test
+ void testDefaultAggregationUsesLastReply() throws Exception {
+ subscribe("firstSubscription", "direct:first", 1);
+ subscribe("secondSubscription", "direct:second", 2);
+
+ resultMock.expectedMessageCount(1);
+
+ template.sendBody("direct:start", "original");
+
+ MockEndpoint.assertIsSatisfied(context, 5, TimeUnit.SECONDS);
+
+ Exchange result = resultMock.getExchanges().get(0);
+ Assertions.assertEquals("modified by second", result.getMessage().getBody(String.class));
+ Assertions.assertEquals("second", result.getProperty("enrichedBy", String.class));
+ }
+
+ private void subscribe(String subscriptionId, String destinationUri, int priority) {
+ DynamicRouterControlMessage controlMessage = DynamicRouterControlMessage.Builder.newBuilder()
+ .subscribeChannel("test")
+ .subscriptionId(subscriptionId)
+ .destinationUri(destinationUri)
+ .priority(priority)
+ .predicate("${body} != null")
+ .expressionLanguage("simple")
+ .build();
+ template.sendBody("dynamic-router-control:subscribe", controlMessage);
+ }
+}
diff --git a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
index 9cd622a9782f..7f9171d5f797 100644
--- a/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
+++ b/components/camel-dynamic-router/src/test/java/org/apache/camel/component/dynamicrouter/routing/DynamicRouterRecipientListHelperTest.java
@@ -28,6 +28,7 @@ import org.apache.camel.Processor;
import org.apache.camel.processor.RecipientList;
import org.apache.camel.processor.aggregate.AggregationStrategyBeanAdapter;
import org.apache.camel.processor.aggregate.AggregationStrategyBiFunctionAdapter;
+import org.apache.camel.processor.aggregate.UseLatestAggregationStrategy;
import org.apache.camel.spi.ExecutorServiceManager;
import org.apache.camel.spi.Registry;
import org.junit.jupiter.api.Assertions;
@@ -225,11 +226,11 @@ class DynamicRouterRecipientListHelperTest {
}
@Test
- void testCreateAggregationStrategyNoOp() {
+ void testCreateAggregationStrategyDefaultsToUseLatest() {
when(mockConfig.getAggregationStrategyBean()).thenReturn(null);
when(mockConfig.getAggregationStrategy()).thenReturn(null);
AggregationStrategy strategy = DynamicRouterRecipientListHelper.createAggregationStrategy(camelContext, mockConfig);
- Assertions.assertInstanceOf(DynamicRouterRecipientListHelper.NoopAggregationStrategy.class, strategy);
+ Assertions.assertInstanceOf(UseLatestAggregationStrategy.class, strategy);
}
@Test