@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 test() 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);
    when(mockConnection.createChannel()).thenReturn(mockChannel);

    when(mockChannel.isOpen()).thenReturn(true);

    Message msg =
        this.messageConverter.toMessage(
            createFieldChangedEvent(
                "/asset/assetMeter/crank-frame-dischargepressure", //$NON-NLS-1$
                "/asset/assetMeter/crank-frame-dischargepressure", //$NON-NLS-1$
                "/asset/compressor-2015", //$NON-NLS-1$
                StringUtil.parseDate("2012-09-11T07:16:13"), // $NON-NLS-1$
                null,
                null),
            null);

    final RabbitTemplate fieldChangedEventTemplate =
        new RabbitTemplate(new CachingConnectionFactory(mockConnectionFactory));
    fieldChangedEventTemplate.convertAndSend("fieldchangedeventMainQ", msg); // $NON-NLS-1$
  }
  @Test
  public void testCloseInvalidConnection() throws Exception {

    com.rabbitmq.client.ConnectionFactory mockConnectionFactory =
        mock(com.rabbitmq.client.ConnectionFactory.class);
    com.rabbitmq.client.Connection mockConnection1 = mock(com.rabbitmq.client.Connection.class);
    com.rabbitmq.client.Connection mockConnection2 = mock(com.rabbitmq.client.Connection.class);

    when(mockConnectionFactory.newConnection((ExecutorService) null))
        .thenReturn(mockConnection1)
        .thenReturn(mockConnection2);
    // simulate a dead connection
    when(mockConnection1.isOpen()).thenReturn(false);

    AbstractConnectionFactory connectionFactory = createConnectionFactory(mockConnectionFactory);

    Connection connection = connectionFactory.createConnection();
    // the dead connection should be discarded
    connection.createChannel(false);
    verify(mockConnectionFactory, times(2)).newConnection((ExecutorService) null);
    verify(mockConnection2, times(1)).createChannel();

    connectionFactory.destroy();
    verify(mockConnection2, times(1)).close();
  }
  // 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());
  }
  @Test
  public void testReplyToOneDeepCustomCorrelationKey() 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);
    when(mockConnection.createChannel()).thenReturn(mockChannel);

    final RabbitTemplate template =
        new RabbitTemplate(new SingleConnectionFactory(mockConnectionFactory));
    template.setCorrelationKey(CORRELATION_HEADER);
    Queue replyQueue = new Queue("new.replyTo");
    template.setReplyQueue(replyQueue);

    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setReplyTo("replyTo1");
    messageProperties.setCorrelationId("saveThis".getBytes());
    Message message = new Message("Hello, world!".getBytes(), messageProperties);
    final AtomicReference<String> replyTo = new AtomicReference<String>();
    final AtomicReference<String> correlationId = new AtomicReference<String>();
    doAnswer(
            new Answer<Object>() {
              public Object answer(InvocationOnMock invocation) throws Throwable {
                BasicProperties basicProps = (BasicProperties) invocation.getArguments()[4];
                replyTo.set(basicProps.getReplyTo());
                correlationId.set((String) basicProps.getHeaders().get(CORRELATION_HEADER));

                MessageProperties springProps =
                    new DefaultMessagePropertiesConverter()
                        .toMessageProperties(basicProps, null, "UTF-8");
                Message replyMessage = new Message("!dlrow olleH".getBytes(), springProps);
                template.onMessage(replyMessage);
                return null;
              }
            })
        .when(mockChannel)
        .basicPublish(
            Mockito.any(String.class),
            Mockito.any(String.class),
            Mockito.anyBoolean(),
            Mockito.anyBoolean(),
            Mockito.any(BasicProperties.class),
            Mockito.any(byte[].class));
    Message reply = template.sendAndReceive(message);
    assertNotNull(reply);

    assertNotNull(replyTo.get());
    assertEquals("new.replyTo", replyTo.get());
    assertNotNull(correlationId.get());
    assertEquals("replyTo1", reply.getMessageProperties().getReplyTo());
    assertTrue(!"saveThis".equals(correlationId.get()));
    assertEquals("replyTo1", reply.getMessageProperties().getReplyTo());
  }
  @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);
  }
  @Test
  public void testWithListenerRegisteredAfterOpen() throws IOException {

    com.rabbitmq.client.ConnectionFactory mockConnectionFactory =
        mock(com.rabbitmq.client.ConnectionFactory.class);
    com.rabbitmq.client.Connection mockConnection = mock(com.rabbitmq.client.Connection.class);

    when(mockConnectionFactory.newConnection((ExecutorService) null)).thenReturn(mockConnection);

    final AtomicInteger called = new AtomicInteger(0);
    AbstractConnectionFactory connectionFactory = createConnectionFactory(mockConnectionFactory);
    Connection con = connectionFactory.createConnection();
    assertEquals(0, called.get());

    connectionFactory.setConnectionListeners(
        Arrays.asList(
            new ConnectionListener() {
              public void onCreate(Connection connection) {
                called.incrementAndGet();
              }

              public void onClose(Connection connection) {
                called.decrementAndGet();
              }
            }));
    assertEquals(1, called.get());

    con.close();
    assertEquals(1, called.get());
    verify(mockConnection, never()).close();

    connectionFactory.createConnection();
    assertEquals(1, called.get());

    connectionFactory.destroy();
    assertEquals(0, called.get());
    verify(mockConnection, atLeastOnce()).close();

    verify(mockConnectionFactory, times(1)).newConnection((ExecutorService) null);
  }
  /**
   * 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();
    */
  }
  /**
   * 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());
  }
  @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());
  }
  /** Verifies that an up-stack RabbitTemplate uses the listener's channel (MessageListener). */
  @SuppressWarnings("unchecked")
  @Test
  public void testMessageListener() throws Exception {
    ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class);
    Connection mockConnection = mock(Connection.class);
    final Channel onlyChannel = mock(Channel.class);
    when(onlyChannel.isOpen()).thenReturn(true);

    final CachingConnectionFactory cachingConnectionFactory =
        new CachingConnectionFactory(mockConnectionFactory);

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

    final AtomicReference<Exception> tooManyChannels = new AtomicReference<Exception>();

    doAnswer(
            new Answer<Channel>() {
              boolean done;

              @Override
              public Channel answer(InvocationOnMock invocation) throws Throwable {
                if (!done) {
                  done = true;
                  return onlyChannel;
                }
                tooManyChannels.set(new Exception("More than one channel requested"));
                Channel channel = mock(Channel.class);
                when(channel.isOpen()).thenReturn(true);
                return channel;
              }
            })
        .when(mockConnection)
        .createChannel();

    final AtomicReference<Consumer> consumer = new AtomicReference<Consumer>();

    doAnswer(
            new Answer<String>() {

              @Override
              public String answer(InvocationOnMock invocation) throws Throwable {
                consumer.set((Consumer) invocation.getArguments()[6]);
                return null;
              }
            })
        .when(onlyChannel)
        .basicConsume(
            anyString(),
            anyBoolean(),
            anyString(),
            anyBoolean(),
            anyBoolean(),
            anyMap(),
            any(Consumer.class));

    final CountDownLatch commitLatch = new CountDownLatch(1);
    doAnswer(
            new Answer<String>() {

              @Override
              public String answer(InvocationOnMock invocation) throws Throwable {
                commitLatch.countDown();
                return null;
              }
            })
        .when(onlyChannel)
        .txCommit();

    final CountDownLatch latch = new CountDownLatch(1);
    SimpleMessageListenerContainer container =
        new SimpleMessageListenerContainer(cachingConnectionFactory);
    container.setMessageListener(
        new MessageListener() {
          @Override
          public void onMessage(Message message) {
            RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
            rabbitTemplate.setChannelTransacted(true);
            // should use same channel as container
            rabbitTemplate.convertAndSend("foo", "bar", "baz");
            latch.countDown();
          }
        });
    container.setQueueNames("queue");
    container.setChannelTransacted(true);
    container.setShutdownTimeout(100);
    container.afterPropertiesSet();
    container.start();

    consumer
        .get()
        .handleDelivery(
            "qux", new Envelope(1, false, "foo", "bar"), new BasicProperties(), new byte[] {0});

    assertTrue(latch.await(10, TimeUnit.SECONDS));

    Exception e = tooManyChannels.get();
    if (e != null) {
      throw e;
    }

    verify(mockConnection, Mockito.times(1)).createChannel();
    assertTrue(commitLatch.await(10, TimeUnit.SECONDS));
    verify(onlyChannel).txCommit();
    verify(onlyChannel)
        .basicPublish(
            Mockito.anyString(),
            Mockito.anyString(),
            Mockito.anyBoolean(),
            Mockito.any(BasicProperties.class),
            Mockito.any(byte[].class));

    // verify close() was never called on the channel
    DirectFieldAccessor dfa = new DirectFieldAccessor(cachingConnectionFactory);
    List<?> channels = (List<?>) dfa.getPropertyValue("cachedChannelsTransactional");
    assertEquals(0, channels.size());

    container.stop();
  }
  /**
   * Verifies that the listener channel is not exposed when so configured and up-stack
   * RabbitTemplate uses the additional channel. created when exposeListenerChannel is false
   * (ChannelAwareMessageListener).
   */
  @SuppressWarnings("unchecked")
  @Test
  public void testChannelAwareMessageListenerDontExpose() throws Exception {
    ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class);
    Connection mockConnection = mock(Connection.class);
    final Channel firstChannel = mock(Channel.class);
    when(firstChannel.isOpen()).thenReturn(true);
    final Channel secondChannel = mock(Channel.class);
    when(secondChannel.isOpen()).thenReturn(true);

    final SingleConnectionFactory singleConnectionFactory =
        new SingleConnectionFactory(mockConnectionFactory);

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

    final AtomicReference<Exception> tooManyChannels = new AtomicReference<Exception>();

    doAnswer(
            new Answer<Channel>() {
              boolean done;

              @Override
              public Channel answer(InvocationOnMock invocation) throws Throwable {
                if (!done) {
                  done = true;
                  return firstChannel;
                }
                return secondChannel;
              }
            })
        .when(mockConnection)
        .createChannel();

    final AtomicReference<Consumer> consumer = new AtomicReference<Consumer>();

    doAnswer(
            new Answer<String>() {

              @Override
              public String answer(InvocationOnMock invocation) throws Throwable {
                consumer.set((Consumer) invocation.getArguments()[6]);
                return null;
              }
            })
        .when(firstChannel)
        .basicConsume(
            anyString(),
            anyBoolean(),
            anyString(),
            anyBoolean(),
            anyBoolean(),
            anyMap(),
            any(Consumer.class));

    final CountDownLatch commitLatch = new CountDownLatch(1);
    doAnswer(
            new Answer<String>() {

              @Override
              public String answer(InvocationOnMock invocation) throws Throwable {
                commitLatch.countDown();
                return null;
              }
            })
        .when(firstChannel)
        .txCommit();

    final CountDownLatch latch = new CountDownLatch(1);
    final AtomicReference<Channel> exposed = new AtomicReference<Channel>();
    SimpleMessageListenerContainer container =
        new SimpleMessageListenerContainer(singleConnectionFactory);
    container.setMessageListener(
        new ChannelAwareMessageListener() {
          @Override
          public void onMessage(Message message, Channel channel) {
            exposed.set(channel);
            RabbitTemplate rabbitTemplate = new RabbitTemplate(singleConnectionFactory);
            rabbitTemplate.setChannelTransacted(true);
            // should use same channel as container
            rabbitTemplate.convertAndSend("foo", "bar", "baz");
            latch.countDown();
          }
        });
    container.setQueueNames("queue");
    container.setChannelTransacted(true);
    container.setExposeListenerChannel(false);
    container.setShutdownTimeout(100);
    container.afterPropertiesSet();
    container.start();

    consumer
        .get()
        .handleDelivery(
            "qux", new Envelope(1, false, "foo", "bar"), new BasicProperties(), new byte[] {0});

    assertTrue(latch.await(10, TimeUnit.SECONDS));

    Exception e = tooManyChannels.get();
    if (e != null) {
      throw e;
    }

    // once for listener, once for exposed + 0 for template (used bound)
    verify(mockConnection, Mockito.times(2)).createChannel();
    assertTrue(commitLatch.await(10, TimeUnit.SECONDS));
    verify(firstChannel).txCommit();
    verify(secondChannel).txCommit();
    verify(secondChannel)
        .basicPublish(
            Mockito.anyString(),
            Mockito.anyString(),
            Mockito.anyBoolean(),
            Mockito.any(BasicProperties.class),
            Mockito.any(byte[].class));

    assertSame(secondChannel, exposed.get());

    verify(firstChannel, Mockito.never()).close();
    verify(secondChannel, Mockito.times(1)).close();
    container.stop();
  }
  @Test
  public void testReplyToThreeDeepCustomCorrelationKey() 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);
    when(mockConnection.createChannel()).thenReturn(mockChannel);

    final RabbitTemplate template =
        new RabbitTemplate(new SingleConnectionFactory(mockConnectionFactory));
    template.setCorrelationKey(CORRELATION_HEADER);
    Queue replyQueue = new Queue("replyTo2");
    template.setReplyQueue(replyQueue);

    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setReplyTo("replyTo1");
    messageProperties.setCorrelationId("a".getBytes());
    Message message = new Message("Hello, world!".getBytes(), messageProperties);
    final AtomicInteger count = new AtomicInteger();
    final List<String> nestedReplyTo = new ArrayList<String>();
    final List<String> nestedCorrelation = new ArrayList<String>();
    doAnswer(
            new Answer<Object>() {
              public Object answer(InvocationOnMock invocation) throws Throwable {
                BasicProperties basicProps = (BasicProperties) invocation.getArguments()[4];
                nestedReplyTo.add(basicProps.getReplyTo());
                nestedCorrelation.add(basicProps.getCorrelationId());
                MessageProperties springProps =
                    new DefaultMessagePropertiesConverter()
                        .toMessageProperties(basicProps, null, "UTF-8");
                Message replyMessage = new Message("!dlrow olleH".getBytes(), springProps);
                if (count.incrementAndGet() < 2) {
                  Message anotherMessage = new Message("Second".getBytes(), springProps);
                  template.setReplyQueue(new Queue("replyTo3"));
                  replyMessage = template.sendAndReceive(anotherMessage);
                  nestedReplyTo.add(replyMessage.getMessageProperties().getReplyTo());
                  nestedCorrelation.add(
                      (String)
                          replyMessage.getMessageProperties().getHeaders().get(CORRELATION_HEADER));
                }
                template.onMessage(replyMessage);
                return null;
              }
            })
        .when(mockChannel)
        .basicPublish(
            Mockito.any(String.class),
            Mockito.any(String.class),
            Mockito.anyBoolean(),
            Mockito.anyBoolean(),
            Mockito.any(BasicProperties.class),
            Mockito.any(byte[].class));
    Message reply = template.sendAndReceive(message);
    assertNotNull(reply);

    assertEquals(3, nestedReplyTo.size());
    assertEquals("replyTo2", nestedReplyTo.get(0));
    assertEquals("replyTo3", nestedReplyTo.get(1));
    assertEquals("replyTo2", nestedReplyTo.get(2)); // intermediate reply

    assertEquals("replyTo1", reply.getMessageProperties().getReplyTo());
    assertEquals("a", new String(reply.getMessageProperties().getCorrelationId(), "UTF-8"));
  }