org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils.java

Source

/*
 * Copyright 2002-2012 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.connection;

import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.amqp.AmqpIOException;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;

import com.rabbitmq.client.Channel;

/**
 * Helper class for managing a Spring based Rabbit
 * {@link org.springframework.amqp.rabbit.connection.ConnectionFactory}, in
 * particular for obtaining transactional Rabbit resources for a given
 * ConnectionFactory.
 * 
 * <p>
 * Mainly for internal use within the framework. Used by
 * {@link org.springframework.amqp.rabbit.core.RabbitTemplate} as well as
 * {@link org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer}.
 * 
 * @author Mark Fisher
 * @author Dave Syer
 * @author Gary Russell
 */
public class ConnectionFactoryUtils {

    private static final Log logger = LogFactory.getLog(ConnectionFactoryUtils.class);

    private static final ThreadLocal<Channel> consumerChannel = new ThreadLocal<Channel>();

    /**
     * If a listener container is configured to use a RabbitTransactionManager,
     * the consumer's channel is registered here so that it is used as the bound
     * resource when the transaction actually starts. It is normally not
     * necessary to use an external transaction manager because local
     * transactions work the same in that the channel is bound to the thread.
     * This is for the case when a user happens to wire in a
     * RabbitTransactionManager.
     * 
     * @param channel
     */
    public static void registerConsumerChannel(Channel channel) {
        if (logger.isDebugEnabled()) {
            logger.debug("Registering consumer channel" + channel);
        }
        consumerChannel.set(channel);
    }

    /**
     * See registerConsumerChannel. This method is called to unregister the
     * channel when the consumer exits.
     */
    public static void unRegisterConsumerChannel() {
        if (logger.isDebugEnabled()) {
            logger.debug("Unregistering consumer channel" + consumerChannel.get());
        }
        consumerChannel.remove();
    }

    /**
     * See registerConsumerChannel. This method is called to retrieve the
     * channel for this consumer.
     */
    public static Channel getConsumerChannel() {
        return consumerChannel.get();
    }

    /**
     * Determine whether the given RabbitMQ Channel is transactional, that is,
     * bound to the current thread by Spring's transaction facilities.
     * 
     * @param channel
     *            the RabbitMQ Channel to check
     * @param connectionFactory
     *            the RabbitMQ ConnectionFactory that the Channel originated
     *            from
     * @return whether the Channel is transactional
     */
    public static boolean isChannelTransactional(Channel channel, ConnectionFactory connectionFactory) {
        if (channel == null || connectionFactory == null) {
            return false;
        }
        RabbitResourceHolder resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager
                .getResource(connectionFactory);
        return (resourceHolder != null && resourceHolder.containsChannel(channel));
    }

    /**
     * Obtain a RabbitMQ Channel that is synchronized with the current
     * transaction, if any.
     * 
     * @param connectionFactory
     *            the ConnectionFactory to obtain a Channel for
     * @param synchedLocalTransactionAllowed
     *            whether to allow for a local RabbitMQ transaction that is
     *            synchronized with a Spring-managed transaction (where the main
     *            transaction might be a JDBC-based one for a specific
     *            DataSource, for example), with the RabbitMQ transaction
     *            committing right after the main transaction. If not allowed,
     *            the given ConnectionFactory needs to handle transaction
     *            enlistment underneath the covers.
     * @return the transactional Channel, or <code>null</code> if none found
     */
    public static RabbitResourceHolder getTransactionalResourceHolder(final ConnectionFactory connectionFactory,
            final boolean synchedLocalTransactionAllowed) {

        RabbitResourceHolder holder = doGetTransactionalResourceHolder(connectionFactory, new ResourceFactory() {
            public Channel getChannel(RabbitResourceHolder holder) {
                return holder.getChannel();
            }

            public Connection getConnection(RabbitResourceHolder holder) {
                return holder.getConnection();
            }

            public Connection createConnection() throws IOException {
                return connectionFactory.createConnection();
            }

            public Channel createChannel(Connection con) throws IOException {
                return con.createChannel(synchedLocalTransactionAllowed);
            }

            public boolean isSynchedLocalTransactionAllowed() {
                return synchedLocalTransactionAllowed;
            }
        });
        return holder;
    }

    /**
     * Obtain a RabbitMQ Channel that is synchronized with the current
     * transaction, if any.
     * 
     * @param connectionFactory
     *            the RabbitMQ ConnectionFactory to bind for (used as
     *            TransactionSynchronizationManager key)
     * @param resourceFactory
     *            the ResourceFactory to use for extracting or creating RabbitMQ
     *            resources
     * @return the transactional Channel, or <code>null</code> if none found
     */
    private static RabbitResourceHolder doGetTransactionalResourceHolder(ConnectionFactory connectionFactory,
            ResourceFactory resourceFactory) {

        Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
        Assert.notNull(resourceFactory, "ResourceFactory must not be null");

        RabbitResourceHolder resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager
                .getResource(connectionFactory);
        if (resourceHolder != null) {
            Channel channel = resourceFactory.getChannel(resourceHolder);
            if (channel != null) {
                return resourceHolder;
            }
        }
        RabbitResourceHolder resourceHolderToUse = resourceHolder;
        if (resourceHolderToUse == null) {
            resourceHolderToUse = new RabbitResourceHolder();
        }
        Connection connection = resourceFactory.getConnection(resourceHolderToUse);
        Channel channel = null;
        try {
            boolean isExistingCon = (connection != null);
            if (!isExistingCon) {
                connection = resourceFactory.createConnection();
                resourceHolderToUse.addConnection(connection);
            }
            channel = consumerChannel.get();
            if (channel == null) {
                channel = resourceFactory.createChannel(connection);
            }
            if (null != channel && channel.isOpen()) {
                resourceHolderToUse.addChannel(channel, connection);
            }

            if (resourceHolderToUse != resourceHolder) {
                bindResourceToTransaction(resourceHolderToUse, connectionFactory,
                        resourceFactory.isSynchedLocalTransactionAllowed());
            }

            return resourceHolderToUse;

        } catch (IOException ex) {
            RabbitUtils.closeChannel(channel);
            RabbitUtils.closeConnection(connection);
            throw new AmqpIOException(ex);
        }
    }

    public static void releaseResources(RabbitResourceHolder resourceHolder) {
        if (resourceHolder == null || resourceHolder.isSynchronizedWithTransaction()) {
            return;
        }
        RabbitUtils.closeChannel(resourceHolder.getChannel());
        RabbitUtils.closeConnection(resourceHolder.getConnection());
    }

    public static void bindResourceToTransaction(RabbitResourceHolder resourceHolder,
            ConnectionFactory connectionFactory, boolean synched) {
        if (TransactionSynchronizationManager.hasResource(connectionFactory)
                || !TransactionSynchronizationManager.isActualTransactionActive() || !synched) {
            return;
        }
        TransactionSynchronizationManager.bindResource(connectionFactory, resourceHolder);
        resourceHolder.setSynchronizedWithTransaction(true);
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(
                    new RabbitResourceSynchronization(resourceHolder, connectionFactory, synched));
        }
    }

    /**
     *
     */
    public static void registerDeliveryTag(ConnectionFactory connectionFactory, Channel channel, Long tag)
            throws IOException {

        Assert.notNull(connectionFactory, "ConnectionFactory must not be null");

        RabbitResourceHolder resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager
                .getResource(connectionFactory);
        if (resourceHolder != null) {
            resourceHolder.addDeliveryTag(channel, tag);
        }
    }

    /**
     * Callback interface for resource creation. Serving as argument for the
     * <code>doGetTransactionalChannel</code> method.
     */
    public interface ResourceFactory {

        /**
         * Fetch an appropriate Channel from the given RabbitResourceHolder.
         * 
         * @param holder
         *            the RabbitResourceHolder
         * @return an appropriate Channel fetched from the holder, or
         *         <code>null</code> if none found
         */
        Channel getChannel(RabbitResourceHolder holder);

        /**
         * Fetch an appropriate Connection from the given RabbitResourceHolder.
         * 
         * @param holder
         *            the RabbitResourceHolder
         * @return an appropriate Connection fetched from the holder, or
         *         <code>null</code> if none found
         */
        Connection getConnection(RabbitResourceHolder holder);

        /**
         * Create a new RabbitMQ Connection for registration with a
         * RabbitResourceHolder.
         * 
         * @return the new RabbitMQ Connection
         * @throws IOException
         *             if thrown by RabbitMQ API methods
         */
        Connection createConnection() throws IOException;

        /**
         * Create a new RabbitMQ Session for registration with a
         * RabbitResourceHolder.
         * 
         * @param con
         *            the RabbitMQ Connection to create a Channel for
         * @return the new RabbitMQ Channel
         * @throws IOException
         *             if thrown by RabbitMQ API methods
         */
        Channel createChannel(Connection con) throws IOException;

        /**
         * Return whether to allow for a local RabbitMQ transaction that is
         * synchronized with a Spring-managed transaction (where the main
         * transaction might be a JDBC-based one for a specific DataSource, for
         * example), with the RabbitMQ transaction committing right after the
         * main transaction.
         * 
         * @return whether to allow for synchronizing a local RabbitMQ
         *         transaction
         */
        boolean isSynchedLocalTransactionAllowed();
    }

    /**
     * Callback for resource cleanup at the end of a non-native RabbitMQ
     * transaction (e.g. when participating in a JtaTransactionManager
     * transaction).
     * 
     * @see org.springframework.transaction.jta.JtaTransactionManager
     */
    private static class RabbitResourceSynchronization
            extends ResourceHolderSynchronization<RabbitResourceHolder, Object> {

        private final boolean transacted;

        private final RabbitResourceHolder resourceHolder;

        public RabbitResourceSynchronization(RabbitResourceHolder resourceHolder, Object resourceKey,
                boolean transacted) {
            super(resourceHolder, resourceKey);
            this.resourceHolder = resourceHolder;
            this.transacted = transacted;
        }

        @Override
        protected boolean shouldReleaseBeforeCompletion() {
            return !this.transacted;
        }

        @Override
        protected void processResourceAfterCommit(RabbitResourceHolder resourceHolder) {
            resourceHolder.commitAll();
        }

        @Override
        public void afterCompletion(int status) {
            if (status != TransactionSynchronization.STATUS_COMMITTED) {
                resourceHolder.rollbackAll();
            }
            if (resourceHolder.isReleaseAfterCompletion()) {
                resourceHolder.setSynchronizedWithTransaction(false);
            }
            super.afterCompletion(status);
        }

        @Override
        protected void releaseResource(RabbitResourceHolder resourceHolder, Object resourceKey) {
            ConnectionFactoryUtils.releaseResources(resourceHolder);
        }
    }

}