/*
* Copyright 2002-2014 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.amqp.rabbit.listener;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.amqp.core.AnonymousQueue;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.rabbit.test.BrokerRunning;
import org.springframework.amqp.rabbit.test.BrokerTestUtils;
import org.springframework.amqp.rabbit.test.LongRunningIntegrationTest;
import org.springframework.amqp.utils.test.TestUtils;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.support.GenericApplicationContext;
/**
* @author Dave Syer
* @author Gunnar Hillert
* @author Gary Russell
* @since 1.3
*
*/
public class SimpleMessageListenerContainerIntegration2Tests {
private static Log logger = LogFactory.getLog(SimpleMessageListenerContainerIntegration2Tests.class);
private final Queue queue = new Queue("test.queue");
private final Queue queue1 = new Queue("test.queue.1");
private final RabbitTemplate template = new RabbitTemplate();
private RabbitAdmin admin;
@Rule
public BrokerRunning brokerIsRunning = BrokerRunning.isRunning();
@Rule
public LongRunningIntegrationTest longRunningIntegrationTest = new LongRunningIntegrationTest();
private SimpleMessageListenerContainer container;
@Before
public void declareQueues() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(BrokerTestUtils.getPort());
template.setConnectionFactory(connectionFactory);
admin = new RabbitAdmin(connectionFactory);
admin.deleteQueue(queue.getName());
admin.declareQueue(queue);
admin.deleteQueue(queue1.getName());
admin.declareQueue(queue1);
}
@After
public void clear() throws Exception {
// Wait for broker communication to finish before trying to stop container
Thread.sleep(300L);
logger.debug("Shutting down at end of test");
if (container != null) {
container.shutdown();
}
((DisposableBean) template.getConnectionFactory()).destroy();
}
@Test
public void testChangeQueues() throws Exception {
CountDownLatch latch = new CountDownLatch(30);
container = createContainer(new MessageListenerAdapter(new PojoListener(latch)), queue.getName(), queue1.getName());
for (int i = 0; i < 10; i++) {
template.convertAndSend(queue.getName(), i + "foo");
template.convertAndSend(queue1.getName(), i + "foo");
}
container.addQueueNames(queue1.getName());
Thread.sleep(1100); // allow current consumer to time out and terminate
for (int i = 0; i < 10; i++) {
template.convertAndSend(queue.getName(), i + "foo");
}
boolean waited = latch.await(10, TimeUnit.SECONDS);
assertTrue("Timed out waiting for message", waited);
assertNull(template.receiveAndConvert(queue.getName()));
assertNull(template.receiveAndConvert(queue1.getName()));
}
@Test
public void testDeleteOneQueue() throws Exception {
CountDownLatch latch = new CountDownLatch(20);
container = createContainer(new MessageListenerAdapter(new PojoListener(latch)), queue.getName(), queue1.getName());
for (int i = 0; i < 10; i++) {
template.convertAndSend(queue.getName(), i + "foo");
template.convertAndSend(queue1.getName(), i + "foo");
}
boolean waited = latch.await(10, TimeUnit.SECONDS);
assertTrue("Timed out waiting for message", waited);
BlockingQueueConsumer consumer = (BlockingQueueConsumer) TestUtils
.getPropertyValue(container, "consumers", Map.class).keySet().iterator().next();
admin.deleteQueue(queue1.getName());
latch = new CountDownLatch(10);
container.setMessageListener(new MessageListenerAdapter(new PojoListener(latch)));
for (int i = 0; i < 10; i++) {
template.convertAndSend(queue.getName(), i + "foo");
}
waited = latch.await(10, TimeUnit.SECONDS);
assertTrue("Timed out waiting for message", waited);
BlockingQueueConsumer newConsumer = (BlockingQueueConsumer) TestUtils
.getPropertyValue(container, "consumers", Map.class).keySet().iterator().next();
int n = 0;
while (n++ < 100 && newConsumer == consumer) {
Thread.sleep(100);
newConsumer = (BlockingQueueConsumer) TestUtils
.getPropertyValue(container, "consumers", Map.class).keySet().iterator().next();
}
assertTrue("Failed to restart consumer", n < 100);
Set<?> missingQueues = TestUtils.getPropertyValue(newConsumer, "missingQueues", Set.class);
n = 0;
while (n++ < 100 && missingQueues.size() == 0) {
Thread.sleep(200);
}
assertTrue("Failed to detect missing queue", n < 100);
DirectFieldAccessor dfa = new DirectFieldAccessor(newConsumer);
dfa.setPropertyValue("lastRetryDeclaration", 0);
dfa.setPropertyValue("retryDeclarationInterval", 100);
admin.declareQueue(queue1);
n = 0;
while (n++ < 100 && missingQueues.size() > 0) {
Thread.sleep(100);
}
assertTrue("Failed to redeclare missing queue", n < 100);
latch = new CountDownLatch(20);
container.setMessageListener(new MessageListenerAdapter(new PojoListener(latch)));
for (int i = 0; i < 10; i++) {
template.convertAndSend(queue.getName(), i + "foo");
template.convertAndSend(queue1.getName(), i + "foo");
}
waited = latch.await(10, TimeUnit.SECONDS);
assertTrue("Timed out waiting for message", waited);
assertNull(template.receiveAndConvert(queue.getName()));
}
@Test
public void testListenFromAnonQueue() throws Exception {
AnonymousQueue queue = new AnonymousQueue();
CountDownLatch latch = new CountDownLatch(10);
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(template.getConnectionFactory());
container.setMessageListener(new MessageListenerAdapter(new PojoListener(latch)));
container.setQueueNames(queue.getName());
container.setConcurrentConsumers(2);
GenericApplicationContext context = new GenericApplicationContext();
context.getBeanFactory().registerSingleton("foo", queue);
context.refresh();
container.setApplicationContext(context);
container.afterPropertiesSet();
container.start();
for (int i = 0; i < 10; i++) {
template.convertAndSend(queue.getName(), i + "foo");
}
assertTrue(latch.await(10, TimeUnit.SECONDS));
container.stop();
container.start();
latch = new CountDownLatch(10);
container.setMessageListener(new MessageListenerAdapter(new PojoListener(latch)));
for (int i = 0; i < 10; i++) {
template.convertAndSend(queue.getName(), i + "foo");
}
assertTrue(latch.await(10, TimeUnit.SECONDS));
container.stop();
}
@Test
public void testExclusive() throws Exception {
CountDownLatch latch1 = new CountDownLatch(1000);
SimpleMessageListenerContainer container1 = new SimpleMessageListenerContainer(template.getConnectionFactory());
container1.setMessageListener(new MessageListenerAdapter(new PojoListener(latch1)));
container1.setQueueNames(queue.getName());
GenericApplicationContext context = new GenericApplicationContext();
context.getBeanFactory().registerSingleton("foo", queue);
context.refresh();
container1.setApplicationContext(context);
container1.setExclusive(true);
container1.afterPropertiesSet();
container1.start();
int n = 0;
while (n++ < 100 && container1.getActiveConsumerCount() < 1) {
Thread.sleep(100);
}
assertTrue(n < 100);
CountDownLatch latch2 = new CountDownLatch(1000);
SimpleMessageListenerContainer container2 = new SimpleMessageListenerContainer(template.getConnectionFactory());
container2.setMessageListener(new MessageListenerAdapter(new PojoListener(latch2)));
container2.setQueueNames(queue.getName());
container2.setApplicationContext(context);
container2.setRecoveryInterval(500);
container2.setExclusive(true); // not really necessary, but likely people will make all consumers exlusive.
container2.afterPropertiesSet();
container2.start();
for (int i = 0; i < 1000; i++) {
template.convertAndSend(queue.getName(), i + "foo");
}
assertTrue(latch1.await(10, TimeUnit.SECONDS));
assertEquals(1000, latch2.getCount());
container1.stop();
// container 2 should recover and process the next batch of messages
for (int i = 0; i < 1000; i++) {
template.convertAndSend(queue.getName(), i + "foo");
}
assertTrue(latch2.await(10, TimeUnit.SECONDS));
container2.stop();
}
@Test
public void testInvalidListener() throws Exception {
PojoListener delegate = new PojoListener(null);
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(delegate);
messageListenerAdapter.setDefaultListenerMethod("foo");
this.container = createContainer(messageListenerAdapter, queue.getName());
assertTrue(containerStoppedForAbortWithBadListener());
}
@Test
public void testMissingListener() throws Exception {
this.container = createContainer(null, queue.getName());
assertTrue(containerStoppedForAbortWithBadListener());
}
private boolean containerStoppedForAbortWithBadListener() throws InterruptedException {
Log logger = spy(TestUtils.getPropertyValue(container, "logger", Log.class));
new DirectFieldAccessor(container).setPropertyValue("logger", logger);
this.template.convertAndSend(queue.getName(), "foo");
int n = 0;
while (n++ < 100 && this.container.isRunning()) {
Thread.sleep(100);
}
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(logger).error(captor.capture());
assertThat(captor.getValue(), containsString("Stopping container from aborted consumer"));
return !this.container.isRunning();
}
private SimpleMessageListenerContainer createContainer(Object listener, String... queueNames) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(template.getConnectionFactory());
if (listener != null) {
container.setMessageListener(listener);
}
container.setQueueNames(queueNames);
container.afterPropertiesSet();
container.start();
return container;
}
public static class PojoListener {
private final AtomicInteger count = new AtomicInteger();
private final CountDownLatch latch;
private final boolean fail;
public PojoListener(CountDownLatch latch) {
this(latch, false);
}
public PojoListener(CountDownLatch latch, boolean fail) {
this.latch = latch;
this.fail = fail;
}
public void handleMessage(String value) {
try {
int counter = count.getAndIncrement();
if (logger.isDebugEnabled() && counter % 100 == 0) {
logger.debug("Handling: " + value + ":" + counter + " - " + latch);
}
if (fail) {
throw new RuntimeException("Planned failure");
}
} finally {
latch.countDown();
}
}
}
}