org.springframework.amqp.rabbit.core.RabbitAdmin.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.amqp.rabbit.core.RabbitAdmin.java

Source

/*
 * 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());
    }
}