AbstractTransactionalConnectionListener.java

/*
 * IronJacamar, a Java EE Connector Architecture implementation
 * Copyright 2015, Red Hat Inc, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the Eclipse Public License 1.0 as
 * published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse
 * Public License for more details.
 *
 * You should have received a copy of the Eclipse Public License 
 * along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.ironjacamar.core.connectionmanager.listener;

import org.ironjacamar.common.api.metadata.common.FlushStrategy;
import org.ironjacamar.core.CoreLogger;
import org.ironjacamar.core.connectionmanager.ConnectionManager;
import org.ironjacamar.core.connectionmanager.Credential;
import org.ironjacamar.core.connectionmanager.TransactionalConnectionManager;
import org.ironjacamar.core.connectionmanager.pool.ManagedConnectionPool;
import org.ironjacamar.core.spi.transaction.ConnectableResource;
import org.ironjacamar.core.spi.transaction.TransactionIntegration;
import org.ironjacamar.core.spi.transaction.TxUtils;
import org.ironjacamar.core.spi.transaction.local.LocalXAResource;
import org.ironjacamar.core.tracer.Tracer;

import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.DESTROY;
import static org.ironjacamar.core.connectionmanager.listener.ConnectionListener.DESTROYED;

import java.util.Iterator;
import java.util.Map;

import javax.resource.ResourceException;
import javax.resource.spi.ConnectionEvent;
import javax.resource.spi.LocalTransaction;
import javax.resource.spi.ManagedConnection;
import javax.transaction.Status;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;

import org.jboss.logging.Logger;

/**
 * An abstract transactional connection listener, which is enlisted on the transaction boundary
 * @author <a href="mailto:jesper.pedersen@ironjacamar.org">Jesper Pedersen</a>
 */
public abstract class AbstractTransactionalConnectionListener extends AbstractConnectionListener
{
   /** The logger */
   private static CoreLogger log = Logger.getMessageLogger(CoreLogger.class,
                                                           AbstractTransactionalConnectionListener.class.getName());

   /** Transaction synchronization instance */
   protected TransactionSynchronization transactionSynchronization;

   /** Enlisted flag */
   protected boolean enlisted;
   
   /** XAResource instance */
   protected XAResource xaResource;

   /** XAResource timeout */
   protected int xaResourceTimeout;
   
   /** Whether there is a local transaction */
   protected boolean localTransaction;

   /**
    * Constructor
    * @param cm The connection manager
    * @param mc The managed connection
    * @param credential The credential
    * @param xaResource The associated XAResource
    * @param xaResourceTimeout The timeout for the XAResource instance
    * @param mcp The ManagedConnectionPool
    * @param flushStrategy The FlushStrategy
    */
   public AbstractTransactionalConnectionListener(ConnectionManager cm, ManagedConnection mc, Credential credential,
                                                  XAResource xaResource, int xaResourceTimeout,
                                                   ManagedConnectionPool mcp, FlushStrategy flushStrategy)

   {
      super(cm, mc, credential, mcp, flushStrategy);

      this.transactionSynchronization = null;
      this.enlisted = false;
      this.xaResource = xaResource;
      this.xaResourceTimeout = xaResourceTimeout;
      this.localTransaction = false;

      if (xaResource instanceof LocalXAResource)
      {
         ((LocalXAResource)xaResource).setConnectionManager(cm);
         ((LocalXAResource)xaResource).setConnectionListener(this);
      }
      if (xaResource instanceof ConnectableResource)
      {
         ((ConnectableResource)xaResource).setConnectableResourceListener(this);
      }

      resetXAResourceTimeout();
   }

   /**
    * {@inheritDoc}
    */
   public boolean isEnlisted()
   {
      return enlisted;
   }

   /**
    * {@inheritDoc}
    */
   public void enlist() throws ResourceException
   {
      if (isEnlisted() || getState() == DESTROY || getState() == DESTROYED)
         return;

      log.tracef("Enlisting: %s", this);
      
      try
      {
         TransactionalConnectionManager txCM = (TransactionalConnectionManager)cm;
         Transaction tx = txCM.getTransactionIntegration().getTransactionManager().getTransaction();
      
         transactionSynchronization = createTransactionSynchronization();
         transactionSynchronization.init(tx);
         transactionSynchronization.enlist();

         txCM.getTransactionIntegration().getTransactionSynchronizationRegistry().
            registerInterposedSynchronization(transactionSynchronization);

         enlisted = true;

         log.tracef("Enlisted: %s", this);
      }
      catch (ResourceException re)
      {
         throw re;
      }
      catch (Exception e)
      {
         throw new ResourceException(e);
      }
   }

   /**
    * {@inheritDoc}
    */
   public void delist() throws ResourceException
   {
      log.tracef("Delisting: %s", this);
      
      try
      {
         TransactionalConnectionManager txCM = (TransactionalConnectionManager)cm;
         TransactionManager tm = txCM.getTransactionIntegration().getTransactionManager();
         int status = tm.getStatus();

         if (status != Status.STATUS_NO_TRANSACTION && enlisted)
         {
            Transaction tx = tm.getTransaction();
            boolean delistResult = tx.delistResource(xaResource, XAResource.TMSUCCESS);

            if (Tracer.isEnabled())
               Tracer.delistConnectionListener(cm.getPool().getConfiguration().getId(),
                                               getManagedConnectionPool(),
                                               this, tx.toString(),
                                               true, false, false);

            if (delistResult)
            {
               log.tracef("delist-success: %s", this);
            }
            else
            {
               log.debugf("delist-success failed: %s", this);
            }
         }

         localTransaction = false;
      
         if (transactionSynchronization != null)
         {
            transactionSynchronization.cancel();
            transactionSynchronization = null;
         }

         enlisted = false;
         log.tracef("Delisted: %s", this);
      }
      catch (Exception e)
      {
         throw new ResourceException(e);
      }
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public void localTransactionStarted(ConnectionEvent ce)
   {
      localTransaction = true;
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public void localTransactionCommitted(ConnectionEvent ce)
   {
      localTransaction = false;
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public void localTransactionRolledback(ConnectionEvent ce)
   {
      localTransaction = false;
   }

   /**
    * {@inheritDoc}
    */
   @Override
   public void toPool() throws ResourceException
   {
      if (localTransaction)
      {
         LocalTransaction localTransaction = null;
         ManagedConnection mc = getManagedConnection();
         try
         {
            localTransaction = mc.getLocalTransaction();
         }
         catch (Throwable t)
         {
            throw new ResourceException(t);
         }

         if (localTransaction == null)
         {
            throw new ResourceException();
         }
         else
         {
            localTransaction.rollback();
         }
      }

      resetXAResourceTimeout();
      
      super.toPool();
   }

   /**
    * {@inheritDoc}
    */
   @Override
   void haltCatchFire()
   {
      if (isEnlisted())
      {
         if (transactionSynchronization != null)
            transactionSynchronization.cancel();

         String txId = "";
         TransactionalConnectionManager txCM = (TransactionalConnectionManager)cm;
         TransactionIntegration ti = txCM.getTransactionIntegration();

         if (ti != null)
         {
            Transaction tx = null;
            try
            {
               tx = ti.getTransactionManager().getTransaction();

               if (Tracer.isEnabled() && tx != null)
                  txId = tx.toString();
               
               if (TxUtils.isUncommitted(tx))
               {
                  log.tracef("connectionErrorOccurred: delistResource(%s, TMFAIL)", xaResource);

                  boolean failResult = tx.delistResource(xaResource, XAResource.TMFAIL);
                  
                  if (failResult)
                  {
                     log.tracef("connectionErrorOccurred: delist-fail: %s", this);
                  }
                  else
                  {
                     log.debugf("connectionErrorOccurred: delist-fail failed: %s", this);
                  }
               }
            }
            catch (Exception e)
            {
               log.debugf(e, "connectionErrorOccurred: Exception during delistResource=%s", e.getMessage());
            }
            finally
            {
               if (TxUtils.isUncommitted(tx))
               {
                  try
                  {
                     tx.setRollbackOnly();
                  }
                  catch (Exception e)
                  {
                     // Just a hint
                  }
               }
            }
         }
         
         if (Tracer.isEnabled())
         {
            Tracer.delistConnectionListener(cm.getPool().getConfiguration().getId(),
                                            getManagedConnectionPool(),
                                            this, txId, false, true, false);
         }
      }

      // Prepare to explode
      enlisted = false;
      transactionSynchronization = null;
   }

   /**
    * Reset XAResource timeout
    */
   private void resetXAResourceTimeout()
   {
      // Do a reset of the underlying XAResource timeout
      if (!(xaResource instanceof LocalXAResource) && xaResourceTimeout > 0)
      {
         try
         {
            xaResource.setTransactionTimeout(xaResourceTimeout);
         }
         catch (XAException e)
         {
            log.debugf(e, "Exception during resetXAResourceTimeout for %s", this);
         }
      }
   }

   /**
    * Create the transaction synchronization object
    * @return The object
    */
   protected TransactionSynchronization createTransactionSynchronization()
   {
      return new TransactionSynchronizationImpl();
   }
   
   /**
    * Transaction synchronization
    */
   class TransactionSynchronizationImpl implements TransactionSynchronization
   {
      /** Transaction */
      private Transaction transaction;

      /** Cancel */
      private boolean cancel;

      /**
       * Constructor
       */
      public TransactionSynchronizationImpl()
      {
      }

      /**
       * {@inheritDoc}
       */
      public void init(Transaction tx)
      {
         this.transaction = tx;
         this.cancel = false;
      }

      /**
       * {@inheritDoc}
       */
      public void enlist() throws ResourceException
      {
         ResourceException enlistError = null;
         try
         {
            if (!transaction.enlistResource(xaResource))
            {
               if (Tracer.isEnabled())
                  Tracer.enlistConnectionListener(cm.getPool().getConfiguration().getId(), 
                                                  getManagedConnectionPool(),
                                                  AbstractTransactionalConnectionListener.this,
                                                  transaction.toString(), false, false);

               enlistError = new ResourceException("Failed to enlist");
            }
            else
            {
               if (Tracer.isEnabled())
                  Tracer.enlistConnectionListener(cm.getPool().getConfiguration().getId(), 
                                                  getManagedConnectionPool(),
                                                  AbstractTransactionalConnectionListener.this,
                                                  transaction.toString(), true, false);
            }
         }
         catch (Exception e)
         {
            enlistError = new ResourceException(e);

            if (Tracer.isEnabled())
               Tracer.enlistConnectionListener(cm.getPool().getConfiguration().getId(), 
                                               getManagedConnectionPool(),
                                               AbstractTransactionalConnectionListener.this,
                                               transaction.toString(), false, false);
         }

         if (enlistError != null)
         {
            if (Tracer.isEnabled())
               Tracer.exception(cm.getPool().getConfiguration().getId(), 
                                getManagedConnectionPool(),
                                AbstractTransactionalConnectionListener.this, enlistError);

            transactionSynchronization = null;
            enlisted = false;

            throw enlistError;
         }
      }

      /**
       * {@inheritDoc}
       */
      public void cancel()
      {
         cancel = true;
      }

      /**
       * {@inheritDoc}
       */
      public void beforeCompletion()
      {
         if (!cancel)
         {
            log.tracef("beforeCompletion: %s", AbstractTransactionalConnectionListener.this);
            try
            {
               if (TxUtils.isUncommitted(transaction))
               {
                  if (TxUtils.isActive(transaction))
                  {
                     log.tracef("delistResource(%s, TMSUCCESS)", xaResource);

                     transaction.delistResource(xaResource, XAResource.TMSUCCESS);

                     if (Tracer.isEnabled())
                        Tracer.delistConnectionListener(cm.getPool().getConfiguration().getId(),
                                                        getManagedConnectionPool(),
                                                        AbstractTransactionalConnectionListener.this,
                                                        transaction.toString(),
                                                        true, false, false);


                  }
                  else
                  {
                     log.tracef("delistResource(%s, TMFAIL)", xaResource);

                     transaction.delistResource(xaResource, XAResource.TMFAIL);

                     if (Tracer.isEnabled())
                        Tracer.delistConnectionListener(cm.getPool().getConfiguration().getId(),
                                                        getManagedConnectionPool(),
                                                        AbstractTransactionalConnectionListener.this,
                                                        transaction.toString(),
                                                        false, false, false);
                  }
               }
               else
               {
                  log.tracef("Non-uncommitted transaction for %s (%s)", AbstractTransactionalConnectionListener.this,
                             transaction != null ? TxUtils.getStatusAsString(transaction.getStatus()) : "None");
               }
            }
            catch (Exception e)
            {
               log.beforeCompletionErrorOccured(AbstractTransactionalConnectionListener.this, e);
            }
         }
         else
         {
            log.tracef("Unenlisted resource: %s", AbstractTransactionalConnectionListener.this);
         }
      }

      /**
       * {@inheritDoc}
       */
      public void afterCompletion(int status)
      {
         if (!cancel)
         {
            log.tracef("afterCompletion(%s): %s", status, AbstractTransactionalConnectionListener.this);

            // "Delist"
            transactionSynchronization = null;
            enlisted = false;

            if (connectionHandles.isEmpty())
            {
               if (Tracer.isEnabled() && status == Status.STATUS_ROLLEDBACK)
                  Tracer.delistConnectionListener(cm.getPool().getConfiguration().getId(),
                                                  getManagedConnectionPool(),
                                                  AbstractTransactionalConnectionListener.this, "",
                                                  true, true, false);

               cm.returnConnectionListener(AbstractTransactionalConnectionListener.this, false);
            }
            else
            {
               if (cm.getConnectionManagerConfiguration().isTracking() == null ||
                   cm.getConnectionManagerConfiguration().isTracking().booleanValue())
               {
                  log.activeHandles(cm.getPool().getConfiguration().getId(), connectionHandles.size());

                  if (connectionTraces != null)
                  {
                     Iterator<Map.Entry<Object, Exception>> it = connectionTraces.entrySet().iterator();
                     while (it.hasNext())
                     {
                        Map.Entry<Object, Exception> entry = it.next();
                        log.activeHandle(entry.getKey(), entry.getValue());
                     }

                     log.txConnectionListenerBoundary(new Exception());
                  }

                  if (Tracer.isEnabled())
                  {
                     for (Object c : connectionHandles)
                     {
                        Tracer.clearConnection(cm.getPool().getConfiguration().getId(),
                                               getManagedConnectionPool(),
                                               AbstractTransactionalConnectionListener.this, c);
                     }
                  }

                  cm.returnConnectionListener(AbstractTransactionalConnectionListener.this, true);
               }
               else
               {
                  log.tracef(new Exception("Connection across boundary"), "ConnectionListener=%s",
                             AbstractTransactionalConnectionListener.this);
               }
            }
         }
      }
   }
}