GH-3204: Add DSL intercept() operator
Fixes https://github.com/spring-projects/spring-integration/issues/3204 * Add an `intercept(ChannelInterceptor...)` method into `BaseIntegrationFlowDefinition` to register one or more channel interceptors at the current flow position. * refactor to reuse `InterceptableChannel` creation from `wireTap` * document the new operator
This commit is contained in:
@@ -90,6 +90,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.messaging.support.InterceptableChannel;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -109,6 +110,7 @@ import reactor.util.function.Tuple2;
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @author Gabriele Del Prete
|
||||
* @author Tim Feuerbach
|
||||
*
|
||||
* @since 5.2.1
|
||||
*
|
||||
@@ -179,6 +181,24 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
|
||||
return this.currentMessageChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current channel if it is an {@link InterceptableChannel}, otherwise register a new implicit
|
||||
* {@link DirectChannel} in the flow and return that one.
|
||||
* @return the current channel after the operation
|
||||
*/
|
||||
protected InterceptableChannel currentInterceptableChannel() {
|
||||
MessageChannel currentChannel = getCurrentMessageChannel();
|
||||
if (currentChannel instanceof InterceptableChannel) {
|
||||
return (InterceptableChannel) currentChannel;
|
||||
}
|
||||
else {
|
||||
DirectChannel newCurrentChannel = new DirectChannel();
|
||||
channel(newCurrentChannel);
|
||||
setImplicitChannel(true);
|
||||
return newCurrentChannel;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setImplicitChannel(boolean implicitChannel) {
|
||||
this.implicitChannel = implicitChannel;
|
||||
}
|
||||
@@ -488,14 +508,9 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
|
||||
*/
|
||||
public B wireTap(WireTapSpec wireTapSpec) {
|
||||
WireTap interceptor = wireTapSpec.get();
|
||||
MessageChannel currentChannel = getCurrentMessageChannel();
|
||||
if (!(currentChannel instanceof InterceptableChannel)) {
|
||||
currentChannel = new DirectChannel();
|
||||
channel(currentChannel);
|
||||
setImplicitChannel(true);
|
||||
}
|
||||
InterceptableChannel currentChannel = currentInterceptableChannel();
|
||||
addComponent(wireTapSpec);
|
||||
((InterceptableChannel) currentChannel).addInterceptor(interceptor);
|
||||
currentChannel.addInterceptor(interceptor);
|
||||
return _this();
|
||||
}
|
||||
|
||||
@@ -2829,6 +2844,26 @@ public abstract class BaseIntegrationFlowDefinition<B extends BaseIntegrationFlo
|
||||
return handle(new ServiceActivatingHandler(triggerAction, "trigger"), endpointConfigurer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more {@link ChannelInterceptor} implementations
|
||||
* to the current {@link #currentMessageChannel}, in the given order, after any interceptors already registered.
|
||||
* @param interceptorArray one or more {@link ChannelInterceptor}s.
|
||||
* @return the current {@link BaseIntegrationFlowDefinition}.
|
||||
* @throws IllegalArgumentException if one or more null arguments are provided
|
||||
* @since 5.3
|
||||
*/
|
||||
public B intercept(ChannelInterceptor... interceptorArray) {
|
||||
Assert.notNull(interceptorArray, "'interceptorArray' must not be null");
|
||||
Assert.noNullElements(interceptorArray, "'interceptorArray' must not contain null elements");
|
||||
|
||||
InterceptableChannel currentChannel = currentInterceptableChannel();
|
||||
for (ChannelInterceptor interceptor : interceptorArray) {
|
||||
currentChannel.addInterceptor(interceptor);
|
||||
}
|
||||
|
||||
return _this();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate a {@link FluxMessageChannel} to start a reactive processing for upstream data,
|
||||
* wrap it to a {@link Flux}, apply provided {@link Function} via {@link Flux#transform(Function)}
|
||||
|
||||
@@ -20,6 +20,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -32,6 +34,7 @@ import java.util.function.Supplier;
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.After;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
@@ -83,6 +86,7 @@ import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
@@ -100,6 +104,7 @@ import reactor.core.publisher.Mono;
|
||||
* @author Tim Ysewyn
|
||||
* @author Gary Russell
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Tim Feuerbach
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@@ -498,6 +503,32 @@ public class IntegrationFlowTests {
|
||||
this.errorChannel.unsubscribe(errorMessageHandler);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Qualifier("interceptorChannelIn")
|
||||
private MessageChannel interceptorChannelIn;
|
||||
|
||||
@Autowired
|
||||
private List<String> outputStringList;
|
||||
|
||||
@Test
|
||||
public void testInterceptorFlow() {
|
||||
this.interceptorChannelIn.send(MessageBuilder.withPayload("foo").build());
|
||||
|
||||
assertThat(outputStringList).containsExactly(
|
||||
"Pre send transform: foo",
|
||||
"Pre send handle: FOO",
|
||||
"Handle: FOO",
|
||||
"Post send handle: FOO",
|
||||
"Post send transform: foo"
|
||||
);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUpList() {
|
||||
outputStringList.clear();
|
||||
}
|
||||
|
||||
|
||||
@MessagingGateway
|
||||
public interface ControlBusGateway {
|
||||
|
||||
@@ -909,6 +940,46 @@ public class IntegrationFlowTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class InterceptorContextConfiguration {
|
||||
|
||||
@Bean
|
||||
public List<String> outputStringList() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IntegrationFlow interceptorFlow(List<String> outputStringList) {
|
||||
return IntegrationFlows.from("interceptorChannelIn")
|
||||
.intercept(new ChannelInterceptor() {
|
||||
@Override
|
||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||
outputStringList.add("Pre send transform: " + message.getPayload());
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
|
||||
outputStringList.add("Post send transform: " + message.getPayload());
|
||||
}
|
||||
})
|
||||
.transform((String s) -> s.toUpperCase())
|
||||
.intercept(new ChannelInterceptor() {
|
||||
@Override
|
||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||
outputStringList.add("Pre send handle: " + message.getPayload());
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postSend(Message<?> message, MessageChannel channel, boolean sent) {
|
||||
outputStringList.add("Post send handle: " + message.getPayload());
|
||||
}
|
||||
})
|
||||
.handle(m -> outputStringList.add("Handle: " + m.getPayload())).get();
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
public static class GreetingService extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
|
||||
@@ -594,6 +594,22 @@ When this operator is used at the end of a flow, it is a one-way handler and the
|
||||
To make it as a reply-producing flow, you can either use a simple `bridge()` after the `log()` or, starting with version 5.1, you can use a `logAndReply()` operator instead.
|
||||
`logAndReply` can only be used at the end of a flow.
|
||||
|
||||
[[java-dsl-intercept]]
|
||||
=== Operator intercept()
|
||||
|
||||
Starting with version 5.3, the `intercept()` operator allows to register one or more `ChannelInterceptor` instances at the current `MessageChannel` in the flow.
|
||||
This is an alternative to creating an explicit `MessageChannel` via the `MessageChannels` API.
|
||||
The following example uses a `MessageSelectingInterceptor` to reject certain messages with an exception:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
.transform(...)
|
||||
.intercept(new MessageSelectingInterceptor(m -> m.getPayload().isValid()))
|
||||
.handle(...)
|
||||
----
|
||||
====
|
||||
|
||||
[[java-dsl-wiretap]]
|
||||
=== `MessageChannelSpec.wireTap()`
|
||||
|
||||
@@ -618,7 +634,7 @@ public IntegrationFlow loggingFlow() {
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
If the `MessageChannel` is an instance of `InterceptableChannel`, the `log()` or `wireTap()` operators are applied to the current `MessageChannel`.
|
||||
If the `MessageChannel` is an instance of `InterceptableChannel`, the `log()`, `wireTap()` or `intercept()` operators are applied to the current `MessageChannel`.
|
||||
Otherwise, an intermediate `DirectChannel` is injected into the flow for the currently configured endpoint.
|
||||
In the following example, the `WireTap` interceptor is added to `myChannel` directly, because `DirectChannel` implements `InterceptableChannel`:
|
||||
|
||||
|
||||
@@ -66,6 +66,9 @@ Transactional support in Spring Integration now also includes options to configu
|
||||
See `TransactionInterceptorBuilder` for more information.
|
||||
See also <<./transactions.adoc#reactive-transactions,Reactive Transactions>>.
|
||||
|
||||
A new `intercept()` operator to register `ChannelInterceptor` instances without creating explicit channels was added into Java DSL.
|
||||
See <<./dsl.adoc#java-dsl-intercept,Operator intercept()>> for more information.
|
||||
|
||||
[[x5.3-amqp]]
|
||||
=== AMQP Changes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user