org.springframework.amqp.rabbit.retry.MissingIdRetryTests.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.amqp.rabbit.retry.MissingIdRetryTests.java

Source

/*
 * Copyright 2002-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.amqp.rabbit.retry;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.aopalliance.aop.Advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.Level;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.config.StatefulRetryOperationsInterceptorFactoryBean;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.BlockingQueueConsumer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.rabbit.test.BrokerRunning;
import org.springframework.amqp.rabbit.test.Log4jLevelAdjuster;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.retry.RetryContext;
import org.springframework.retry.policy.MapRetryContextCache;
import org.springframework.retry.policy.RetryContextCache;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

/**
 * @author Gary Russell
 * @since 1.1.2
 *
 */
public class MissingIdRetryTests {

    private final Log logger = LogFactory.getLog(MissingIdRetryTests.class);

    private volatile CountDownLatch latch;

    @ClassRule
    public static BrokerRunning brokerIsRunning = BrokerRunning.isRunning();

    @Rule
    public Log4jLevelAdjuster adjuster = new Log4jLevelAdjuster(Level.DEBUG, BlockingQueueConsumer.class,
            MissingIdRetryTests.class, RetryTemplate.class, SimpleRetryPolicy.class);

    @BeforeClass
    @AfterClass
    public static void setupAndCleanUp() {
        RabbitAdmin admin = brokerIsRunning.getAdmin();
        admin.deleteQueue("retry.test.queue");
        admin.deleteExchange("retry.test.exchange");
    }

    @SuppressWarnings("rawtypes")
    @Test
    public void testWithNoId() throws Exception {
        // 2 messages; each retried once by missing id interceptor
        this.latch = new CountDownLatch(4);
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("retry-context.xml",
                this.getClass());
        RabbitTemplate template = ctx.getBean(RabbitTemplate.class);
        ConnectionFactory connectionFactory = ctx.getBean(ConnectionFactory.class);
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        container.setStatefulRetryFatalWithNullMessageId(false);
        container.setMessageListener(new MessageListenerAdapter(new POJO()));
        container.setQueueNames("retry.test.queue");

        StatefulRetryOperationsInterceptorFactoryBean fb = new StatefulRetryOperationsInterceptorFactoryBean();

        // use an external template so we can share his cache
        RetryTemplate retryTemplate = new RetryTemplate();
        RetryContextCache cache = spy(new MapRetryContextCache());
        retryTemplate.setRetryContextCache(cache);
        fb.setRetryOperations(retryTemplate);

        Advice retryInterceptor = fb.getObject();
        container.setAdviceChain(retryInterceptor);
        container.start();

        template.convertAndSend("retry.test.exchange", "retry.test.binding", "Hello, world!");
        template.convertAndSend("retry.test.exchange", "retry.test.binding", "Hello, world!");
        try {
            assertTrue(latch.await(30, TimeUnit.SECONDS));
            Map map = (Map) new DirectFieldAccessor(cache).getPropertyValue("map");
            int n = 0;
            while (n++ < 100 && map.size() != 0) {
                Thread.sleep(100);
            }
            verify(cache, never()).put(any(), any(RetryContext.class));
            verify(cache, never()).remove(any());
            assertEquals("Expected map.size() = 0, was: " + map.size(), 0, map.size());
        } finally {
            container.stop();
            ctx.close();
        }
    }

    @SuppressWarnings("rawtypes")
    @Test
    public void testWithId() throws Exception {
        // 2 messages; each retried twice by retry interceptor
        this.latch = new CountDownLatch(6);
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("retry-context.xml",
                this.getClass());
        RabbitTemplate template = ctx.getBean(RabbitTemplate.class);
        ConnectionFactory connectionFactory = ctx.getBean(ConnectionFactory.class);
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        container.setMessageListener(new MessageListenerAdapter(new POJO()));
        container.setQueueNames("retry.test.queue");

        StatefulRetryOperationsInterceptorFactoryBean fb = new StatefulRetryOperationsInterceptorFactoryBean();

        // use an external template so we can share his cache
        RetryTemplate retryTemplate = new RetryTemplate();
        RetryContextCache cache = spy(new MapRetryContextCache());
        retryTemplate.setRetryContextCache(cache);
        fb.setRetryOperations(retryTemplate);
        fb.setMessageRecoverer(new RejectAndDontRequeueRecoverer());

        Advice retryInterceptor = fb.getObject();
        container.setAdviceChain(retryInterceptor);
        container.start();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("text/plain");
        messageProperties.setMessageId("foo");
        Message message = new Message("Hello, world!".getBytes(), messageProperties);
        template.send("retry.test.exchange", "retry.test.binding", message);
        template.send("retry.test.exchange", "retry.test.binding", message);
        try {
            assertTrue(latch.await(30, TimeUnit.SECONDS));
            Map map = (Map) new DirectFieldAccessor(cache).getPropertyValue("map");
            int n = 0;
            while (n++ < 100 && map.size() != 0) {
                Thread.sleep(100);
            }
            ArgumentCaptor putCaptor = ArgumentCaptor.forClass(Object.class);
            ArgumentCaptor getCaptor = ArgumentCaptor.forClass(Object.class);
            ArgumentCaptor removeCaptor = ArgumentCaptor.forClass(Object.class);
            verify(cache, times(6)).put(putCaptor.capture(), any(RetryContext.class));
            verify(cache, times(6)).get(getCaptor.capture());
            verify(cache, atLeast(2)).remove(removeCaptor.capture());
            verify(cache, atMost(4)).remove(removeCaptor.capture());
            logger.debug("puts:" + putCaptor.getAllValues());
            logger.debug("gets:" + putCaptor.getAllValues());
            logger.debug("removes:" + removeCaptor.getAllValues());
            assertEquals("Expected map.size() = 0, was: " + map.size(), 0, map.size());
        } finally {
            container.stop();
            ctx.close();
        }
    }

    @SuppressWarnings("rawtypes")
    @Test
    public void testWithIdAndSuccess() throws Exception {
        // 2 messages; each retried twice by retry interceptor
        this.latch = new CountDownLatch(6);
        ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("retry-context.xml",
                this.getClass());
        RabbitTemplate template = ctx.getBean(RabbitTemplate.class);
        ConnectionFactory connectionFactory = ctx.getBean(ConnectionFactory.class);
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        final Set<String> processed = new HashSet<>();
        final CountDownLatch latch = new CountDownLatch(4);
        container.setMessageListener(m -> {
            latch.countDown();
            if (!processed.contains(m.getMessageProperties().getMessageId())) {
                processed.add(m.getMessageProperties().getMessageId());
                throw new RuntimeException("fail");
            }
        });
        container.setQueueNames("retry.test.queue");

        StatefulRetryOperationsInterceptorFactoryBean fb = new StatefulRetryOperationsInterceptorFactoryBean();

        // use an external template so we can share his cache
        RetryTemplate retryTemplate = new RetryTemplate();
        RetryContextCache cache = spy(new MapRetryContextCache());
        retryTemplate.setRetryContextCache(cache);
        fb.setRetryOperations(retryTemplate);
        fb.setMessageRecoverer(new RejectAndDontRequeueRecoverer());

        Advice retryInterceptor = fb.getObject();
        container.setAdviceChain(retryInterceptor);
        container.start();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("text/plain");
        messageProperties.setMessageId("foo");
        Message message = new Message("Hello, world!".getBytes(), messageProperties);
        template.send("retry.test.exchange", "retry.test.binding", message);
        messageProperties.setMessageId("bar");
        template.send("retry.test.exchange", "retry.test.binding", message);
        try {
            assertTrue(latch.await(30, TimeUnit.SECONDS));
            Map map = (Map) new DirectFieldAccessor(cache).getPropertyValue("map");
            int n = 0;
            while (n++ < 100 && map.size() != 0) {
                Thread.sleep(100);
            }
            ArgumentCaptor putCaptor = ArgumentCaptor.forClass(Object.class);
            ArgumentCaptor getCaptor = ArgumentCaptor.forClass(Object.class);
            ArgumentCaptor removeCaptor = ArgumentCaptor.forClass(Object.class);
            verify(cache, times(2)).put(putCaptor.capture(), any(RetryContext.class));
            verify(cache, times(2)).get(getCaptor.capture());
            verify(cache, atLeast(2)).remove(removeCaptor.capture());
            verify(cache, atMost(4)).remove(removeCaptor.capture());
            logger.debug("puts:" + putCaptor.getAllValues());
            logger.debug("gets:" + putCaptor.getAllValues());
            logger.debug("removes:" + removeCaptor.getAllValues());
            assertEquals("Expected map.size() = 0, was: " + map.size(), 0, map.size());
        } finally {
            container.stop();
            ctx.close();
        }
    }

    public class POJO {

        public void handleMessage(String foo) {
            latch.countDown();
            throw new RuntimeException("fail");
        }

    }

}