/*
* Copyright 2016 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.jms.dsl;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.channel.ChannelInterceptorAware;
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.config.GlobalChannelInterceptor;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlowDefinition;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.Pollers;
import org.springframework.integration.dsl.channel.MessageChannels;
import org.springframework.integration.endpoint.MethodInvokingMessageSource;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
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.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Artem Bilan
* @author Gary Russell
* @author Nasko Vasilev
*
* @since 5.0
*/
@RunWith(SpringRunner.class)
@DirtiesContext
public class JmsTests {
@Autowired
private ListableBeanFactory beanFactory;
@Autowired
private ControlBusGateway controlBus;
@Autowired
@Qualifier("flow1QueueChannel")
private PollableChannel outputChannel;
@Autowired
@Qualifier("jmsOutboundFlow.input")
private MessageChannel jmsOutboundInboundChannel;
@Autowired
@Qualifier("jmsOutboundInboundReplyChannel")
private PollableChannel jmsOutboundInboundReplyChannel;
@Autowired
@Qualifier("jmsOutboundGatewayFlow.input")
private MessageChannel jmsOutboundGatewayChannel;
@Autowired
private TestChannelInterceptor testChannelInterceptor;
@Autowired
private ConnectionFactory jmsConnectionFactory;
@Autowired
private PollableChannel jmsPubSubBridgeChannel;
@Autowired
@Qualifier("jmsOutboundGateway.handler")
private MessageHandler jmsOutboundGatewayHandler;
@Autowired
private AtomicBoolean jmsMessageDrivenChannelCalled;
@Autowired
private AtomicBoolean jmsInboundGatewayChannelCalled;
@Test
public void testPollingFlow() {
this.controlBus.send("@'jmsTests.ContextConfiguration.integerMessageSource.inboundChannelAdapter'.start()");
assertThat(this.beanFactory.getBean("integerChannel"), instanceOf(FixedSubscriberChannel.class));
for (int i = 0; i < 5; i++) {
Message<?> message = this.outputChannel.receive(20000);
assertNotNull(message);
assertEquals("" + i, message.getPayload());
}
this.controlBus.send("@'jmsTests.ContextConfiguration.integerMessageSource.inboundChannelAdapter'.stop()");
assertTrue(((ChannelInterceptorAware) this.outputChannel).getChannelInterceptors()
.contains(this.testChannelInterceptor));
assertThat(this.testChannelInterceptor.invoked.get(), Matchers.greaterThanOrEqualTo(5));
}
@Test
public void testJmsOutboundInboundFlow() {
this.jmsOutboundInboundChannel.send(MessageBuilder.withPayload("hello THROUGH the JMS")
.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "jmsInbound")
.build());
Message<?> receive = this.jmsOutboundInboundReplyChannel.receive(10000);
assertNotNull(receive);
assertEquals("HELLO THROUGH THE JMS", receive.getPayload());
this.jmsOutboundInboundChannel.send(MessageBuilder.withPayload("hello THROUGH the JMS")
.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "jmsMessageDriven")
.build());
receive = this.jmsOutboundInboundReplyChannel.receive(10000);
assertNotNull(receive);
assertEquals("hello through the jms", receive.getPayload());
assertTrue(this.jmsMessageDrivenChannelCalled.get());
this.jmsOutboundInboundChannel.send(MessageBuilder.withPayload(" foo ")
.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "containerSpecDestination")
.build());
receive = this.jmsOutboundInboundReplyChannel.receive(10000);
assertNotNull(receive);
assertEquals("foo", receive.getPayload());
}
@Test
public void testJmsPipelineFlow() {
assertEquals(new Long(10000),
TestUtils.getPropertyValue(this.jmsOutboundGatewayHandler, "idleReplyContainerTimeout", Long.class));
PollableChannel replyChannel = new QueueChannel();
Message<String> message = MessageBuilder.withPayload("hello through the jms pipeline")
.setReplyChannel(replyChannel)
.setHeader("destination", "jmsPipelineTest")
.build();
this.jmsOutboundGatewayChannel.send(message);
Message<?> receive = replyChannel.receive(5000);
assertNotNull(receive);
assertEquals("HELLO THROUGH THE JMS PIPELINE", receive.getPayload());
assertTrue(this.jmsInboundGatewayChannelCalled.get());
}
@Test
public void testPubSubFlow() {
JmsTemplate template = new JmsTemplate(this.jmsConnectionFactory);
template.setPubSubDomain(true);
template.setDefaultDestinationName("pubsub");
template.convertAndSend("foo");
Message<?> received = this.jmsPubSubBridgeChannel.receive(5000);
assertNotNull(received);
assertEquals("foo", received.getPayload());
}
@Autowired
private CountDownLatch redeliveryLatch;
@Test
public void testJmsRedeliveryFlow() throws InterruptedException {
this.jmsOutboundInboundChannel.send(MessageBuilder.withPayload("foo")
.setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "jmsMessageDrivenRedelivery")
.build());
assertTrue(this.redeliveryLatch.await(10, TimeUnit.SECONDS));
}
@MessagingGateway(defaultRequestChannel = "controlBus.input")
private interface ControlBusGateway {
void send(String command);
}
@Configuration
@EnableIntegration
@IntegrationComponentScan
@ComponentScan
public static class ContextConfiguration {
@Bean
public ConnectionFactory cachingConnectionFactory() {
return new CachingConnectionFactory(jmsConnectionFactory());
}
@Bean
public ActiveMQConnectionFactory jmsConnectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(
"vm://localhost?broker.persistent=false");
activeMQConnectionFactory.setTrustAllPackages(true);
return activeMQConnectionFactory;
}
@Bean
public JmsTemplate jmsTemplate() {
return new JmsTemplate(jmsConnectionFactory());
}
@Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return Pollers.fixedRate(500).get();
}
@Bean
public IntegrationFlow controlBus() {
return IntegrationFlowDefinition::controlBus;
}
@Bean
@InboundChannelAdapter(value = "flow1.input", autoStartup = "false", poller = @Poller(fixedRate = "100"))
public MessageSource<?> integerMessageSource() {
MethodInvokingMessageSource source = new MethodInvokingMessageSource();
source.setObject(new AtomicInteger());
source.setMethodName("getAndIncrement");
return source;
}
@Bean
public IntegrationFlow flow1() {
return f -> f
.fixedSubscriberChannel("integerChannel")
.transform("payload.toString()")
.channel(Jms.pollableChannel("flow1QueueChannel", cachingConnectionFactory())
.destination("flow1QueueChannel"));
}
@Bean
public IntegrationFlow jmsOutboundFlow() {
return f -> f.handle(Jms.outboundAdapter(jmsConnectionFactory())
.destinationExpression("headers." + SimpMessageHeaderAccessor.DESTINATION_HEADER));
}
@Bean
public MessageChannel jmsOutboundInboundReplyChannel() {
return MessageChannels.queue().get();
}
@Bean
public IntegrationFlow jmsInboundFlow() {
return IntegrationFlows
.from(Jms.inboundAdapter(cachingConnectionFactory()).destination("jmsInbound"))
.<String, String>transform(String::toUpperCase)
.channel(this.jmsOutboundInboundReplyChannel())
.get();
}
@Bean
public IntegrationFlow pubSubFlow() {
return IntegrationFlows
.from(Jms.publishSubscribeChannel(jmsConnectionFactory())
.destination("pubsub"))
.channel(c -> c.queue("jmsPubSubBridgeChannel"))
.get();
}
@Bean
public IntegrationFlow jmsMessageDrivenFlow() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(jmsConnectionFactory())
.outputChannel(jmsMessageDrivenInputChannel())
.destination("jmsMessageDriven"))
.<String, String>transform(String::toLowerCase)
.channel(jmsOutboundInboundReplyChannel())
.get();
}
@Bean
public AtomicBoolean jmsMessageDrivenChannelCalled() {
return new AtomicBoolean();
}
@Bean
public MessageChannel jmsMessageDrivenInputChannel() {
DirectChannel directChannel = new DirectChannel();
directChannel.addInterceptor(new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
jmsMessageDrivenChannelCalled().set(true);
return super.preSend(message, channel);
}
});
return directChannel;
}
@Bean
public IntegrationFlow jmsMessageDrivenFlowWithContainer() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(
Jms.container(jmsConnectionFactory(), "containerSpecDestination")
.pubSubDomain(false)
.taskExecutor(Executors.newCachedThreadPool())
.get()))
.transform(String::trim)
.channel(jmsOutboundInboundReplyChannel())
.get();
}
@Bean
public IntegrationFlow jmsOutboundGatewayFlow() {
return f -> f.handle(Jms.outboundGateway(jmsConnectionFactory())
.replyContainer(c -> c.idleReplyContainerTimeout(10))
.requestDestination("jmsPipelineTest"),
e -> e.id("jmsOutboundGateway"));
}
@Bean
public IntegrationFlow jmsInboundGatewayFlow() {
return IntegrationFlows.from(Jms.inboundGateway(jmsConnectionFactory())
.requestChannel(jmsInboundGatewayInputChannel())
.destination("jmsPipelineTest"))
.<String, String>transform(String::toUpperCase)
.get();
}
@Bean
public AtomicBoolean jmsInboundGatewayChannelCalled() {
return new AtomicBoolean();
}
@Bean
public MessageChannel jmsInboundGatewayInputChannel() {
DirectChannel directChannel = new DirectChannel();
directChannel.addInterceptor(new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
jmsInboundGatewayChannelCalled().set(true);
return super.preSend(message, channel);
}
});
return directChannel;
}
@Bean
public IntegrationFlow jmsMessageDrivenRedeliveryFlow() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(jmsConnectionFactory())
.errorChannel(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)
.destination("jmsMessageDrivenRedelivery"))
.<String, String>transform(p -> {
throw new RuntimeException("intentional");
})
.get();
}
@Bean
public CountDownLatch redeliveryLatch() {
return new CountDownLatch(3);
}
@Bean
public IntegrationFlow errorHandlingFlow() {
return IntegrationFlows.from(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)
.handle(m -> {
MessagingException exception = (MessagingException) m.getPayload();
redeliveryLatch().countDown();
throw exception;
})
.get();
}
}
@Component
@GlobalChannelInterceptor(patterns = "flow1QueueChannel")
public static class TestChannelInterceptor extends ChannelInterceptorAdapter {
private final AtomicInteger invoked = new AtomicInteger();
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
this.invoked.incrementAndGet();
return message;
}
}
}