org.springframework.amqp.rabbit.transaction.RabbitTransactionManager.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.amqp.rabbit.transaction.RabbitTransactionManager.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.transaction;

import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactoryUtils;
import org.springframework.amqp.rabbit.connection.RabbitResourceHolder;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.InvalidIsolationLevelException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.ResourceTransactionManager;
import org.springframework.transaction.support.SmartTransactionObject;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;

/**
 * {@link org.springframework.transaction.PlatformTransactionManager} implementation for a single Rabbit
 * {@link ConnectionFactory}. Binds a Rabbit Channel from the specified ConnectionFactory to the thread, potentially
 * allowing for one thread-bound channel per ConnectionFactory.
 *
 * <p>
 * This local strategy is an alternative to executing Rabbit operations within, and synchronized with, external
 * transactions. This strategy is <i>not</i> able to provide XA transactions, for example in order to share transactions
 * between messaging and database access.
 *
 * <p>
 * Application code is required to retrieve the transactional Rabbit resources via
 * {@link ConnectionFactoryUtils#getTransactionalResourceHolder(ConnectionFactory, boolean)} instead of a standard
 * {@link org.springframework.amqp.rabbit.connection.Connection#createChannel(boolean)} call with subsequent
 * Channel creation. Spring's
 * {@link org.springframework.amqp.rabbit.core.RabbitTemplate} will
 * autodetect a thread-bound Channel and automatically participate in it.
 *
 * <p>
 * <b>The use of {@link org.springframework.amqp.rabbit.connection.CachingConnectionFactory}
 * as a target for this transaction manager is strongly recommended.</b>
 * CachingConnectionFactory uses a single Rabbit Connection for all Rabbit access in order to avoid the overhead of
 * repeated Connection creation, as well as maintaining a cache of Channels. Each transaction will then share the same
 * Rabbit Connection, while still using its own individual Rabbit Channel.
 *
 * <p>
 * Transaction synchronization is turned off by default, as this manager might be used alongside a datastore-based
 * Spring transaction manager such as the JDBC org.springframework.jdbc.datasource.DataSourceTransactionManager,
 * which has stronger needs for synchronization.
 *
 * @author Dave Syer
 */
@SuppressWarnings("serial")
public class RabbitTransactionManager extends AbstractPlatformTransactionManager
        implements ResourceTransactionManager, InitializingBean {

    private ConnectionFactory connectionFactory;

    /**
     * Create a new RabbitTransactionManager for bean-style usage.
     * <p>
     * Note: The ConnectionFactory has to be set before using the instance. This constructor can be used to prepare a
     * RabbitTemplate via a BeanFactory, typically setting the ConnectionFactory via setConnectionFactory.
     * <p>
     * Turns off transaction synchronization by default, as this manager might be used alongside a datastore-based
     * Spring transaction manager like DataSourceTransactionManager, which has stronger needs for synchronization. Only
     * one manager is allowed to drive synchronization at any point of time.
     * @see #setConnectionFactory
     * @see #setTransactionSynchronization
     */
    public RabbitTransactionManager() {
        setTransactionSynchronization(SYNCHRONIZATION_NEVER);
    }

    /**
     * Create a new RabbitTransactionManager, given a ConnectionFactory.
     * @param connectionFactory the ConnectionFactory to use
     */
    public RabbitTransactionManager(ConnectionFactory connectionFactory) {
        Assert.notNull(connectionFactory, "'connectionFactory' cannot be null");
        this.connectionFactory = connectionFactory;
    }

    /**
     * @param connectionFactory the connectionFactory to set
     */
    public void setConnectionFactory(ConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }

    /**
     * @return the connectionFactory
     */
    public ConnectionFactory getConnectionFactory() {
        return this.connectionFactory;
    }

    /**
     * Make sure the ConnectionFactory has been set.
     */
    @Override
    public void afterPropertiesSet() {
        Assert.state(getConnectionFactory() != null, "Property 'connectionFactory' is required");
    }

    @Override
    public Object getResourceFactory() {
        return getConnectionFactory();
    }

    @Override
    protected Object doGetTransaction() {
        RabbitTransactionObject txObject = new RabbitTransactionObject();
        txObject.setResourceHolder(
                (RabbitResourceHolder) TransactionSynchronizationManager.getResource(getConnectionFactory()));
        return txObject;
    }

    @Override
    protected boolean isExistingTransaction(Object transaction) {
        RabbitTransactionObject txObject = (RabbitTransactionObject) transaction;
        return (txObject.getResourceHolder() != null);
    }

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
            throw new InvalidIsolationLevelException("AMQP does not support an isolation level concept");
        }
        RabbitTransactionObject txObject = (RabbitTransactionObject) transaction;
        RabbitResourceHolder resourceHolder = null;
        try {
            resourceHolder = ConnectionFactoryUtils.getTransactionalResourceHolder(getConnectionFactory(), true);
            if (logger.isDebugEnabled()) {
                logger.debug("Created AMQP transaction on channel [" + resourceHolder.getChannel() + "]");
            }
            // resourceHolder.declareTransactional();
            txObject.setResourceHolder(resourceHolder);
            txObject.getResourceHolder().setSynchronizedWithTransaction(true);
            int timeout = determineTimeout(definition);
            if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                txObject.getResourceHolder().setTimeoutInSeconds(timeout);
            }
            TransactionSynchronizationManager.bindResource(getConnectionFactory(), txObject.getResourceHolder());
        } catch (AmqpException ex) {
            if (resourceHolder != null) {
                ConnectionFactoryUtils.releaseResources(resourceHolder);
            }
            throw new CannotCreateTransactionException("Could not create AMQP transaction", ex);
        }
    }

    @Override
    protected Object doSuspend(Object transaction) {
        RabbitTransactionObject txObject = (RabbitTransactionObject) transaction;
        txObject.setResourceHolder(null);
        return TransactionSynchronizationManager.unbindResource(getConnectionFactory());
    }

    @Override
    protected void doResume(Object transaction, Object suspendedResources) {
        RabbitResourceHolder conHolder = (RabbitResourceHolder) suspendedResources;
        TransactionSynchronizationManager.bindResource(getConnectionFactory(), conHolder);
    }

    @Override
    protected void doCommit(DefaultTransactionStatus status) {
        RabbitTransactionObject txObject = (RabbitTransactionObject) status.getTransaction();
        RabbitResourceHolder resourceHolder = txObject.getResourceHolder();
        resourceHolder.commitAll();
    }

    @Override
    protected void doRollback(DefaultTransactionStatus status) {
        RabbitTransactionObject txObject = (RabbitTransactionObject) status.getTransaction();
        RabbitResourceHolder resourceHolder = txObject.getResourceHolder();
        resourceHolder.rollbackAll();
    }

    @Override
    protected void doSetRollbackOnly(DefaultTransactionStatus status) {
        RabbitTransactionObject txObject = (RabbitTransactionObject) status.getTransaction();
        txObject.getResourceHolder().setRollbackOnly();
    }

    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        RabbitTransactionObject txObject = (RabbitTransactionObject) transaction;
        TransactionSynchronizationManager.unbindResource(getConnectionFactory());
        txObject.getResourceHolder().closeAll();
        txObject.getResourceHolder().clear();
    }

    /**
     * Rabbit transaction object, representing a RabbitResourceHolder. Used as transaction object by
     * RabbitTransactionManager.
     * @see RabbitResourceHolder
     */
    private static class RabbitTransactionObject implements SmartTransactionObject {

        private RabbitResourceHolder resourceHolder;

        RabbitTransactionObject() {
            super();
        }

        public void setResourceHolder(RabbitResourceHolder resourceHolder) {
            this.resourceHolder = resourceHolder;
        }

        public RabbitResourceHolder getResourceHolder() {
            return this.resourceHolder;
        }

        @Override
        public boolean isRollbackOnly() {
            return this.resourceHolder.isRollbackOnly();
        }

        @Override
        public void flush() {
            // no-op
        }
    }
}