/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.amqp.rabbit.core; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.UUID; import org.junit.After; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.springframework.amqp.AmqpIOException; import org.springframework.amqp.core.AbstractExchange; import org.springframework.amqp.core.AnonymousQueue; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.Binding.DestinationType; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Exchange; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageBuilder; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.connection.AutoRecoverConnectionNotCurrentlyOpenException; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.RabbitUtils; import org.springframework.amqp.rabbit.junit.BrokerRunning; import org.springframework.amqp.rabbit.junit.BrokerTestUtils; import org.springframework.context.support.GenericApplicationContext; import com.rabbitmq.client.AMQP.Queue.DeclareOk; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; /** * @author Dave Syer * @author Ed Scriven * @author Gary Russell * @author Gunnar Hillert * @author Artem Bilan */ public class RabbitAdminIntegrationTests { private final CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); @Rule public BrokerRunning brokerIsRunning = BrokerRunning.isBrokerAndManagementRunning(); private GenericApplicationContext context; private RabbitAdmin rabbitAdmin; public RabbitAdminIntegrationTests() { connectionFactory.setPort(BrokerTestUtils.getPort()); } @Before public void init() { connectionFactory.setHost("localhost"); context = new GenericApplicationContext(); context.refresh(); rabbitAdmin = new RabbitAdmin(connectionFactory); rabbitAdmin.deleteQueue("test.queue"); // Force connection factory to forget that it has been used to delete the queue connectionFactory.destroy(); rabbitAdmin.setApplicationContext(context); rabbitAdmin.setAutoStartup(true); } @After public void close() { if (context != null) { context.close(); } if (connectionFactory != null) { connectionFactory.destroy(); } } @Test public void testStartupWithLazyDeclaration() throws Exception { Queue queue = new Queue("test.queue"); context.getBeanFactory().registerSingleton("foo", queue); rabbitAdmin.afterPropertiesSet(); // A new connection is initialized so the queue is declared assertTrue(rabbitAdmin.deleteQueue(queue.getName())); } @Test(expected = AmqpIOException.class) public void testDoubleDeclarationOfExclusiveQueue() throws Exception { // Expect exception because the queue is locked when it is declared a second time. CachingConnectionFactory connectionFactory1 = new CachingConnectionFactory(); connectionFactory1.setHost("localhost"); connectionFactory1.setPort(BrokerTestUtils.getPort()); CachingConnectionFactory connectionFactory2 = new CachingConnectionFactory(); connectionFactory2.setHost("localhost"); connectionFactory2.setPort(BrokerTestUtils.getPort()); Queue queue = new Queue("test.queue", false, true, true); rabbitAdmin.deleteQueue(queue.getName()); new RabbitAdmin(connectionFactory1).declareQueue(queue); try { new RabbitAdmin(connectionFactory2).declareQueue(queue); } finally { // Need to release the connection so the exclusive queue is deleted connectionFactory1.destroy(); connectionFactory2.destroy(); } } @Test public void testDoubleDeclarationOfAutodeleteQueue() throws Exception { // No error expected here: the queue is autodeleted when the last consumer is cancelled, but this one never has // any consumers. CachingConnectionFactory connectionFactory1 = new CachingConnectionFactory(); connectionFactory1.setHost("localhost"); connectionFactory1.setPort(BrokerTestUtils.getPort()); CachingConnectionFactory connectionFactory2 = new CachingConnectionFactory(); connectionFactory2.setHost("localhost"); connectionFactory2.setPort(BrokerTestUtils.getPort()); Queue queue = new Queue("test.queue", false, false, true); new RabbitAdmin(connectionFactory1).declareQueue(queue); new RabbitAdmin(connectionFactory2).declareQueue(queue); connectionFactory1.destroy(); connectionFactory2.destroy(); } @Test public void testQueueWithAutoDelete() throws Exception { final Queue queue = new Queue("test.queue", false, true, true); context.getBeanFactory().registerSingleton("foo", queue); rabbitAdmin.afterPropertiesSet(); // Queue created on spring startup rabbitAdmin.initialize(); assertTrue(queueExists(queue)); // Stop and broker deletes queue (only verifiable in native API) connectionFactory.destroy(); assertFalse(queueExists(queue)); // Start and queue re-created by the connection listener connectionFactory.createConnection(); assertTrue(queueExists(queue)); // Queue manually deleted assertTrue(rabbitAdmin.deleteQueue(queue.getName())); assertFalse(queueExists(queue)); } @Test public void testQueueWithoutAutoDelete() throws Exception { final Queue queue = new Queue("test.queue", false, false, false); context.getBeanFactory().registerSingleton("foo", queue); rabbitAdmin.afterPropertiesSet(); // Queue created on Spring startup rabbitAdmin.initialize(); assertTrue(queueExists(queue)); // Stop and broker retains queue (only verifiable in native API) connectionFactory.destroy(); assertTrue(queueExists(queue)); // Start and queue still exists connectionFactory.createConnection(); assertTrue(queueExists(queue)); // Queue manually deleted assertTrue(rabbitAdmin.deleteQueue(queue.getName())); assertFalse(queueExists(queue)); connectionFactory.destroy(); } @Test public void testQueueWithoutName() throws Exception { final Queue queue = new Queue("", true, false, true); String generatedName = rabbitAdmin.declareQueue(queue); assertEquals("", queue.getName()); Queue queueWithGeneratedName = new Queue(generatedName, true, false, true); assertTrue(queueExists(queueWithGeneratedName)); // Stop and broker retains queue (only verifiable in native API) connectionFactory.destroy(); assertTrue(queueExists(queueWithGeneratedName)); // Start and queue still exists connectionFactory.createConnection(); assertTrue(queueExists(queueWithGeneratedName)); // Queue manually deleted assertTrue(rabbitAdmin.deleteQueue(generatedName)); assertFalse(queueExists(queueWithGeneratedName)); connectionFactory.destroy(); } @Test public void testDeclareExchangeWithDefaultExchange() throws Exception { Exchange exchange = new DirectExchange(RabbitAdmin.DEFAULT_EXCHANGE_NAME); rabbitAdmin.declareExchange(exchange); // Pass by virtue of RabbitMQ not firing a 403 reply code } @Test public void testSpringWithDefaultExchange() throws Exception { Exchange exchange = new DirectExchange(RabbitAdmin.DEFAULT_EXCHANGE_NAME); context.getBeanFactory().registerSingleton("foo", exchange); rabbitAdmin.afterPropertiesSet(); rabbitAdmin.initialize(); // Pass by virtue of RabbitMQ not firing a 403 reply code } @Test public void testDeleteExchangeWithDefaultExchange() throws Exception { boolean result = rabbitAdmin.deleteExchange(RabbitAdmin.DEFAULT_EXCHANGE_NAME); assertTrue(result); } @Test public void testDeleteExchangeWithInternalOption() throws Exception { String exchangeName = "test.exchange.internal"; AbstractExchange exchange = new DirectExchange(exchangeName); exchange.setInternal(true); rabbitAdmin.declareExchange(exchange); Exchange exchange2 = getExchange(exchangeName); assertThat(exchange2, instanceOf(DirectExchange.class)); assertTrue(exchange2.isInternal()); boolean result = rabbitAdmin.deleteExchange(exchangeName); assertTrue(result); } @Test public void testDeclareBindingWithDefaultExchangeImplicitBinding() throws Exception { Exchange exchange = new DirectExchange(RabbitAdmin.DEFAULT_EXCHANGE_NAME); String queueName = "test.queue"; final Queue queue = new Queue(queueName, false, false, false); rabbitAdmin.declareQueue(queue); Binding binding = new Binding(queueName, DestinationType.QUEUE, exchange.getName(), queueName, null); rabbitAdmin.declareBinding(binding); // Pass by virtue of RabbitMQ not firing a 403 reply code for both exchange and binding declaration assertTrue(queueExists(queue)); } @Test public void testSpringWithDefaultExchangeImplicitBinding() throws Exception { Exchange exchange = new DirectExchange(RabbitAdmin.DEFAULT_EXCHANGE_NAME); context.getBeanFactory().registerSingleton("foo", exchange); String queueName = "test.queue"; final Queue queue = new Queue(queueName, false, false, false); context.getBeanFactory().registerSingleton("bar", queue); Binding binding = new Binding(queueName, DestinationType.QUEUE, exchange.getName(), queueName, null); context.getBeanFactory().registerSingleton("baz", binding); rabbitAdmin.afterPropertiesSet(); rabbitAdmin.initialize(); // Pass by virtue of RabbitMQ not firing a 403 reply code for both exchange and binding declaration assertTrue(queueExists(queue)); } @Test public void testRemoveBindingWithDefaultExchangeImplicitBinding() throws Exception { String queueName = "test.queue"; final Queue queue = new Queue(queueName, false, false, false); rabbitAdmin.declareQueue(queue); Binding binding = new Binding(queueName, DestinationType.QUEUE, RabbitAdmin.DEFAULT_EXCHANGE_NAME, queueName, null); rabbitAdmin.removeBinding(binding); // Pass by virtue of RabbitMQ not firing a 403 reply code } @Test public void testDeclareBindingWithDefaultExchangeNonImplicitBinding() throws Exception { Exchange exchange = new DirectExchange(RabbitAdmin.DEFAULT_EXCHANGE_NAME); String queueName = "test.queue"; final Queue queue = new Queue(queueName, false, false, false); rabbitAdmin.declareQueue(queue); Binding binding = new Binding(queueName, DestinationType.QUEUE, exchange.getName(), "test.routingKey", null); try { rabbitAdmin.declareBinding(binding); } catch (AmqpIOException ex) { Throwable cause = ex; Throwable rootCause = null; while (cause != null) { rootCause = cause; cause = cause.getCause(); } assertTrue(rootCause.getMessage().contains("reply-code=403")); assertTrue(rootCause.getMessage().contains("operation not permitted on the default exchange")); } } @Test public void testSpringWithDefaultExchangeNonImplicitBinding() throws Exception { Exchange exchange = new DirectExchange(RabbitAdmin.DEFAULT_EXCHANGE_NAME); context.getBeanFactory().registerSingleton("foo", exchange); String queueName = "test.queue"; final Queue queue = new Queue(queueName, false, false, false); context.getBeanFactory().registerSingleton("bar", queue); Binding binding = new Binding(queueName, DestinationType.QUEUE, exchange.getName(), "test.routingKey", null); context.getBeanFactory().registerSingleton("baz", binding); rabbitAdmin.afterPropertiesSet(); try { rabbitAdmin.declareBinding(binding); } catch (AmqpIOException ex) { Throwable cause = ex; Throwable rootCause = null; while (cause != null) { rootCause = cause; cause = cause.getCause(); } assertTrue(rootCause.getMessage().contains("reply-code=403")); assertTrue(rootCause.getMessage().contains("operation not permitted on the default exchange")); } } @Test public void testQueueDeclareBad() { this.rabbitAdmin.setIgnoreDeclarationExceptions(true); Queue queue = new AnonymousQueue(); assertEquals(queue.getName(), this.rabbitAdmin.declareQueue(queue)); queue = new Queue(queue.getName()); assertNull(this.rabbitAdmin.declareQueue(queue)); this.rabbitAdmin.deleteQueue(queue.getName()); } @Test public void testDeclareDelayedExchange() throws Exception { DirectExchange exchange = new DirectExchange("test.delayed.exchange"); exchange.setDelayed(true); Queue queue = new Queue(UUID.randomUUID().toString(), true, false, false); String exchangeName = exchange.getName(); Binding binding = new Binding(queue.getName(), DestinationType.QUEUE, exchangeName, queue.getName(), null); try { this.rabbitAdmin.declareExchange(exchange); } catch (AmqpIOException e) { if (RabbitUtils.isExchangeDeclarationFailure(e) && e.getCause().getCause().getMessage().contains("exchange type 'x-delayed-message'")) { Assume.assumeTrue("Broker does not have the delayed message exchange plugin installed", false); } else { throw e; } } catch (AutoRecoverConnectionNotCurrentlyOpenException e) { Assume.assumeTrue("Broker does not have the delayed message exchange plugin installed", false); } this.rabbitAdmin.declareQueue(queue); this.rabbitAdmin.declareBinding(binding); RabbitTemplate template = new RabbitTemplate(this.connectionFactory); template.setReceiveTimeout(10000); template.convertAndSend(exchangeName, queue.getName(), "foo", message -> { message.getMessageProperties().setDelay(1000); return message; }); MessageProperties properties = new MessageProperties(); properties.setDelay(500); template.send(exchangeName, queue.getName(), MessageBuilder.withBody("foo".getBytes()).andProperties(properties).build()); long t1 = System.currentTimeMillis(); Message received = template.receive(queue.getName()); assertNotNull(received); assertEquals(Integer.valueOf(500), received.getMessageProperties().getReceivedDelay()); received = template.receive(queue.getName()); assertNotNull(received); assertEquals(Integer.valueOf(1000), received.getMessageProperties().getReceivedDelay()); assertThat(System.currentTimeMillis() - t1, greaterThan(950L)); Exchange exchange2 = getExchange(exchangeName); assertNotNull(exchange2); assertThat(exchange2, instanceOf(DirectExchange.class)); assertTrue(exchange2.isDelayed()); this.rabbitAdmin.deleteQueue(queue.getName()); this.rabbitAdmin.deleteExchange(exchangeName); } private Exchange getExchange(String exchangeName) throws InterruptedException { RabbitManagementTemplate rmt = new RabbitManagementTemplate(); int n = 0; Exchange exchange = rmt.getExchange(exchangeName); while (n++ < 100 && exchange == null) { Thread.sleep(100); exchange = rmt.getExchange(exchangeName); } return exchange; } /** * Verify that a queue exists using the native Rabbit API to bypass all the connection and * channel caching and callbacks in Spring AMQP. * * @param queue The queue to verify * @return True if the queue exists */ private boolean queueExists(final Queue queue) throws Exception { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("localhost"); connectionFactory.setPort(BrokerTestUtils.getPort()); Connection connection = connectionFactory.newConnection(); Channel channel = connection.createChannel(); try { DeclareOk result = channel.queueDeclarePassive(queue.getName()); return result != null; } catch (IOException e) { return e.getCause().getMessage().contains("RESOURCE_LOCKED"); } finally { connection.close(); } } }