/*
* Copyright 2016-2017 the original author or authors.
*
* Licensed 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.springframework.integration.dsl.flows;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.MessageDispatchingException;
import org.springframework.integration.MessageRejectedException;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.FixedSubscriberChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.dsl.channel.MessageChannels;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.handler.GenericHandler;
import org.springframework.integration.handler.advice.ErrorMessageSendingRecoverer;
import org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice;
import org.springframework.integration.handler.advice.RequestHandlerRetryAdvice;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.integration.store.MessageStore;
import org.springframework.integration.store.SimpleMessageStore;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.MutableMessageBuilder;
import org.springframework.integration.transformer.PayloadDeserializingTransformer;
import org.springframework.integration.transformer.PayloadSerializingTransformer;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
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.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Artem Bilan
* @author Tim Ysewyn
* @author Gary Russell
*
* @since 5.0
*/
@RunWith(SpringRunner.class)
@DirtiesContext
public class IntegrationFlowTests {
@Autowired
private ListableBeanFactory beanFactory;
@Autowired
private ControlBusGateway controlBus;
@Autowired
@Qualifier("inputChannel")
private MessageChannel inputChannel;
@Autowired
@Qualifier("discardChannel")
private PollableChannel discardChannel;
@Autowired
@Qualifier("foo")
private SubscribableChannel foo;
@Autowired
@Qualifier("successChannel")
private PollableChannel successChannel;
@Autowired
@Qualifier("bridgeFlowInput")
private PollableChannel bridgeFlowInput;
@Autowired
@Qualifier("bridgeFlowOutput")
private PollableChannel bridgeFlowOutput;
@Autowired
@Qualifier("bridgeFlow2Input")
private MessageChannel bridgeFlow2Input;
@Autowired
@Qualifier("bridgeFlow2Output")
private PollableChannel bridgeFlow2Output;
@Autowired
@Qualifier("methodInvokingInput")
private MessageChannel methodInvokingInput;
@Autowired
@Qualifier("delayedAdvice")
private DelayedAdvice delayedAdvice;
@Autowired
private MessageStore messageStore;
@Autowired
@Qualifier("claimCheckInput")
private MessageChannel claimCheckInput;
@Autowired
@Qualifier("lambdasInput")
private MessageChannel lambdasInput;
@Autowired
@Qualifier("gatewayInput")
private MessageChannel gatewayInput;
@Autowired
@Qualifier("gatewayError")
private PollableChannel gatewayError;
@Test
public void testDirectFlow() {
assertTrue(this.beanFactory.containsBean("filter"));
assertTrue(this.beanFactory.containsBean("filter.handler"));
assertTrue(this.beanFactory.containsBean("expressionFilter"));
assertTrue(this.beanFactory.containsBean("expressionFilter.handler"));
QueueChannel replyChannel = new QueueChannel();
Message<String> message = MessageBuilder.withPayload("100").setReplyChannel(replyChannel).build();
try {
this.inputChannel.send(message);
fail("Expected MessageDispatchingException");
}
catch (Exception e) {
assertThat(e, instanceOf(MessageDeliveryException.class));
assertThat(e.getCause(), instanceOf(MessageDispatchingException.class));
assertThat(e.getMessage(), containsString("Dispatcher has no subscribers"));
}
this.controlBus.send("@payloadSerializingTransformer.start()");
final AtomicBoolean used = new AtomicBoolean();
this.foo.subscribe(m -> used.set(true));
this.inputChannel.send(message);
Message<?> reply = replyChannel.receive(5000);
assertNotNull(reply);
assertEquals(200, reply.getPayload());
Message<?> successMessage = this.successChannel.receive(5000);
assertNotNull(successMessage);
assertEquals(100, successMessage.getPayload());
assertTrue(used.get());
this.inputChannel.send(new GenericMessage<Object>(1000));
Message<?> discarded = this.discardChannel.receive(5000);
assertNotNull(discarded);
assertEquals("Discarded: 1000", discarded.getPayload());
}
@Test
public void testBridge() {
GenericMessage<String> message = new GenericMessage<>("test");
this.bridgeFlowInput.send(message);
Message<?> reply = this.bridgeFlowOutput.receive(5000);
assertNotNull(reply);
assertEquals("test", reply.getPayload());
assertTrue(this.beanFactory.containsBean("bridgeFlow2.channel#0"));
assertThat(this.beanFactory.getBean("bridgeFlow2.channel#0"), instanceOf(FixedSubscriberChannel.class));
try {
this.bridgeFlow2Input.send(message);
fail("Expected MessageDispatchingException");
}
catch (Exception e) {
assertThat(e, instanceOf(MessageDeliveryException.class));
assertThat(e.getCause(), instanceOf(MessageDispatchingException.class));
assertThat(e.getMessage(), containsString("Dispatcher has no subscribers"));
}
this.controlBus.send("@bridge.start()");
this.bridgeFlow2Input.send(message);
reply = this.bridgeFlow2Output.receive(5000);
assertNotNull(reply);
assertEquals("test", reply.getPayload());
assertTrue(this.delayedAdvice.getInvoked());
}
@Test
public void testWrongLastMessageChannel() {
ConfigurableApplicationContext context = null;
try {
context = new AnnotationConfigApplicationContext(InvalidLastMessageChannelFlowContext.class);
fail("BeanCreationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanCreationException.class));
assertThat(e.getMessage(), containsString("'.fixedSubscriberChannel()' " +
"can't be the last EIP-method in the IntegrationFlow definition"));
}
finally {
if (context != null) {
context.close();
}
}
}
@Test
public void testMethodInvokingMessageHandler() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message = MessageBuilder.withPayload("world")
.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel)
.build();
this.methodInvokingInput.send(message);
Message<?> receive = replyChannel.receive(5000);
assertNotNull(receive);
assertEquals("Hello World and world", receive.getPayload());
}
@Test
public void testLambdas() {
QueueChannel replyChannel = new QueueChannel();
Message<?> message = MessageBuilder.withPayload("World")
.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel)
.build();
this.lambdasInput.send(message);
Message<?> receive = replyChannel.receive(5000);
assertNotNull(receive);
assertEquals("Hello World", receive.getPayload());
message = MessageBuilder.withPayload("Spring")
.setHeader(MessageHeaders.REPLY_CHANNEL, replyChannel)
.build();
this.lambdasInput.send(message);
assertNull(replyChannel.receive(10));
}
@Test
public void testClaimCheck() {
QueueChannel replyChannel = new QueueChannel();
Message<String> message = MutableMessageBuilder.withPayload("foo").setReplyChannel(replyChannel).build();
this.claimCheckInput.send(message);
Message<?> receive = replyChannel.receive(2000);
assertNotNull(receive);
assertSame(message, receive);
assertEquals(1, this.messageStore.getMessageCount());
assertSame(message, this.messageStore.getMessage(message.getHeaders().getId()));
}
@Test
public void testGatewayFlow() throws Exception {
PollableChannel replyChannel = new QueueChannel();
Message<String> message = MessageBuilder.withPayload("foo").setReplyChannel(replyChannel).build();
this.gatewayInput.send(message);
Message<?> receive = replyChannel.receive(2000);
assertNotNull(receive);
assertEquals("From Gateway SubFlow: FOO", receive.getPayload());
assertNull(this.gatewayError.receive(1));
message = MessageBuilder.withPayload("bar").setReplyChannel(replyChannel).build();
this.gatewayInput.send(message);
receive = replyChannel.receive(1);
assertNull(receive);
receive = this.gatewayError.receive(2000);
assertNotNull(receive);
assertThat(receive, instanceOf(ErrorMessage.class));
assertThat(receive.getPayload(), instanceOf(MessageRejectedException.class));
assertThat(((Exception) receive.getPayload()).getMessage(), containsString("' rejected Message"));
}
@Autowired
private SubscribableChannel tappedChannel1;
@Autowired
@Qualifier("wireTapFlow2.input")
private SubscribableChannel tappedChannel2;
@Autowired
@Qualifier("wireTapFlow3.input")
private SubscribableChannel tappedChannel3;
@Autowired
private SubscribableChannel tappedChannel4;
@Autowired
@Qualifier("tapChannel")
private QueueChannel tapChannel;
@Autowired
@Qualifier("wireTapFlow5.input")
private SubscribableChannel tappedChannel5;
@Autowired
private PollableChannel wireTapSubflowResult;
@Test
public void testWireTap() {
this.tappedChannel1.send(new GenericMessage<>("foo"));
this.tappedChannel1.send(new GenericMessage<>("bar"));
Message<?> out = this.tapChannel.receive(10000);
assertNotNull(out);
assertEquals("foo", out.getPayload());
assertNull(this.tapChannel.receive(0));
this.tappedChannel2.send(new GenericMessage<>("foo"));
this.tappedChannel2.send(new GenericMessage<>("bar"));
out = this.tapChannel.receive(10000);
assertNotNull(out);
assertEquals("foo", out.getPayload());
assertNull(this.tapChannel.receive(0));
this.tappedChannel3.send(new GenericMessage<>("foo"));
this.tappedChannel3.send(new GenericMessage<>("bar"));
out = this.tapChannel.receive(10000);
assertNotNull(out);
assertEquals("foo", out.getPayload());
assertNull(this.tapChannel.receive(0));
this.tappedChannel4.send(new GenericMessage<>("foo"));
this.tappedChannel4.send(new GenericMessage<>("bar"));
out = this.tapChannel.receive(10000);
assertNotNull(out);
assertEquals("foo", out.getPayload());
out = this.tapChannel.receive(10000);
assertNotNull(out);
assertEquals("bar", out.getPayload());
this.tappedChannel5.send(new GenericMessage<>("foo"));
out = this.wireTapSubflowResult.receive(10000);
assertNotNull(out);
assertEquals("FOO", out.getPayload());
}
@Autowired
@Qualifier("subscribersFlow.input")
private MessageChannel subscribersFlowInput;
@Autowired
@Qualifier("subscriber1Results")
private PollableChannel subscriber1Results;
@Autowired
@Qualifier("subscriber2Results")
private PollableChannel subscriber2Results;
@Autowired
@Qualifier("subscriber3Results")
private PollableChannel subscriber3Results;
@Test
public void testSubscribersSubFlows() {
this.subscribersFlowInput.send(new GenericMessage<>(2));
Message<?> receive1 = this.subscriber1Results.receive(5000);
assertNotNull(receive1);
assertEquals(1, receive1.getPayload());
Message<?> receive2 = this.subscriber2Results.receive(5000);
assertNotNull(receive2);
assertEquals(4, receive2.getPayload());
Message<?> receive3 = this.subscriber3Results.receive(5000);
assertNotNull(receive3);
assertEquals(6, receive3.getPayload());
}
@Autowired
private ErrorRecovererFlowGateway errorRecovererFlowGateway;
@Test
public void testReplyChannelFromReplyMessage() {
assertEquals("foo", this.errorRecovererFlowGateway.testIt("foo"));
}
@MessagingGateway
public interface ControlBusGateway {
void send(String command);
}
@Configuration
@EnableIntegration
public static class ContextConfiguration {
@Bean
public IntegrationFlow controlBusFlow() {
return IntegrationFlows.from(ControlBusGateway.class)
.controlBus()
.get();
}
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return Pollers.fixedRate(500).get();
}
@Bean(name = IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME)
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(100);
return threadPoolTaskScheduler;
}
@Bean
public MessageChannel inputChannel() {
return MessageChannels.direct().get();
}
@Bean
public MessageChannel foo() {
return MessageChannels.publishSubscribe().get();
}
}
@Configuration
@ComponentScan
public static class ContextConfiguration2 {
@Autowired
@Qualifier("inputChannel")
private MessageChannel inputChannel;
@Autowired
@Qualifier("successChannel")
private PollableChannel successChannel;
@Bean
public Advice expressionAdvice() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setOnSuccessExpressionString("payload");
advice.setSuccessChannel(this.successChannel);
return advice;
}
@Bean
public IntegrationFlow flow2() {
return IntegrationFlows.from(this.inputChannel)
.filter(p -> p instanceof String, e -> e
.id("filter")
.discardFlow(df -> df
.transform(String.class, "Discarded: "::concat)
.channel(MessageChannels.queue("discardChannel"))))
.channel("foo")
.fixedSubscriberChannel()
.<String, Integer>transform(Integer::parseInt)
.transform(new PayloadSerializingTransformer(),
c -> c.autoStartup(false).id("payloadSerializingTransformer"))
.channel(MessageChannels.queue(new SimpleMessageStore(), "fooQueue"))
.transform(new PayloadDeserializingTransformer())
.filter("true", e -> e.id("expressionFilter"))
.channel(publishSubscribeChannel())
.transform((Integer p) -> p * 2, c -> c.advice(this.expressionAdvice()))
.get();
}
@Bean
public MessageChannel publishSubscribeChannel() {
return MessageChannels.publishSubscribe().get();
}
@Bean
public IntegrationFlow subscribersFlow() {
return flow -> flow
.publishSubscribeChannel(Executors.newCachedThreadPool(), s -> s
.subscribe(f -> f
.<Integer>handle((p, h) -> p / 2)
.channel(MessageChannels.queue("subscriber1Results")))
.subscribe(f -> f
.<Integer>handle((p, h) -> p * 2)
.channel(MessageChannels.queue("subscriber2Results"))))
.<Integer>handle((p, h) -> p * 3)
.channel(MessageChannels.queue("subscriber3Results"));
}
@Bean
public IntegrationFlow wireTapFlow1() {
return IntegrationFlows.from("tappedChannel1")
.wireTap("tapChannel", wt -> wt.selector(m -> m.getPayload().equals("foo")))
.channel("nullChannel")
.get();
}
@Bean
public IntegrationFlow wireTapFlow2() {
return f -> f
.wireTap("tapChannel", wt -> wt.selector(m -> m.getPayload().equals("foo")))
.channel("nullChannel");
}
@Bean
public IntegrationFlow wireTapFlow3() {
return f -> f
.transform("payload")
.wireTap("tapChannel", wt -> wt.selector("payload == 'foo'"))
.channel("nullChannel");
}
@Bean
public IntegrationFlow wireTapFlow4() {
return IntegrationFlows.from("tappedChannel4")
.wireTap(tapChannel())
.channel("nullChannel")
.get();
}
@Bean
public IntegrationFlow wireTapFlow5() {
return f -> f
.wireTap(sf -> sf
.<String, String>transform(String::toUpperCase)
.channel(MessageChannels.queue("wireTapSubflowResult")))
.channel("nullChannel");
}
@Bean
public QueueChannel tapChannel() {
return new QueueChannel();
}
}
@MessageEndpoint
public static class AnnotationTestService {
@ServiceActivator(inputChannel = "publishSubscribeChannel")
public void handle(Object payload) {
assertEquals(100, payload);
}
}
@Configuration
public static class ContextConfiguration3 {
@Autowired
@Qualifier("delayedAdvice")
private MethodInterceptor delayedAdvice;
@Bean
public QueueChannel successChannel() {
return MessageChannels.queue().get();
}
@Bean
public IntegrationFlow bridgeFlow() {
return IntegrationFlows.from(MessageChannels.queue("bridgeFlowInput"))
.channel(MessageChannels.queue("bridgeFlowOutput"))
.get();
}
@Bean
public IntegrationFlow bridgeFlow2() {
return IntegrationFlows.from("bridgeFlow2Input")
.bridge(c -> c.autoStartup(false).id("bridge"))
.fixedSubscriberChannel()
.delay("delayer", d -> d
.delayExpression("200")
.advice(this.delayedAdvice)
.messageStore(this.messageStore()))
.channel(MessageChannels.queue("bridgeFlow2Output"))
.get();
}
@Bean
public SimpleMessageStore messageStore() {
return new SimpleMessageStore();
}
@Bean
public IntegrationFlow claimCheckFlow() {
return IntegrationFlows.from("claimCheckInput")
.claimCheckIn(this.messageStore())
.claimCheckOut(this.messageStore())
.get();
}
}
@Component("delayedAdvice")
public static class DelayedAdvice implements MethodInterceptor {
private final AtomicBoolean invoked = new AtomicBoolean();
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
this.invoked.set(true);
return invocation.proceed();
}
public Boolean getInvoked() {
return invoked.get();
}
}
@Configuration
public static class ContextConfiguration4 {
@Autowired
@Qualifier("integrationFlowTests.GreetingService")
private MessageHandler greetingService;
@Bean
public IntegrationFlow methodInvokingFlow() {
return IntegrationFlows.from("methodInvokingInput")
.handle(this.greetingService)
.get();
}
@Bean
public IntegrationFlow lambdasFlow() {
return IntegrationFlows.from("lambdasInput")
.filter("World"::equals)
.transform("Hello "::concat)
.get();
}
@Bean
public IntegrationFlow gatewayFlow() {
return IntegrationFlows.from("gatewayInput")
.gateway("gatewayRequest", g -> g.errorChannel("gatewayError").replyTimeout(10L))
.gateway(f -> f.transform("From Gateway SubFlow: "::concat))
.get();
}
@Bean
public IntegrationFlow gatewayRequestFlow() {
return IntegrationFlows.from("gatewayRequest")
.filter("foo"::equals, f -> f.throwExceptionOnRejection(true))
.<String, String>transform(String::toUpperCase)
.get();
}
@Bean
public MessageChannel gatewayError() {
return MessageChannels.queue().get();
}
@Bean
public IntegrationFlow errorRecovererFlow() {
return IntegrationFlows.from(ErrorRecovererFlowGateway.class)
.handle((GenericHandler<?>) (p, h) -> {
throw new RuntimeException("intentional");
}, e -> e.advice(retryAdvice()))
.get();
}
@Bean
public RequestHandlerRetryAdvice retryAdvice() {
RequestHandlerRetryAdvice requestHandlerRetryAdvice = new RequestHandlerRetryAdvice();
requestHandlerRetryAdvice.setRecoveryCallback(new ErrorMessageSendingRecoverer(recoveryChannel()));
return requestHandlerRetryAdvice;
}
@Bean
public MessageChannel recoveryChannel() {
return new DirectChannel();
}
@Bean
public IntegrationFlow recoveryFlow() {
return IntegrationFlows.from(recoveryChannel())
.<MessagingException, Message<?>>transform(MessagingException::getFailedMessage)
.get();
}
}
@MessagingGateway
private interface ErrorRecovererFlowGateway {
String testIt(String payload);
}
@Service
public static class GreetingService extends AbstractReplyProducingMessageHandler {
@Autowired
private WorldService worldService;
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return "Hello " + this.worldService.world() + " and " + requestMessage.getPayload();
}
}
@Service
public static class WorldService {
public String world() {
return "World";
}
}
private static class InvalidLastMessageChannelFlowContext {
@Bean
public IntegrationFlow wrongLastComponent() {
return IntegrationFlows.from(MessageChannels.direct())
.fixedSubscriberChannel()
.get();
}
}
}