/*
* Copyright 2002-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.jms;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.jms.Destination;
import javax.jms.MessageListener;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.commons.logging.Log;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.integration.jms.config.ActiveMqTestUtils;
import org.springframework.integration.jms.config.JmsChannelFactoryBean;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.listener.AbstractMessageListenerContainer;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.GenericMessage;
/**
* @author Mark Fisher
* @author Gary Russell
* @author Gunnar Hillert
* @author Artem Bilan
* @since 2.0
*/
public class SubscribableJmsChannelTests {
private static final int TIMEOUT = 30000;
private CachingConnectionFactory connectionFactory;
private Destination topic;
private Destination queue;
@Before
public void setup() {
ActiveMqTestUtils.prepare();
ActiveMQConnectionFactory targetConnectionFactory = new ActiveMQConnectionFactory();
targetConnectionFactory.setBrokerURL("vm://localhost?broker.persistent=false");
targetConnectionFactory.setTrustAllPackages(true);
this.connectionFactory = new CachingConnectionFactory(targetConnectionFactory);
this.topic = new ActiveMQTopic("testTopic");
this.queue = new ActiveMQQueue("testQueue");
}
@After
public void tearDown() throws Exception {
this.connectionFactory.resetConnection();
}
@Test
public void queueReference() throws Exception {
final CountDownLatch latch = new CountDownLatch(2);
final List<Message<?>> receivedList1 = Collections.synchronizedList(new ArrayList<Message<?>>());
MessageHandler handler1 = message -> {
receivedList1.add(message);
latch.countDown();
};
final List<Message<?>> receivedList2 = Collections.synchronizedList(new ArrayList<Message<?>>());
MessageHandler handler2 = message -> {
receivedList2.add(message);
latch.countDown();
};
JmsChannelFactoryBean factoryBean = new JmsChannelFactoryBean(true);
factoryBean.setConnectionFactory(this.connectionFactory);
factoryBean.setDestination(this.queue);
factoryBean.setBeanFactory(mock(BeanFactory.class));
factoryBean.afterPropertiesSet();
SubscribableJmsChannel channel = (SubscribableJmsChannel) factoryBean.getObject();
channel.afterPropertiesSet();
channel.start();
channel.subscribe(handler1);
channel.subscribe(handler2);
channel.send(new GenericMessage<String>("foo"));
channel.send(new GenericMessage<String>("bar"));
latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
assertEquals(1, receivedList1.size());
assertNotNull(receivedList1.get(0));
assertEquals("foo", receivedList1.get(0).getPayload());
assertEquals(1, receivedList2.size());
assertNotNull(receivedList2.get(0));
assertEquals("bar", receivedList2.get(0).getPayload());
channel.stop();
}
@Test
public void topicReference() throws Exception {
final CountDownLatch latch = new CountDownLatch(4);
final List<Message<?>> receivedList1 = Collections.synchronizedList(new ArrayList<Message<?>>());
MessageHandler handler1 = message -> {
receivedList1.add(message);
latch.countDown();
};
final List<Message<?>> receivedList2 = Collections.synchronizedList(new ArrayList<Message<?>>());
MessageHandler handler2 = message -> {
receivedList2.add(message);
latch.countDown();
};
JmsChannelFactoryBean factoryBean = new JmsChannelFactoryBean(true);
factoryBean.setConnectionFactory(this.connectionFactory);
factoryBean.setDestination(this.topic);
factoryBean.setBeanFactory(mock(BeanFactory.class));
factoryBean.afterPropertiesSet();
SubscribableJmsChannel channel = (SubscribableJmsChannel) factoryBean.getObject();
channel.afterPropertiesSet();
channel.subscribe(handler1);
channel.subscribe(handler2);
channel.start();
if (!waitUntilRegisteredWithDestination(channel, 10000)) {
fail("Listener failed to subscribe to topic");
}
channel.send(new GenericMessage<String>("foo"));
channel.send(new GenericMessage<String>("bar"));
latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
assertEquals(2, receivedList1.size());
assertEquals("foo", receivedList1.get(0).getPayload());
assertEquals("bar", receivedList1.get(1).getPayload());
assertEquals(2, receivedList2.size());
assertEquals("foo", receivedList2.get(0).getPayload());
assertEquals("bar", receivedList2.get(1).getPayload());
channel.stop();
}
@Test
public void queueName() throws Exception {
final CountDownLatch latch = new CountDownLatch(2);
final List<Message<?>> receivedList1 = Collections.synchronizedList(new ArrayList<Message<?>>());
MessageHandler handler1 = message -> {
receivedList1.add(message);
latch.countDown();
};
final List<Message<?>> receivedList2 = Collections.synchronizedList(new ArrayList<Message<?>>());
MessageHandler handler2 = message -> {
receivedList2.add(message);
latch.countDown();
};
JmsChannelFactoryBean factoryBean = new JmsChannelFactoryBean(true);
factoryBean.setConnectionFactory(this.connectionFactory);
factoryBean.setDestinationName("dynamicQueue");
factoryBean.setPubSubDomain(false);
factoryBean.setBeanFactory(mock(BeanFactory.class));
factoryBean.afterPropertiesSet();
SubscribableJmsChannel channel = (SubscribableJmsChannel) factoryBean.getObject();
channel.afterPropertiesSet();
channel.start();
channel.subscribe(handler1);
channel.subscribe(handler2);
channel.send(new GenericMessage<String>("foo"));
channel.send(new GenericMessage<String>("bar"));
assertTrue("Countdown latch should have counted down to 0 but was "
+ latch.getCount(), latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
assertEquals(1, receivedList1.size());
assertNotNull(receivedList1.get(0));
assertEquals("foo", receivedList1.get(0).getPayload());
assertEquals(1, receivedList2.size());
assertNotNull(receivedList2.get(0));
assertEquals("bar", receivedList2.get(0).getPayload());
channel.stop();
}
@Test
public void topicName() throws Exception {
final CountDownLatch latch = new CountDownLatch(4);
final List<Message<?>> receivedList1 = Collections.synchronizedList(new ArrayList<Message<?>>());
MessageHandler handler1 = message -> {
receivedList1.add(message);
latch.countDown();
};
final List<Message<?>> receivedList2 = Collections.synchronizedList(new ArrayList<Message<?>>());
MessageHandler handler2 = message -> {
receivedList2.add(message);
latch.countDown();
};
JmsChannelFactoryBean factoryBean = new JmsChannelFactoryBean(true);
factoryBean.setConnectionFactory(this.connectionFactory);
factoryBean.setDestinationName("dynamicTopic");
factoryBean.setPubSubDomain(true);
factoryBean.setBeanFactory(mock(BeanFactory.class));
factoryBean.afterPropertiesSet();
SubscribableJmsChannel channel = (SubscribableJmsChannel) factoryBean.getObject();
channel.afterPropertiesSet();
channel.start();
if (!waitUntilRegisteredWithDestination(channel, 10000)) {
fail("Listener failed to subscribe to topic");
}
channel.subscribe(handler1);
channel.subscribe(handler2);
channel.send(new GenericMessage<String>("foo"));
channel.send(new GenericMessage<String>("bar"));
latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
assertEquals(2, receivedList1.size());
assertEquals("foo", receivedList1.get(0).getPayload());
assertEquals("bar", receivedList1.get(1).getPayload());
assertEquals(2, receivedList2.size());
assertEquals("foo", receivedList2.get(0).getPayload());
assertEquals("bar", receivedList2.get(1).getPayload());
channel.stop();
}
@Test
public void contextManagesLifecycle() {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(JmsChannelFactoryBean.class);
builder.addConstructorArgValue(true);
builder.addPropertyValue("connectionFactory", this.connectionFactory);
builder.addPropertyValue("destinationName", "dynamicQueue");
builder.addPropertyValue("pubSubDomain", false);
StaticApplicationContext context = new StaticApplicationContext();
context.registerBeanDefinition("channel", builder.getBeanDefinition());
SubscribableJmsChannel channel = context.getBean("channel", SubscribableJmsChannel.class);
assertFalse(channel.isRunning());
context.refresh();
assertTrue(channel.isRunning());
context.stop();
assertFalse(channel.isRunning());
context.close();
}
@Test
public void dispatcherHasNoSubscribersQueue() throws Exception {
JmsChannelFactoryBean factoryBean = new JmsChannelFactoryBean(true);
factoryBean.setConnectionFactory(this.connectionFactory);
factoryBean.setDestinationName("noSubscribersQueue");
factoryBean.setBeanName("noSubscribersChannel");
factoryBean.setBeanFactory(mock(BeanFactory.class));
factoryBean.afterPropertiesSet();
SubscribableJmsChannel channel = (SubscribableJmsChannel) factoryBean.getObject();
channel.afterPropertiesSet();
AbstractMessageListenerContainer container = TestUtils
.getPropertyValue(channel, "container",
AbstractMessageListenerContainer.class);
MessageListener listener = (MessageListener) container.getMessageListener();
try {
listener.onMessage(new StubTextMessage("Hello, world!"));
fail("Exception expected");
}
catch (MessageDeliveryException e) {
assertThat(e.getMessage(),
containsString("Dispatcher has no subscribers for jms-channel 'noSubscribersChannel'."));
}
}
@Test
public void dispatcherHasNoSubscribersTopic() throws Exception {
JmsChannelFactoryBean factoryBean = new JmsChannelFactoryBean(true);
factoryBean.setConnectionFactory(this.connectionFactory);
factoryBean.setDestinationName("noSubscribersTopic");
factoryBean.setBeanName("noSubscribersChannel");
factoryBean.setPubSubDomain(true);
factoryBean.setBeanFactory(mock(BeanFactory.class));
factoryBean.afterPropertiesSet();
SubscribableJmsChannel channel = (SubscribableJmsChannel) factoryBean.getObject();
channel.afterPropertiesSet();
AbstractMessageListenerContainer container = TestUtils
.getPropertyValue(channel, "container",
AbstractMessageListenerContainer.class);
MessageListener listener = (MessageListener) container.getMessageListener();
List<String> logList = insertMockLoggerInListener(channel);
listener.onMessage(new StubTextMessage("Hello, world!"));
verifyLogReceived(logList);
}
private List<String> insertMockLoggerInListener(
SubscribableJmsChannel channel) {
AbstractMessageListenerContainer container = TestUtils.getPropertyValue(
channel, "container", AbstractMessageListenerContainer.class);
Log logger = mock(Log.class);
final ArrayList<String> logList = new ArrayList<String>();
doAnswer(invocation -> {
String message = invocation.getArgument(0);
if (message.startsWith("Dispatcher has no subscribers")) {
logList.add(message);
}
return null;
}).when(logger).warn(anyString(), any(Exception.class));
when(logger.isWarnEnabled()).thenReturn(true);
Object listener = container.getMessageListener();
DirectFieldAccessor dfa = new DirectFieldAccessor(listener);
dfa.setPropertyValue("logger", logger);
return logList;
}
private void verifyLogReceived(final List<String> logList) {
assertTrue("Failed to get expected exception", logList.size() > 0);
boolean expectedExceptionFound = false;
while (logList.size() > 0) {
String message = logList.remove(0);
assertNotNull("Failed to get expected exception", message);
if (message.startsWith("Dispatcher has no subscribers")) {
expectedExceptionFound = true;
assertEquals("Dispatcher has no subscribers for jms-channel 'noSubscribersChannel'.", message);
break;
}
}
assertTrue("Failed to get expected exception", expectedExceptionFound);
}
/**
* Blocks until the listener container has subscribed; if the container does not support
* this test, or the caching mode is incompatible, true is returned. Otherwise blocks
* until timeout milliseconds have passed, or the consumer has registered.
* @see DefaultMessageListenerContainer#isRegisteredWithDestination()
* @param timeout Timeout in milliseconds.
* @return True if a subscriber has connected or the container/attributes does not support
* the test. False if a valid container does not have a registered consumer within
* timeout milliseconds.
*/
private static boolean waitUntilRegisteredWithDestination(SubscribableJmsChannel channel, long timeout) {
AbstractMessageListenerContainer container =
(AbstractMessageListenerContainer) new DirectFieldAccessor(channel).getPropertyValue("container");
if (container instanceof DefaultMessageListenerContainer) {
DefaultMessageListenerContainer listenerContainer =
(DefaultMessageListenerContainer) container;
if (listenerContainer.getCacheLevel() != DefaultMessageListenerContainer.CACHE_CONSUMER) {
return true;
}
while (timeout > 0) {
if (listenerContainer.isRegisteredWithDestination()) {
return true;
}
try {
Thread.sleep(100);
}
catch (InterruptedException e) { }
timeout -= 100;
}
return false;
}
return true;
}
}