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

com.atomikos.datasource.xa.session.SessionHandleState Maven / Gradle / Ivy

/**
 * Copyright (C) 2000-2020 Atomikos 
 *
 * LICENSE CONDITIONS
 *
 * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
 */

package com.atomikos.datasource.xa.session;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.transaction.xa.XAResource;

import com.atomikos.datasource.xa.XATransactionalResource;
import com.atomikos.icatch.CompositeTransaction;
import com.atomikos.logging.Logger;
import com.atomikos.logging.LoggerFactory;
 
 /**
  * 
  * 
  * A reusable state tracker for XA session/connection handles.
  * An instance of this class can be used for automatically tracking the 
  * enlistment and termination states of all branches
  * that a connection handle is involved in. It does this by switching states
  * behind the scenes, so the same instance can be used for several branches.
  */

public class SessionHandleState 
{
	private static final Logger LOGGER = LoggerFactory.createLogger(SessionHandleState.class);

	private TransactionContext currentContext;
	private Set allContexts;
	private XATransactionalResource resource;
	private XAResource xaResource;
	private boolean erroneous;
	private boolean closed;
	private List sessionHandleStateChangeListeners = new ArrayList();
	
	
	public SessionHandleState ( XATransactionalResource resource , XAResource xaResource )
	{
		this.resource = resource;
		this.xaResource = xaResource;
		this.allContexts = new HashSet();
		this.erroneous = false;
		this.closed = true;
	}
	
	/**
	 * Checks if the session handle is terminated (i.e., can be discarded) and the
	 * underlying vendor xa connection/session can be reused or destroyed. 
	 * 
	 * @return True if the underlying vendor connection can be reused or destroyed. 
	 * The session handle itself (i.e., the Atomikos proxy) should be discarded.
	 */
	
	public synchronized boolean isTerminated()
	{
		boolean terminated = true;
		if (LOGGER.isTraceEnabled()) {
			LOGGER.logTrace("isTerminated: checking " + allContexts.size() + " contexts...");
		}
		Iterator it = allContexts.iterator();
		while ( it.hasNext() ) {
			TransactionContext b = ( TransactionContext ) it.next();
			if ( b.isTerminated() ) {
				it.remove();
			}
			else terminated = false;
		}
		
		if ( terminated ) currentContext = null;
		
		return terminated;
	}
	
	/**
	 * Notification that the session was gotten from the pool.
	 * 
	 * 
	 */
	public synchronized void notifySessionBorrowed()
	{
		if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": notifySessionBorrowed" );
		currentContext = new TransactionContext ( resource , xaResource );
		allContexts.add ( currentContext );
		closed = false;
	}
	
	/**
	 * Notification that the session handle has been closed by 
	 * the application. 
	 *
	 */
	
	public void notifySessionClosed()
	{
		if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": entering notifySessionClosed" );
		boolean notifyOfClosedEvent = false;
	
		synchronized ( this ) {
			boolean alreadyTerminated = isTerminated();
			Iterator it = allContexts.iterator();
			while ( it.hasNext() ) {
				TransactionContext b =  it.next();
				if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": delegating session close to " + b ) ;
				b.sessionClosed();
			}
			closed = true;
			if ( isTerminated() && !alreadyTerminated ) notifyOfClosedEvent = true;
		}
		//do callbacks out of synch!!!
		if ( notifyOfClosedEvent ) {
			if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": all contexts terminated, firing TerminatedEvent" );
			fireTerminatedEvent();
		}
	}
	
	/**
	 * Notification that the session handle is about to be used in the current
	 * transaction context (i.e. whatever transaction exists for the calling thread). 
	 * This method MUST be called BEFORE any work is delegated to the underlying
	 * vendor connection.
	 * @param ct The current transaction, or null if none. 
	 * 
	 * @throws InvalidSessionHandleStateException 
	 */
	
	public synchronized void notifyBeforeUse ( CompositeTransaction ct ) throws InvalidSessionHandleStateException
	{
		if ( closed ) throw new InvalidSessionHandleStateException ( "The underlying XA session is closed" );
		
		try {
			//first check if a suspended context exists for the current tx;
			//this happens if a transaction was suspended and now resumed
			TransactionContext suspended = null;			
			if ( ct != null ) {
				Iterator it = allContexts.iterator();
				while ( it.hasNext() && suspended == null ) {
					TransactionContext b = ( TransactionContext ) it.next();
					if ( b.isSuspendedInTransaction ( ct ) ) {
						suspended = b;
					}
				}
			}
			//check enlistment
			if ( suspended != null ) {
				if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( this + ": resuming suspended XA context for transaction " + ct.getTid() );
				currentContext = suspended;
				currentContext.transactionResumed();
			}
			else {
				//no suspended branch was found -> try to use the current branch
				try {
					if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace ( this + ": checking XA context for transaction " + ct );
					currentContext.checkEnlistBeforeUse ( ct );
				}
				catch ( UnexpectedTransactionContextException txBoundaryPassed ) {
					//we are being used in a different context than expected -> suspend!
					if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug (  this + ": suspending existing XA context and creating a new one for transaction " + ct );
					currentContext.transactionSuspended();
					currentContext = new TransactionContext ( resource , xaResource );
					allContexts.add ( currentContext );
					//note: we keep all branches - if the new current branch is a Subtransaction 
					//then it will not terminate early and needs to stay around
					try {
						currentContext.checkEnlistBeforeUse ( ct );
					} catch ( UnexpectedTransactionContextException e )  {
						String msg = "Unexpected error in session handle";
						LOGGER.logError ( msg , e );
						throw new InvalidSessionHandleStateException ( msg );
					}
				}
			}
		} catch ( InvalidSessionHandleStateException e ) {
			//avoid reuse in pool
			notifySessionErrorOccurred();
			throw e;
		}
		
	}
	
	/**
	 * Checks if the session has had any errors.
	 * This method can be used to decide whether or not to reuse the underlying 
	 * vendor connection in the pool.
	 * 
	 * @return True if sessionErrorOccurred has been called, false if not.
	 */
	
	public boolean isErroneous()
	{
		return erroneous;
	}
	
	/**
	 * Marks this session as erroneous. This has no other effect than that
	 * isErroneous returns true. 
	 *
	 */
	
	public void notifySessionErrorOccurred()
	{
		this.erroneous = true;
	}
	
	/**
	 * Notifies the session that the transaction was terminated.
	 * 
	 * @param ct
	 */
	
	public void notifyTransactionTerminated ( CompositeTransaction ct ) 
	{
		
		boolean notifyOfTerminatedEvent = false;
		synchronized ( this ) {
			boolean alreadyTerminated = isTerminated();
			Iterator it = allContexts.iterator();
			while ( it.hasNext() ) {
				TransactionContext b = it.next();
				b.transactionTerminated ( ct );
			}
			if ( isTerminated() && !alreadyTerminated ) notifyOfTerminatedEvent = true;
		}
		
		//check termination status CHANGES - only fire event once for safety!
		if ( notifyOfTerminatedEvent ) {
			if ( LOGGER.isTraceEnabled() ) LOGGER.logTrace( this + ": all contexts terminated, firing TerminatedEvent for " + this);
			fireTerminatedEvent();
		}
	}

	
	public void registerSessionHandleStateChangeListener(SessionHandleStateChangeListener listener) 
	{
		sessionHandleStateChangeListeners.add(listener);
	}
	
	public void unregisterSessionHandleStateChangeListener(SessionHandleStateChangeListener listener) 
	{
		sessionHandleStateChangeListeners.remove(listener);
	}

	private void fireTerminatedEvent() 
	{
		for (int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy