Skip to content

Commit d01e405

Browse files
committed
spring-projectsGH-3946: Revise Router channelKeyFallback option
Fixes spring-projects#3946 The `AbstractMappingMessageRouter` has both `resolutionRequired` and `channelKeyFallback` as `true` by default. End-users expects them to back off when they set a `defaultOutputChannel`. They really want something similar to Java `switch` statement * Change the logic in the `AbstractMappingMessageRouter` to reset `channelKeyFallback` to `false` when `defaultOutputChannel` to avoid attempts to resolve channel from name, but rather fallback to `defaultOutputChannel` as it states from th mentioned Java `switch` statement experience * Deprecate `RouterSpec.noChannelKeyFallback()` in favor of newly introduced `channelKeyFallback(boolean)` * Call `channelKeyFallback(false)` from an overloaded `defaultOutputToParentFlow()` to reflect the mentioned expected behavior in Java DSL as well. * Respectively, deprecate `KotlinRouterSpec.noChannelKeyFallback()` wrapper in favor of newly introduced `channelKeyFallback(channelKeyFallback: Boolean)` * Remove redundant already `noChannelKeyFallback()` option in the `NoFallbackAllowedTests` * Document the change and new behavior
1 parent 60ba544 commit d01e405

File tree

6 files changed

+103
-12
lines changed

6 files changed

+103
-12
lines changed

spring-integration-core/src/main/java/org/springframework/integration/dsl/RouterSpec.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2021 the original author or authors.
2+
* Copyright 2016-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import org.springframework.core.convert.support.DefaultConversionService;
2424
import org.springframework.integration.context.IntegrationObjectSupport;
2525
import org.springframework.integration.router.AbstractMappingMessageRouter;
26+
import org.springframework.integration.router.AbstractMessageRouter;
2627
import org.springframework.integration.support.context.NamedComponent;
2728
import org.springframework.integration.support.management.MappingMessageRouterManagement;
2829
import org.springframework.messaging.MessageChannel;
@@ -118,9 +119,24 @@ public RouterSpec<K, R> suffix(String suffix) {
118119
* cause a stack overflow.
119120
* @return the router spec.
120121
* @since 5.2
122+
* @deprecated since 6.0 in favor of {@link #channelKeyFallback(boolean)}
121123
*/
124+
@Deprecated(since = "6.0", forRemoval = true)
122125
public RouterSpec<K, R> noChannelKeyFallback() {
123-
this.handler.setChannelKeyFallback(false);
126+
return channelKeyFallback(false);
127+
}
128+
129+
/**
130+
* When true (default), if a resolved channel key does not exist in the channel map,
131+
* the key itself is used as the channel name, which we will attempt to resolve to a
132+
* channel. Set to {@code false} to disable this feature.
133+
* @param channelKeyFallback false to disable the fallback.
134+
* @return the router spec.
135+
* @since 6.0
136+
* @see AbstractMappingMessageRouter#setChannelKeyFallback(boolean)
137+
*/
138+
public RouterSpec<K, R> channelKeyFallback(boolean channelKeyFallback) {
139+
this.handler.setChannelKeyFallback(channelKeyFallback);
124140
return _this();
125141
}
126142

@@ -205,6 +221,20 @@ public RouterSpec<K, R> subFlowMapping(K key, IntegrationFlow subFlow) {
205221
return _this();
206222
}
207223

224+
/**
225+
* Make a default output mapping of the router to the parent flow.
226+
* Use the next, after router, parent flow {@link MessageChannel} as a
227+
* {@link AbstractMessageRouter#setDefaultOutputChannel(MessageChannel)} of this router.
228+
* This option also disables {@link AbstractMappingMessageRouter#setChannelKeyFallback(boolean)},
229+
* if not called explicitly, to skip an attempt to resolve channel name.
230+
* @return the router spec.
231+
* @since 6.0
232+
*/
233+
public RouterSpec<K, R> defaultOutputToParentFlow() {
234+
return super.defaultOutputToParentFlow()
235+
.channelKeyFallback(false);
236+
}
237+
208238
@Override
209239
public Map<Object, String> getComponentsToRegister() {
210240
// The 'mappingProvider' must be added to the 'componentsToRegister' in the end to

spring-integration-core/src/main/java/org/springframework/integration/router/AbstractMappingMessageRouter.java

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ public abstract class AbstractMappingMessageRouter extends AbstractMessageRouter
6262
@SuppressWarnings("serial")
6363
private final Map<String, MessageChannel> dynamicChannels =
6464
Collections.synchronizedMap(
65-
new LinkedHashMap<String, MessageChannel>(DEFAULT_DYNAMIC_CHANNEL_LIMIT, 0.75f, true) {
65+
new LinkedHashMap<>(DEFAULT_DYNAMIC_CHANNEL_LIMIT, 0.75f, true) {
6666

6767
@Override
6868
protected boolean removeEldestEntry(Entry<String, MessageChannel> eldest) {
@@ -79,6 +79,8 @@ protected boolean removeEldestEntry(Entry<String, MessageChannel> eldest) {
7979

8080
private boolean channelKeyFallback = true;
8181

82+
private boolean isChannelKeyFallbackSetExplicitly;
83+
8284
private volatile Map<String, String> channelMappings = new LinkedHashMap<>();
8385

8486

@@ -124,15 +126,62 @@ public void setResolutionRequired(boolean resolutionRequired) {
124126
/**
125127
* When true (default), if a resolved channel key does not exist in the channel map,
126128
* the key itself is used as the channel name, which we will attempt to resolve to a
127-
* channel. Set to false to disable this feature. This could be useful to prevent
129+
* channel. Set to {@code false} to disable this feature. This could be useful to prevent
128130
* malicious actors from generating a message that could cause the message to be
129131
* routed to an unexpected channel, such as one upstream of the router, which would
130132
* cause a stack overflow.
131-
* @param channelKeyFallback false to disable the fall back.
133+
* @param channelKeyFallback false to disable the fallback.
132134
* @since 5.2
133135
*/
134136
public void setChannelKeyFallback(boolean channelKeyFallback) {
135137
this.channelKeyFallback = channelKeyFallback;
138+
this.isChannelKeyFallbackSetExplicitly = true;
139+
}
140+
141+
/**
142+
* Set the default channel where Messages should be sent if channel resolution
143+
* fails to return any channels.
144+
* It also sets {@link #channelKeyFallback} to {@code false} to avoid
145+
* an attempt to resolve a channel from its key, but instead send message
146+
* directly to this channel.
147+
* If {@link #channelKeyFallback} is set explicitly to {@code true},
148+
* the further logic depend on a {@link #resolutionRequired} options
149+
* ({@code true} by default), abd therefore a fallback to
150+
* this default output channel may never happen.
151+
* @param defaultOutputChannel The default output channel.
152+
* @since 6.0
153+
* @see #setChannelKeyFallback(boolean)
154+
* @see #setResolutionRequired(boolean)
155+
*/
156+
@Override
157+
public void setDefaultOutputChannel(MessageChannel defaultOutputChannel) {
158+
super.setDefaultOutputChannel(defaultOutputChannel);
159+
if (!this.isChannelKeyFallbackSetExplicitly) {
160+
this.channelKeyFallback = false;
161+
}
162+
}
163+
164+
/**
165+
* Set the default channel where Messages should be sent if channel resolution
166+
* fails to return any channels.
167+
* It also sets {@link #channelKeyFallback} to {@code false} to avoid
168+
* an attempt to resolve a channel from its key, but instead send message
169+
* directly to this channel.
170+
* If {@link #channelKeyFallback} is set explicitly to {@code true},
171+
* the further logic depend on a {@link #resolutionRequired} options
172+
* ({@code true} by default), abd therefore a fallback to
173+
* this default output channel may never happen.
174+
* @param defaultOutputChannelName the name of the channel bean for default output.
175+
* @since 6.0
176+
* @see #setChannelKeyFallback(boolean)
177+
* @see #setResolutionRequired(boolean)
178+
*/
179+
@Override
180+
public void setDefaultOutputChannelName(String defaultOutputChannelName) {
181+
super.setDefaultOutputChannelName(defaultOutputChannelName);
182+
if (!this.isChannelKeyFallbackSetExplicitly) {
183+
this.channelKeyFallback = false;
184+
}
136185
}
137186

138187
/**

spring-integration-core/src/main/kotlin/org/springframework/integration/dsl/KotlinRouterSpec.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020 the original author or authors.
2+
* Copyright 2020-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -47,8 +47,13 @@ class KotlinRouterSpec<K, R : AbstractMappingMessageRouter>(override val delegat
4747
this.delegate.suffix(suffix)
4848
}
4949

50+
@Deprecated(message = "Since 6.0", replaceWith = ReplaceWith("channelKeyFallback(false)"))
5051
fun noChannelKeyFallback() {
51-
this.delegate.noChannelKeyFallback()
52+
channelKeyFallback(false)
53+
}
54+
55+
fun channelKeyFallback(channelKeyFallback: Boolean) {
56+
this.delegate.channelKeyFallback(channelKeyFallback)
5257
}
5358

5459
fun channelMapping(key: K, channelName: String) {

spring-integration-core/src/test/java/org/springframework/integration/dsl/routers/NoFallbackAllowedTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333

3434
/**
3535
* @author Gary Russell
36+
* @author Artem Bilan
37+
*
3638
* @since 5.2
3739
*
3840
*/
@@ -53,9 +55,7 @@ public static class Config {
5355

5456
@Bean
5557
public IntegrationFlow flow() {
56-
return f -> f.route("headers.whereTo", r -> r
57-
.noChannelKeyFallback()
58-
.defaultOutputChannel(queue()));
58+
return f -> f.route("headers.whereTo", r -> r.defaultOutputChannel(queue()));
5959
}
6060

6161
@Bean

src/reference/asciidoc/router.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,9 @@ If set, this attribute provides a reference to the channel where messages should
349349
If no default output channel is provided, the router throws an exception.
350350
If you would like to silently drop those messages instead, set the default output channel attribute value to `nullChannel`.
351351
+
352-
NOTE: A message is sent only to the `default-output-channel` if `resolution-required` is `false` and the channel is not resolved.
352+
NOTE: Starting with version 6.0, setting a default output channel also resets `channelKeyFallback` option to `false`.
353+
So, no attempts to resolve a channel from name, but rather fallback to this default output channel - similar to Java `switch` statement.
354+
If `channelKeyFallback` is set to `true` explicitly, the further logic depends on the `resolutionRequired` option: the message can reach a `defaultOutputChannel` only if `resolutionRequired` is `false`.
353355

354356
`resolution-required`::
355357
This attribute specifies whether channel names must always be successfully resolved to channel instances that exist.

src/reference/asciidoc/whats-new.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ For convenience, the XML and Java DSL for Scatter-Gather, based on the `Recipien
110110

111111
See <<./scatter-gather.adoc#scatter-gather,Scatter-Gather>> for more information.
112112

113+
Another convenient behaviour change has been done for an `AbstractMappingMessageRouter`.
114+
Now setting a `defaultOutputChannel` also resets a `channelKeyFallback` to `false`, so no attempts to resolve channel from its key, but immediate fallback to `defaultOutputChannel`.
115+
116+
See <<./router.adoc#router-common-parameters-all,Router Options>> for more information.
117+
113118
The `AggregatingMessageHandler` now does not split a `Collection<Message<?>>` result of the `MessageGroupProcessor` (unless it is a `SimpleMessageGroupProcessor`) on the output, but emits a single message containing this whole collection as a payload.
114119

115120
See <<./aggregator.adoc#aggregator,Aggregator>> for more information.

0 commit comments

Comments
 (0)