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