All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bitronix.tm.resource.common.TransactionContextHelper Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * Copyright (C) 2006-2013 Bitronix Software (http://www.bitronix.be)
 *
 * 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 bitronix.tm.resource.common;

import bitronix.tm.BitronixTransaction;
import bitronix.tm.BitronixXid;
import bitronix.tm.TransactionManagerServices;
import bitronix.tm.internal.BitronixSystemException;
import bitronix.tm.internal.LogDebugCheck;
import bitronix.tm.internal.XAResourceHolderState;
import bitronix.tm.resource.common.XAStatefulHolder.State;
import bitronix.tm.utils.Scheduler;
import bitronix.tm.utils.Uid;

import jakarta.transaction.RollbackException;
import jakarta.transaction.Synchronization;
import jakarta.transaction.SystemException;
import javax.transaction.xa.XAResource;
import java.util.List;

/**
 * Helper class that contains static logic common across all resource types.
 *
 * @author Ludovic Orban
 */
public final class TransactionContextHelper
{

	private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(TransactionContextHelper.class.toString());
	private static final String FROM_SPACE = " from ";

	/**
	 * Constructor TransactionContextHelper creates a new TransactionContextHelper instance.
	 */
	// do not instantiate
	private TransactionContextHelper()
	{
	}

	/**
	 * Enlist the {@link XAResourceHolder} in the current transaction or do nothing if there is no global transaction
	 * context for this thread.
	 *
	 * @param xaResourceHolder
	 * 		the {@link XAResourceHolder} to enlist.
	 *
	 * @throws SystemException
	 * 		if an internal error happens.
	 * @throws RollbackException
	 * 		if the current transaction has been marked as rollback only.
	 */
	public static void enlistInCurrentTransaction(XAResourceHolder xaResourceHolder) throws SystemException, RollbackException
	{
		BitronixTransaction currentTransaction = currentTransaction();
		ResourceBean bean = xaResourceHolder.getResourceBean();
		if (LogDebugCheck.isDebugEnabled())
		{
			log.finer("enlisting " + xaResourceHolder + " into " + currentTransaction);
		}

		if (currentTransaction != null)
		{
			if (currentTransaction.timedOut())
			{
				throw new BitronixSystemException("transaction timed out");
			}

			// in case multiple unjoined branches of the current transaction have run on the resource,
			// only the last one counts as all the first ones are ended already
			XAResourceHolderState alreadyEnlistedXAResourceHolderState = TransactionContextHelper.getLatestAlreadyEnlistedXAResourceHolderState(xaResourceHolder,
			                                                                                                                                    currentTransaction);
			if (alreadyEnlistedXAResourceHolderState == null || alreadyEnlistedXAResourceHolderState.isEnded())
			{
				currentTransaction.enlistResource(xaResourceHolder.getXAResource());
			}
			else if (LogDebugCheck.isDebugEnabled())
			{
				log.finer("avoiding re-enlistment of already enlisted but not ended resource " + alreadyEnlistedXAResourceHolderState);
			}
		}
		else
		{
			if (bean.getAllowLocalTransactions())
			{
				if (LogDebugCheck.isDebugEnabled())
				{
					log.finer("in local transaction context, skipping enlistment");
				}
			}
			else
			{
				throw new BitronixSystemException("resource '" + bean.getUniqueName() + "' cannot be used outside XA " +
				                                  "transaction scope. Set allowLocalTransactions to true if you want to allow this and you know " +
				                                  "your resource supports this.");
			}
		}
	}

	/**
	 * Get the transaction running on the current thead context.
	 *
	 * @return null if there is no transaction on the current context or if the transaction manager is not running.
	 */
	public static BitronixTransaction currentTransaction()
	{
		if (!TransactionManagerServices.isTransactionManagerRunning())
		{
			return null;
		}
		return TransactionManagerServices.getTransactionManager()
		                                 .getCurrentTransaction();
	}

	/**
	 * Method getLatestAlreadyEnlistedXAResourceHolderState ...
	 *
	 * @param xaResourceHolder
	 * 		of type XAResourceHolder
	 * @param currentTransaction
	 * 		of type BitronixTransaction
	 *
	 * @return XAResourceHolderState
	 */
	private static XAResourceHolderState getLatestAlreadyEnlistedXAResourceHolderState(XAResourceHolder xaResourceHolder, BitronixTransaction currentTransaction)
	{
		if (currentTransaction == null)
		{
			return null;
		}

		class LocalVisitor
				implements XAResourceHolderStateVisitor
		{
			private XAResourceHolderState latestEnlistedHolder;

			/**
			 * Called when visiting all {@link bitronix.tm.internal.XAResourceHolderState}s.
			 * @param xaResourceHolderState the currently visited {@link bitronix.tm.internal.XAResourceHolderState}
			 * @return return true to continue visitation, false to stop visitation
			 */
			@Override
			public boolean visit(XAResourceHolderState xaResourceHolderState)
			{
				if (xaResourceHolderState != null && xaResourceHolderState.getXid() != null)
				{
					BitronixXid bitronixXid = xaResourceHolderState.getXid();
					Uid resourceGtrid = bitronixXid.getGlobalTransactionIdUid();
					Uid currentTransactionGtrid = currentTransaction.getResourceManager()
					                                                .getGtrid();

					if (currentTransactionGtrid.equals(resourceGtrid))
					{
						latestEnlistedHolder = xaResourceHolderState;
					}
				}
				return true;  // continue visitation
			}
		}
		LocalVisitor xaResourceHolderStateVisitor = new LocalVisitor();
		xaResourceHolder.acceptVisitorForXAResourceHolderStates(currentTransaction.getResourceManager()
		                                                                          .getGtrid(), xaResourceHolderStateVisitor);

		return xaResourceHolderStateVisitor.latestEnlistedHolder;
	}

	/**
	 * Delist the {@link XAResourceHolder} from the current transaction or do nothing if there is no global transaction
	 * context for this thread.
	 *
	 * @param xaResourceHolder
	 * 		the {@link XAResourceHolder} to delist.
	 *
	 * @throws SystemException
	 * 		if an internal error happens.
	 */
	public static void delistFromCurrentTransaction(XAResourceHolder xaResourceHolder) throws SystemException
	{
		BitronixTransaction currentTransaction = currentTransaction();
		ResourceBean bean = xaResourceHolder.getResourceBean();
		if (LogDebugCheck.isDebugEnabled())
		{
			log.finer("delisting " + xaResourceHolder + FROM_SPACE + currentTransaction);
		}

		// End resource as eagerly as possible. This allows to release connections to the pool much earlier
		// with resources fully supporting transaction interleaving.
		if (isInEnlistingGlobalTransactionContext(xaResourceHolder, currentTransaction) && !bean.getDeferConnectionRelease())
		{

			class LocalVisitor
					implements XAResourceHolderStateVisitor
			{
				private SystemException systemException = null;

				/**
				 * Called when visiting all {@link bitronix.tm.internal.XAResourceHolderState}s.
				 * @param xaResourceHolderState the currently visited {@link bitronix.tm.internal.XAResourceHolderState}
				 * @return return true to continue visitation, false to stop visitation
				 */
				@Override
				public boolean visit(XAResourceHolderState xaResourceHolderState)
				{
					if (!xaResourceHolderState.isEnded())
					{
						if (LogDebugCheck.isDebugEnabled())
						{
							log.finer("delisting resource " + xaResourceHolderState + FROM_SPACE + currentTransaction);
						}

						// Watch out: the delistResource() call might throw a BitronixRollbackSystemException to indicate a unilateral rollback.
						try
						{
							currentTransaction.delistResource(xaResourceHolderState.getXAResource(), XAResource.TMSUCCESS);
						}
						catch (SystemException e)
						{
							systemException = e;
							return false; // stop visitation
						}
					}
					else if (LogDebugCheck.isDebugEnabled())
					{
						log.finer("avoiding delistment of not enlisted resource " + xaResourceHolderState);
					}
					return true; // continue visitation
				}
			}

			LocalVisitor xaResourceHolderStateVisitor = new LocalVisitor();
			xaResourceHolder.acceptVisitorForXAResourceHolderStates(currentTransaction.getResourceManager()
			                                                                          .getGtrid(), xaResourceHolderStateVisitor);

			if (xaResourceHolderStateVisitor.systemException != null)
			{
				throw xaResourceHolderStateVisitor.systemException;
			}
		} // isInEnlistingGlobalTransactionContext
	}

	/**
	 * Method isInEnlistingGlobalTransactionContext ...
	 *
	 * @param xaResourceHolder
	 * 		of type XAResourceHolder
	 * @param currentTransaction
	 * 		of type BitronixTransaction
	 *
	 * @return boolean
	 */
	private static boolean isInEnlistingGlobalTransactionContext(XAResourceHolder xaResourceHolder, BitronixTransaction currentTransaction)
	{
		boolean globalTransactionMode = false;
		if (currentTransaction != null && xaResourceHolder.isExistXAResourceHolderStatesForGtrid(currentTransaction.getResourceManager()
		                                                                                                           .getGtrid()))
		{
			globalTransactionMode = true;
		}
		if (LogDebugCheck.isDebugEnabled())
		{
			log.finer("resource is " + (globalTransactionMode ? "" : "not ") + "in enlisting global transaction context: " + xaResourceHolder);
		}
		return globalTransactionMode;
	}


	/* private methods must not call TransactionManagerServices.getTransactionManager().getCurrentTransaction() */

	/**
	 * Switch the {@link XAStatefulHolder}'s state appropriately after the acquired resource handle has been closed.
	 * The pooled resource will either be marked as closed or not accessible, depending on the value of the bean's
	 * deferConnectionRelease property and will be marked for release after 2PC execution in the latter case.
	 *
	 * @param xaStatefulHolder
	 * 		the {@link XAStatefulHolder} to requeue.
	 * @param bean
	 * 		the {@link ResourceBean} of the {@link XAResourceHolder}.
	 *
	 * @throws BitronixSystemException
	 * 		if an internal error happens.
	 */
	public static void requeue(XAStatefulHolder xaStatefulHolder, ResourceBean bean) throws BitronixSystemException
	{
		BitronixTransaction currentTransaction = currentTransaction();
		if (LogDebugCheck.isDebugEnabled())
		{
			log.finer("requeuing " + xaStatefulHolder + FROM_SPACE + currentTransaction);
		}

		if (!TransactionContextHelper.isInEnlistingGlobalTransactionContext(xaStatefulHolder, currentTransaction))
		{
			if (!TransactionContextHelper.isEnlistedInSomeTransaction(xaStatefulHolder))
			{
				// local mode, always requeue connection immediately
				if (LogDebugCheck.isDebugEnabled())
				{
					log.finer("resource not in enlisting global transaction context, immediately releasing to pool " + xaStatefulHolder);
				}
				xaStatefulHolder.setState(State.IN_POOL);
			}
			else
			{
				throw new BitronixSystemException("cannot close a resource when its XAResource is taking part in an unfinished global transaction");
			}
		}
		else if (bean.getDeferConnectionRelease())
		{
			// global mode, defer connection requeuing
			if (LogDebugCheck.isDebugEnabled())
			{
				log.finer("deferring release to pool of " + xaStatefulHolder);
			}

			if (!TransactionContextHelper.isAlreadyRegisteredForDeferredRelease(xaStatefulHolder, currentTransaction))
			{
				if (LogDebugCheck.isDebugEnabled())
				{
					log.finer("registering DeferredReleaseSynchronization for " + xaStatefulHolder);
				}
				DeferredReleaseSynchronization synchronization = new DeferredReleaseSynchronization(xaStatefulHolder);
				currentTransaction.getSynchronizationScheduler()
				                  .add(synchronization, Scheduler.ALWAYS_LAST_POSITION);
			}
			else if (LogDebugCheck.isDebugEnabled())
			{
				log.finer("already registered DeferredReleaseSynchronization for " + xaStatefulHolder);
			}

			xaStatefulHolder.setState(State.NOT_ACCESSIBLE);
		}
		else
		{
			// global mode, immediate connection requeuing
			if (LogDebugCheck.isDebugEnabled())
			{
				log.finer("immediately releasing to pool " + xaStatefulHolder);
			}
			xaStatefulHolder.setState(State.IN_POOL);
		}
	}

	/**
	 * Method isInEnlistingGlobalTransactionContext ...
	 *
	 * @param xaStatefulHolder
	 * 		of type XAStatefulHolder
	 * @param currentTransaction
	 * 		of type BitronixTransaction
	 *
	 * @return boolean
	 */
	private static boolean isInEnlistingGlobalTransactionContext(XAStatefulHolder xaStatefulHolder, BitronixTransaction currentTransaction)
	{
		List> xaResourceHolders = xaStatefulHolder.getXAResourceHolders();
		if (xaResourceHolders == null || xaResourceHolders.isEmpty())
		{
			return false;
		}

		for (XAResourceHolder xaResourceHolder : xaResourceHolders)
		{
			if (isInEnlistingGlobalTransactionContext(xaResourceHolder, currentTransaction))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Method isEnlistedInSomeTransaction ...
	 *
	 * @param xaStatefulHolder
	 * 		of type XAStatefulHolder
	 *
	 * @return boolean
	 */
	private static boolean isEnlistedInSomeTransaction(XAStatefulHolder xaStatefulHolder)
	{
		List> xaResourceHolders = xaStatefulHolder.getXAResourceHolders();
		if (xaResourceHolders == null || xaResourceHolders.isEmpty())
		{
			return false;
		}

		for (XAResourceHolder xaResourceHolder : xaResourceHolders)
		{
			if (isEnlistedInSomeTransaction(xaResourceHolder))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Method isAlreadyRegisteredForDeferredRelease ...
	 *
	 * @param xaStatefulHolder
	 * 		of type XAStatefulHolder
	 * @param currentTransaction
	 * 		of type BitronixTransaction
	 *
	 * @return boolean
	 */
	private static boolean isAlreadyRegisteredForDeferredRelease(XAStatefulHolder xaStatefulHolder, BitronixTransaction currentTransaction)
	{
		boolean alreadyDeferred = findDeferredRelease(xaStatefulHolder, currentTransaction) != null;
		if (LogDebugCheck.isDebugEnabled())
		{
			log.finer(xaStatefulHolder + " is " + (alreadyDeferred ? "" : "not ") + "already registered for deferred release in " + currentTransaction);
		}
		return alreadyDeferred;
	}

	/**
	 * Method isEnlistedInSomeTransaction ...
	 *
	 * @param xaResourceHolder
	 * 		of type XAResourceHolder
	 *
	 * @return boolean
	 *
	 * @throws BitronixSystemException
	 * 		when
	 */
	private static boolean isEnlistedInSomeTransaction(XAResourceHolder xaResourceHolder)
	{
		if (LogDebugCheck.isDebugEnabled())
		{
			log.finer("looking in in-flight transactions for XAResourceHolderState of " + xaResourceHolder);
		}

		if (!TransactionManagerServices.isTransactionManagerRunning())
		{
			if (LogDebugCheck.isDebugEnabled())
			{
				log.finer("transaction manager not running, there is no in-flight transaction");
			}
			return false;
		}

		return xaResourceHolder.hasStateForXAResource(xaResourceHolder);
	}

	/**
	 * Method findDeferredRelease ...
	 *
	 * @param xaStatefulHolder
	 * 		of type XAStatefulHolder
	 * @param currentTransaction
	 * 		of type BitronixTransaction
	 *
	 * @return DeferredReleaseSynchronization
	 */
	private static DeferredReleaseSynchronization findDeferredRelease(XAStatefulHolder xaStatefulHolder, BitronixTransaction currentTransaction)
	{
		Scheduler synchronizationScheduler = currentTransaction.getSynchronizationScheduler();

		for (Synchronization synchronization : synchronizationScheduler)
		{
			if (synchronization instanceof DeferredReleaseSynchronization)
			{
				DeferredReleaseSynchronization deferredReleaseSynchronization = (DeferredReleaseSynchronization) synchronization;
				if (deferredReleaseSynchronization.getXAStatefulHolder() == xaStatefulHolder)
				{
					return deferredReleaseSynchronization;
				}
			} // if synchronization instanceof DeferredReleaseSynchronization
		} // for

		return null;
	}

	/**
	 * Ensure the {@link XAStatefulHolder}'s release won't be deferred anymore (when appropriate) as it has been recycled.
	 *
	 * @param xaStatefulHolder
	 * 		the recycled {@link XAStatefulHolder}.
	 */
	public static void recycle(XAStatefulHolder xaStatefulHolder)
	{
		BitronixTransaction currentTransaction = currentTransaction();
		if (LogDebugCheck.isDebugEnabled())
		{
			log.finer("marking " + xaStatefulHolder + " as recycled in " + currentTransaction);
		}
		if (currentTransaction != null)
		{
			Scheduler synchronizationScheduler = currentTransaction.getSynchronizationScheduler();

			DeferredReleaseSynchronization deferredReleaseSynchronization = findDeferredRelease(xaStatefulHolder, currentTransaction);
			if (deferredReleaseSynchronization != null)
			{
				if (LogDebugCheck.isDebugEnabled())
				{
					log.finer(xaStatefulHolder + " has been recycled, unregistering deferred release from " + currentTransaction);
				}
				synchronizationScheduler.remove(deferredReleaseSynchronization);
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy