public final class QueueParserIntegrationTests {

  @Rule public BrokerRunning brokerIsRunning = BrokerRunning.isRunning();

  private XmlBeanFactory beanFactory;

  @Before
  public void setUpDefaultBeanFactory() throws Exception {
    beanFactory =
        new XmlBeanFactory(
            new ClassPathResource(getClass().getSimpleName() + "-context.xml", getClass()));
  }

  @Test
  public void testArgumentsQueue() throws Exception {

    Queue queue = beanFactory.getBean("arguments", Queue.class);
    assertNotNull(queue);

    RabbitTemplate template =
        new RabbitTemplate(new CachingConnectionFactory(BrokerTestUtils.getPort()));
    RabbitAdmin rabbitAdmin = new RabbitAdmin(template.getConnectionFactory());
    rabbitAdmin.deleteQueue(queue.getName());
    rabbitAdmin.declareQueue(queue);

    assertEquals(100L, queue.getArguments().get("x-message-ttl"));
    template.convertAndSend(queue.getName(), "message");

    Thread.sleep(200);
    String result = (String) template.receiveAndConvert(queue.getName());
    assertEquals(null, result);
  }
}
/**
 * @author Gary Russell
 * @author Gunar Hillert
 * @author Artem Bilan
 * @since 1.1
 */
public class RabbitTemplatePublisherCallbacksIntegrationTests {

  private static final String ROUTE = "test.queue";

  private CachingConnectionFactory connectionFactory;

  private CachingConnectionFactory connectionFactoryWithConfirmsEnabled;

  private CachingConnectionFactory connectionFactoryWithReturnsEnabled;

  private RabbitTemplate templateWithConfirmsEnabled;

  private RabbitTemplate templateWithReturnsEnabled;

  @Before
  public void create() {
    connectionFactory = new CachingConnectionFactory();
    connectionFactory.setHost("localhost");
    connectionFactory.setChannelCacheSize(10);
    connectionFactory.setPort(BrokerTestUtils.getPort());
    connectionFactoryWithConfirmsEnabled = new CachingConnectionFactory();
    connectionFactoryWithConfirmsEnabled.setHost("localhost");
    // When using publisher confirms, the cache size needs to be large enough
    // otherwise channels can be closed before confirms are received.
    connectionFactoryWithConfirmsEnabled.setChannelCacheSize(100);
    connectionFactoryWithConfirmsEnabled.setPort(BrokerTestUtils.getPort());
    connectionFactoryWithConfirmsEnabled.setPublisherConfirms(true);
    templateWithConfirmsEnabled = new RabbitTemplate(connectionFactoryWithConfirmsEnabled);
    connectionFactoryWithReturnsEnabled = new CachingConnectionFactory();
    connectionFactoryWithReturnsEnabled.setHost("localhost");
    connectionFactoryWithReturnsEnabled.setChannelCacheSize(1);
    connectionFactoryWithReturnsEnabled.setPort(BrokerTestUtils.getPort());
    connectionFactoryWithReturnsEnabled.setPublisherReturns(true);
    templateWithReturnsEnabled = new RabbitTemplate(connectionFactoryWithReturnsEnabled);
  }

  @After
  public void cleanUp() {
    if (connectionFactory != null) {
      connectionFactory.destroy();
    }

    if (connectionFactoryWithConfirmsEnabled != null) {
      connectionFactoryWithConfirmsEnabled.destroy();
    }

    if (connectionFactoryWithReturnsEnabled != null) {
      connectionFactoryWithReturnsEnabled.destroy();
    }
    this.brokerIsRunning.removeTestQueues();
  }

  @Rule public BrokerRunning brokerIsRunning = BrokerRunning.isRunningWithEmptyQueues(ROUTE);

  @Test
  public void testPublisherConfirmReceived() throws Exception {
    final CountDownLatch latch = new CountDownLatch(10000);
    final AtomicInteger acks = new AtomicInteger();
    templateWithConfirmsEnabled.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            acks.incrementAndGet();
            latch.countDown();
          }
        });
    ExecutorService exec = Executors.newCachedThreadPool();
    for (int i = 0; i < 100; i++) {
      exec.submit(
          new Runnable() {

            @Override
            public void run() {
              try {
                for (int i = 0; i < 100; i++) {
                  templateWithConfirmsEnabled.convertAndSend(
                      ROUTE, (Object) "message", new CorrelationData("abc"));
                }
              } catch (Throwable t) {
                t.printStackTrace();
              }
            }
          });
    }
    exec.shutdown();
    assertTrue(exec.awaitTermination(300, TimeUnit.SECONDS));
    assertTrue("" + latch.getCount(), latch.await(60, TimeUnit.SECONDS));
    assertNull(templateWithConfirmsEnabled.getUnconfirmed(-1));
    this.templateWithConfirmsEnabled.execute(
        new ChannelCallback<Void>() {

          @Override
          public Void doInRabbit(Channel channel) throws Exception {
            Map<?, ?> listenerMap =
                TestUtils.getPropertyValue(
                    ((ChannelProxy) channel).getTargetChannel(), "listenerForSeq", Map.class);
            int n = 0;
            while (n++ < 100 && listenerMap.size() > 0) {
              Thread.sleep(100);
            }
            assertEquals(0, listenerMap.size());
            return null;
          }
        });

    Log logger =
        spy(TestUtils.getPropertyValue(connectionFactoryWithConfirmsEnabled, "logger", Log.class));
    new DirectFieldAccessor(connectionFactoryWithConfirmsEnabled)
        .setPropertyValue("logger", logger);
    cleanUp();
    verify(logger, never()).error(any());
  }

  @Test
  public void testPublisherConfirmWithSendAndReceive() throws Exception {
    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicReference<CorrelationData> confirmCD = new AtomicReference<CorrelationData>();
    templateWithConfirmsEnabled.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            confirmCD.set(correlationData);
            latch.countDown();
          }
        });
    SimpleMessageListenerContainer container =
        new SimpleMessageListenerContainer(this.connectionFactoryWithConfirmsEnabled);
    container.setQueueNames(ROUTE);
    container.setMessageListener(
        new MessageListenerAdapter(
            new Object() {

              @SuppressWarnings("unused")
              public String handleMessage(String in) {
                return in.toUpperCase();
              }
            }));
    container.start();
    CorrelationData correlationData = new CorrelationData("abc");
    String result =
        (String)
            this.templateWithConfirmsEnabled.convertSendAndReceive(
                ROUTE, (Object) "message", correlationData);
    container.stop();
    assertEquals("MESSAGE", result);
    assertTrue(latch.await(10, TimeUnit.SECONDS));
    assertEquals(correlationData, confirmCD.get());
  }

  @Test
  public void testPublisherConfirmReceivedConcurrentThreads() throws Exception {
    final CountDownLatch latch = new CountDownLatch(2);
    templateWithConfirmsEnabled.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            latch.countDown();
          }
        });

    // Hold up the first thread so we get two channels
    final CountDownLatch threadLatch = new CountDownLatch(1);
    // Thread 1
    Executors.newSingleThreadExecutor()
        .execute(
            new Runnable() {

              @Override
              public void run() {
                templateWithConfirmsEnabled.execute(
                    new ChannelCallback<Object>() {
                      @Override
                      public Object doInRabbit(Channel channel) throws Exception {
                        try {
                          threadLatch.await(10, TimeUnit.SECONDS);
                        } catch (InterruptedException e) {
                          Thread.currentThread().interrupt();
                        }
                        templateWithConfirmsEnabled.doSend(
                            channel,
                            "",
                            ROUTE,
                            new SimpleMessageConverter()
                                .toMessage("message", new MessageProperties()),
                            false,
                            new CorrelationData("def"));
                        return null;
                      }
                    });
              }
            });

    // Thread 2
    templateWithConfirmsEnabled.convertAndSend(
        ROUTE, (Object) "message", new CorrelationData("abc"));
    threadLatch.countDown();
    assertTrue(latch.await(5000, TimeUnit.MILLISECONDS));
    assertNull(templateWithConfirmsEnabled.getUnconfirmed(-1));
  }

  @Test
  public void testPublisherConfirmReceivedTwoTemplates() throws Exception {
    final CountDownLatch latch1 = new CountDownLatch(1);
    final CountDownLatch latch2 = new CountDownLatch(1);
    templateWithConfirmsEnabled.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            latch1.countDown();
          }
        });
    templateWithConfirmsEnabled.convertAndSend(
        ROUTE, (Object) "message", new CorrelationData("abc"));
    RabbitTemplate secondTemplate = new RabbitTemplate(connectionFactoryWithConfirmsEnabled);
    secondTemplate.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            latch2.countDown();
          }
        });
    secondTemplate.convertAndSend(ROUTE, (Object) "message", new CorrelationData("def"));
    assertTrue(latch1.await(10, TimeUnit.SECONDS));
    assertTrue(latch2.await(10, TimeUnit.SECONDS));
    assertNull(templateWithConfirmsEnabled.getUnconfirmed(-1));
    assertNull(secondTemplate.getUnconfirmed(-1));
  }

  @Test
  public void testPublisherReturns() throws Exception {
    final CountDownLatch latch = new CountDownLatch(1);
    final List<Message> returns = new ArrayList<Message>();
    templateWithReturnsEnabled.setReturnCallback(
        new ReturnCallback() {
          @Override
          public void returnedMessage(
              Message message,
              int replyCode,
              String replyText,
              String exchange,
              String routingKey) {
            returns.add(message);
            latch.countDown();
          }
        });
    templateWithReturnsEnabled.setMandatory(true);
    templateWithReturnsEnabled.convertAndSend(
        ROUTE + "junk", (Object) "message", new CorrelationData("abc"));
    assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
    assertEquals(1, returns.size());
    Message message = returns.get(0);
    assertEquals("message", new String(message.getBody(), "utf-8"));
  }

  @Test
  public void testPublisherReturnsWithMandatoryExpression() throws Exception {
    final CountDownLatch latch = new CountDownLatch(1);
    final List<Message> returns = new ArrayList<Message>();
    templateWithReturnsEnabled.setReturnCallback(
        new ReturnCallback() {
          @Override
          public void returnedMessage(
              Message message,
              int replyCode,
              String replyText,
              String exchange,
              String routingKey) {
            returns.add(message);
            latch.countDown();
          }
        });
    Expression mandatoryExpression =
        new SpelExpressionParser().parseExpression("'message'.bytes == body");
    templateWithReturnsEnabled.setMandatoryExpression(mandatoryExpression);
    templateWithReturnsEnabled.convertAndSend(
        ROUTE + "junk", (Object) "message", new CorrelationData("abc"));
    templateWithReturnsEnabled.convertAndSend(
        ROUTE + "junk", (Object) "foo", new CorrelationData("abc"));
    assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
    assertEquals(1, returns.size());
    Message message = returns.get(0);
    assertEquals("message", new String(message.getBody(), "utf-8"));
  }

  @Test
  public void testPublisherConfirmNotReceived() throws Exception {
    ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class);
    Connection mockConnection = mock(Connection.class);
    Channel mockChannel = mock(Channel.class);

    when(mockConnectionFactory.newConnection((ExecutorService) null)).thenReturn(mockConnection);
    when(mockConnection.isOpen()).thenReturn(true);
    doReturn(new PublisherCallbackChannelImpl(mockChannel)).when(mockConnection).createChannel();

    CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory);
    ccf.setPublisherConfirms(true);
    final RabbitTemplate template = new RabbitTemplate(ccf);

    final AtomicBoolean confirmed = new AtomicBoolean();
    template.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            confirmed.set(true);
          }
        });
    template.convertAndSend(ROUTE, (Object) "message", new CorrelationData("abc"));
    Thread.sleep(5);
    Collection<CorrelationData> unconfirmed = template.getUnconfirmed(-1);
    assertEquals(1, unconfirmed.size());
    assertEquals("abc", unconfirmed.iterator().next().getId());
    assertFalse(confirmed.get());
  }

  @Test
  public void testPublisherConfirmNotReceivedMultiThreads() throws Exception {
    ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class);
    Connection mockConnection = mock(Connection.class);
    Channel mockChannel1 = mock(Channel.class);
    Channel mockChannel2 = mock(Channel.class);
    when(mockChannel1.isOpen()).thenReturn(true);
    when(mockChannel2.isOpen()).thenReturn(true);
    when(mockChannel1.getNextPublishSeqNo()).thenReturn(1L, 2L, 3L, 4L);
    when(mockChannel2.getNextPublishSeqNo()).thenReturn(1L, 2L, 3L, 4L);

    when(mockConnectionFactory.newConnection((ExecutorService) null)).thenReturn(mockConnection);
    when(mockConnection.isOpen()).thenReturn(true);
    PublisherCallbackChannelImpl channel1 = new PublisherCallbackChannelImpl(mockChannel1);
    PublisherCallbackChannelImpl channel2 = new PublisherCallbackChannelImpl(mockChannel2);
    when(mockConnection.createChannel()).thenReturn(channel1).thenReturn(channel2);

    CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory);
    ccf.setPublisherConfirms(true);
    ccf.setChannelCacheSize(3);
    final RabbitTemplate template = new RabbitTemplate(ccf);

    final AtomicBoolean confirmed = new AtomicBoolean();
    template.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            confirmed.set(true);
          }
        });

    // Hold up the first thread so we get two channels
    final CountDownLatch threadLatch = new CountDownLatch(1);
    final CountDownLatch threadSentLatch = new CountDownLatch(1);
    // Thread 1
    ExecutorService exec = Executors.newSingleThreadExecutor();
    exec.execute(
        new Runnable() {

          @Override
          public void run() {
            template.execute(
                new ChannelCallback<Object>() {
                  @Override
                  public Object doInRabbit(Channel channel) throws Exception {
                    try {
                      threadLatch.await(10, TimeUnit.SECONDS);
                    } catch (InterruptedException e) {
                      Thread.currentThread().interrupt();
                    }
                    template.doSend(
                        channel,
                        "",
                        ROUTE,
                        new SimpleMessageConverter().toMessage("message", new MessageProperties()),
                        false,
                        new CorrelationData("def"));
                    threadSentLatch.countDown();
                    return null;
                  }
                });
          }
        });

    // Thread 2
    template.convertAndSend(ROUTE, (Object) "message", new CorrelationData("abc")); // channel y
    threadLatch.countDown();
    assertTrue(threadSentLatch.await(5, TimeUnit.SECONDS));
    Collection<CorrelationData> unconfirmed = template.getUnconfirmed(-1);
    assertEquals(2, unconfirmed.size());
    Set<String> ids = new HashSet<String>();
    Iterator<CorrelationData> iterator = unconfirmed.iterator();
    ids.add(iterator.next().getId());
    ids.add(iterator.next().getId());
    assertTrue(ids.remove("abc"));
    assertTrue(ids.remove("def"));
    assertFalse(confirmed.get());
    DirectFieldAccessor dfa = new DirectFieldAccessor(template);
    Map<?, ?> pendingConfirms = (Map<?, ?>) dfa.getPropertyValue("pendingConfirms");
    assertThat(pendingConfirms.size(), greaterThan(0)); // might use 2 or only 1 channel
    exec.shutdown();
    assertTrue(exec.awaitTermination(10, TimeUnit.SECONDS));
    ccf.destroy();
    assertEquals(0, pendingConfirms.size());
  }

  @Test
  public void testPublisherConfirmNotReceivedAged() throws Exception {
    ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class);
    Connection mockConnection = mock(Connection.class);
    Channel mockChannel = mock(Channel.class);

    when(mockConnectionFactory.newConnection((ExecutorService) null)).thenReturn(mockConnection);
    when(mockConnection.isOpen()).thenReturn(true);
    doReturn(new PublisherCallbackChannelImpl(mockChannel)).when(mockConnection).createChannel();

    final AtomicInteger count = new AtomicInteger();
    doAnswer(
            new Answer<Object>() {
              @Override
              public Object answer(InvocationOnMock invocation) throws Throwable {
                return count.incrementAndGet();
              }
            })
        .when(mockChannel)
        .getNextPublishSeqNo();

    CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory);
    ccf.setPublisherConfirms(true);
    final RabbitTemplate template = new RabbitTemplate(ccf);

    final AtomicBoolean confirmed = new AtomicBoolean();
    template.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            confirmed.set(true);
          }
        });
    template.convertAndSend(ROUTE, (Object) "message", new CorrelationData("abc"));
    Thread.sleep(100);
    template.convertAndSend(ROUTE, (Object) "message", new CorrelationData("def"));
    Collection<CorrelationData> unconfirmed = template.getUnconfirmed(50);
    assertEquals(1, unconfirmed.size());
    assertEquals("abc", unconfirmed.iterator().next().getId());
    assertFalse(confirmed.get());
    Thread.sleep(100);
    unconfirmed = template.getUnconfirmed(50);
    assertEquals(1, unconfirmed.size());
    assertEquals("def", unconfirmed.iterator().next().getId());
    assertFalse(confirmed.get());
  }

  @Test
  public void testPublisherConfirmMultiple() throws Exception {
    ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class);
    Connection mockConnection = mock(Connection.class);
    Channel mockChannel = mock(Channel.class);

    when(mockConnectionFactory.newConnection((ExecutorService) null)).thenReturn(mockConnection);
    when(mockConnection.isOpen()).thenReturn(true);
    PublisherCallbackChannelImpl callbackChannel = new PublisherCallbackChannelImpl(mockChannel);
    when(mockConnection.createChannel()).thenReturn(callbackChannel);

    final AtomicInteger count = new AtomicInteger();
    doAnswer(
            new Answer<Object>() {
              @Override
              public Object answer(InvocationOnMock invocation) throws Throwable {
                return count.incrementAndGet();
              }
            })
        .when(mockChannel)
        .getNextPublishSeqNo();

    CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory);
    ccf.setPublisherConfirms(true);
    final RabbitTemplate template = new RabbitTemplate(ccf);

    final CountDownLatch latch = new CountDownLatch(2);
    template.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            if (ack) {
              latch.countDown();
            }
          }
        });
    template.convertAndSend(ROUTE, (Object) "message", new CorrelationData("abc"));
    template.convertAndSend(ROUTE, (Object) "message", new CorrelationData("def"));
    callbackChannel.handleAck(2, true);
    assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
    Collection<CorrelationData> unconfirmed = template.getUnconfirmed(-1);
    assertNull(unconfirmed);
  }

  /**
   * Tests that piggy-backed confirms (multiple=true) are distributed to the proper template.
   *
   * @throws Exception
   */
  @Test
  public void testPublisherConfirmMultipleWithTwoListeners() throws Exception {
    ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class);
    Connection mockConnection = mock(Connection.class);
    Channel mockChannel = mock(Channel.class);

    when(mockConnectionFactory.newConnection((ExecutorService) null)).thenReturn(mockConnection);
    when(mockConnection.isOpen()).thenReturn(true);
    PublisherCallbackChannelImpl callbackChannel = new PublisherCallbackChannelImpl(mockChannel);
    when(mockConnection.createChannel()).thenReturn(callbackChannel);

    final AtomicInteger count = new AtomicInteger();
    doAnswer(
            new Answer<Object>() {
              @Override
              public Object answer(InvocationOnMock invocation) throws Throwable {
                return count.incrementAndGet();
              }
            })
        .when(mockChannel)
        .getNextPublishSeqNo();

    CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory);
    ccf.setPublisherConfirms(true);
    final RabbitTemplate template1 = new RabbitTemplate(ccf);

    final Set<String> confirms = new HashSet<String>();
    final CountDownLatch latch1 = new CountDownLatch(1);
    template1.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            if (ack) {
              confirms.add(correlationData.getId() + "1");
              latch1.countDown();
            }
          }
        });
    final RabbitTemplate template2 = new RabbitTemplate(ccf);

    final CountDownLatch latch2 = new CountDownLatch(1);
    template2.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            if (ack) {
              confirms.add(correlationData.getId() + "2");
              latch2.countDown();
            }
          }
        });
    template1.convertAndSend(ROUTE, (Object) "message", new CorrelationData("abc"));
    template2.convertAndSend(ROUTE, (Object) "message", new CorrelationData("def"));
    template2.convertAndSend(ROUTE, (Object) "message", new CorrelationData("ghi"));
    callbackChannel.handleAck(3, true);
    assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS));
    assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS));
    Collection<CorrelationData> unconfirmed1 = template1.getUnconfirmed(-1);
    assertNull(unconfirmed1);
    Collection<CorrelationData> unconfirmed2 = template2.getUnconfirmed(-1);
    assertNull(unconfirmed2);
    assertTrue(confirms.contains("abc1"));
    assertTrue(confirms.contains("def2"));
    assertTrue(confirms.contains("ghi2"));
    assertEquals(3, confirms.size());
  }

  /**
   * AMQP-262 Sets up a situation where we are processing 'multi' acks at the same time as adding a
   * new pending ack to the map. Test verifies we don't get a {@link
   * ConcurrentModificationException}.
   */
  @SuppressWarnings({"rawtypes", "unchecked", "deprecation"})
  @Test
  public void testConcurrentConfirms() throws Exception {
    ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class);
    Connection mockConnection = mock(Connection.class);
    Channel mockChannel = mock(Channel.class);
    when(mockChannel.getNextPublishSeqNo()).thenReturn(1L, 2L, 3L, 4L);

    when(mockConnectionFactory.newConnection((ExecutorService) null)).thenReturn(mockConnection);
    when(mockConnection.isOpen()).thenReturn(true);
    final PublisherCallbackChannelImpl channel = new PublisherCallbackChannelImpl(mockChannel);
    when(mockConnection.createChannel()).thenReturn(channel);

    CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory);
    ccf.setPublisherConfirms(true);
    ccf.setChannelCacheSize(3);
    final RabbitTemplate template = new RabbitTemplate(ccf);

    final CountDownLatch first2SentOnThread1Latch = new CountDownLatch(1);
    final CountDownLatch delayAckProcessingLatch = new CountDownLatch(1);
    final CountDownLatch startedProcessingMultiAcksLatch = new CountDownLatch(1);
    final CountDownLatch waitForAll3AcksLatch = new CountDownLatch(3);
    final CountDownLatch allSentLatch = new CountDownLatch(1);
    final AtomicInteger acks = new AtomicInteger();
    template.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            try {
              startedProcessingMultiAcksLatch.countDown();
              // delay processing here; ensures thread 2 put would be concurrent
              delayAckProcessingLatch.await(2, TimeUnit.SECONDS);
              // only delay first time through
              delayAckProcessingLatch.countDown();
              waitForAll3AcksLatch.countDown();
              acks.incrementAndGet();
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
        });
    Executors.newSingleThreadExecutor()
        .execute(
            new Runnable() {
              @Override
              public void run() {
                template.convertAndSend(ROUTE, (Object) "message", new CorrelationData("abc"));
                template.convertAndSend(ROUTE, (Object) "message", new CorrelationData("def"));
                first2SentOnThread1Latch.countDown();
              }
            });
    Executors.newSingleThreadExecutor()
        .execute(
            new Runnable() {
              @Override
              public void run() {
                try {
                  startedProcessingMultiAcksLatch.await();
                  template.convertAndSend(ROUTE, (Object) "message", new CorrelationData("ghi"));
                  allSentLatch.countDown();
                } catch (InterruptedException e) {
                  e.printStackTrace();
                }
              }
            });
    assertTrue(first2SentOnThread1Latch.await(10, TimeUnit.SECONDS));
    // there should be no concurrent execution exception here
    channel.handleAck(2, true);
    assertTrue(allSentLatch.await(10, TimeUnit.SECONDS));
    channel.handleAck(3, false);
    assertTrue(waitForAll3AcksLatch.await(10, TimeUnit.SECONDS));
    assertEquals(3, acks.get());

    // 3.3.1 client
    channel.basicConsume("foo", false, (Map) null, null);
    verify(mockChannel).basicConsume("foo", false, (Map) null, null);

    channel.basicQos(3, false);
    verify(mockChannel).basicQos(3, false);

    doReturn(true).when(mockChannel).flowBlocked();
    assertTrue(channel.flowBlocked());

    try {
      channel.flow(true);
      fail("Expected exception");
    } catch (UnsupportedOperationException e) {
    }

    try {
      channel.getFlow();
      fail("Expected exception");
    } catch (UnsupportedOperationException e) {
    }

    // 3.2.4 client
    /*
    		try {
    			channel.basicConsume("foo", false, (Map) null, (Consumer) null);
    			fail("Expected exception");
    		}
    		catch (UnsupportedOperationException e) {}

    		try {
    			channel.basicQos(3, false);
    			fail("Expected exception");
    		}
    		catch (UnsupportedOperationException e) {}

    		try {
    			channel.flowBlocked();
    			fail("Expected exception");
    		}
    		catch (UnsupportedOperationException e) {}

    		channel.flow(true);
    		verify(mockChannel).flow(true);

    		channel.getFlow();
    		verify(mockChannel).getFlow();
    */
  }

  @Test
  public void testNackForBadExchange() throws Exception {
    final AtomicBoolean nack = new AtomicBoolean(true);
    final AtomicReference<CorrelationData> correlation = new AtomicReference<CorrelationData>();
    final AtomicReference<String> reason = new AtomicReference<String>();
    final CountDownLatch latch = new CountDownLatch(2);
    this.templateWithConfirmsEnabled.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            nack.set(ack);
            correlation.set(correlationData);
            reason.set(cause);
            latch.countDown();
          }
        });
    Log logger =
        spy(TestUtils.getPropertyValue(connectionFactoryWithConfirmsEnabled, "logger", Log.class));
    new DirectFieldAccessor(connectionFactoryWithConfirmsEnabled)
        .setPropertyValue("logger", logger);
    final AtomicReference<String> log = new AtomicReference<String>();
    doAnswer(
            new Answer<Object>() {

              @Override
              public Object answer(InvocationOnMock invocation) throws Throwable {
                log.set((String) invocation.getArguments()[0]);
                invocation.callRealMethod();
                latch.countDown();
                return null;
              }
            })
        .when(logger)
        .error(any());

    CorrelationData correlationData = new CorrelationData("bar");
    String exchange = UUID.randomUUID().toString();
    this.templateWithConfirmsEnabled.convertAndSend(exchange, "key", "foo", correlationData);
    assertTrue(latch.await(10, TimeUnit.SECONDS));
    assertFalse(nack.get());
    assertEquals(correlationData.toString(), correlation.get().toString());
    assertThat(reason.get(), containsString("NOT_FOUND - no exchange '" + exchange));
    assertThat(log.get(), containsString("NOT_FOUND - no exchange '" + exchange));
  }

  @Test
  public void testConfirmReceivedAfterPublisherCallbackChannelScheduleClose() throws Exception {
    final CountDownLatch latch = new CountDownLatch(40);
    templateWithConfirmsEnabled.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            latch.countDown();
          }
        });

    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 20; i++) {
      executorService.execute(
          new Runnable() {

            @Override
            public void run() {
              templateWithConfirmsEnabled.convertAndSend(
                  ROUTE, (Object) "message", new CorrelationData("abc"));
              templateWithConfirmsEnabled.convertAndSend(
                  "BAD_ROUTE", (Object) "bad", new CorrelationData("cba"));
            }
          });
    }

    assertTrue(latch.await(10, TimeUnit.SECONDS));
    assertNull(templateWithConfirmsEnabled.getUnconfirmed(-1));
  }

  // AMQP-506 ConcurrentModificationException
  @Test
  public void testPublisherConfirmGetUnconfirmedConcurrency() throws Exception {
    ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class);
    Connection mockConnection = mock(Connection.class);
    Channel mockChannel = mock(Channel.class);
    when(mockChannel.isOpen()).thenReturn(true);
    final AtomicLong seq = new AtomicLong();
    doAnswer(
            new Answer<Long>() {

              @Override
              public Long answer(InvocationOnMock invocation) throws Throwable {
                return seq.incrementAndGet();
              }
            })
        .when(mockChannel)
        .getNextPublishSeqNo();

    when(mockConnectionFactory.newConnection((ExecutorService) null)).thenReturn(mockConnection);
    when(mockConnection.isOpen()).thenReturn(true);
    doReturn(mockChannel).when(mockConnection).createChannel();

    CachingConnectionFactory ccf = new CachingConnectionFactory(mockConnectionFactory);
    ccf.setPublisherConfirms(true);
    final RabbitTemplate template = new RabbitTemplate(ccf);

    final AtomicBoolean confirmed = new AtomicBoolean();
    template.setConfirmCallback(
        new ConfirmCallback() {

          @Override
          public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            confirmed.set(true);
          }
        });
    ExecutorService exec = Executors.newSingleThreadExecutor();
    final AtomicBoolean sentAll = new AtomicBoolean();
    exec.execute(
        new Runnable() {

          @Override
          public void run() {
            for (int i = 0; i < 10000; i++) {
              template.convertAndSend(ROUTE, (Object) "message", new CorrelationData("abc"));
            }
            sentAll.set(true);
          }
        });
    Collection<CorrelationData> unconfirmed = template.getUnconfirmed(-1);
    long t1 = System.currentTimeMillis();
    while (!sentAll.get() && System.currentTimeMillis() < t1 + 20000) {
      unconfirmed = template.getUnconfirmed(-1);
    }
    assertTrue(sentAll.get());
    assertFalse(confirmed.get());
  }
}
@RunWith(Parameterized.class)
public class MessageListenerContainerLifecycleIntegrationTests {

  private static Log logger =
      LogFactory.getLog(MessageListenerContainerLifecycleIntegrationTests.class);

  private static Queue queue = new Queue("test.queue");

  private enum TransactionMode {
    ON,
    OFF;

    public boolean isTransactional() {
      return this == ON;
    }
  }

  private enum Concurrency {
    LOW(1);
    private final int value;

    private Concurrency(int value) {
      this.value = value;
    }

    public int value() {
      return this.value;
    }
  }

  private enum MessageCount {
    LOW(1),
    HIGH(200);
    private final int value;

    private MessageCount(int value) {
      this.value = value;
    }

    public int value() {
      return this.value;
    }
  }

  private RabbitTemplate template = new RabbitTemplate();

  private final int concurrentConsumers;

  private final boolean transactional;

  @Rule public BrokerRunning brokerIsRunning = BrokerRunning.isRunningWithEmptyQueue(queue);

  private final int messageCount;

  public MessageListenerContainerLifecycleIntegrationTests(
      MessageCount messageCount, Concurrency concurrency, TransactionMode transacted) {
    this.messageCount = messageCount.value();
    this.concurrentConsumers = concurrency.value();
    this.transactional = transacted.isTransactional();
  }

  @Parameters
  public static List<Object[]> getParameters() {
    @SuppressWarnings("unused")
    Object[] debug = new Object[] {MessageCount.LOW, Concurrency.LOW, TransactionMode.OFF};
    // return Collections.singletonList(debug);
    return Arrays.asList( //
        new Object[] {MessageCount.HIGH, Concurrency.LOW, TransactionMode.ON}, //
        new Object[] {MessageCount.HIGH, Concurrency.LOW, TransactionMode.OFF});
  }

  @Before
  public void declareQueue() {
    CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
    connectionFactory.setChannelCacheSize(concurrentConsumers);
    // connectionFactory.setPort(5673);
    template.setConnectionFactory(connectionFactory);
  }

  @Test
  public void testListenerSunnyDay() throws Exception {
    CountDownLatch latch = new CountDownLatch(messageCount);
    for (int i = 0; i < messageCount; i++) {
      template.convertAndSend(queue.getName(), i + "foo");
    }
    SimpleMessageListenerContainer container =
        new SimpleMessageListenerContainer(template.getConnectionFactory());
    container.setMessageListener(new MessageListenerAdapter(new PojoListener(latch)));
    container.setChannelTransacted(transactional);
    container.setConcurrentConsumers(concurrentConsumers);
    container.setQueueName(queue.getName());
    container.afterPropertiesSet();
    container.start();
    try {
      boolean waited = latch.await(50, TimeUnit.MILLISECONDS);
      assertFalse("Expected time out waiting for message", waited);
      container.stop();
      Thread.sleep(500L);
      container.start();
      if (transactional) {
        waited = latch.await(5, TimeUnit.SECONDS);
        assertTrue("Timed out waiting for message", waited);
      } else {
        waited = latch.await(500, TimeUnit.MILLISECONDS);
        // If non-transactional we half expect to lose messages
        assertFalse("Expected time out waiting for message", waited);
      }
    } finally {
      // Wait for broker communication to finish before trying to stop
      // container
      Thread.sleep(300L);
      container.shutdown();
    }
    assertNull(template.receiveAndConvert(queue.getName()));
  }

  public static class PojoListener {
    private 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 {
        logger.debug(value + count.getAndIncrement());
        if (fail) {
          throw new RuntimeException("Planned failure");
        }
      } finally {
        latch.countDown();
      }
    }
  }
}
/**
 * @author Dave Syer
 * @author Gunnar Hillert
 * @author Gary Russell
 * @since 1.0
 */
public class MessageListenerTxSizeIntegrationTests {

  private static Log logger = LogFactory.getLog(MessageListenerTxSizeIntegrationTests.class);

  private final Queue queue = new Queue("test.queue");

  private final RabbitTemplate template = new RabbitTemplate();

  private final int concurrentConsumers = 1;

  private final int messageCount = 12;

  private final int txSize = 4;

  private boolean transactional = true;

  private SimpleMessageListenerContainer container;

  @Rule
  public Log4jLevelAdjuster logLevels =
      new Log4jLevelAdjuster(
          Level.ERROR,
          RabbitTemplate.class,
          SimpleMessageListenerContainer.class,
          BlockingQueueConsumer.class);

  @Rule public BrokerRunning brokerIsRunning = BrokerRunning.isRunningWithEmptyQueues(queue);

  @Before
  public void createConnectionFactory() {
    CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
    connectionFactory.setChannelCacheSize(concurrentConsumers);
    connectionFactory.setPort(BrokerTestUtils.getPort());
    template.setConnectionFactory(connectionFactory);
  }

  @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 testListenerTransactionalSunnyDay() throws Exception {
    transactional = true;
    CountDownLatch latch = new CountDownLatch(messageCount);
    container = createContainer(new TestListener(latch, false));
    for (int i = 0; i < messageCount; i++) {
      template.convertAndSend(queue.getName(), i + "foo");
    }
    int timeout = Math.min(1 + messageCount / (4 * concurrentConsumers), 30);
    logger.debug("Waiting for messages with timeout = " + timeout + " (s)");
    boolean waited = latch.await(timeout, TimeUnit.SECONDS);
    assertTrue("Timed out waiting for message", waited);
    assertNull(template.receiveAndConvert(queue.getName()));
  }

  @Test
  public void testListenerTransactionalFails() throws Exception {
    transactional = true;
    CountDownLatch latch = new CountDownLatch(messageCount);
    container = createContainer(new TestListener(latch, true));
    for (int i = 0; i < txSize; i++) {
      template.convertAndSend(queue.getName(), i + "foo");
    }
    int timeout = Math.min(1 + messageCount / (4 * concurrentConsumers), 30);
    logger.debug("Waiting for messages with timeout = " + timeout + " (s)");
    boolean waited = latch.await(timeout, TimeUnit.SECONDS);
    assertTrue("Timed out waiting for message", waited);
    assertNull(template.receiveAndConvert(queue.getName()));
  }

  private SimpleMessageListenerContainer createContainer(Object listener) {
    SimpleMessageListenerContainer container =
        new SimpleMessageListenerContainer(template.getConnectionFactory());
    container.setMessageListener(new MessageListenerAdapter(listener));
    container.setQueueNames(queue.getName());
    container.setTxSize(txSize);
    container.setPrefetchCount(txSize);
    container.setConcurrentConsumers(concurrentConsumers);
    container.setChannelTransacted(transactional);
    container.setAcknowledgeMode(AcknowledgeMode.AUTO);
    container.afterPropertiesSet();
    container.start();
    return container;
  }

  public class TestListener implements ChannelAwareMessageListener {

    private final ThreadLocal<Integer> count = new ThreadLocal<Integer>();

    private final CountDownLatch latch;

    private final boolean fail;

    public TestListener(CountDownLatch latch, boolean fail) {
      this.latch = latch;
      this.fail = fail;
    }

    public void handleMessage(String value) {}

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
      String value = new String(message.getBody());
      try {
        logger.debug("Received: " + value);
        if (count.get() == null) {
          count.set(1);
        } else {
          count.set(count.get() + 1);
        }
        if (count.get() == txSize && fail) {
          logger.debug("Failing: " + value);
          count.set(0);
          throw new RuntimeException("Planned");
        }
      } finally {
        latch.countDown();
      }
    }
  }
}
/**
 * @author Dave Syer
 * @author Gunnar Hillert
 * @author Gary Russell
 * @since 1.0
 */
@RunWith(Parameterized.class)
public class SimpleMessageListenerContainerIntegrationTests {

  private static Log logger =
      LogFactory.getLog(SimpleMessageListenerContainerIntegrationTests.class);

  private final Queue queue = new Queue("test.queue");

  private final RabbitTemplate template = new RabbitTemplate();

  private final int concurrentConsumers;

  private final AcknowledgeMode acknowledgeMode;

  @Rule public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();

  @Rule
  public Log4jLevelAdjuster logLevels =
      new Log4jLevelAdjuster(
          Level.OFF,
          RabbitTemplate.class,
          ConditionalRejectingErrorHandler.class,
          SimpleMessageListenerContainer.class,
          BlockingQueueConsumer.class,
          CachingConnectionFactory.class);

  @Rule
  public Log4jLevelAdjuster testLogLevels =
      new Log4jLevelAdjuster(Level.DEBUG, SimpleMessageListenerContainerIntegrationTests.class);

  @Rule public BrokerRunning brokerIsRunning = BrokerRunning.isRunningWithEmptyQueues(queue);

  private final int messageCount;

  private SimpleMessageListenerContainer container;

  private final int txSize;

  private final boolean externalTransaction;

  private final boolean transactional;

  public SimpleMessageListenerContainerIntegrationTests(
      int messageCount,
      int concurrency,
      AcknowledgeMode acknowledgeMode,
      boolean transactional,
      int txSize,
      boolean externalTransaction) {
    this.messageCount = messageCount;
    this.concurrentConsumers = concurrency;
    this.acknowledgeMode = acknowledgeMode;
    this.transactional = transactional;
    this.txSize = txSize;
    this.externalTransaction = externalTransaction;
  }

  @Parameters
  public static List<Object[]> getParameters() {
    return Arrays.asList( //
        params(0, 1, 1, AcknowledgeMode.AUTO), //
        params(1, 1, 1, AcknowledgeMode.NONE), //
        params(2, 4, 1, AcknowledgeMode.AUTO), //
        extern(3, 4, 1, AcknowledgeMode.AUTO), //
        params(4, 4, 1, AcknowledgeMode.AUTO, false), //
        params(5, 2, 2, AcknowledgeMode.AUTO), //
        params(6, 2, 2, AcknowledgeMode.NONE), //
        params(7, 20, 4, AcknowledgeMode.AUTO), //
        params(8, 20, 4, AcknowledgeMode.NONE), //
        params(9, 300, 4, AcknowledgeMode.AUTO), //
        params(10, 300, 4, AcknowledgeMode.NONE), //
        params(11, 300, 4, AcknowledgeMode.AUTO, 10) //
        );
  }

  private static Object[] params(
      int i,
      int messageCount,
      int concurrency,
      AcknowledgeMode acknowledgeMode,
      boolean transactional,
      int txSize) {
    // "i" is just a counter to make it easier to identify the test in the log
    return new Object[] {messageCount, concurrency, acknowledgeMode, transactional, txSize, false};
  }

  private static Object[] params(
      int i, int messageCount, int concurrency, AcknowledgeMode acknowledgeMode, int txSize) {
    // For this test always us a transaction if it makes sense...
    return params(
        i,
        messageCount,
        concurrency,
        acknowledgeMode,
        acknowledgeMode.isTransactionAllowed(),
        txSize);
  }

  private static Object[] params(
      int i,
      int messageCount,
      int concurrency,
      AcknowledgeMode acknowledgeMode,
      boolean transactional) {
    return params(i, messageCount, concurrency, acknowledgeMode, transactional, 1);
  }

  private static Object[] params(
      int i, int messageCount, int concurrency, AcknowledgeMode acknowledgeMode) {
    return params(i, messageCount, concurrency, acknowledgeMode, 1);
  }

  private static Object[] extern(
      int i, int messageCount, int concurrency, AcknowledgeMode acknowledgeMode) {
    return new Object[] {messageCount, concurrency, acknowledgeMode, true, 1, true};
  }

  @Before
  public void declareQueue() {
    CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
    connectionFactory.setHost("localhost");
    connectionFactory.setChannelCacheSize(concurrentConsumers);
    connectionFactory.setPort(BrokerTestUtils.getPort());
    template.setConnectionFactory(connectionFactory);
  }

  @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();
    this.brokerIsRunning.removeTestQueues();
  }

  @Test
  public void testPojoListenerSunnyDay() throws Exception {
    CountDownLatch latch = new CountDownLatch(messageCount);
    doSunnyDayTest(latch, new MessageListenerAdapter(new PojoListener(latch)));
  }

  @Test
  public void testListenerSunnyDay() throws Exception {
    CountDownLatch latch = new CountDownLatch(messageCount);
    doSunnyDayTest(latch, new Listener(latch));
  }

  @Test
  public void testChannelAwareListenerSunnyDay() throws Exception {
    CountDownLatch latch = new CountDownLatch(messageCount);
    doSunnyDayTest(latch, new ChannelAwareListener(latch));
  }

  @Test
  public void testPojoListenerWithException() throws Exception {
    CountDownLatch latch = new CountDownLatch(messageCount);
    doListenerWithExceptionTest(latch, new MessageListenerAdapter(new PojoListener(latch, true)));
  }

  @Test
  public void testListenerWithException() throws Exception {
    CountDownLatch latch = new CountDownLatch(messageCount);
    doListenerWithExceptionTest(latch, new Listener(latch, true));
  }

  @Test
  public void testChannelAwareListenerWithException() throws Exception {
    CountDownLatch latch = new CountDownLatch(messageCount);
    doListenerWithExceptionTest(latch, new ChannelAwareListener(latch, true));
  }

  private void doSunnyDayTest(CountDownLatch latch, Object listener) throws Exception {
    container = createContainer(listener);
    for (int i = 0; i < messageCount; i++) {
      template.convertAndSend(queue.getName(), i + "foo");
    }
    boolean waited = latch.await(Math.max(10, messageCount / 20), TimeUnit.SECONDS);
    assertTrue("Timed out waiting for message", waited);
    assertNull(template.receiveAndConvert(queue.getName()));
  }

  private void doListenerWithExceptionTest(CountDownLatch latch, Object listener) throws Exception {
    container = createContainer(listener);
    if (acknowledgeMode.isTransactionAllowed()) {
      // Should only need one message if it is going to fail
      for (int i = 0; i < concurrentConsumers; i++) {
        template.convertAndSend(queue.getName(), i + "foo");
      }
    } else {
      for (int i = 0; i < messageCount; i++) {
        template.convertAndSend(queue.getName(), i + "foo");
      }
    }
    try {
      boolean waited = latch.await(10 + Math.max(1, messageCount / 10), TimeUnit.SECONDS);
      assertTrue("Timed out waiting for message", waited);
    } finally {
      // Wait for broker communication to finish before trying to stop
      // container
      Thread.sleep(300L);
      container.shutdown();
      Thread.sleep(300L);
    }
    if (acknowledgeMode.isTransactionAllowed()) {
      assertNotNull(template.receiveAndConvert(queue.getName()));
    } else {
      assertNull(template.receiveAndConvert(queue.getName()));
    }
  }

  private SimpleMessageListenerContainer createContainer(Object listener) {
    SimpleMessageListenerContainer container =
        new SimpleMessageListenerContainer(template.getConnectionFactory());
    container.setMessageListener(listener);
    container.setQueueNames(queue.getName());
    container.setTxSize(txSize);
    container.setPrefetchCount(txSize);
    container.setConcurrentConsumers(concurrentConsumers);
    container.setChannelTransacted(transactional);
    container.setAcknowledgeMode(acknowledgeMode);
    // requires RabbitMQ 3.2.x
    //		container.setConsumerArguments(Collections. <String, Object> singletonMap("x-priority",
    // Integer.valueOf(10)));
    if (externalTransaction) {
      container.setTransactionManager(new TestTransactionManager());
    }
    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();
      }
    }
  }

  public static class Listener implements MessageListener {
    private final AtomicInteger count = new AtomicInteger();

    private final CountDownLatch latch;

    private final boolean fail;

    public Listener(CountDownLatch latch) {
      this(latch, false);
    }

    public Listener(CountDownLatch latch, boolean fail) {
      this.latch = latch;
      this.fail = fail;
    }

    @Override
    public void onMessage(Message message) {
      String value = new String(message.getBody());
      try {
        int counter = count.getAndIncrement();
        if (logger.isDebugEnabled() && counter % 100 == 0) {
          logger.debug(value + counter);
        }
        if (fail) {
          throw new RuntimeException("Planned failure");
        }
      } finally {
        latch.countDown();
      }
    }
  }

  public static class ChannelAwareListener implements ChannelAwareMessageListener {
    private final AtomicInteger count = new AtomicInteger();

    private final CountDownLatch latch;

    private final boolean fail;

    public ChannelAwareListener(CountDownLatch latch) {
      this(latch, false);
    }

    public ChannelAwareListener(CountDownLatch latch, boolean fail) {
      this.latch = latch;
      this.fail = fail;
    }

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
      String value = new String(message.getBody());
      try {
        int counter = count.getAndIncrement();
        if (logger.isDebugEnabled() && counter % 100 == 0) {
          logger.debug(value + counter);
        }
        if (fail) {
          throw new RuntimeException("Planned failure");
        }
      } finally {
        latch.countDown();
      }
    }
  }

  @SuppressWarnings("serial")
  private class TestTransactionManager extends AbstractPlatformTransactionManager {

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition)
        throws TransactionException {}

    @Override
    protected void doCommit(DefaultTransactionStatus status) throws TransactionException {}

    @Override
    protected Object doGetTransaction() throws TransactionException {
      return new Object();
    }

    @Override
    protected void doRollback(DefaultTransactionStatus status) throws TransactionException {}
  }
}