Java tutorial
/* * Copyright 2002-2014 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 java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.Declarable; import org.springframework.amqp.core.Exchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory.CacheMode; import org.springframework.amqp.rabbit.connection.Connection; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionListener; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.util.Assert; import com.rabbitmq.client.AMQP.Queue.DeclareOk; import com.rabbitmq.client.Channel; /** * RabbitMQ implementation of portable AMQP administrative operations for AMQP >= 0.9.1 * * @author Mark Pollack * @author Mark Fisher * @author Dave Syer * @author Ed Scriven * @author Gary Russell * @author Artem Bilan */ public class RabbitAdmin implements AmqpAdmin, ApplicationContextAware, InitializingBean { protected static final String DEFAULT_EXCHANGE_NAME = ""; protected static final Object QUEUE_NAME = "QUEUE_NAME"; protected static final Object QUEUE_MESSAGE_COUNT = "QUEUE_MESSAGE_COUNT"; protected static final Object QUEUE_CONSUMER_COUNT = "QUEUE_CONSUMER_COUNT"; /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); private final RabbitTemplate rabbitTemplate; private volatile boolean running = false; private volatile boolean autoStartup = true; private volatile ApplicationContext applicationContext; private volatile boolean ignoreDeclarationExceptions; private final Object lifecycleMonitor = new Object(); private final ConnectionFactory connectionFactory; public RabbitAdmin(ConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; Assert.notNull(connectionFactory, "ConnectionFactory must not be null"); this.rabbitTemplate = new RabbitTemplate(connectionFactory); } public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } public void setIgnoreDeclarationExceptions(boolean ignoreDeclarationExceptions) { this.ignoreDeclarationExceptions = ignoreDeclarationExceptions; } public RabbitTemplate getRabbitTemplate() { return this.rabbitTemplate; } // Exchange operations @Override public void declareExchange(final Exchange exchange) { this.rabbitTemplate.execute(new ChannelCallback<Object>() { @Override public Object doInRabbit(Channel channel) throws Exception { declareExchanges(channel, exchange); return null; } }); } @Override @ManagedOperation public boolean deleteExchange(final String exchangeName) { return this.rabbitTemplate.execute(new ChannelCallback<Boolean>() { @Override public Boolean doInRabbit(Channel channel) throws Exception { if (isDeletingDefaultExchange(exchangeName)) { return true; } try { channel.exchangeDelete(exchangeName); } catch (IOException e) { return false; } return true; } }); } // Queue operations /** * Declare the given queue. * If the queue doesn't have a value for 'name' property, * the queue name will be generated by Broker and returned from this method. * But the 'name' property of the queue remains as is. */ @Override @ManagedOperation public String declareQueue(final Queue queue) { return this.rabbitTemplate.execute(new ChannelCallback<String>() { @Override public String doInRabbit(Channel channel) throws Exception { return declareQueues(channel, queue)[0].getQueue(); } }); } /** * Declares a server-named exclusive, autodelete, non-durable queue. */ @Override @ManagedOperation public Queue declareQueue() { DeclareOk declareOk = this.rabbitTemplate.execute(new ChannelCallback<DeclareOk>() { @Override public DeclareOk doInRabbit(Channel channel) throws Exception { return channel.queueDeclare(); } }); Queue queue = new Queue(declareOk.getQueue(), false, true, true); return queue; } @Override @ManagedOperation public boolean deleteQueue(final String queueName) { return this.rabbitTemplate.execute(new ChannelCallback<Boolean>() { @Override public Boolean doInRabbit(Channel channel) throws Exception { try { channel.queueDelete(queueName); } catch (IOException e) { return false; } return true; } }); } @Override @ManagedOperation public void deleteQueue(final String queueName, final boolean unused, final boolean empty) { this.rabbitTemplate.execute(new ChannelCallback<Object>() { @Override public Object doInRabbit(Channel channel) throws Exception { channel.queueDelete(queueName, unused, empty); return null; } }); } @Override @ManagedOperation public void purgeQueue(final String queueName, final boolean noWait) { this.rabbitTemplate.execute(new ChannelCallback<Object>() { @Override public Object doInRabbit(Channel channel) throws Exception { channel.queuePurge(queueName); return null; } }); } // Binding @Override @ManagedOperation public void declareBinding(final Binding binding) { this.rabbitTemplate.execute(new ChannelCallback<Object>() { @Override public Object doInRabbit(Channel channel) throws Exception { declareBindings(channel, binding); return null; } }); } @Override @ManagedOperation public void removeBinding(final Binding binding) { rabbitTemplate.execute(new ChannelCallback<Object>() { @Override public Object doInRabbit(Channel channel) throws Exception { if (binding.isDestinationQueue()) { if (isRemovingImplicitQueueBinding(binding)) { return null; } channel.queueUnbind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(), binding.getArguments()); } else { channel.exchangeUnbind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(), binding.getArguments()); } return null; } }); } /** * Returns 3 properties {@link #QUEUE_NAME}, {@link #QUEUE_MESSAGE_COUNT}, * {@link #QUEUE_CONSUMER_COUNT}, or null if the queue doesn't exist. */ @Override public Properties getQueueProperties(final String queueName) { Assert.hasText(queueName, "'queueName' cannot be null or empty"); return this.rabbitTemplate.execute(new ChannelCallback<Properties>() { @Override public Properties doInRabbit(Channel channel) throws Exception { try { DeclareOk declareOk = channel.queueDeclarePassive(queueName); Properties props = new Properties(); props.put(QUEUE_NAME, declareOk.getQueue()); props.put(QUEUE_MESSAGE_COUNT, declareOk.getMessageCount()); props.put(QUEUE_CONSUMER_COUNT, declareOk.getConsumerCount()); return props; } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("Queue '" + queueName + "' does not exist"); } return null; } } }); } // Lifecycle implementation public boolean isAutoStartup() { return this.autoStartup; } /** * If {@link #setAutoStartup(boolean) autoStartup} is set to true, registers a callback on the * {@link ConnectionFactory} to declare all exchanges and queues in the enclosing application context. If the * callback fails then it may cause other clients of the connection factory to fail, but since only exchanges, * queues and bindings are declared failure is not expected. * * @see InitializingBean#afterPropertiesSet() * @see #initialize() */ @Override public void afterPropertiesSet() { synchronized (this.lifecycleMonitor) { if (this.running || !this.autoStartup) { return; } if (this.connectionFactory instanceof CachingConnectionFactory && ((CachingConnectionFactory) this.connectionFactory).getCacheMode() == CacheMode.CONNECTION) { logger.warn("RabbitAdmin auto declaration is not supported with CacheMode.CONNECTION"); return; } this.connectionFactory.addConnectionListener(new ConnectionListener() { // Prevent stack overflow... private final AtomicBoolean initializing = new AtomicBoolean(false); @Override public void onCreate(Connection connection) { if (!initializing.compareAndSet(false, true)) { // If we are already initializing, we don't need to do it again... return; } try { /* * ...but it is possible for this to happen twice in the same ConnectionFactory (if more than * one concurrent Connection is allowed). It's idempotent, so no big deal (a bit of network * chatter). In fact it might even be a good thing: exclusive queues only make sense if they are * declared for every connection. If anyone has a problem with it: use auto-startup="false". */ initialize(); } finally { initializing.compareAndSet(true, false); } } @Override public void onClose(Connection connection) { } }); this.running = true; } } /** * Declares all the exchanges, queues and bindings in the enclosing application context, if any. It should be safe * (but unnecessary) to call this method more than once. */ public void initialize() { if (this.applicationContext == null) { if (this.logger.isDebugEnabled()) { this.logger.debug( "no ApplicationContext has been set, cannot auto-declare Exchanges, Queues, and Bindings"); } return; } logger.debug("Initializing declarations"); final Collection<Exchange> exchanges = filterDeclarables( applicationContext.getBeansOfType(Exchange.class).values()); final Collection<Queue> queues = filterDeclarables(applicationContext.getBeansOfType(Queue.class).values()); final Collection<Binding> bindings = filterDeclarables( applicationContext.getBeansOfType(Binding.class).values()); for (Exchange exchange : exchanges) { if (!exchange.isDurable()) { logger.warn("Auto-declaring a non-durable Exchange (" + exchange.getName() + "). It will be deleted by the broker if it shuts down, and can be redeclared by closing and reopening the connection."); } if (exchange.isAutoDelete()) { logger.warn("Auto-declaring an auto-delete Exchange (" + exchange.getName() + "). It will be deleted by the broker if not in use (if all bindings are deleted), but will only be redeclared if the connection is closed and reopened."); } } for (Queue queue : queues) { if (!queue.isDurable()) { logger.warn("Auto-declaring a non-durable Queue (" + queue.getName() + "). It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost."); } if (queue.isAutoDelete()) { logger.warn("Auto-declaring an auto-delete Queue (" + queue.getName() + "). It will be deleted by the broker if not in use, and all messages will be lost. Redeclared when the connection is closed and reopened."); } if (queue.isExclusive()) { logger.warn("Auto-declaring an exclusive Queue (" + queue.getName() + "). It cannot be accessed by consumers on another connection, and will be redeclared if the connection is reopened."); } } rabbitTemplate.execute(new ChannelCallback<Object>() { @Override public Object doInRabbit(Channel channel) throws Exception { declareExchanges(channel, exchanges.toArray(new Exchange[exchanges.size()])); declareQueues(channel, queues.toArray(new Queue[queues.size()])); declareBindings(channel, bindings.toArray(new Binding[bindings.size()])); return null; } }); logger.debug("Declarations finished"); } /** * Remove any instances that should not be declared by this admin. * @param declarables the collection of {@link Declarable}s. * @return a new collection containing {@link Declarable}s that should be declared by this * admin. */ private <T extends Declarable> Collection<T> filterDeclarables(Collection<T> declarables) { Collection<T> filtered = new ArrayList<T>(); for (T declarable : declarables) { Collection<?> adminsWithWhichToDeclare = declarable.getDeclaringAdmins(); if (declarable.shouldDeclare() && (adminsWithWhichToDeclare.isEmpty() || adminsWithWhichToDeclare.contains(this))) { filtered.add(declarable); } } return filtered; } // private methods for declaring Exchanges, Queues, and Bindings on a Channel private void declareExchanges(final Channel channel, final Exchange... exchanges) throws IOException { for (final Exchange exchange : exchanges) { if (logger.isDebugEnabled()) { logger.debug("declaring Exchange '" + exchange.getName() + "'"); } if (!isDeclaringDefaultExchange(exchange)) { try { channel.exchangeDeclare(exchange.getName(), exchange.getType(), exchange.isDurable(), exchange.isAutoDelete(), exchange.getArguments()); } catch (IOException e) { if (this.ignoreDeclarationExceptions) { if (logger.isWarnEnabled()) { logger.warn("Failed to declare exchange:" + exchange + ", continuing...", e); } } else { throw e; } } } } } private DeclareOk[] declareQueues(final Channel channel, final Queue... queues) throws IOException { DeclareOk[] declareOks = new DeclareOk[queues.length]; for (int i = 0; i < queues.length; i++) { Queue queue = queues[i]; if (!queue.getName().startsWith("amq.")) { if (logger.isDebugEnabled()) { logger.debug("declaring Queue '" + queue.getName() + "'"); } try { DeclareOk declareOk = channel.queueDeclare(queue.getName(), queue.isDurable(), queue.isExclusive(), queue.isAutoDelete(), queue.getArguments()); declareOks[i] = declareOk; } catch (IOException e) { if (this.ignoreDeclarationExceptions) { if (logger.isWarnEnabled()) { logger.warn("Failed to declare queue:" + queue + ", continuing...", e); } } else { throw e; } } } else if (logger.isDebugEnabled()) { logger.debug("Queue with name that starts with 'amq.' cannot be declared."); } } return declareOks; } private void declareBindings(final Channel channel, final Binding... bindings) throws IOException { for (Binding binding : bindings) { if (logger.isDebugEnabled()) { logger.debug("Binding destination [" + binding.getDestination() + " (" + binding.getDestinationType() + ")] to exchange [" + binding.getExchange() + "] with routing key [" + binding.getRoutingKey() + "]"); } try { if (binding.isDestinationQueue()) { if (!isDeclaringImplicitQueueBinding(binding)) { channel.queueBind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(), binding.getArguments()); } } else { channel.exchangeBind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(), binding.getArguments()); } } catch (IOException e) { if (this.ignoreDeclarationExceptions) { if (logger.isWarnEnabled()) { logger.warn("Failed to declare binding:" + binding + ", continuing...", e); } } else { throw e; } } } } private boolean isDeclaringDefaultExchange(Exchange exchange) { if (isDefaultExchange(exchange.getName())) { if (logger.isDebugEnabled()) { logger.debug("Default exchange is pre-declared by server."); } return true; } return false; } private boolean isDeletingDefaultExchange(String exchangeName) { if (isDefaultExchange(exchangeName)) { if (logger.isDebugEnabled()) { logger.debug("Default exchange cannot be deleted."); } return true; } return false; } private boolean isDefaultExchange(String exchangeName) { return DEFAULT_EXCHANGE_NAME.equals(exchangeName); } private boolean isDeclaringImplicitQueueBinding(Binding binding) { if (isImplicitQueueBinding(binding)) { if (logger.isDebugEnabled()) { logger.debug( "The default exchange is implicitly bound to every queue, with a routing key equal to the queue name."); } return true; } return false; } private boolean isRemovingImplicitQueueBinding(Binding binding) { if (isImplicitQueueBinding(binding)) { if (logger.isDebugEnabled()) { logger.debug("Cannot remove implicit default exchange binding to queue."); } return true; } return false; } private boolean isImplicitQueueBinding(Binding binding) { return isDefaultExchange(binding.getExchange()) && binding.getDestination().equals(binding.getRoutingKey()); } }