org.springframework.cloud.stream.binder.rabbit.RabbitBinderTests.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.cloud.stream.binder.rabbit.RabbitBinderTests.java

Source

/*
 * Copyright 2013-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.cloud.stream.binder.rabbit;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.Deflater;

import org.apache.commons.logging.Log;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.mockito.ArgumentCaptor;

import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.MessageDeliveryMode;
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.RabbitManagementTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.amqp.support.postprocessor.DelegatingDecompressingPostProcessor;
import org.springframework.amqp.utils.test.TestUtils;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.cloud.stream.binder.BinderHeaders;
import org.springframework.cloud.stream.binder.Binding;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
import org.springframework.cloud.stream.binder.PartitionCapableBinderTests;
import org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy;
import org.springframework.cloud.stream.binder.PartitionSelectorStrategy;
import org.springframework.cloud.stream.binder.PartitionTestSupport;
import org.springframework.cloud.stream.binder.Spy;
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitConsumerProperties;
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitProducerProperties;
import org.springframework.cloud.stream.binder.rabbit.provisioning.RabbitExchangeQueueProvisioner;
import org.springframework.cloud.stream.binder.test.junit.rabbit.RabbitTestSupport;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.Lifecycle;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.retry.support.RetryTemplate;

import com.rabbitmq.http.client.domain.QueueInfo;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * @author Mark Fisher
 * @author Gary Russell
 * @author David Turanski
 * @author Artem Bilan
 */
public class RabbitBinderTests extends
        PartitionCapableBinderTests<RabbitTestBinder, ExtendedConsumerProperties<RabbitConsumerProperties>, ExtendedProducerProperties<RabbitProducerProperties>> {

    private final String CLASS_UNDER_TEST_NAME = RabbitMessageChannelBinder.class.getSimpleName();

    public static final String TEST_PREFIX = "bindertest.";

    @Rule
    public RabbitTestSupport rabbitAvailableRule = new RabbitTestSupport(true);

    @Rule
    public TestName testName = new TestName();

    @Override
    protected RabbitTestBinder getBinder() {
        if (testBinder == null) {
            testBinder = new RabbitTestBinder(rabbitAvailableRule.getResource(), new RabbitProperties());
        }
        return testBinder;
    }

    @Override
    protected ExtendedConsumerProperties<RabbitConsumerProperties> createConsumerProperties() {
        return new ExtendedConsumerProperties<>(new RabbitConsumerProperties());
    }

    @Override
    protected ExtendedProducerProperties<RabbitProducerProperties> createProducerProperties() {
        ExtendedProducerProperties<RabbitProducerProperties> props = new ExtendedProducerProperties<>(
                new RabbitProducerProperties());
        if (testName.getMethodName().equals("testPartitionedModuleSpEL")) {
            props.getExtension().setRoutingKeyExpression("'part.0'");
        }
        return props;
    }

    @Override
    protected boolean usesExplicitRouting() {
        return true;
    }

    @Test
    public void testSendAndReceiveBad() throws Exception {
        RabbitTestBinder binder = getBinder();
        DirectChannel moduleOutputChannel = createBindableChannel("output", new BindingProperties());
        DirectChannel moduleInputChannel = createBindableChannel("input", new BindingProperties());
        Binding<MessageChannel> producerBinding = binder.bindProducer("bad.0", moduleOutputChannel,
                createProducerProperties());
        Binding<MessageChannel> consumerBinding = binder.bindConsumer("bad.0", "test", moduleInputChannel,
                createConsumerProperties());
        Message<?> message = MessageBuilder.withPayload("bad").setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar")
                .build();
        final CountDownLatch latch = new CountDownLatch(3);
        moduleInputChannel.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                latch.countDown();
                throw new RuntimeException("bad");
            }
        });
        moduleOutputChannel.send(message);
        assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
        producerBinding.unbind();
        consumerBinding.unbind();
    }

    @Test
    public void testConsumerProperties() throws Exception {
        RabbitTestBinder binder = getBinder();
        ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
        properties.getExtension().setRequeueRejected(true);
        properties.getExtension().setTransacted(true);
        properties.getExtension().setExclusive(true);
        Binding<MessageChannel> consumerBinding = binder.bindConsumer("props.0", null,
                createBindableChannel("input", new BindingProperties()), properties);
        Lifecycle endpoint = extractEndpoint(consumerBinding);
        SimpleMessageListenerContainer container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
                SimpleMessageListenerContainer.class);
        assertThat(container.getAcknowledgeMode()).isEqualTo(AcknowledgeMode.AUTO);
        assertThat(container.getQueueNames()[0]).startsWith(properties.getExtension().getPrefix());
        assertThat(TestUtils.getPropertyValue(container, "transactional", Boolean.class)).isTrue();
        assertThat(TestUtils.getPropertyValue(container, "exclusive", Boolean.class)).isTrue();
        assertThat(TestUtils.getPropertyValue(container, "concurrentConsumers")).isEqualTo(1);
        assertThat(TestUtils.getPropertyValue(container, "maxConcurrentConsumers")).isNull();
        assertThat(TestUtils.getPropertyValue(container, "defaultRequeueRejected", Boolean.class)).isTrue();
        assertThat(TestUtils.getPropertyValue(container, "prefetchCount")).isEqualTo(1);
        assertThat(TestUtils.getPropertyValue(container, "txSize")).isEqualTo(1);
        RetryTemplate retry = TestUtils.getPropertyValue(endpoint, "retryTemplate", RetryTemplate.class);
        assertThat(TestUtils.getPropertyValue(retry, "retryPolicy.maxAttempts")).isEqualTo(3);
        assertThat(TestUtils.getPropertyValue(retry, "backOffPolicy.initialInterval")).isEqualTo(1000L);
        assertThat(TestUtils.getPropertyValue(retry, "backOffPolicy.maxInterval")).isEqualTo(10000L);
        assertThat(TestUtils.getPropertyValue(retry, "backOffPolicy.multiplier")).isEqualTo(2.0);
        consumerBinding.unbind();
        assertThat(endpoint.isRunning()).isFalse();

        properties = createConsumerProperties();
        properties.getExtension().setAcknowledgeMode(AcknowledgeMode.NONE);
        properties.setBackOffInitialInterval(2000);
        properties.setBackOffMaxInterval(20000);
        properties.setBackOffMultiplier(5.0);
        properties.setConcurrency(2);
        properties.setMaxAttempts(23);
        properties.getExtension().setMaxConcurrency(3);
        properties.getExtension().setPrefix("foo.");
        properties.getExtension().setPrefetch(20);
        properties.getExtension().setHeaderPatterns(new String[] { "foo" });
        properties.getExtension().setTxSize(10);
        properties.setInstanceIndex(0);
        consumerBinding = binder.bindConsumer("props.0", "test",
                createBindableChannel("input", new BindingProperties()), properties);

        endpoint = extractEndpoint(consumerBinding);
        container = verifyContainer(endpoint);

        assertThat(container.getQueueNames()[0]).isEqualTo("foo.props.0.test");

        consumerBinding.unbind();
        assertThat(endpoint.isRunning()).isFalse();
    }

    @Test
    public void testConsumerPropertiesWithUserInfrastructureNoBind() throws Exception {
        RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
        Queue queue = new Queue("propsUser1.infra");
        admin.declareQueue(queue);
        DirectExchange exchange = new DirectExchange("propsUser1");
        admin.declareExchange(exchange);
        admin.declareBinding(BindingBuilder.bind(queue).to(exchange).with("foo"));

        RabbitTestBinder binder = getBinder();
        ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
        properties.getExtension().setDeclareExchange(false);
        properties.getExtension().setBindQueue(false);

        Binding<MessageChannel> consumerBinding = binder.bindConsumer("propsUser1", "infra",
                createBindableChannel("input", new BindingProperties()), properties);
        Lifecycle endpoint = extractEndpoint(consumerBinding);
        SimpleMessageListenerContainer container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
                SimpleMessageListenerContainer.class);
        assertThat(container.isRunning()).isTrue();
        consumerBinding.unbind();
        assertThat(container.isRunning()).isFalse();
        RabbitManagementTemplate rmt = new RabbitManagementTemplate();
        List<org.springframework.amqp.core.Binding> bindings = rmt.getBindingsForExchange("/", exchange.getName());
        assertThat(bindings.size()).isEqualTo(1);
    }

    @Test
    public void testConsumerPropertiesWithUserInfrastructureCustomExchangeAndRK() throws Exception {
        RabbitTestBinder binder = getBinder();
        ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
        properties.getExtension().setExchangeType(ExchangeTypes.DIRECT);
        properties.getExtension().setBindingRoutingKey("foo");
        //      properties.getExtension().setDelayedExchange(true); // requires delayed message exchange plugin; tested locally

        Binding<MessageChannel> consumerBinding = binder.bindConsumer("propsUser2", "infra",
                createBindableChannel("input", new BindingProperties()), properties);
        Lifecycle endpoint = extractEndpoint(consumerBinding);
        SimpleMessageListenerContainer container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
                SimpleMessageListenerContainer.class);
        assertThat(container.isRunning()).isTrue();
        consumerBinding.unbind();
        assertThat(container.isRunning()).isFalse();
        RabbitManagementTemplate rmt = new RabbitManagementTemplate();
        List<org.springframework.amqp.core.Binding> bindings = rmt.getBindingsForExchange("/", "propsUser2");
        int n = 0;
        while (n++ < 100 && bindings == null || bindings.size() < 1) {
            Thread.sleep(100);
            bindings = rmt.getBindingsForExchange("/", "propsUser2");
        }
        assertThat(bindings.size()).isEqualTo(1);
        assertThat(bindings.get(0).getExchange()).isEqualTo("propsUser2");
        assertThat(bindings.get(0).getDestination()).isEqualTo("propsUser2.infra");
        assertThat(bindings.get(0).getRoutingKey()).isEqualTo("foo");

        //      // TODO: AMQP-696
        //      // Exchange exchange = rmt.getExchange("propsUser2");
        //      ExchangeInfo ei = rmt.getClient().getExchange("/", "propsUser2"); // requires delayed message exchange plugin
        //      assertThat(ei.getType()).isEqualTo("x-delayed-message");
        //      assertThat(ei.getArguments().get("x-delayed-type")).isEqualTo("direct");

        Exchange exchange = rmt.getExchange("propsUser2");
        while (n++ < 100 && exchange == null) {
            Thread.sleep(100);
            exchange = rmt.getExchange("propsUser2");
        }
        assertThat(exchange).isInstanceOf(DirectExchange.class);
        assertThat(exchange.isDurable()).isEqualTo(true);
        assertThat(exchange.isAutoDelete()).isEqualTo(false);
    }

    @Test
    public void testConsumerPropertiesWithUserInfrastructureCustomQueueArgs() throws Exception {
        RabbitTestBinder binder = getBinder();
        ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
        RabbitConsumerProperties extProps = properties.getExtension();
        extProps.setExchangeType(ExchangeTypes.DIRECT);
        extProps.setExchangeDurable(false);
        extProps.setExchangeAutoDelete(true);
        extProps.setBindingRoutingKey("foo");
        extProps.setExpires(30_000);
        extProps.setLazy(true);
        extProps.setMaxLength(10_000);
        extProps.setMaxLengthBytes(100_000);
        extProps.setMaxPriority(10);
        extProps.setTtl(2_000);
        extProps.setAutoBindDlq(true);
        extProps.setDeadLetterQueueName("customDLQ");
        extProps.setDeadLetterExchange("customDLX");
        extProps.setDeadLetterRoutingKey("customDLRK");
        extProps.setDlqDeadLetterExchange("propsUser3");
        extProps.setDlqDeadLetterRoutingKey("propsUser3");
        extProps.setDlqExpires(60_000);
        extProps.setDlqLazy(true);
        extProps.setDlqMaxLength(20_000);
        extProps.setDlqMaxLengthBytes(40_000);
        extProps.setDlqMaxPriority(8);
        extProps.setDlqTtl(1_000);

        Binding<MessageChannel> consumerBinding = binder.bindConsumer("propsUser3", "infra",
                createBindableChannel("input", new BindingProperties()), properties);
        Lifecycle endpoint = extractEndpoint(consumerBinding);
        SimpleMessageListenerContainer container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
                SimpleMessageListenerContainer.class);
        assertThat(container.isRunning()).isTrue();
        consumerBinding.unbind();
        assertThat(container.isRunning()).isFalse();
        RabbitManagementTemplate rmt = new RabbitManagementTemplate();
        List<org.springframework.amqp.core.Binding> bindings = rmt.getBindingsForExchange("/", "propsUser3");
        int n = 0;
        while (n++ < 100 && bindings == null || bindings.size() < 1) {
            Thread.sleep(100);
            bindings = rmt.getBindingsForExchange("/", "propsUser3");
        }
        assertThat(bindings.size()).isEqualTo(1);
        assertThat(bindings.get(0).getExchange()).isEqualTo("propsUser3");
        assertThat(bindings.get(0).getDestination()).isEqualTo("propsUser3.infra");
        assertThat(bindings.get(0).getRoutingKey()).isEqualTo("foo");

        Exchange exchange = rmt.getExchange("propsUser3");
        n = 0;
        while (n++ < 100 && exchange == null) {
            Thread.sleep(100);
            exchange = rmt.getExchange("propsUser3");
        }
        assertThat(exchange).isInstanceOf(DirectExchange.class);
        assertThat(exchange.isDurable()).isEqualTo(false);
        assertThat(exchange.isAutoDelete()).isEqualTo(true);

        //      Queue queue = rmt.getQueue("propsUser3"); AMQP-698
        QueueInfo queue = rmt.getClient().getQueue("/", "propsUser3.infra");
        n = 0;
        while (n++ < 100 && queue == null) {
            Thread.sleep(100);
            queue = rmt.getClient().getQueue("/", "propsUser3.infra");
        }
        assertThat(queue).isNotNull();
        Map<String, Object> args = queue.getArguments();
        assertThat(args.get("x-expires")).isEqualTo(30_000);
        assertThat(args.get("x-max-length")).isEqualTo(10_000);
        assertThat(args.get("x-max-length-bytes")).isEqualTo(100_000);
        assertThat(args.get("x-max-priority")).isEqualTo(10);
        assertThat(args.get("x-message-ttl")).isEqualTo(2_000);
        assertThat(args.get("x-dead-letter-exchange")).isEqualTo("customDLX");
        assertThat(args.get("x-dead-letter-routing-key")).isEqualTo("customDLRK");
        assertThat(args.get("x-queue-mode")).isEqualTo("lazy");

        queue = rmt.getClient().getQueue("/", "customDLQ");

        n = 0;
        while (n++ < 100 && queue == null) {
            Thread.sleep(100);
            queue = rmt.getClient().getQueue("/", "customDLQ");
        }
        assertThat(queue).isNotNull();
        args = queue.getArguments();
        assertThat(args.get("x-expires")).isEqualTo(60_000);
        assertThat(args.get("x-max-length")).isEqualTo(20_000);
        assertThat(args.get("x-max-length-bytes")).isEqualTo(40_000);
        assertThat(args.get("x-max-priority")).isEqualTo(8);
        assertThat(args.get("x-message-ttl")).isEqualTo(1_000);
        assertThat(args.get("x-dead-letter-exchange")).isEqualTo("propsUser3");
        assertThat(args.get("x-dead-letter-routing-key")).isEqualTo("propsUser3");
        assertThat(args.get("x-queue-mode")).isEqualTo("lazy");
    }

    @Test
    public void testProducerProperties() throws Exception {
        RabbitTestBinder binder = getBinder();
        Binding<MessageChannel> producerBinding = binder.bindProducer("props.0",
                createBindableChannel("input", new BindingProperties()), createProducerProperties());
        Lifecycle endpoint = extractEndpoint(producerBinding);
        MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "defaultDeliveryMode",
                MessageDeliveryMode.class);
        assertThat(mode).isEqualTo(MessageDeliveryMode.PERSISTENT);
        List<?> requestHeaders = TestUtils.getPropertyValue(endpoint, "headerMapper.requestHeaderMatcher.matchers",
                List.class);
        assertThat(requestHeaders).hasSize(2);
        producerBinding.unbind();
        assertThat(endpoint.isRunning()).isFalse();
        assertThat(TestUtils.getPropertyValue(endpoint, "amqpTemplate.transactional", Boolean.class)).isFalse();

        ExtendedProducerProperties<RabbitProducerProperties> producerProperties = createProducerProperties();
        producerProperties.getExtension().setPrefix("foo.");
        producerProperties.getExtension().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
        producerProperties.getExtension().setHeaderPatterns(new String[] { "foo" });
        producerProperties.setPartitionKeyExpression(spelExpressionParser.parseExpression("'foo'"));
        producerProperties.setPartitionKeyExtractorClass(TestPartitionKeyExtractorClass.class);
        producerProperties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("0"));
        producerProperties.setPartitionSelectorClass(TestPartitionSelectorClass.class);
        producerProperties.setPartitionCount(1);
        producerProperties.getExtension().setTransacted(true);
        producerProperties.getExtension().setDelayExpression("42");
        producerProperties.setRequiredGroups("prodPropsRequired");

        BindingProperties producerBindingProperties = createProducerBindingProperties(producerProperties);
        DirectChannel channel = createBindableChannel("output", producerBindingProperties);
        producerBinding = binder.bindProducer("props.0", channel, producerProperties);
        endpoint = extractEndpoint(producerBinding);
        assertThat(getEndpointRouting(endpoint))
                .isEqualTo("'props.0-' + headers['" + BinderHeaders.PARTITION_HEADER + "']");
        assertThat(
                TestUtils.getPropertyValue(endpoint, "delayExpression", SpelExpression.class).getExpressionString())
                        .isEqualTo("42");
        mode = TestUtils.getPropertyValue(endpoint, "defaultDeliveryMode", MessageDeliveryMode.class);
        assertThat(mode).isEqualTo(MessageDeliveryMode.NON_PERSISTENT);
        assertThat(TestUtils.getPropertyValue(endpoint, "amqpTemplate.transactional", Boolean.class)).isTrue();
        verifyFooRequestProducer(endpoint);
        channel.send(new GenericMessage<>("foo"));
        org.springframework.amqp.core.Message received = new RabbitTemplate(this.rabbitAvailableRule.getResource())
                .receive("foo.props.0.prodPropsRequired-0", 10_000);
        assertThat(received).isNotNull();
        assertThat(received.getMessageProperties().getReceivedDelay()).isEqualTo(42);

        producerBinding.unbind();
        assertThat(endpoint.isRunning()).isFalse();
    }

    @Test
    public void testDurablePubSubWithAutoBindDLQ() throws Exception {
        RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());

        RabbitTestBinder binder = getBinder();

        ExtendedConsumerProperties<RabbitConsumerProperties> consumerProperties = createConsumerProperties();
        consumerProperties.getExtension().setPrefix(TEST_PREFIX);
        consumerProperties.getExtension().setAutoBindDlq(true);
        consumerProperties.getExtension().setDurableSubscription(true);
        consumerProperties.setMaxAttempts(1); // disable retry
        DirectChannel moduleInputChannel = createBindableChannel("input",
                createConsumerBindingProperties(consumerProperties));
        moduleInputChannel.setBeanName("durableTest");
        moduleInputChannel.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                throw new RuntimeException("foo");
            }

        });
        Binding<MessageChannel> consumerBinding = binder.bindConsumer("durabletest.0", "tgroup", moduleInputChannel,
                consumerProperties);

        RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
        template.convertAndSend(TEST_PREFIX + "durabletest.0", "", "foo");

        int n = 0;
        while (n++ < 100) {
            Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "durabletest.0.tgroup.dlq");
            if (deadLetter != null) {
                assertThat(deadLetter).isEqualTo("foo");
                break;
            }
            Thread.sleep(100);
        }
        assertThat(n).isLessThan(100);

        consumerBinding.unbind();
        assertThat(admin.getQueueProperties(TEST_PREFIX + "durabletest.0.tgroup.dlq")).isNotNull();
    }

    @Test
    public void testNonDurablePubSubWithAutoBindDLQ() throws Exception {
        RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());

        RabbitTestBinder binder = getBinder();
        ExtendedConsumerProperties<RabbitConsumerProperties> consumerProperties = createConsumerProperties();
        consumerProperties.getExtension().setPrefix(TEST_PREFIX);
        consumerProperties.getExtension().setAutoBindDlq(true);
        consumerProperties.getExtension().setDurableSubscription(false);
        consumerProperties.setMaxAttempts(1); // disable retry
        BindingProperties bindingProperties = createConsumerBindingProperties(consumerProperties);
        DirectChannel moduleInputChannel = createBindableChannel("input", bindingProperties);
        moduleInputChannel.setBeanName("nondurabletest");
        moduleInputChannel.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                throw new RuntimeException("foo");
            }

        });
        Binding<MessageChannel> consumerBinding = binder.bindConsumer("nondurabletest.0", "tgroup",
                moduleInputChannel, consumerProperties);

        consumerBinding.unbind();
        assertThat(admin.getQueueProperties(TEST_PREFIX + "nondurabletest.0.dlq")).isNull();
    }

    @Test
    public void testAutoBindDLQ() throws Exception {
        RabbitTestBinder binder = getBinder();
        ExtendedConsumerProperties<RabbitConsumerProperties> consumerProperties = createConsumerProperties();
        consumerProperties.getExtension().setPrefix(TEST_PREFIX);
        consumerProperties.getExtension().setAutoBindDlq(true);
        consumerProperties.setMaxAttempts(1); // disable retry
        consumerProperties.getExtension().setDurableSubscription(true);
        BindingProperties bindingProperties = createConsumerBindingProperties(consumerProperties);
        DirectChannel moduleInputChannel = createBindableChannel("input", bindingProperties);
        moduleInputChannel.setBeanName("dlqTest");
        moduleInputChannel.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                throw new RuntimeException("foo");
            }

        });
        Binding<MessageChannel> consumerBinding = binder.bindConsumer("dlqtest", "default", moduleInputChannel,
                consumerProperties);

        RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
        template.convertAndSend("", TEST_PREFIX + "dlqtest.default", "foo");

        int n = 0;
        while (n++ < 100) {
            Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "dlqtest.default.dlq");
            if (deadLetter != null) {
                assertThat(deadLetter).isEqualTo("foo");
                break;
            }
            Thread.sleep(100);
        }
        assertThat(n).isLessThan(100);

        consumerBinding.unbind();

        ApplicationContext context = TestUtils.getPropertyValue(binder,
                "binder.provisioningProvider.autoDeclareContext", ApplicationContext.class);
        assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default.binding")).isFalse();
        assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default")).isFalse();
        assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default.dlq.binding")).isFalse();
        assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default.dlq")).isFalse();
    }

    @Test
    public void testAutoBindDLQPartionedConsumerFirst() throws Exception {
        RabbitTestBinder binder = getBinder();
        ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
        properties.getExtension().setPrefix("bindertest.");
        properties.getExtension().setAutoBindDlq(true);
        properties.setMaxAttempts(1); // disable retry
        properties.setPartitioned(true);
        properties.setInstanceIndex(0);
        DirectChannel input0 = createBindableChannel("input", createConsumerBindingProperties(properties));
        input0.setBeanName("test.input0DLQ");
        Binding<MessageChannel> input0Binding = binder.bindConsumer("partDLQ.0", "dlqPartGrp", input0, properties);
        Binding<MessageChannel> defaultConsumerBinding1 = binder.bindConsumer("partDLQ.0", "default",
                new QueueChannel(), properties);
        properties.setInstanceIndex(1);
        DirectChannel input1 = createBindableChannel("input1", createConsumerBindingProperties(properties));
        input1.setBeanName("test.input1DLQ");
        Binding<MessageChannel> input1Binding = binder.bindConsumer("partDLQ.0", "dlqPartGrp", input1, properties);
        Binding<MessageChannel> defaultConsumerBinding2 = binder.bindConsumer("partDLQ.0", "default",
                new QueueChannel(), properties);

        ExtendedProducerProperties<RabbitProducerProperties> producerProperties = createProducerProperties();
        producerProperties.getExtension().setPrefix("bindertest.");
        producerProperties.getExtension().setAutoBindDlq(true);
        producerProperties.setPartitionKeyExtractorClass(PartitionTestSupport.class);
        producerProperties.setPartitionSelectorClass(PartitionTestSupport.class);
        producerProperties.setPartitionCount(2);
        BindingProperties bindingProperties = createProducerBindingProperties(producerProperties);
        DirectChannel output = createBindableChannel("output", bindingProperties);
        output.setBeanName("test.output");
        Binding<MessageChannel> outputBinding = binder.bindProducer("partDLQ.0", output, producerProperties);

        final CountDownLatch latch0 = new CountDownLatch(1);
        input0.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                if (latch0.getCount() <= 0) {
                    throw new RuntimeException("dlq");
                }
                latch0.countDown();
            }

        });

        final CountDownLatch latch1 = new CountDownLatch(1);
        input1.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                if (latch1.getCount() <= 0) {
                    throw new RuntimeException("dlq");
                }
                latch1.countDown();
            }

        });

        output.send(new GenericMessage<>(1));
        assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue();

        output.send(new GenericMessage<>(0));
        assertThat(latch0.await(10, TimeUnit.SECONDS)).isTrue();

        output.send(new GenericMessage<>(1));

        RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
        template.setReceiveTimeout(10000);

        String streamDLQName = "bindertest.partDLQ.0.dlqPartGrp.dlq";

        org.springframework.amqp.core.Message received = template.receive(streamDLQName);
        assertThat(received).isNotNull();
        assertThat(received.getMessageProperties().getReceivedRoutingKey())
                .isEqualTo("bindertest.partDLQ.0.dlqPartGrp-1");
        assertThat(received.getMessageProperties().getHeaders()).doesNotContainKey(BinderHeaders.PARTITION_HEADER);

        output.send(new GenericMessage<>(0));
        received = template.receive(streamDLQName);
        assertThat(received).isNotNull();
        assertThat(received.getMessageProperties().getReceivedRoutingKey())
                .isEqualTo("bindertest.partDLQ.0.dlqPartGrp-0");
        assertThat(received.getMessageProperties().getHeaders()).doesNotContainKey(BinderHeaders.PARTITION_HEADER);

        input0Binding.unbind();
        input1Binding.unbind();
        defaultConsumerBinding1.unbind();
        defaultConsumerBinding2.unbind();
        outputBinding.unbind();
    }

    @Test
    public void testAutoBindDLQPartionedConsumerFirstWithRepublishNoRetry() throws Exception {
        testAutoBindDLQPartionedConsumerFirstWithRepublishGuts(false);
    }

    @Test
    public void testAutoBindDLQPartionedConsumerFirstWithRepublishWithRetry() throws Exception {
        testAutoBindDLQPartionedConsumerFirstWithRepublishGuts(true);
    }

    private void testAutoBindDLQPartionedConsumerFirstWithRepublishGuts(final boolean withRetry) throws Exception {
        RabbitTestBinder binder = getBinder();
        ExtendedConsumerProperties<RabbitConsumerProperties> properties = createConsumerProperties();
        properties.getExtension().setPrefix("bindertest.");
        properties.getExtension().setAutoBindDlq(true);
        properties.getExtension().setRepublishToDlq(true);
        properties.getExtension().setRepublishDeliveyMode(MessageDeliveryMode.NON_PERSISTENT);
        properties.setMaxAttempts(withRetry ? 2 : 1);
        properties.setPartitioned(true);
        properties.setInstanceIndex(0);
        DirectChannel input0 = createBindableChannel("input", createConsumerBindingProperties(properties));
        input0.setBeanName("test.input0DLQ");
        Binding<MessageChannel> input0Binding = binder.bindConsumer("partPubDLQ.0", "dlqPartGrp", input0,
                properties);
        Binding<MessageChannel> defaultConsumerBinding1 = binder.bindConsumer("partPubDLQ.0", "default",
                new QueueChannel(), properties);
        properties.setInstanceIndex(1);
        DirectChannel input1 = createBindableChannel("input1", createConsumerBindingProperties(properties));
        input1.setBeanName("test.input1DLQ");
        Binding<MessageChannel> input1Binding = binder.bindConsumer("partPubDLQ.0", "dlqPartGrp", input1,
                properties);
        Binding<MessageChannel> defaultConsumerBinding2 = binder.bindConsumer("partPubDLQ.0", "default",
                new QueueChannel(), properties);

        ExtendedProducerProperties<RabbitProducerProperties> producerProperties = createProducerProperties();
        producerProperties.getExtension().setPrefix("bindertest.");
        producerProperties.getExtension().setAutoBindDlq(true);
        producerProperties.setPartitionKeyExtractorClass(PartitionTestSupport.class);
        producerProperties.setPartitionSelectorClass(PartitionTestSupport.class);
        producerProperties.setPartitionCount(2);
        BindingProperties bindingProperties = createProducerBindingProperties(producerProperties);
        DirectChannel output = createBindableChannel("output", bindingProperties);
        output.setBeanName("test.output");
        Binding<MessageChannel> outputBinding = binder.bindProducer("partPubDLQ.0", output, producerProperties);

        final CountDownLatch latch0 = new CountDownLatch(1);
        input0.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                if (latch0.getCount() <= 0) {
                    throw new RuntimeException("dlq");
                }
                latch0.countDown();
            }

        });

        final CountDownLatch latch1 = new CountDownLatch(1);
        input1.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                if (latch1.getCount() <= 0) {
                    throw new RuntimeException("dlq");
                }
                latch1.countDown();
            }

        });

        ApplicationContext context = TestUtils.getPropertyValue(binder.getBinder(), "applicationContext",
                ApplicationContext.class);
        SubscribableChannel boundErrorChannel = context.getBean("bindertest.partPubDLQ.0.dlqPartGrp-0.errors",
                SubscribableChannel.class);
        SubscribableChannel globalErrorChannel = context.getBean("errorChannel", SubscribableChannel.class);
        final AtomicReference<Message<?>> boundErrorChannelMessage = new AtomicReference<>();
        final AtomicReference<Message<?>> globalErrorChannelMessage = new AtomicReference<>();
        final AtomicBoolean hasRecovererInCallStack = new AtomicBoolean(!withRetry);
        boundErrorChannel.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                boundErrorChannelMessage.set(message);
                String stackTrace = Arrays.toString(new RuntimeException().getStackTrace());
                hasRecovererInCallStack.set(stackTrace.contains("ErrorMessageSendingRecoverer"));
            }

        });
        globalErrorChannel.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                globalErrorChannelMessage.set(message);
            }

        });

        output.send(new GenericMessage<>(1));
        assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue();

        output.send(new GenericMessage<>(0));
        assertThat(latch0.await(10, TimeUnit.SECONDS)).isTrue();

        output.send(new GenericMessage<>(1));

        RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
        template.setReceiveTimeout(10000);

        String streamDLQName = "bindertest.partPubDLQ.0.dlqPartGrp.dlq";

        org.springframework.amqp.core.Message received = template.receive(streamDLQName);
        assertThat(received).isNotNull();
        assertThat(received.getMessageProperties().getHeaders().get("x-original-routingKey"))
                .isEqualTo("partPubDLQ.0-1");
        assertThat(received.getMessageProperties().getHeaders()).doesNotContainKey(BinderHeaders.PARTITION_HEADER);
        assertThat(received.getMessageProperties().getReceivedDeliveryMode())
                .isEqualTo(MessageDeliveryMode.NON_PERSISTENT);

        output.send(new GenericMessage<>(0));
        received = template.receive(streamDLQName);
        assertThat(received).isNotNull();
        assertThat(received.getMessageProperties().getHeaders().get("x-original-routingKey"))
                .isEqualTo("partPubDLQ.0-0");
        assertThat(received.getMessageProperties().getHeaders()).doesNotContainKey(BinderHeaders.PARTITION_HEADER);

        // verify we got a message on the dedicated error channel and the global (via bridge)
        assertThat(boundErrorChannelMessage.get()).isNotNull();
        assertThat(globalErrorChannelMessage.get()).isNotNull();
        assertThat(hasRecovererInCallStack.get()).isEqualTo(withRetry);

        input0Binding.unbind();
        input1Binding.unbind();
        defaultConsumerBinding1.unbind();
        defaultConsumerBinding2.unbind();
        outputBinding.unbind();
    }

    @Test
    public void testAutoBindDLQPartitionedProducerFirst() throws Exception {
        RabbitTestBinder binder = getBinder();
        ExtendedProducerProperties<RabbitProducerProperties> properties = createProducerProperties();

        properties.getExtension().setPrefix("bindertest.");
        properties.getExtension().setAutoBindDlq(true);
        properties.setRequiredGroups("dlqPartGrp");
        properties.setPartitionKeyExtractorClass(PartitionTestSupport.class);
        properties.setPartitionSelectorClass(PartitionTestSupport.class);
        properties.setPartitionCount(2);
        DirectChannel output = createBindableChannel("output", createProducerBindingProperties(properties));
        output.setBeanName("test.output");
        Binding<MessageChannel> outputBinding = binder.bindProducer("partDLQ.1", output, properties);

        ExtendedConsumerProperties<RabbitConsumerProperties> consumerProperties = createConsumerProperties();
        consumerProperties.getExtension().setPrefix("bindertest.");
        consumerProperties.getExtension().setAutoBindDlq(true);
        consumerProperties.setMaxAttempts(1); // disable retry
        consumerProperties.setPartitioned(true);
        consumerProperties.setInstanceIndex(0);
        DirectChannel input0 = createBindableChannel("input", createConsumerBindingProperties(consumerProperties));
        input0.setBeanName("test.input0DLQ");
        Binding<MessageChannel> input0Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input0,
                consumerProperties);
        Binding<MessageChannel> defaultConsumerBinding1 = binder.bindConsumer("partDLQ.1", "defaultConsumer",
                new QueueChannel(), consumerProperties);
        consumerProperties.setInstanceIndex(1);
        DirectChannel input1 = createBindableChannel("input1", createConsumerBindingProperties(consumerProperties));
        input1.setBeanName("test.input1DLQ");
        Binding<MessageChannel> input1Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input1,
                consumerProperties);
        Binding<MessageChannel> defaultConsumerBinding2 = binder.bindConsumer("partDLQ.1", "defaultConsumer",
                new QueueChannel(), consumerProperties);

        final CountDownLatch latch0 = new CountDownLatch(1);
        input0.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                if (latch0.getCount() <= 0) {
                    throw new RuntimeException("dlq");
                }
                latch0.countDown();
            }

        });

        final CountDownLatch latch1 = new CountDownLatch(1);
        input1.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                if (latch1.getCount() <= 0) {
                    throw new RuntimeException("dlq");
                }
                latch1.countDown();
            }

        });

        output.send(new GenericMessage<Integer>(1));
        assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue();

        output.send(new GenericMessage<Integer>(0));
        assertThat(latch0.await(10, TimeUnit.SECONDS)).isTrue();

        output.send(new GenericMessage<Integer>(1));

        RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
        template.setReceiveTimeout(10000);

        String streamDLQName = "bindertest.partDLQ.1.dlqPartGrp.dlq";

        org.springframework.amqp.core.Message received = template.receive(streamDLQName);
        assertThat(received).isNotNull();
        assertThat(received.getMessageProperties().getReceivedRoutingKey())
                .isEqualTo("bindertest.partDLQ.1.dlqPartGrp-1");
        assertThat(received.getMessageProperties().getHeaders()).doesNotContainKey(BinderHeaders.PARTITION_HEADER);
        assertThat(received.getMessageProperties().getReceivedDeliveryMode())
                .isEqualTo(MessageDeliveryMode.PERSISTENT);

        output.send(new GenericMessage<Integer>(0));
        received = template.receive(streamDLQName);
        assertThat(received).isNotNull();
        assertThat(received.getMessageProperties().getReceivedRoutingKey())
                .isEqualTo("bindertest.partDLQ.1.dlqPartGrp-0");
        assertThat(received.getMessageProperties().getHeaders()).doesNotContainKey(BinderHeaders.PARTITION_HEADER);

        input0Binding.unbind();
        input1Binding.unbind();
        defaultConsumerBinding1.unbind();
        defaultConsumerBinding2.unbind();
        outputBinding.unbind();
    }

    @Test
    public void testAutoBindDLQwithRepublish() throws Exception {
        // pre-declare the queue with dead-lettering, users can also use a policy
        RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
        Map<String, Object> args = new HashMap<String, Object>();
        args.put("x-dead-letter-exchange", TEST_PREFIX + "DLX");
        args.put("x-dead-letter-routing-key", TEST_PREFIX + "dlqpubtest.default");
        Queue queue = new Queue(TEST_PREFIX + "dlqpubtest.default", true, false, false, args);
        admin.declareQueue(queue);

        RabbitTestBinder binder = getBinder();
        ExtendedConsumerProperties<RabbitConsumerProperties> consumerProperties = createConsumerProperties();
        consumerProperties.getExtension().setPrefix(TEST_PREFIX);
        consumerProperties.getExtension().setAutoBindDlq(true);
        consumerProperties.getExtension().setRepublishToDlq(true);
        consumerProperties.setMaxAttempts(1); // disable retry
        consumerProperties.getExtension().setDurableSubscription(true);
        DirectChannel moduleInputChannel = createBindableChannel("input",
                createConsumerBindingProperties(consumerProperties));
        moduleInputChannel.setBeanName("dlqPubTest");
        moduleInputChannel.subscribe(new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                throw new RuntimeException("foo");
            }

        });
        Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo.dlqpubtest", "foo", moduleInputChannel,
                consumerProperties);

        RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
        template.convertAndSend("", TEST_PREFIX + "foo.dlqpubtest.foo", "foo");

        int n = 0;
        while (n++ < 100) {
            org.springframework.amqp.core.Message deadLetter = template
                    .receive(TEST_PREFIX + "foo.dlqpubtest.foo.dlq");
            if (deadLetter != null) {
                assertThat(new String(deadLetter.getBody())).isEqualTo("foo");
                assertThat(deadLetter.getMessageProperties().getHeaders()).containsKey(("x-exception-stacktrace"));
                break;
            }
            Thread.sleep(100);
        }
        assertThat(n).isLessThan(100);

        consumerBinding.unbind();
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testBatchingAndCompression() throws Exception {
        RabbitTestBinder binder = getBinder();
        ExtendedProducerProperties<RabbitProducerProperties> producerProperties = createProducerProperties();
        producerProperties.getExtension().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
        producerProperties.getExtension().setBatchingEnabled(true);
        producerProperties.getExtension().setBatchSize(2);
        producerProperties.getExtension().setBatchBufferLimit(100000);
        producerProperties.getExtension().setBatchTimeout(30000);
        producerProperties.getExtension().setCompress(true);
        producerProperties.setRequiredGroups("default");

        DirectChannel output = createBindableChannel("input", createProducerBindingProperties(producerProperties));
        output.setBeanName("batchingProducer");
        Binding<MessageChannel> producerBinding = binder.bindProducer("batching.0", output, producerProperties);

        Log logger = spy(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor.logger", Log.class));
        new DirectFieldAccessor(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor"))
                .setPropertyValue("logger", logger);
        when(logger.isTraceEnabled()).thenReturn(true);

        assertThat(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor.level"))
                .isEqualTo(Deflater.BEST_SPEED);

        output.send(new GenericMessage<>("foo".getBytes()));
        output.send(new GenericMessage<>("bar".getBytes()));

        Object out = spyOn("batching.0.default").receive(false);
        assertThat(out).isInstanceOf(byte[].class);
        assertThat(new String((byte[]) out)).isEqualTo("\u0000\u0000\u0000\u0003foo\u0000\u0000\u0000\u0003bar");

        ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
        verify(logger).trace(captor.capture());
        assertThat(captor.getValue().toString()).contains(("Compressed 14 to "));

        QueueChannel input = new QueueChannel();
        input.setBeanName("batchingConsumer");
        Binding<MessageChannel> consumerBinding = binder.bindConsumer("batching.0", "test", input,
                createConsumerProperties());

        output.send(new GenericMessage<>("foo".getBytes()));
        output.send(new GenericMessage<>("bar".getBytes()));

        Message<byte[]> in = (Message<byte[]>) input.receive(10000);
        assertThat(in).isNotNull();
        assertThat(new String(in.getPayload())).isEqualTo("foo");
        in = (Message<byte[]>) input.receive(10000);
        assertThat(in).isNotNull();
        assertThat(new String(in.getPayload())).isEqualTo("bar");
        assertThat(in.getHeaders().get(AmqpHeaders.DELIVERY_MODE)).isNull();

        producerBinding.unbind();
        consumerBinding.unbind();
    }

    /*
     * Test late binding due to broker down; queues with and without DLQs, and partitioned
     * queues.
     */
    @Test
    public void testLateBinding() throws Exception {
        RabbitTestSupport.RabbitProxy proxy = new RabbitTestSupport.RabbitProxy();
        CachingConnectionFactory cf = new CachingConnectionFactory("localhost", proxy.getPort());

        RabbitMessageChannelBinder rabbitBinder = new RabbitMessageChannelBinder(cf, new RabbitProperties(),
                new RabbitExchangeQueueProvisioner(cf));
        RabbitTestBinder binder = new RabbitTestBinder(cf, rabbitBinder);

        ExtendedProducerProperties<RabbitProducerProperties> producerProperties = createProducerProperties();
        producerProperties.getExtension().setPrefix("latebinder.");
        producerProperties.getExtension().setAutoBindDlq(true);

        MessageChannel moduleOutputChannel = createBindableChannel("output",
                createProducerBindingProperties(producerProperties));
        Binding<MessageChannel> late0ProducerBinding = binder.bindProducer("late.0", moduleOutputChannel,
                producerProperties);

        QueueChannel moduleInputChannel = new QueueChannel();
        ExtendedConsumerProperties<RabbitConsumerProperties> rabbitConsumerProperties = createConsumerProperties();
        rabbitConsumerProperties.getExtension().setPrefix("latebinder.");
        Binding<MessageChannel> late0ConsumerBinding = binder.bindConsumer("late.0", "test", moduleInputChannel,
                rabbitConsumerProperties);

        producerProperties
                .setPartitionKeyExpression(spelExpressionParser.parseExpression("payload.equals('0') ? 0 : 1"));
        producerProperties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("hashCode()"));
        producerProperties.setPartitionCount(2);

        MessageChannel partOutputChannel = createBindableChannel("output",
                createProducerBindingProperties(producerProperties));
        Binding<MessageChannel> partlate0ProducerBinding = binder.bindProducer("partlate.0", partOutputChannel,
                producerProperties);

        QueueChannel partInputChannel0 = new QueueChannel();
        QueueChannel partInputChannel1 = new QueueChannel();

        ExtendedConsumerProperties<RabbitConsumerProperties> partLateConsumerProperties = createConsumerProperties();
        partLateConsumerProperties.getExtension().setPrefix("latebinder.");
        partLateConsumerProperties.setPartitioned(true);
        partLateConsumerProperties.setInstanceIndex(0);
        Binding<MessageChannel> partlate0Consumer0Binding = binder.bindConsumer("partlate.0", "test",
                partInputChannel0, partLateConsumerProperties);
        partLateConsumerProperties.setInstanceIndex(1);
        Binding<MessageChannel> partlate0Consumer1Binding = binder.bindConsumer("partlate.0", "test",
                partInputChannel1, partLateConsumerProperties);

        ExtendedProducerProperties<RabbitProducerProperties> noDlqProducerProperties = createProducerProperties();
        noDlqProducerProperties.getExtension().setPrefix("latebinder.");
        MessageChannel noDLQOutputChannel = createBindableChannel("output",
                createProducerBindingProperties(noDlqProducerProperties));
        Binding<MessageChannel> noDlqProducerBinding = binder.bindProducer("lateNoDLQ.0", noDLQOutputChannel,
                noDlqProducerProperties);

        QueueChannel noDLQInputChannel = new QueueChannel();
        ExtendedConsumerProperties<RabbitConsumerProperties> noDlqConsumerProperties = createConsumerProperties();
        noDlqConsumerProperties.getExtension().setPrefix("latebinder.");
        Binding<MessageChannel> noDlqConsumerBinding = binder.bindConsumer("lateNoDLQ.0", "test", noDLQInputChannel,
                noDlqConsumerProperties);

        MessageChannel outputChannel = createBindableChannel("output",
                createProducerBindingProperties(noDlqProducerProperties));
        Binding<MessageChannel> pubSubProducerBinding = binder.bindProducer("latePubSub", outputChannel,
                noDlqProducerProperties);
        QueueChannel pubSubInputChannel = new QueueChannel();
        noDlqConsumerProperties.getExtension().setDurableSubscription(false);
        Binding<MessageChannel> nonDurableConsumerBinding = binder.bindConsumer("latePubSub", "lategroup",
                pubSubInputChannel, noDlqConsumerProperties);
        QueueChannel durablePubSubInputChannel = new QueueChannel();
        noDlqConsumerProperties.getExtension().setDurableSubscription(true);
        Binding<MessageChannel> durableConsumerBinding = binder.bindConsumer("latePubSub", "lateDurableGroup",
                durablePubSubInputChannel, noDlqConsumerProperties);

        proxy.start();

        moduleOutputChannel.send(new GenericMessage<>("foo"));
        Message<?> message = moduleInputChannel.receive(10000);
        assertThat(message).isNotNull();
        assertThat(message.getPayload()).isNotNull();

        noDLQOutputChannel.send(new GenericMessage<>("bar"));
        message = noDLQInputChannel.receive(10000);
        assertThat(message);
        assertThat(message.getPayload()).isEqualTo("bar");

        outputChannel.send(new GenericMessage<>("baz"));
        message = pubSubInputChannel.receive(10000);
        assertThat(message);
        assertThat(message.getPayload()).isEqualTo("baz");
        message = durablePubSubInputChannel.receive(10000);
        assertThat(message).isNotNull();
        assertThat(message.getPayload()).isEqualTo("baz");

        partOutputChannel.send(new GenericMessage<>("0"));
        partOutputChannel.send(new GenericMessage<>("1"));
        message = partInputChannel0.receive(10000);
        assertThat(message).isNotNull();
        assertThat(message.getPayload()).isEqualTo("0");
        message = partInputChannel1.receive(10000);
        assertThat(message).isNotNull();
        assertThat(message.getPayload()).isEqualTo("1");

        late0ProducerBinding.unbind();
        late0ConsumerBinding.unbind();
        partlate0ProducerBinding.unbind();
        partlate0Consumer0Binding.unbind();
        partlate0Consumer1Binding.unbind();
        noDlqProducerBinding.unbind();
        noDlqConsumerBinding.unbind();
        pubSubProducerBinding.unbind();
        nonDurableConsumerBinding.unbind();
        durableConsumerBinding.unbind();

        binder.cleanup();

        proxy.stop();
        cf.destroy();

        this.rabbitAvailableRule.getResource().destroy();
    }

    private SimpleMessageListenerContainer verifyContainer(Lifecycle endpoint) {
        SimpleMessageListenerContainer container;
        RetryTemplate retry;
        container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
                SimpleMessageListenerContainer.class);
        assertThat(container.getAcknowledgeMode()).isEqualTo(AcknowledgeMode.NONE);
        assertThat(container.getQueueNames()[0]).startsWith("foo.props.0");
        assertThat(TestUtils.getPropertyValue(container, "transactional", Boolean.class)).isFalse();
        assertThat(TestUtils.getPropertyValue(container, "concurrentConsumers")).isEqualTo(2);
        assertThat(TestUtils.getPropertyValue(container, "maxConcurrentConsumers")).isEqualTo(3);
        assertThat(TestUtils.getPropertyValue(container, "defaultRequeueRejected", Boolean.class)).isFalse();
        assertThat(TestUtils.getPropertyValue(container, "prefetchCount")).isEqualTo(20);
        assertThat(TestUtils.getPropertyValue(container, "txSize")).isEqualTo(10);
        retry = TestUtils.getPropertyValue(endpoint, "retryTemplate", RetryTemplate.class);
        assertThat(TestUtils.getPropertyValue(retry, "retryPolicy.maxAttempts")).isEqualTo(23);
        assertThat(TestUtils.getPropertyValue(retry, "backOffPolicy.initialInterval")).isEqualTo(2000L);
        assertThat(TestUtils.getPropertyValue(retry, "backOffPolicy.maxInterval")).isEqualTo(20000L);
        assertThat(TestUtils.getPropertyValue(retry, "backOffPolicy.multiplier")).isEqualTo(5.0);

        List<?> requestMatchers = TestUtils.getPropertyValue(endpoint, "headerMapper.requestHeaderMatcher.matchers",
                List.class);
        assertThat(requestMatchers).hasSize(1);
        assertThat(TestUtils.getPropertyValue(requestMatchers.get(0), "pattern")).isEqualTo("foo");

        return container;
    }

    private void verifyFooRequestProducer(Lifecycle endpoint) {
        List<?> requestMatchers = TestUtils.getPropertyValue(endpoint, "headerMapper.requestHeaderMatcher.matchers",
                List.class);
        assertThat(requestMatchers).hasSize(2);
        assertThat(TestUtils.getPropertyValue(requestMatchers.get(1), "pattern")).isEqualTo("foo");
    }

    @Override
    protected String getEndpointRouting(Object endpoint) {
        return TestUtils.getPropertyValue(endpoint, "routingKeyExpression", SpelExpression.class)
                .getExpressionString();
    }

    @Override
    protected String getExpectedRoutingBaseDestination(String name, String group) {
        return name;
    }

    @Override
    protected String getClassUnderTestName() {
        return CLASS_UNDER_TEST_NAME;
    }

    @Override
    protected void checkRkExpressionForPartitionedModuleSpEL(Object endpoint) {
        assertThat(getEndpointRouting(endpoint)).contains(getExpectedRoutingBaseDestination("'part.0'", "test")
                + " + '-' + headers['" + BinderHeaders.PARTITION_HEADER + "']");
    }

    @Override
    public Spy spyOn(final String queue) {
        final RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
        template.setAfterReceivePostProcessors(new DelegatingDecompressingPostProcessor());
        return new Spy() {

            @Override
            public Object receive(boolean expectNull) throws Exception {
                if (expectNull) {
                    Thread.sleep(50);
                    return template.receiveAndConvert(new RabbitConsumerProperties().getPrefix() + queue);
                }
                Object bar = null;
                int n = 0;
                while (n++ < 100 && bar == null) {
                    bar = template.receiveAndConvert(new RabbitConsumerProperties().getPrefix() + queue);
                    Thread.sleep(100);
                }
                assertThat(n).isLessThan(100).withFailMessage("Message did not arrive in RabbitMQ");
                return bar;
            }

        };
    }

    public static class TestPartitionKeyExtractorClass implements PartitionKeyExtractorStrategy {

        @Override
        public Object extractKey(Message<?> message) {
            return 0;
        }

    }

    public static class TestPartitionSelectorClass implements PartitionSelectorStrategy {

        @Override
        public int selectPartition(Object key, int partitionCount) {
            return 0;
        }

    }

}