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-2019 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
 *
 *      https://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.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Declarable;
import org.springframework.amqp.core.Declarables;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueInformation;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory.CacheMode;
import org.springframework.amqp.rabbit.connection.ChannelProxy;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.lang.Nullable;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.rabbitmq.client.AMQP.Queue.DeclareOk;
import com.rabbitmq.client.AMQP.Queue.PurgeOk;
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
 */
@ManagedResource(description = "Admin Tasks")
public class RabbitAdmin implements AmqpAdmin, ApplicationContextAware, ApplicationEventPublisherAware,
        BeanNameAware, InitializingBean {

    private static final String UNUSED = "unused";

    private static final int DECLARE_MAX_ATTEMPTS = 5;

    private static final int DECLARE_INITIAL_RETRY_INTERVAL = 1000;

    private static final int DECLARE_MAX_RETRY_INTERVAL = 5000;

    private static final double DECLARE_RETRY_MULTIPLIER = 2.0;

    /**
     * The default exchange name.
     */
    public static final String DEFAULT_EXCHANGE_NAME = "";

    /**
     * Property key for the queue name in the {@link Properties} returned by
     * {@link #getQueueProperties(String)}.
     */
    public static final Object QUEUE_NAME = "QUEUE_NAME";

    /**
     * Property key for the message count in the {@link Properties} returned by
     * {@link #getQueueProperties(String)}.
     */
    public static final Object QUEUE_MESSAGE_COUNT = "QUEUE_MESSAGE_COUNT";

    /**
     * Property key for the consumer count in the {@link Properties} returned by
     * {@link #getQueueProperties(String)}.
     */
    public static final Object QUEUE_CONSUMER_COUNT = "QUEUE_CONSUMER_COUNT";

    private static final String DELAYED_MESSAGE_EXCHANGE = "x-delayed-message";

    /** Logger available to subclasses. */
    protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR

    private final RabbitTemplate rabbitTemplate;

    private final Object lifecycleMonitor = new Object();

    private final ConnectionFactory connectionFactory;

    private String beanName;

    private RetryTemplate retryTemplate;

    private boolean retryDisabled;

    private boolean autoStartup = true;

    private ApplicationContext applicationContext;

    private boolean ignoreDeclarationExceptions;

    private ApplicationEventPublisher applicationEventPublisher;

    private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();

    private volatile boolean running = false;

    private volatile DeclarationExceptionEvent lastDeclarationExceptionEvent;

    /**
     * Construct an instance using the provided {@link ConnectionFactory}.
     * @param connectionFactory the connection factory - must not be null.
     */
    public RabbitAdmin(ConnectionFactory connectionFactory) {
        Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
        this.connectionFactory = connectionFactory;
        this.rabbitTemplate = new RabbitTemplate(connectionFactory);
    }

    /**
     * Construct an instance using the provided {@link RabbitTemplate}. Use this
     * constructor when, for example, you want the admin operations to be performed within
     * the scope of the provided template's {@code invoke()} method.
     * @param rabbitTemplate the template - must not be null and must have a connection
     * factory.
     * @since 2.0
     */
    public RabbitAdmin(RabbitTemplate rabbitTemplate) {
        Assert.notNull(rabbitTemplate, "RabbitTemplate must not be null");
        Assert.notNull(rabbitTemplate.getConnectionFactory(),
                "RabbitTemplate's ConnectionFactory must not be null");
        this.connectionFactory = rabbitTemplate.getConnectionFactory();
        this.rabbitTemplate = rabbitTemplate;
    }

    public void setAutoStartup(boolean autoStartup) {
        this.autoStartup = autoStartup;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void setIgnoreDeclarationExceptions(boolean ignoreDeclarationExceptions) {
        this.ignoreDeclarationExceptions = ignoreDeclarationExceptions;
    }

    /**
     * @return the last {@link DeclarationExceptionEvent} that was detected in this admin.
     *
     * @since 1.6
     */
    public DeclarationExceptionEvent getLastDeclarationExceptionEvent() {
        return this.lastDeclarationExceptionEvent;
    }

    /**
     * Set a task executor to use for async operations. Currently only used
     * with {@link #purgeQueue(String, boolean)}.
     * @param taskExecutor the executor to use.
     * @since 2.1
     */
    public void setTaskExecutor(TaskExecutor taskExecutor) {
        Assert.notNull(taskExecutor, "'taskExecutor' cannot be null");
        this.taskExecutor = taskExecutor;
    }

    public RabbitTemplate getRabbitTemplate() {
        return this.rabbitTemplate;
    }

    // Exchange operations

    @Override
    public void declareExchange(final Exchange exchange) {
        try {
            this.rabbitTemplate.execute(channel -> {
                declareExchanges(channel, exchange);
                return null;
            });
        } catch (AmqpException e) {
            logOrRethrowDeclarationException(exchange, "exchange", e);
        }
    }

    @Override
    @ManagedOperation(description = "Delete an exchange from the broker")
    public boolean deleteExchange(final String exchangeName) {
        return this.rabbitTemplate.execute(channel -> { // NOSONAR never returns null
            if (isDeletingDefaultExchange(exchangeName)) {
                return true;
            }

            try {
                channel.exchangeDelete(exchangeName);
            } catch (@SuppressWarnings(UNUSED) 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.
     * The declaredName property of the queue will be updated to reflect this value.
     * @param queue the queue
     * @return the queue name if successful, null if not successful and
     * {@link #setIgnoreDeclarationExceptions(boolean) ignoreDeclarationExceptions} is
     * true.
     */
    @Override
    @ManagedOperation(description = "Declare a queue on the broker (this operation is not available remotely)")
    @Nullable
    public String declareQueue(final Queue queue) {
        try {
            return this.rabbitTemplate.execute(channel -> {
                DeclareOk[] declared = declareQueues(channel, queue);
                return declared.length > 0 ? declared[0].getQueue() : null;
            });
        } catch (AmqpException e) {
            logOrRethrowDeclarationException(queue, "queue", e);
            return null;
        }
    }

    /**
     * Declares a server-named exclusive, autodelete, non-durable queue.
     *
     * @return the queue or null if an exception occurred and
     * {@link #setIgnoreDeclarationExceptions(boolean) ignoreDeclarationExceptions}
     * is true.
     */
    @Override
    @ManagedOperation(description = "Declare a queue with a broker-generated name (this operation is not available remotely)")
    @Nullable
    public Queue declareQueue() {
        try {
            DeclareOk declareOk = this.rabbitTemplate.execute(Channel::queueDeclare);
            return new Queue(declareOk.getQueue(), false, true, true); // NOSONAR never null
        } catch (AmqpException e) {
            logOrRethrowDeclarationException(null, "queue", e);
            return null;
        }
    }

    @Override
    @ManagedOperation(description = "Delete a queue from the broker")
    public boolean deleteQueue(final String queueName) {
        return this.rabbitTemplate.execute(channel -> { // NOSONAR never returns null
            try {
                channel.queueDelete(queueName);
            } catch (@SuppressWarnings(UNUSED) IOException e) {
                return false;
            }
            return true;
        });
    }

    @Override
    @ManagedOperation(description = "Delete a queue from the broker if unused and empty (when corresponding arguments are true")
    public void deleteQueue(final String queueName, final boolean unused, final boolean empty) {
        this.rabbitTemplate.execute(channel -> {
            channel.queueDelete(queueName, unused, empty);
            return null;
        });
    }

    @Override
    @ManagedOperation(description = "Purge a queue and optionally don't wait for the purge to occur")
    public void purgeQueue(final String queueName, final boolean noWait) {
        if (noWait) {
            this.taskExecutor.execute(() -> purgeQueue(queueName));
        } else {
            purgeQueue(queueName);
        }
    }

    @Override
    @ManagedOperation(description = "Purge a queue and return the number of messages purged")
    public int purgeQueue(final String queueName) {
        return this.rabbitTemplate.execute(channel -> { // NOSONAR never returns null
            PurgeOk queuePurged = channel.queuePurge(queueName);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Purged queue: " + queueName + ", " + queuePurged);
            }
            return queuePurged.getMessageCount();
        });
    }

    // Binding
    @Override
    @ManagedOperation(description = "Declare a binding on the broker (this operation is not available remotely)")
    public void declareBinding(final Binding binding) {
        try {
            this.rabbitTemplate.execute(channel -> {
                declareBindings(channel, binding);
                return null;
            });
        } catch (AmqpException e) {
            logOrRethrowDeclarationException(binding, "binding", e);
        }
    }

    @Override
    @ManagedOperation(description = "Remove a binding from the broker (this operation is not available remotely)")
    public void removeBinding(final Binding binding) {
        this.rabbitTemplate.execute(channel -> {
            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
    @ManagedOperation(description = "Get queue name, message count and consumer count")
    public Properties getQueueProperties(final String queueName) {
        QueueInformation queueInfo = getQueueInfo(queueName);
        if (queueInfo != null) {
            Properties props = new Properties();
            props.put(QUEUE_NAME, queueInfo.getName());
            props.put(QUEUE_MESSAGE_COUNT, queueInfo.getMessageCount());
            props.put(QUEUE_CONSUMER_COUNT, queueInfo.getConsumerCount());
            return props;
        } else {
            return null;
        }
    }

    @Override
    public QueueInformation getQueueInfo(String queueName) {
        Assert.hasText(queueName, "'queueName' cannot be null or empty");
        return this.rabbitTemplate.execute(channel -> {
            try {
                DeclareOk declareOk = channel.queueDeclarePassive(queueName);
                return new QueueInformation(declareOk.getQueue(), declareOk.getMessageCount(),
                        declareOk.getConsumerCount());
            } catch (IllegalArgumentException e) {
                if (RabbitAdmin.this.logger.isDebugEnabled()) {
                    RabbitAdmin.this.logger.error("Exception while fetching Queue properties: '" + queueName + "'",
                            e);
                }
                try {
                    if (channel instanceof ChannelProxy) {
                        ((ChannelProxy) channel).getTargetChannel().close();
                    }
                } catch (@SuppressWarnings(UNUSED) TimeoutException e1) {
                }
                return null;
            } catch (@SuppressWarnings(UNUSED) Exception e) {
                if (RabbitAdmin.this.logger.isDebugEnabled()) {
                    RabbitAdmin.this.logger.debug("Queue '" + queueName + "' does not exist");
                }
                return null;
            }
        });
    }

    /**
     * Set a retry template for auto declarations. There is a race condition with
     * auto-delete, exclusive queues in that the queue might still exist for a short time,
     * preventing the redeclaration. The default retry configuration will try 5 times with
     * an exponential backOff starting at 1 second a multiplier of 2.0 and a max interval
     * of 5 seconds. To disable retry, set the argument to {@code null}. Note that this
     * retry is at the macro level - all declarations will be retried within the scope of
     * this template. If you supplied a {@link RabbitTemplate} that is configured with a
     * {@link RetryTemplate}, its template will retry each individual declaration.
     * @param retryTemplate the retry template.
     * @since 1.7.8
     */
    public void setRetryTemplate(@Nullable RetryTemplate retryTemplate) {
        this.retryTemplate = retryTemplate;
        if (retryTemplate == null) {
            this.retryDisabled = true;
        }
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    public String getBeanName() {
        return this.beanName;
    }

    // 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.retryTemplate == null && !this.retryDisabled) {
                this.retryTemplate = new RetryTemplate();
                this.retryTemplate.setRetryPolicy(new SimpleRetryPolicy(DECLARE_MAX_ATTEMPTS));
                ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
                backOffPolicy.setInitialInterval(DECLARE_INITIAL_RETRY_INTERVAL);
                backOffPolicy.setMultiplier(DECLARE_RETRY_MULTIPLIER);
                backOffPolicy.setMaxInterval(DECLARE_MAX_RETRY_INTERVAL);
                this.retryTemplate.setBackOffPolicy(backOffPolicy);
            }
            if (this.connectionFactory instanceof CachingConnectionFactory
                    && ((CachingConnectionFactory) this.connectionFactory).getCacheMode() == CacheMode.CONNECTION) {
                this.logger.warn("RabbitAdmin auto declaration is not supported with CacheMode.CONNECTION");
                return;
            }

            // Prevent stack overflow...
            final AtomicBoolean initializing = new AtomicBoolean(false);

            this.connectionFactory.addConnectionListener(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".
                     */
                    if (this.retryTemplate != null) {
                        this.retryTemplate.execute(c -> {
                            initialize();
                            return null;
                        });
                    } else {
                        initialize();
                    }
                } finally {
                    initializing.compareAndSet(true, false);
                }

            });

            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.
     */
    @Override // NOSONAR complexity
    public void initialize() {

        if (this.applicationContext == null) {
            this.logger.debug(
                    "no ApplicationContext has been set, cannot auto-declare Exchanges, Queues, and Bindings");
            return;
        }

        this.logger.debug("Initializing declarations");
        Collection<Exchange> contextExchanges = new LinkedList<Exchange>(
                this.applicationContext.getBeansOfType(Exchange.class).values());
        Collection<Queue> contextQueues = new LinkedList<Queue>(
                this.applicationContext.getBeansOfType(Queue.class).values());
        Collection<Binding> contextBindings = new LinkedList<Binding>(
                this.applicationContext.getBeansOfType(Binding.class).values());

        processDeclarables(contextExchanges, contextQueues, contextBindings);

        final Collection<Exchange> exchanges = filterDeclarables(contextExchanges);
        final Collection<Queue> queues = filterDeclarables(contextQueues);
        final Collection<Binding> bindings = filterDeclarables(contextBindings);

        for (Exchange exchange : exchanges) {
            if ((!exchange.isDurable() || exchange.isAutoDelete()) && this.logger.isInfoEnabled()) {
                this.logger.info("Auto-declaring a non-durable or auto-delete Exchange (" + exchange.getName()
                        + ") durable:" + exchange.isDurable() + ", auto-delete:" + exchange.isAutoDelete() + ". "
                        + "It will be deleted by the broker if it shuts down, and can be redeclared by closing and "
                        + "reopening the connection.");
            }
        }

        for (Queue queue : queues) {
            if ((!queue.isDurable() || queue.isAutoDelete() || queue.isExclusive())
                    && this.logger.isInfoEnabled()) {
                this.logger.info("Auto-declaring a non-durable, auto-delete, or exclusive Queue (" + queue.getName()
                        + ") durable:" + queue.isDurable() + ", auto-delete:" + queue.isAutoDelete()
                        + ", exclusive:" + queue.isExclusive() + ". "
                        + "It will be redeclared if the broker stops and is restarted while the connection factory is "
                        + "alive, but all messages will be lost.");
            }
        }

        if (exchanges.size() == 0 && queues.size() == 0 && bindings.size() == 0) {
            this.logger.debug("Nothing to declare");
            return;
        }
        this.rabbitTemplate.execute(channel -> {
            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;
        });
        this.logger.debug("Declarations finished");

    }

    private void processDeclarables(Collection<Exchange> contextExchanges, Collection<Queue> contextQueues,
            Collection<Binding> contextBindings) {
        Collection<Declarables> declarables = this.applicationContext.getBeansOfType(Declarables.class, false, true)
                .values();
        declarables.forEach(d -> {
            d.getDeclarables().forEach(declarable -> {
                if (declarable instanceof Exchange) {
                    contextExchanges.add((Exchange) declarable);
                } else if (declarable instanceof Queue) {
                    contextQueues.add((Queue) declarable);
                } else if (declarable instanceof Binding) {
                    contextBindings.add((Binding) declarable);
                }
            });
        });
    }

    /**
     * Remove any instances that should not be declared by this admin.
     * @param declarables the collection of {@link Declarable}s.
     * @param <T> the declarable type.
     * @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) {
        return declarables.stream().filter(d -> d.shouldDeclare() // NOSONAR boolean complexity
                && (d.getDeclaringAdmins().isEmpty() || d.getDeclaringAdmins().contains(this)
                        || (this.beanName != null && d.getDeclaringAdmins().contains(this.beanName))))
                .collect(Collectors.toList());
    }

    // 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 (this.logger.isDebugEnabled()) {
                this.logger.debug("declaring Exchange '" + exchange.getName() + "'");
            }

            if (!isDeclaringDefaultExchange(exchange)) {
                try {
                    if (exchange.isDelayed()) {
                        Map<String, Object> arguments = exchange.getArguments();
                        if (arguments == null) {
                            arguments = new HashMap<String, Object>();
                        } else {
                            arguments = new HashMap<String, Object>(arguments);
                        }
                        arguments.put("x-delayed-type", exchange.getType());
                        channel.exchangeDeclare(exchange.getName(), DELAYED_MESSAGE_EXCHANGE, exchange.isDurable(),
                                exchange.isAutoDelete(), exchange.isInternal(), arguments);
                    } else {
                        channel.exchangeDeclare(exchange.getName(), exchange.getType(), exchange.isDurable(),
                                exchange.isAutoDelete(), exchange.isInternal(), exchange.getArguments());
                    }
                } catch (IOException e) {
                    logOrRethrowDeclarationException(exchange, "exchange", e);
                }
            }
        }
    }

    private DeclareOk[] declareQueues(final Channel channel, final Queue... queues) throws IOException {
        List<DeclareOk> declareOks = new ArrayList<DeclareOk>(queues.length);
        for (int i = 0; i < queues.length; i++) {
            Queue queue = queues[i];
            if (!queue.getName().startsWith("amq.")) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("declaring Queue '" + queue.getName() + "'");
                }
                try {
                    try {
                        DeclareOk declareOk = channel.queueDeclare(queue.getName(), queue.isDurable(),
                                queue.isExclusive(), queue.isAutoDelete(), queue.getArguments());
                        if (StringUtils.hasText(declareOk.getQueue())) {
                            queue.setActualName(declareOk.getQueue());
                        }
                        declareOks.add(declareOk);
                    } catch (IllegalArgumentException e) {
                        closeChannelAfterIllegalArg(channel, queue);
                        throw new IOException(e);
                    }
                } catch (IOException e) { // NOSONAR exceptions for flow control
                    logOrRethrowDeclarationException(queue, "queue", e);
                }
            } else if (this.logger.isDebugEnabled()) {
                this.logger
                        .debug(queue.getName() + ": Queue with name that starts with 'amq.' cannot be declared.");
            }
        }
        return declareOks.toArray(new DeclareOk[declareOks.size()]);
    }

    private void closeChannelAfterIllegalArg(final Channel channel, Queue queue) {
        if (this.logger.isDebugEnabled()) {
            this.logger.error("Exception while declaring queue: '" + queue.getName() + "'");
        }
        try {
            if (channel instanceof ChannelProxy) {
                ((ChannelProxy) channel).getTargetChannel().close();
            }
        } catch (IOException | TimeoutException e1) {
            this.logger.error("Failed to close channel after illegal argument", e1);
        }
    }

    private void declareBindings(final Channel channel, final Binding... bindings) throws IOException {
        for (Binding binding : bindings) {
            if (this.logger.isDebugEnabled()) {
                this.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) {
                logOrRethrowDeclarationException(binding, "binding", e);
            }
        }
    }

    private <T extends Throwable> void logOrRethrowDeclarationException(@Nullable Declarable element,
            String elementType, T t) throws T {

        publishDeclarationExceptionEvent(element, t);
        if (this.ignoreDeclarationExceptions || (element != null && element.isIgnoreDeclarationExceptions())) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Failed to declare " + elementType + ": "
                        + (element == null ? "broker-generated" : element) + ", continuing...", t);
            } else if (this.logger.isWarnEnabled()) {
                Throwable cause = t;
                if (t instanceof IOException && t.getCause() != null) {
                    cause = t.getCause();
                }
                this.logger.warn("Failed to declare " + elementType + ": "
                        + (element == null ? "broker-generated" : element) + ", continuing... " + cause);
            }
        } else {
            throw t;
        }
    }

    private <T extends Throwable> void publishDeclarationExceptionEvent(Declarable element, T t) {
        DeclarationExceptionEvent event = new DeclarationExceptionEvent(this, element, t);
        this.lastDeclarationExceptionEvent = event;
        if (this.applicationEventPublisher != null) {
            this.applicationEventPublisher.publishEvent(event);
        }
    }

    private boolean isDeclaringDefaultExchange(Exchange exchange) {
        if (isDefaultExchange(exchange.getName())) {
            this.logger.debug("Default exchange is pre-declared by server.");
            return true;
        }
        return false;
    }

    private boolean isDeletingDefaultExchange(String exchangeName) {
        if (isDefaultExchange(exchangeName)) {
            this.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)) {
            this.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)) {
            this.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());
    }

}