Java tutorial
/* * 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.connection; import java.io.IOException; import org.springframework.amqp.AmqpIOException; import org.springframework.lang.Nullable; 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 * @author Artem Bilan */ public final class ConnectionFactoryUtils { private ConnectionFactoryUtils() { super(); } /** * 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) { return getTransactionalResourceHolder(connectionFactory, synchedLocalTransactionAllowed, false); } /** * 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. * @param publisherConnectionIfPossible obtain a connection from a separate publisher connection * if possible. * @return the transactional Channel, or <code>null</code> if none found */ public static RabbitResourceHolder getTransactionalResourceHolder(final ConnectionFactory connectionFactory, final boolean synchedLocalTransactionAllowed, final boolean publisherConnectionIfPossible) { return doGetTransactionalResourceHolder(connectionFactory, new RabbitResourceFactory(connectionFactory, synchedLocalTransactionAllowed, publisherConnectionIfPossible)); } /** * 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(// NOSONAR complexity 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); //NOSONAR Channel channel = null; try { /* * If we are in a listener container, first see if there's a channel registered * for this consumer and the consumer is using the same connection factory. */ channel = ConsumerChannelRegistry.getConsumerChannel(connectionFactory); if (channel == null && connection == null) { connection = resourceFactory.createConnection(); if (resourceHolder == null) { /* * While creating a connection, a connection listener might have created a * transactional channel and bound it to the transaction. */ resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager .getResource(connectionFactory); if (resourceHolder != null) { channel = resourceHolder.getChannel(); resourceHolderToUse = resourceHolder; } } resourceHolderToUse.addConnection(connection); } if (channel == null) { channel = resourceFactory.createChannel(connection); } resourceHolderToUse.addChannel(channel, connection); if (!resourceHolderToUse.equals(resourceHolder)) { bindResourceToTransaction(resourceHolderToUse, connectionFactory, resourceFactory.isSynchedLocalTransactionAllowed()); } return resourceHolderToUse; } catch (IOException ex) { RabbitUtils.closeConnection(connection); throw new AmqpIOException(ex); } } public static void releaseResources(@Nullable RabbitResourceHolder resourceHolder) { if (resourceHolder == null || resourceHolder.isSynchronizedWithTransaction()) { return; } RabbitUtils.closeChannel(resourceHolder.getChannel()); RabbitUtils.closeConnection(resourceHolder.getConnection()); } public static RabbitResourceHolder bindResourceToTransaction(RabbitResourceHolder resourceHolder, ConnectionFactory connectionFactory, boolean synched) { if (TransactionSynchronizationManager.hasResource(connectionFactory) || !TransactionSynchronizationManager.isActualTransactionActive() || !synched) { return (RabbitResourceHolder) TransactionSynchronizationManager.getResource(connectionFactory); // NOSONAR never null } TransactionSynchronizationManager.bindResource(connectionFactory, resourceHolder); resourceHolder.setSynchronizedWithTransaction(true); if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager .registerSynchronization(new RabbitResourceSynchronization(resourceHolder, connectionFactory)); } return resourceHolder; } public static void registerDeliveryTag(ConnectionFactory connectionFactory, Channel channel, Long tag) { Assert.notNull(connectionFactory, "ConnectionFactory must not be null"); RabbitResourceHolder resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager .getResource(connectionFactory); if (resourceHolder != null) { resourceHolder.addDeliveryTag(channel, tag); } } /** * Create a connection with this connection factory and/or its publisher factory. * @param connectionFactory the connection factory. * @param publisherConnectionIfPossible true to use the publisher factory, if present. * @return the connection. * @since 2.0.2 */ public static Connection createConnection(final ConnectionFactory connectionFactory, final boolean publisherConnectionIfPossible) { if (publisherConnectionIfPossible) { ConnectionFactory publisherFactory = connectionFactory.getPublisherConnectionFactory(); if (publisherFactory != null) { return publisherFactory.createConnection(); } } return connectionFactory.createConnection(); } /** * 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 */ @Nullable 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(); } private static class RabbitResourceFactory implements ResourceFactory { private final ConnectionFactory connectionFactory; private final boolean synchedLocalTransactionAllowed; private final boolean publisherConnectionIfPossible; RabbitResourceFactory(ConnectionFactory connectionFactory, boolean synchedLocalTransactionAllowed, boolean publisherConnectionIfPossible) { this.connectionFactory = connectionFactory; this.synchedLocalTransactionAllowed = synchedLocalTransactionAllowed; this.publisherConnectionIfPossible = publisherConnectionIfPossible; } @Override @Nullable public Channel getChannel(RabbitResourceHolder holder) { return holder.getChannel(); } @Override @Nullable public Connection getConnection(RabbitResourceHolder holder) { return holder.getConnection(); } @Override public Connection createConnection() { return ConnectionFactoryUtils.createConnection(this.connectionFactory, this.publisherConnectionIfPossible); } @Override public Channel createChannel(Connection con) { return con.createChannel(this.synchedLocalTransactionAllowed); } @Override public boolean isSynchedLocalTransactionAllowed() { return this.synchedLocalTransactionAllowed; } } /** * 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 final class RabbitResourceSynchronization extends ResourceHolderSynchronization<RabbitResourceHolder, Object> { private final RabbitResourceHolder resourceHolder; RabbitResourceSynchronization(RabbitResourceHolder resourceHolder, Object resourceKey) { super(resourceHolder, resourceKey); this.resourceHolder = resourceHolder; } @Override protected boolean shouldReleaseBeforeCompletion() { return false; } @Override public void afterCompletion(int status) { if (status == TransactionSynchronization.STATUS_COMMITTED) { this.resourceHolder.commitAll(); } else { this.resourceHolder.rollbackAll(); } if (this.resourceHolder.isReleaseAfterCompletion()) { this.resourceHolder.setSynchronizedWithTransaction(false); } super.afterCompletion(status); } @Override protected void releaseResource(RabbitResourceHolder resourceHolder, Object resourceKey) { ConnectionFactoryUtils.releaseResources(resourceHolder); } } }