/*
* 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.manualflow;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
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.Date;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlowAdapter;
import org.springframework.integration.dsl.IntegrationFlowDefinition;
import org.springframework.integration.dsl.channel.MessageChannels;
import org.springframework.integration.dsl.context.IntegrationFlowContext;
import org.springframework.integration.dsl.context.IntegrationFlowRegistration;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Artem Bilan
*
* @since 5.0
*/
@ContextConfiguration(classes = ManualFlowTests.RootConfiguration.class)
@RunWith(SpringRunner.class)
@DirtiesContext
public class ManualFlowTests {
@Autowired
private IntegrationFlowContext integrationFlowContext;
@Autowired
private BeanFactory beanFactory;
@Test
public void testManualFlowRegistration() throws InterruptedException {
IntegrationFlow myFlow = f -> f
.<String, String>transform(String::toUpperCase)
.channel(MessageChannels.queue())
.transform("Hello, "::concat, e -> e
.poller(p -> p
.fixedDelay(10)
.maxMessagesPerPoll(1)
.receiveTimeout(10)))
.handle(new BeanFactoryHandler());
BeanFactoryHandler additionalBean = new BeanFactoryHandler();
IntegrationFlowRegistration flowRegistration =
this.integrationFlowContext.registration(myFlow)
.addBean(additionalBean)
.register();
BeanFactoryHandler bean =
this.beanFactory.getBean(flowRegistration.getId() + BeanFactoryHandler.class.getName() + "#0",
BeanFactoryHandler.class);
assertSame(additionalBean, bean);
assertSame(this.beanFactory, bean.beanFactory);
MessagingTemplate messagingTemplate = flowRegistration.getMessagingTemplate();
messagingTemplate.setReceiveTimeout(10000);
assertEquals("Hello, FOO", messagingTemplate.convertSendAndReceive("foo", String.class));
assertEquals("Hello, BAR", messagingTemplate.convertSendAndReceive("bar", String.class));
try {
messagingTemplate.receive();
fail("UnsupportedOperationException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(UnsupportedOperationException.class));
assertThat(e.getMessage(), containsString("The 'receive()/receiveAndConvert()' isn't supported"));
}
flowRegistration.destroy();
assertFalse(this.beanFactory.containsBean(flowRegistration.getId()));
assertFalse(this.beanFactory.containsBean(flowRegistration.getId() + ".input"));
assertFalse(this.beanFactory.containsBean(flowRegistration.getId() + BeanFactoryHandler.class.getName() + "#0"));
ThreadPoolTaskScheduler taskScheduler = this.beanFactory.getBean(ThreadPoolTaskScheduler.class);
Thread.sleep(100);
assertEquals(0, taskScheduler.getActiveCount());
assertTrue(additionalBean.destroyed);
}
@Test
public void testWrongLifecycle() {
class MyIntegrationFlow implements IntegrationFlow {
@Override
public void configure(IntegrationFlowDefinition<?> flow) {
flow.bridge();
}
}
IntegrationFlow testFlow = new MyIntegrationFlow();
// This is fine because we are not going to start it automatically.
assertNotNull(this.integrationFlowContext.registration(testFlow)
.autoStartup(false)
.register());
try {
this.integrationFlowContext.registration(testFlow).register();
fail("IllegalStateException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(IllegalStateException.class));
assertThat(e.getMessage(), containsString("Consider to implement it for [" + testFlow + "]."));
}
try {
this.integrationFlowContext.remove("foo");
fail("IllegalStateException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(IllegalStateException.class));
assertThat(e.getMessage(), containsString("But [" + "foo" + "] ins't one of them."));
}
}
@Test
public void testDynamicSubFlow() {
PollableChannel resultChannel = new QueueChannel();
this.integrationFlowContext.registration(flow ->
flow.publishSubscribeChannel(p -> p
.minSubscribers(1)
.subscribe(f -> f.channel(resultChannel))
))
.id("dynamicFlow")
.register();
this.integrationFlowContext.messagingTemplateFor("dynamicFlow").send(new GenericMessage<>("test"));
Message<?> receive = resultChannel.receive(1000);
assertNotNull(receive);
assertEquals("test", receive.getPayload());
}
@Test
public void testDynamicAdapterFlow() {
this.integrationFlowContext.registration(new MyFlowAdapter()).register();
PollableChannel resultChannel = this.beanFactory.getBean("flowAdapterOutput", PollableChannel.class);
Message<?> receive = resultChannel.receive(1000);
assertNotNull(receive);
assertEquals("flowAdapterMessage", receive.getPayload());
}
@Test
public void testWrongIntegrationFlowScope() {
try {
new AnnotationConfigApplicationContext(InvalidIntegrationFlowScopeConfiguration.class).close();
fail("BeanCreationNotAllowedException expected");
}
catch (Exception e) {
assertThat(e, instanceOf(BeanCreationNotAllowedException.class));
assertThat(e.getMessage(), containsString("IntegrationFlows can not be scoped beans."));
}
}
@Test
public void testMessageProducerForOutputChannel() {
class MessageProducingHandler implements MessageHandler, MessageProducer {
private MessageChannel outputChannel;
@Override
public void setOutputChannel(MessageChannel outputChannel) {
this.outputChannel = outputChannel;
}
@Override
public MessageChannel getOutputChannel() {
return this.outputChannel;
}
@Override
public void handleMessage(Message<?> message) throws MessagingException {
this.outputChannel.send(message);
}
}
PollableChannel resultChannel = new QueueChannel();
IntegrationFlowRegistration flowRegistration =
this.integrationFlowContext.registration(flow ->
flow.handle(new MessageProducingHandler())
.channel(resultChannel))
.register();
this.integrationFlowContext.messagingTemplateFor(flowRegistration.getId())
.send(new GenericMessage<>("test"));
Message<?> receive = resultChannel.receive(1000);
assertNotNull(receive);
assertEquals("test", receive.getPayload());
}
@Configuration
@EnableIntegration
public static class RootConfiguration {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Date foo() {
return new Date();
}
}
private static class MyFlowAdapter extends IntegrationFlowAdapter {
private final AtomicReference<Date> nextExecutionTime = new AtomicReference<>(new Date());
@Override
protected IntegrationFlowDefinition<?> buildFlow() {
return from(() -> new GenericMessage<>("flowAdapterMessage"),
e -> e.poller(p -> p
.trigger(ctx -> this.nextExecutionTime.getAndSet(null))))
.channel(MessageChannels.queue("flowAdapterOutput"));
}
}
@Configuration
@EnableIntegration
public static class InvalidIntegrationFlowScopeConfiguration {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public IntegrationFlow wrongScopeFlow() {
return IntegrationFlowDefinition::bridge;
}
}
private final class BeanFactoryHandler extends AbstractReplyProducingMessageHandler
implements DisposableBean {
@Autowired
private BeanFactory beanFactory;
private boolean destroyed;
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
Objects.requireNonNull(this.beanFactory);
return requestMessage;
}
@Override
protected void doInit() {
this.beanFactory.getClass(); // ensure wiring before afterPropertiesSet()
}
@Override
public void destroy() throws Exception {
this.destroyed = true;
}
}
}