[
https://issues.apache.org/jira/browse/CAMEL-23735?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Thomas LE GUEN updated CAMEL-23735:
-----------------------------------
Environment: (was: h2. Summary
Since the CAMEL-20302 refactoring (Camel 4.4.0), the {{dynamic-router}}
component uses a private {{NoopAggregationStrategy}} as its default aggregation
strategy, which keeps the *first* recipient's result. Both the user
documentation and the component's own javadoc still state that the *last* reply
is used by default (the pre-4.4 behavior, {{UseLatestAggregationStrategy}}).
In {{recipientMode=allMatch}}, any modification made by a recipient other than
the first one (body, exchange properties) is silently discarded when the
exchange returns to the calling route. Whether a downstream {{choice()}} sees a
property set by a subscriber now depends on subscription priorities, which is
surprising and undocumented.
h2. Expected behavior (as documented)
The component documentation for the {{aggregationStrategy}} option states:
{quote}Refers to an AggregationStrategy to be used to assemble the replies from
the multicasts, into a single outgoing message from the Multicast. By default,
Camel will use the last reply as the outgoing message.{quote}
The javadoc of {{DynamicRouterRecipientListHelper#createAggregationStrategy}}
also states:
{quote}If all else fails, then a {{UseLatestAggregationStrategy}} is
used.{quote}
h2. Actual behavior
{{DynamicRouterRecipientListHelper.createAggregationStrategy}} falls back to a
private {{NoopAggregationStrategy}}:
{code:java}
.orElse(new NoopAggregationStrategy());
...
public static class NoopAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
return oldExchange == null ? newExchange : oldExchange; // keeps the
FIRST result
}
}
{code}
The aggregated (first) copy is then written back onto the original exchange by
{{MulticastProcessor.doDone}} via {{ExchangeHelper.copyResults}}, so the caller
route only ever sees the first recipient's outcome.
Note that the unused import of {{UseLatestAggregationStrategy}} is still
present in {{DynamicRouterRecipientListHelper}}, which suggests the behavior
change was unintentional.
h2. Regression evidence
* Camel 4.3.0, {{DynamicRouterEndpoint#determineAggregationStrategy}}:
{code:java}
return cfgStrategy != null
? CamelContextHelper.mandatoryLookup(camelContext, cfgStrategy,
AggregationStrategy.class)
: new UseLatestAggregationStrategy();
{code}
* Camel 4.4.0 (commit {{c4711c97}}, CAMEL-20302) introduced
{{DynamicRouterRecipientListHelper}} with the {{NoopAggregationStrategy}}
fallback shown above.
* Still present and verified on 4.14.1 (LTS) and 4.20.0.
h2. Reproduction (JUnit 5, camel-test-junit5, verified on 4.14.1 and 4.20.0)
{code:java}
public class DynamicRouterDefaultAggregationTest extends CamelTestSupport {
@Override
protected RoutesBuilder createRouteBuilder() {
return new RouteBuilder() {
@Override
public void configure() {
from("direct:start")
.to("dynamic-router:demo?recipientMode=allMatch")
.to("mock:result");
// first recipient (priority 1): leaves its copy untouched
from("direct:first").log("first recipient");
// second recipient (priority 2): modifies its copy
from("direct:second")
.setProperty("enrichedBy", constant("second"))
.setBody(constant("modified by second"));
}
};
}
@Test
void defaultAggregationShouldKeepLastReplyAsDocumented() throws Exception {
subscribe("first-sub", "direct:first", 1);
subscribe("second-sub", "direct:second", 2);
getMockEndpoint("mock:result").expectedMessageCount(1);
template.sendBody("direct:start", "original");
MockEndpoint.assertIsSatisfied(context);
Exchange result = getMockEndpoint("mock:result").getExchanges().get(0);
// Documentation: "By default, Camel will use the last reply as the
outgoing message."
// Both assertions FAIL: the body is still "original" and the property
is null,
// because the first recipient's (untouched) copy wins.
assertEquals("modified by second",
result.getMessage().getBody(String.class));
assertEquals("second", result.getProperty("enrichedBy", String.class));
}
private void subscribe(String id, String uri, int priority) {
template.sendBody("dynamic-router-control:subscribe",
DynamicRouterControlMessage.Builder.newBuilder()
.subscribeChannel("demo")
.subscriptionId(id)
.destinationUri(uri)
.priority(priority)
.predicate("${body} != null")
.expressionLanguage("simple")
.build());
}
}
{code}
h2. Suggested resolution
Either:
# restore {{UseLatestAggregationStrategy}} as the default, matching the
documentation and the pre-4.4 behavior, or
# keep {{NoopAggregationStrategy}} but update the option description, the
javadoc of {{createAggregationStrategy}} (and remove the stale
{{UseLatestAggregationStrategy}} import), clearly stating that the first
matching recipient's result is kept by default.
Option 1 seems preferable since the current behavior looks unintentional (stale
import, contradicting javadoc) and silently drops data.)
> camel-dynamic-router: default aggregation keeps the FIRST reply,
> contradicting the documented "last reply" behavior (regression since 4.4.0)
> --------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-23735
> URL: https://issues.apache.org/jira/browse/CAMEL-23735
> Project: Camel
> Issue Type: Bug
> Components: camel-core
> Affects Versions: 4.4.0
> Reporter: Thomas LE GUEN
> Priority: Minor
> Labels: dynamic-router, dynamic-router-eip
>
--
This message was sent by Atlassian Jira
(v8.20.10#820010)