TransactionTracker.java :  » Database-ORM » beankeeper » hu » netmind » persistence » Java Open Source

Java Open Source » Database ORM » beankeeper 
beankeeper » hu » netmind » persistence » TransactionTracker.java
/**
 * Copyright (C) 2006 NetMind Consulting Bt.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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 GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package hu.netmind.persistence;

import java.util.LinkedList;
import java.util.Iterator;
import java.util.Vector;
import java.sql.Connection;
import org.apache.log4j.Logger;
import java.util.HashSet;
import java.io.IOException;
import java.io.StringWriter;
import java.io.PrintWriter;

/**
 * This class keeps track of all transaction objects allocated.
 * @author Brautigam Robert
 * @version Revision: $Revision$
 */
public class TransactionTracker
{
   public static int TX_REQUIRED = 1;
   public static int TX_NEW = 2;
   public static int TX_OPTIONAL = 3;

   private static Logger logger = Logger.getLogger(TransactionTracker.class);
   
   StoreContext context;
   private ThreadLocal transactions;
   private LinkedList listeners;
   private LinkedList allTransactions;
   private HashSet allTransactionIds;
   private Store store;
   private ThreadLocal runningListeners;

   TransactionTracker(StoreContext context)
   {
      this.context=context;
      transactions = new ThreadLocal();
      listeners = new LinkedList();
      allTransactions = new LinkedList();
      allTransactionIds = new HashSet();
      runningListeners =new ThreadLocal();
   }

   void registerTransactionId(Long serial)
   {
      synchronized ( allTransactions )
      {
         allTransactionIds.add(serial);
      }
   }

   boolean hasOpenTransaction(Long serial)
   {
      synchronized ( allTransactions )
      {
         return allTransactionIds.contains(serial);
      }
   }
   
   /**
    * Mark all current transactions as rollback only.
    */
   public void markRollbackAll(StoreException ex)
   {
      synchronized ( allTransactions )
      {
         for ( int i=0; i<allTransactions.size(); i++ )
         {
            Transaction tx = (Transaction) allTransactions.get(i);
            tx.setRollbackException(ex);
         }
      }
   }

   /**
    * Get a transaction. Following modes are supported:
    * <ul>
    *    <li>TX_REQUIRED: A new transaction is allocated if no current
    *    transaction exists, otherwise the current transaction is returned.</li>
    *    <li>TX_NEW: A new transaction is allocated either way.</li>
    *    <li>TX_OPTIONAL: If there is a current transaction, that is returned,
    *    null otherwise.</li>
    * </ul>
    * Note, that each transaction can support multiple levels of begin-commit
    * blocks. Each transaction only commits/rollsback is the most outer
    * block is commited/rolled back.
    */
   public Transaction getTransaction(int mode)
   {
      LinkedList list = (LinkedList) transactions.get();
      if ( list == null )
      {
         // No list yet, initialize threadlocal
         list = new LinkedList();
         transactions.set(list);
      }
      if ( (list.size()==0) && (mode==TX_OPTIONAL) )
         return null;
      if ( (list.size() == 0) || (mode==TX_NEW) )
      {
         // No transaction, or new is explicitly required
         Transaction transaction = new Transaction(this);
         if ( logger.isTraceEnabled() )
            logger.trace("transaction allocation trace: "+getStackTrace(transaction.getAllocateTrace()));
         transaction.setConnection(context.getDatabase().getConnectionSource().getConnection());
         list.add(transaction);
         synchronized ( allTransactions )
         {
            allTransactions.add(transaction);
         }
      }
      return (Transaction) list.getLast();
   }

   /**
    * Send notifications.
    * @param transaction The transaction which caused the event.
    */
   private void sendNotifications(Transaction transaction, boolean commited)
   {
      // Initialize running list if nescesssary. Running lists keep
      // track of current threads listeners who are currently running.
      // This is to prevent infinite recursions, inside a notification run
      // a listener cannot cause any transacitions in which they will be
      // called a second time.
      HashSet runningList = (HashSet) runningListeners.get();
      if ( runningList == null )
      {
         runningList = new HashSet();
         runningListeners.set(runningList);
      }
      if ( logger.isDebugEnabled() )
         logger.debug("sending notifications for "+transaction+", running listeners: "+runningList);
      // Do notifications
      Vector listenersCopy = null;
      synchronized ( listeners )
      {
         listenersCopy = new Vector(listeners);
      }
      Iterator iterator = listenersCopy.iterator();
      while ( iterator.hasNext() )
      {
         TransactionListener listener = (TransactionListener) iterator.next();
         if ( ! runningList.contains(listener) )
         {
            runningList.add(listener); // Add to running list
            try
            {
               if ( commited )
                  listener.transactionCommited(transaction); // Run listener
               else
                  listener.transactionRolledback(transaction); // Run listener
            } catch ( Throwable e ) {
               logger.warn("a transaction listener threw error:",e);
            } finally {
               runningList.remove(listener); // Remove from running list
            }
         }
      }
   }

   /**
    * Internal commit, this is a callback from store.
    */
   void internalCommit(Transaction transaction)
   {
      // First commit sql
      Connection connection = transaction.getConnection();
      try
      {
         connection.commit();
         try
         {
            if ( transaction.getTransactionIsolation() != Connection.TRANSACTION_READ_COMMITTED )
               transaction.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
         } catch ( Exception e ) {
            logger.warn("could not set transaction isolation back to READ_COMMITED.",e);
         }
      } catch ( Exception e ) {
         throw new StoreException("could not commit transaction",e);
      } finally {
         // Remove transaction from queue
         removeTransaction(transaction);
      }
      // Send notifications
      sendNotifications(transaction,true);
   }

   /**
    * Commit a transaction.
    */
   void commit(Transaction transaction)
   {
      // If the transaction is internal, don't go through store
      if ( (transaction.getSerial()==null) &&
            (transaction.getSaveTables()!=null) && (transaction.getRemoveTables()!=null) )
      {
         internalCommit(transaction);
      } else {
         // Commit is going through Store
         context.getStore().commit(transaction);
      }
   }

   void rollback(Transaction transaction)
   {
      // If the transaction is internal, don't go through store
      if ( (transaction.getSerial()==null) &&
            (transaction.getSaveTables()!=null) && (transaction.getRemoveTables()!=null) )
      {
         internalRollback(transaction);
      } else {
         // Commit is going through Store
         context.getStore().rollback(transaction);
      }
   }

   /**
    * Rollback a transaction.
    */
   void internalRollback(Transaction transaction)
   {
      // First rollback sql
      Connection connection = transaction.getConnection();
      try
      {
         connection.rollback();
      } catch ( Exception e ) {
         throw new StoreException("could not roll back transaction",e);
      } finally {
         // Remove transaction from queue
         removeTransaction(transaction);
      }
      // Send notifications
      sendNotifications(transaction,false);
   }

   /**
    * Remove transaction from queue. Also, if this transaction is not
    * toplevel, then throw exception.
    */
   private void removeTransaction(Transaction transaction)
   {
      // Remove from threadlocal list
      LinkedList list = (LinkedList) transactions.get();
      if ( (list == null) || (list.size()==0) )
         throw new StoreException("no transactions present, and tried to use one.");
      if ( ! list.getLast().equals(transaction) )
      {
         Transaction top = (Transaction) list.getLast();
         logger.warn("possible transaction leak, tried to commit/rollback transaction, which was not the currenct transaction, investigate! Top was: "+top+", but tried to close: "+transaction+". "+
               "Last operation of top was: "+top.getLastOperation()+", other's: "+transaction.getLastOperation()+". "+
               "Now follows the allocation trace of top and the transaction to be closed: \n"+
               getStackTrace(top.getAllocateTrace())+"\n"+getStackTrace(transaction.getAllocateTrace()));
         throw new StoreException("tried to commit/rollback a transaction which is not the current transaction, possible transaction leak!");
      }
      // Give back connection
      context.getDatabase().getConnectionSource().releaseConnection(transaction.getConnection());
      transaction.setConnection(null);
      // Remove from list
      list.removeLast();
      // Remove from global list
      synchronized ( allTransactions )
      {
         allTransactions.remove(transaction);
         allTransactionIds.remove(transaction.getSerial());
      }
   }

   private String getStackTrace(Exception e)
   {
      StringWriter trace = new StringWriter();
      PrintWriter writer = new PrintWriter(trace);
      e.printStackTrace(writer);
      return trace.toString();
   }

   /**
    * Register a listener to this tracker.
    */
   public void addListener(TransactionListener listener)
   {
      synchronized ( listeners )
      {
         if ( ! listeners.contains(listener) )
            listeners.add(listener);
      }
   }

   /**
    * Remove a listener from this tracker.
    */
   public void removeListener(TransactionListener listener)
   {
      synchronized ( listeners )
      {
         listeners.remove(listener);
      }
   }

}


java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.