Skip to content

Commit e01d0a9

Browse files
authored
Introduce HeaderFilterSpec to streamline DSL API (#8636)
* Introduce HeaderFilterSpec to streamline DSL API The concern has been driven by the discussion from: #8625 The point is that Java method arguments are not so descriptive when we read the code. Therefore, it is better to design DSL the way it would be cleaner from reading perspective. Plus less choice of methods to chain would give a better end-user experience from coding. * Add a `HeaderFilterSpec` which can accept `headersToRemove` and `patternMatch` as individual options instead of top-level deprecated `headerFilter(headersToRemove, patternMatch)` `IntegrationFlow` method. This way Kotlin and Groovy DSLs get a gain from their "inner section" style. * Such a `Consumer<HeaderFilterSpec>` way to configure an endpoint is similar to already existing `aggregate(Consumer<AggregatorSpec>)`, `resequence(Consumer<ResequencerSpec>)` etc. In other words those components which has a dedicated `ConsumerEndpointSpec` extension are OK from an idiomatic DSL style perspective * Expose a `HeaderFilter.setHeadersToRemove()` to make it working smoothly with this new DSL requirements * Apply a new `headerFilter()` style into Kotlin and Groovy DSLs This is just an initial work to surface an idea. If it is OK, I'll slow continue with others to realign and simplify the paradox of choice. * * Fix asterisk imports in the `KotlinIntegrationFlowDefinition`
1 parent 8b004e9 commit e01d0a9

File tree

8 files changed

+170
-16
lines changed

8 files changed

+170
-16
lines changed

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,11 +1626,23 @@ public B headerFilter(String... headersToRemove) {
16261626
* @param patternMatch the {@code boolean} flag to indicate if {@code headersToRemove}
16271627
* should be interpreted as patterns or direct header names.
16281628
* @return this {@link BaseIntegrationFlowDefinition}.
1629+
* @deprecated since 6.2 in favor of {@link #headerFilter(Consumer)}
16291630
*/
1631+
@Deprecated(since = "6.2", forRemoval = true)
16301632
public B headerFilter(String headersToRemove, boolean patternMatch) {
1631-
HeaderFilter headerFilter = new HeaderFilter(StringUtils.delimitedListToStringArray(headersToRemove, ",", " "));
1632-
headerFilter.setPatternMatch(patternMatch);
1633-
return headerFilter(headerFilter, null);
1633+
return headerFilter((headerFilterSpec) -> headerFilterSpec
1634+
.headersToRemove(StringUtils.delimitedListToStringArray(headersToRemove, ",", " "))
1635+
.patternMatch(patternMatch));
1636+
}
1637+
1638+
/**
1639+
* Provide the {@link HeaderFilter} options via fluent API of the {@link HeaderFilterSpec}.
1640+
* @param headerFilter the {@link Consumer} to provide header filter and its endpoint options.
1641+
* @return this {@link BaseIntegrationFlowDefinition}.
1642+
* @since 6.2
1643+
*/
1644+
public B headerFilter(Consumer<HeaderFilterSpec> headerFilter) {
1645+
return register(new HeaderFilterSpec(), headerFilter);
16341646
}
16351647

16361648
/**
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.dsl;
18+
19+
import org.springframework.integration.transformer.HeaderFilter;
20+
import org.springframework.integration.transformer.MessageTransformingHandler;
21+
import org.springframework.util.Assert;
22+
23+
/**
24+
* A {@link ConsumerEndpointSpec} implementation for the {@link HeaderFilter}.
25+
*
26+
* @author Artem Bilan
27+
*
28+
* @since 6.2
29+
*/
30+
public class HeaderFilterSpec extends ConsumerEndpointSpec<HeaderFilterSpec, MessageTransformingHandler> {
31+
32+
private final HeaderFilter headerFilter;
33+
34+
private final boolean headerFilterExplicitlySet;
35+
36+
protected HeaderFilterSpec() {
37+
this(new HeaderFilter(), false);
38+
}
39+
40+
protected HeaderFilterSpec(HeaderFilter headerFilter) {
41+
this(headerFilter, true);
42+
}
43+
44+
private HeaderFilterSpec(HeaderFilter headerFilter, boolean headerFilterExplicitlySet) {
45+
super(new MessageTransformingHandler(headerFilter));
46+
this.headerFilter = headerFilter;
47+
this.componentsToRegister.put(this.headerFilter, null);
48+
this.headerFilterExplicitlySet = headerFilterExplicitlySet;
49+
}
50+
51+
public HeaderFilterSpec headersToRemove(String... headersToRemove) {
52+
assertHeaderFilterNotExplicitlySet();
53+
this.headerFilter.setHeadersToRemove(headersToRemove);
54+
return this;
55+
}
56+
57+
public HeaderFilterSpec patternMatch(boolean patternMatch) {
58+
assertHeaderFilterNotExplicitlySet();
59+
this.headerFilter.setPatternMatch(patternMatch);
60+
return this;
61+
}
62+
63+
private void assertHeaderFilterNotExplicitlySet() {
64+
Assert.isTrue(!this.headerFilterExplicitlySet,
65+
() -> "Cannot override already set header filter: " + this.headerFilter);
66+
}
67+
68+
}

spring-integration-core/src/main/java/org/springframework/integration/transformer/HeaderFilter.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -40,16 +40,32 @@
4040
*/
4141
public class HeaderFilter extends IntegrationObjectSupport implements Transformer, IntegrationPattern {
4242

43-
private final String[] headersToRemove;
43+
private String[] headersToRemove;
4444

4545
private volatile boolean patternMatch = true;
4646

4747

48+
/**
49+
* Create an instance of the class.
50+
* The {@link #setHeadersToRemove} must be called afterwards.
51+
* @since 6.2
52+
*/
53+
public HeaderFilter() {
54+
}
55+
4856
public HeaderFilter(String... headersToRemove) {
49-
Assert.notEmpty(headersToRemove, "At least one header name to remove is required.");
50-
this.headersToRemove = Arrays.copyOf(headersToRemove, headersToRemove.length);
57+
setHeadersToRemove(headersToRemove);
5158
}
5259

60+
/**
61+
* Set a list of header names (or patterns) to remove from a request message.
62+
* @param headersToRemove the list of header names (or patterns) to remove from a request message.
63+
* @since 6.2
64+
*/
65+
public final void setHeadersToRemove(String... headersToRemove) {
66+
assertHeadersToRemoveNotEmpty(headersToRemove);
67+
this.headersToRemove = Arrays.copyOf(headersToRemove, headersToRemove.length);
68+
}
5369
public void setPatternMatch(boolean patternMatch) {
5470
this.patternMatch = patternMatch;
5571
}
@@ -66,6 +82,7 @@ public IntegrationPatternType getIntegrationPatternType() {
6682

6783
@Override
6884
protected void onInit() {
85+
assertHeadersToRemoveNotEmpty(this.headersToRemove);
6986
super.onInit();
7087
if (getMessageBuilderFactory() instanceof DefaultMessageBuilderFactory) {
7188
for (String header : this.headersToRemove) {
@@ -94,4 +111,8 @@ public Message<?> transform(Message<?> message) {
94111
return builder.build();
95112
}
96113

114+
private static void assertHeadersToRemoveNotEmpty(String[] headersToRemove) {
115+
Assert.notEmpty(headersToRemove, "At least one header name to remove is required.");
116+
}
117+
97118
}

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ import org.springframework.integration.aggregator.AggregatingMessageHandler
2222
import org.springframework.integration.channel.BroadcastCapableChannel
2323
import org.springframework.integration.channel.FluxMessageChannel
2424
import org.springframework.integration.channel.interceptor.WireTap
25+
import org.springframework.integration.core.GenericHandler
2526
import org.springframework.integration.core.MessageSelector
2627
import org.springframework.integration.dsl.support.MessageChannelReference
2728
import org.springframework.integration.filter.MessageFilter
2829
import org.springframework.integration.filter.MethodInvokingSelector
2930
import org.springframework.integration.handler.BridgeHandler
3031
import org.springframework.integration.handler.DelayHandler
31-
import org.springframework.integration.core.GenericHandler
3232
import org.springframework.integration.handler.LoggingHandler
3333
import org.springframework.integration.handler.MessageProcessor
3434
import org.springframework.integration.handler.MessageTriggerAction
@@ -56,6 +56,7 @@ import org.springframework.messaging.MessageChannel
5656
import org.springframework.messaging.MessageHandler
5757
import org.springframework.messaging.MessageHeaders
5858
import org.springframework.messaging.support.ChannelInterceptor
59+
import org.springframework.util.StringUtils
5960
import reactor.core.publisher.Flux
6061
import java.util.function.Consumer
6162

@@ -711,8 +712,23 @@ class KotlinIntegrationFlowDefinition(@PublishedApi internal val delegate: Integ
711712
/**
712713
* Provide the [HeaderFilter] to the current [IntegrationFlow].
713714
*/
715+
@Deprecated("since 6.2",
716+
ReplaceWith("""headerFilter {
717+
patternMatch()
718+
headersToRemove()
719+
}"""))
714720
fun headerFilter(headersToRemove: String, patternMatch: Boolean = true) {
715-
this.delegate.headerFilter(headersToRemove, patternMatch)
721+
headerFilter {
722+
patternMatch(patternMatch)
723+
headersToRemove(*StringUtils.delimitedListToStringArray(headersToRemove, ",", " "))
724+
}
725+
}
726+
727+
/**
728+
* Provide the [HeaderFilter] to the current [IntegrationFlow].
729+
*/
730+
fun headerFilter(endpointConfigurer: HeaderFilterSpec.() -> Unit) {
731+
this.delegate.headerFilter(endpointConfigurer)
716732
}
717733

718734
/**

spring-integration-core/src/test/java/org/springframework/integration/dsl/correlation/CorrelationHandlerTests.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2022 the original author or authors.
2+
* Copyright 2016-2023 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.
@@ -60,7 +60,6 @@
6060
/**
6161
* @author Artem Bilan
6262
* @author Gary Russell
63-
*
6463
* @since 5.0
6564
*/
6665
@RunWith(SpringRunner.class)
@@ -241,7 +240,7 @@ public IntegrationFlow splitResequenceFlow(MessageChannel executorChannel, TaskE
241240
.enrichHeaders(h ->
242241
h.headerFunction(IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER, Message::getPayload))
243242
.resequence(r -> r.releasePartialSequences(true).correlationExpression("'foo'"))
244-
.headerFilter("foo", false);
243+
.headerFilter(headerFilterSpec -> headerFilterSpec.headersToRemove("foo").patternMatch(false));
245244
}
246245

247246

spring-integration-core/src/test/kotlin/org/springframework/integration/dsl/KotlinDslTests.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,16 @@ class KotlinDslTests {
182182
@Test
183183
fun `flow from lambda`() {
184184
val replyChannel = QueueChannel()
185-
val message = MessageBuilder.withPayload("test").setReplyChannel(replyChannel).build()
185+
val message = MessageBuilder.withPayload("test")
186+
.setHeader("headerToRemove", "no value")
187+
.setReplyChannel(replyChannel)
188+
.build()
186189

187190
this.flowLambdaInput.send(message)
188191

189-
assertThat(replyChannel.receive(10_000)?.payload).isNotNull().isEqualTo("TEST")
192+
val receive = replyChannel.receive(10_000)
193+
assertThat(receive?.payload).isNotNull().isEqualTo("TEST")
194+
assertThat(receive.headers).doesNotContain("headerToRemove", null)
190195
assertThat(this.wireTapChannel.receive(10_000)?.payload).isNotNull().isEqualTo("test")
191196
}
192197

@@ -308,6 +313,10 @@ class KotlinDslTests {
308313
fun flowLambda() =
309314
integrationFlow {
310315
filter<String>({ it === "test" }) { id("filterEndpoint") }
316+
headerFilter {
317+
patternMatch(false)
318+
headersToRemove("notAHeader", "headerToRemove")
319+
}
311320
wireTap {
312321
channel { queue("wireTapChannel") }
313322
}

spring-integration-groovy/src/main/groovy/org/springframework/integration/groovy/dsl/GroovyIntegrationFlowDefinition.groovy

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import org.springframework.integration.dsl.FilterEndpointSpec
3535
import org.springframework.integration.dsl.GatewayEndpointSpec
3636
import org.springframework.integration.dsl.GenericEndpointSpec
3737
import org.springframework.integration.dsl.HeaderEnricherSpec
38+
import org.springframework.integration.dsl.HeaderFilterSpec
3839
import org.springframework.integration.dsl.IntegrationFlow
3940
import org.springframework.integration.dsl.IntegrationFlowDefinition
4041
import org.springframework.integration.dsl.MessageChannelSpec
@@ -818,8 +819,10 @@ class GroovyIntegrationFlowDefinition {
818819
* {@link HeaderFilter}.
819820
* @param headerFilter the {@link HeaderFilter} to use.
820821
* @param endpointConfigurer the {@link Consumer} to provide integration endpoint options.
822+
* @deprecated since 6.2 in favor of {@link #headerFilter(groovy.lang.Closure)}
821823
* @see GenericEndpointSpec
822824
*/
825+
@Deprecated(since = "6.2", forRemoval = true)
823826
GroovyIntegrationFlowDefinition headerFilter(
824827
String headersToRemove,
825828
boolean patternMatch = true,
@@ -834,6 +837,21 @@ class GroovyIntegrationFlowDefinition {
834837
this
835838
}
836839

840+
/**
841+
* Populate {@link HeaderFilter} based on the options from a {@link HeaderFilterSpec}.
842+
* @param endpointConfigurer the {@link Consumer} to provide {@link HeaderFilter} and its endpoint options.
843+
* @see HeaderFilterSpec
844+
* @since 6.2
845+
*/
846+
GroovyIntegrationFlowDefinition headerFilter(
847+
@DelegatesTo(value = HeaderFilterSpec, strategy = Closure.DELEGATE_FIRST)
848+
@ClosureParams(value = SimpleType.class, options = 'org.springframework.integration.dsl.HeaderFilterSpec')
849+
Closure<?> headerFilterConfigurer) {
850+
851+
this.delegate.headerFilter createConfigurerIfAny(headerFilterConfigurer)
852+
this
853+
}
854+
837855
/**
838856
* Populate the {@link MessageTransformingHandler} for the
839857
* {@link org.springframework.integration.transformer.ClaimCheckInTransformer} with provided {@link MessageStore}.

spring-integration-groovy/src/test/groovy/org/springframework/integration/groovy/dsl/test/GroovyDslTests.groovy

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,18 @@ class GroovyDslTests {
189189
@Test
190190
void 'flow from lambda'() {
191191
def replyChannel = new QueueChannel()
192-
def message = MessageBuilder.withPayload('test').setReplyChannel(replyChannel).build()
192+
def message =
193+
MessageBuilder.withPayload('test')
194+
.setHeader('headerToRemove', 'no value')
195+
.setReplyChannel(replyChannel)
196+
.build()
193197

194198
this.flowLambdaInput.send message
195199

196-
assert replyChannel.receive(10_000)?.payload == 'TEST'
200+
def receive = replyChannel.receive(10_000)
201+
202+
assert receive?.payload == 'TEST'
203+
assert !receive?.headers?.containsKey('headerToRemove')
197204
assert this.wireTapChannel.receive(10_000)?.payload == 'test'
198205
}
199206

@@ -300,6 +307,10 @@ class GroovyDslTests {
300307
flowLambda() {
301308
integrationFlow {
302309
filter String, { it == 'test' }, { id 'filterEndpoint' }
310+
headerFilter {
311+
patternMatch false
312+
headersToRemove "notAHeader", "headerToRemove"
313+
}
303314
wireTap integrationFlow {
304315
channel { queue 'wireTapChannel' }
305316
}

0 commit comments

Comments
 (0)