org.etk.entity.engine.plugins.transaction.TransactionUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.etk.entity.engine.plugins.transaction.TransactionUtil.java

Source

/*
 * Copyright (C) 2003-2011 eXo Platform SAS.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.etk.entity.engine.plugins.transaction;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import javax.sql.XAConnection;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.etk.common.logging.Logger;
import org.etk.entity.base.utils.UtilDateTime;
import org.etk.entity.base.utils.UtilGenerics;
import org.etk.entity.base.utils.UtilValidate;
import org.etk.entity.engine.core.GenericEntityException;
import org.apache.commons.collections.map.ListOrderedMap;

import javolution.util.FastList;
import javolution.util.FastMap;

/**
 * <p>
 * Transaction Utility to help with some common transaction tasks
 * <p>
 * Provides a wrapper around the transaction objects to allow for changes in
 * underlying implementations in the future.
 */
public class TransactionUtil implements Status {
    // Debug module name
    private static final Logger logger = Logger.getLogger(TransactionUtil.class);

    public static Map<Xid, DebugXaResource> debugResMap = Collections
            .<Xid, DebugXaResource>synchronizedMap(new HashMap<Xid, DebugXaResource>());

    public static boolean debugResources = true;

    private static ThreadLocal<List<Transaction>> suspendedTxStack = new ThreadLocal<List<Transaction>>();

    private static ThreadLocal<List<Exception>> suspendedTxLocationStack = new ThreadLocal<List<Exception>>();

    private static ThreadLocal<Exception> transactionBeginStack = new ThreadLocal<Exception>();

    private static ThreadLocal<List<Exception>> transactionBeginStackSave = new ThreadLocal<List<Exception>>();

    private static Map<Long, Exception> allThreadsTransactionBeginStack = Collections
            .<Long, Exception>synchronizedMap(FastMap.<Long, Exception>newInstance());

    private static Map<Long, List<Exception>> allThreadsTransactionBeginStackSave = Collections
            .<Long, List<Exception>>synchronizedMap(FastMap.<Long, List<Exception>>newInstance());

    private static ThreadLocal<RollbackOnlyCause> setRollbackOnlyCause = new ThreadLocal<RollbackOnlyCause>();

    private static ThreadLocal<List<RollbackOnlyCause>> setRollbackOnlyCauseSave = new ThreadLocal<List<RollbackOnlyCause>>();

    private static ThreadLocal<Timestamp> transactionStartStamp = new ThreadLocal<Timestamp>();

    private static ThreadLocal<Timestamp> transactionLastNowStamp = new ThreadLocal<Timestamp>();

    @Deprecated
    public static <V> V doNewTransaction(String ifErrorMessage, Callable<V> callable)
            throws GenericEntityException {
        return inTransaction(noTransaction(callable), ifErrorMessage, 0, true).call();
    }

    @Deprecated
    public static <V> V doNewTransaction(String ifErrorMessage, boolean printException, Callable<V> callable)
            throws GenericEntityException {
        return inTransaction(noTransaction(callable), ifErrorMessage, 0, printException).call();
    }

    public static <V> V doNewTransaction(Callable<V> callable, String ifErrorMessage, int timeout,
            boolean printException) throws GenericEntityException {
        return inTransaction(noTransaction(callable), ifErrorMessage, timeout, printException).call();
    }

    @Deprecated
    public static <V> V doTransaction(String ifErrorMessage, Callable<V> callable) throws GenericEntityException {
        return inTransaction(callable, ifErrorMessage, 0, true).call();
    }

    @Deprecated
    public static <V> V doTransaction(String ifErrorMessage, boolean printException, Callable<V> callable)
            throws GenericEntityException {
        return inTransaction(callable, ifErrorMessage, 0, printException).call();
    }

    public static <V> V doTransaction(Callable<V> callable, String ifErrorMessage, int timeout,
            boolean printException) throws GenericEntityException {
        return inTransaction(callable, ifErrorMessage, timeout, printException).call();
    }

    public static <V> Callable<V> noTransaction(Callable<V> callable) {
        return new NoTransaction<V>(callable);
    }

    // This syntax is groovy compatible, with the primary(callable) as the first
    // arg.
    // You could do:
    // use (TransactionUtil) {
    // Callable callable = ....
    // Object result = callable.noTransaction().inTransaction(ifError, timeout,
    // print).call()
    // }
    public static <V> InTransaction<V> inTransaction(Callable<V> callable, String ifErrorMessage, int timeout,
            boolean printException) {
        return new InTransaction<V>(callable, ifErrorMessage, timeout, printException);
    }

    /**
     * Begins a transaction in the current thread IF transactions are available;
     * only tries if the current transaction status is ACTIVE, if not active it
     * returns false. If and on only if it begins a transaction it will return
     * true. In other words, if a transaction is already in place it will return
     * false and do nothing.
     */
    public static boolean begin() throws GenericTransactionException {
        return begin(0);
    }

    /**
     * Begins a transaction in the current thread IF transactions are available;
     * only tries if the current transaction status is ACTIVE, if not active it
     * returns false. If and on only if it begins a transaction it will return
     * true. In other words, if a transaction is already in place it will return
     * false and do nothing.
     */
    public static boolean begin(int timeout) throws GenericTransactionException {
        UserTransaction ut = TransactionFactory.getUserTransaction();
        if (ut != null) {
            try {
                int currentStatus = ut.getStatus();
                if (logger.isDebugEnabled()) {
                    logger.debug(
                            "[TransactionUtil.begin] current status : " + getTransactionStateString(currentStatus));
                }
                if (currentStatus == Status.STATUS_ACTIVE) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                                "[TransactionUtil.begin] active transaction in place, so no transaction begun");
                    }
                    return false;
                } else if (currentStatus == Status.STATUS_MARKED_ROLLBACK) {
                    Exception e = getTransactionBeginStack();
                    if (e != null) {
                        logger.warn(
                                "[TransactionUtil.begin] active transaction marked for rollback in place, so no transaction begun; this stack trace shows when the exception began: ",
                                e);
                    } else {
                        logger.warn(
                                "[TransactionUtil.begin] active transaction marked for rollback in place, so no transaction begun");
                    }

                    RollbackOnlyCause roc = getSetRollbackOnlyCause();
                    // do we have a cause? if so, throw special exception
                    if (roc != null && !roc.isEmpty()) {
                        throw new GenericTransactionException(
                                "The current transaction is marked for rollback, not beginning a new transaction and aborting current operation; the rollbackOnly was caused by: "
                                        + roc.getCauseMessage(),
                                roc.getCauseThrowable());
                    } else {
                        return false;
                    }
                }

                internalBegin(ut, timeout);

                // reset the transaction stamps, just in case...
                clearTransactionStamps();
                // initialize the start stamp
                getTransactionStartStamp();
                // set the tx begin stack placeholder
                setTransactionBeginStack();

                // initialize the debug resource
                if (debugResources) {
                    DebugXaResource dxa = new DebugXaResource();
                    try {
                        dxa.enlist();
                    } catch (XAException e) {
                        logger.error(e.getMessage(), e);
                    }
                }

                return true;
            } catch (NotSupportedException e) {
                // This is Java 1.4 only, but useful for certain debuggins: Throwable t
                // = e.getCause() == null ? e : e.getCause();
                throw new GenericTransactionException(
                        "Not Supported error, could not begin transaction (probably a nesting problem)", e);
            } catch (SystemException e) {
                // This is Java 1.4 only, but useful for certain debuggins: Throwable t
                // = e.getCause() == null ? e : e.getCause();
                throw new GenericTransactionException("System error, could not begin transaction", e);
            }
        } else {
            if (logger.isInfoEnabled())
                logger.info("[TransactionUtil.begin] no user transaction, so no transaction begun");
            return false;
        }
    }

    protected static synchronized void internalBegin(UserTransaction ut, int timeout)
            throws SystemException, NotSupportedException {
        // set the timeout for THIS transaction
        if (timeout > 0) {
            ut.setTransactionTimeout(timeout);
            if (logger.isDebugEnabled()) {
                logger.debug("[TransactionUtil.begin] set transaction timeout to : " + timeout + " seconds");
            }
        }

        // begin the transaction
        ut.begin();
        if (logger.isDebugEnabled()) {
            logger.debug("[TransactionUtil.begin] transaction begun");
        }

        // reset the timeout to the default
        if (timeout > 0) {
            ut.setTransactionTimeout(0);
        }
    }

    /**
     * Gets the status of the transaction in the current thread IF transactions
     * are available, otherwise returns STATUS_NO_TRANSACTION
     */
    public static int getStatus() throws GenericTransactionException {
        UserTransaction ut = TransactionFactory.getUserTransaction();
        if (ut != null) {
            try {
                return ut.getStatus();
            } catch (SystemException e) {
                throw new GenericTransactionException("System error, could not get status", e);
            }
        } else {
            return STATUS_NO_TRANSACTION;
        }
    }

    public static String getStatusString() throws GenericTransactionException {
        return getTransactionStateString(getStatus());
    }

    public static boolean isTransactionInPlace() throws GenericTransactionException {
        int status = getStatus();
        if (status == STATUS_NO_TRANSACTION) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Commits the transaction in the current thread IF transactions are available
     * AND if beganTransaction is true
     */
    public static void commit(boolean beganTransaction) throws GenericTransactionException {
        if (beganTransaction) {
            TransactionUtil.commit();
        }
    }

    /**
     * Commits the transaction in the current thread IF transactions are available
     */
    public static void commit() throws GenericTransactionException {
        UserTransaction ut = TransactionFactory.getUserTransaction();

        if (ut != null) {
            try {
                int status = ut.getStatus();
                logger.debug("[TransactionUtil.commit] current status : " + getTransactionStateString(status));

                if (status != STATUS_NO_TRANSACTION && status != STATUS_COMMITTING && status != STATUS_COMMITTED
                        && status != STATUS_ROLLING_BACK && status != STATUS_ROLLEDBACK) {
                    ut.commit();

                    // clear out the stamps to keep it clean
                    clearTransactionStamps();
                    // clear out the stack too
                    clearTransactionBeginStack();
                    clearSetRollbackOnlyCause();

                    logger.debug("[TransactionUtil.commit] transaction committed");
                } else {
                    logger.warn(
                            "[TransactionUtil.commit] Not committing transaction, status is " + getStatusString());
                }
            } catch (RollbackException e) {
                RollbackOnlyCause rollbackOnlyCause = getSetRollbackOnlyCause();

                if (rollbackOnlyCause != null) {
                    // the transaction is now definitely over, so clear stuff as normal
                    // now that we have the info from it that we want
                    clearTransactionStamps();
                    clearTransactionBeginStack();
                    clearSetRollbackOnlyCause();

                    logger.error(
                            "Rollback Only was set when trying to commit transaction here; throwing rollbackOnly cause exception",
                            e);
                    throw new GenericTransactionException(
                            "Roll back error, could not commit transaction, was rolled back instead because of: "
                                    + rollbackOnlyCause.getCauseMessage(),
                            rollbackOnlyCause.getCauseThrowable());
                } else {
                    Throwable t = e.getCause() == null ? e : e.getCause();
                    throw new GenericTransactionException(
                            "Roll back error (with no rollbackOnly cause found), could not commit transaction, was rolled back instead: "
                                    + t.toString(),
                            t);
                }
            } catch (IllegalStateException e) {
                Throwable t = e.getCause() == null ? e : e.getCause();
                throw new GenericTransactionException(
                        "Could not commit transaction, IllegalStateException exception: " + t.toString(), t);
            } catch (HeuristicMixedException e) {
                Throwable t = e.getCause() == null ? e : e.getCause();
                throw new GenericTransactionException(
                        "Could not commit transaction, HeuristicMixed exception: " + t.toString(), t);
            } catch (HeuristicRollbackException e) {
                Throwable t = e.getCause() == null ? e : e.getCause();
                throw new GenericTransactionException(
                        "Could not commit transaction, HeuristicRollback exception: " + t.toString(), t);
            } catch (SystemException e) {
                Throwable t = e.getCause() == null ? e : e.getCause();
                throw new GenericTransactionException("System error, could not commit transaction: " + t.toString(),
                        t);
            }
        } else {
            logger.info("[TransactionUtil.commit] UserTransaction is null, not commiting");
        }
    }

    /**
     * Rolls back transaction in the current thread IF transactions are available
     * AND if beganTransaction is true; if beganTransaction is not true,
     * setRollbackOnly is called to insure that the transaction will be rolled
     * back
     */
    public static void rollback(boolean beganTransaction, String causeMessage, Throwable causeThrowable)
            throws GenericTransactionException {
        if (beganTransaction) {
            TransactionUtil.rollback(causeThrowable);
        } else {
            TransactionUtil.setRollbackOnly(causeMessage, causeThrowable);
        }
    }

    /** Rolls back transaction in the current thread IF transactions are available */
    public static void rollback() throws GenericTransactionException {
        rollback(null);
    }

    /** Rolls back transaction in the current thread IF transactions are available */
    public static void rollback(Throwable causeThrowable) throws GenericTransactionException {
        UserTransaction ut = TransactionFactory.getUserTransaction();

        if (ut != null) {
            try {
                int status = ut.getStatus();
                logger.debug("[TransactionUtil.rollback] current status : " + getTransactionStateString(status));

                if (status != STATUS_NO_TRANSACTION) {
                    // if (Debug.infoOn()) Thread.dumpStack();
                    if (causeThrowable == null && logger.isInfoEnabled()) {
                        Exception newE = new Exception("Stack Trace");
                        logger.error("[TransactionUtil.rollback]");
                    }

                    // clear out the stamps to keep it clean
                    clearTransactionStamps();
                    // clear out the stack too
                    clearTransactionBeginStack();
                    clearSetRollbackOnlyCause();

                    ut.rollback();
                    logger.info("[TransactionUtil.rollback] transaction rolled back");
                } else {
                    logger.warn(
                            "[TransactionUtil.rollback] transaction not rolled back, status is STATUS_NO_TRANSACTION");
                }
            } catch (IllegalStateException e) {
                Throwable t = e.getCause() == null ? e : e.getCause();
                throw new GenericTransactionException(
                        "Could not rollback transaction, IllegalStateException exception: " + t.toString(), t);
            } catch (SystemException e) {
                Throwable t = e.getCause() == null ? e : e.getCause();
                throw new GenericTransactionException(
                        "System error, could not rollback transaction: " + t.toString(), t);
            }
        } else {
            logger.info("[TransactionUtil.rollback] No UserTransaction, transaction not rolled back");
        }
    }

    /**
     * Makes a rollback the only possible outcome of the transaction in the
     * current thread IF transactions are available
     */
    public static void setRollbackOnly(String causeMessage, Throwable causeThrowable)
            throws GenericTransactionException {
        UserTransaction ut = TransactionFactory.getUserTransaction();
        if (ut != null) {
            try {
                int status = ut.getStatus();
                logger.debug(
                        "[TransactionUtil.setRollbackOnly] current code : " + getTransactionStateString(status));

                if (status != STATUS_NO_TRANSACTION) {
                    if (status != STATUS_MARKED_ROLLBACK) {
                        if (logger.isWarnEnabled()) {
                            logger.warn(
                                    "[TransactionUtil.setRollbackOnly] Calling transaction setRollbackOnly; this stack trace shows where this is happening:",
                                    new Exception(causeMessage));
                        }
                        ut.setRollbackOnly();
                        setSetRollbackOnlyCause(causeMessage, causeThrowable);
                    } else {
                        logger.info(
                                "[TransactionUtil.setRollbackOnly] transaction rollback only not set, rollback only is already set.");
                    }
                } else {
                    logger.warn(
                            "[TransactionUtil.setRollbackOnly] transaction rollback only not set, status is STATUS_NO_TRANSACTION");
                }
            } catch (IllegalStateException e) {
                Throwable t = e.getCause() == null ? e : e.getCause();
                throw new GenericTransactionException(
                        "Could not set rollback only on transaction, IllegalStateException exception: "
                                + t.toString(),
                        t);
            } catch (SystemException e) {
                Throwable t = e.getCause() == null ? e : e.getCause();
                throw new GenericTransactionException(
                        "System error, could not set rollback only on transaction: " + t.toString(), t);
            }
        } else {
            logger.info("[TransactionUtil.setRollbackOnly] No UserTransaction, transaction rollback only not set");
        }
    }

    public static Transaction suspend() throws GenericTransactionException {
        try {
            if (TransactionUtil.getStatus() != STATUS_NO_TRANSACTION) {
                TransactionManager txMgr = TransactionFactory.getTransactionManager();
                if (txMgr != null) {
                    pushTransactionBeginStackSave(clearTransactionBeginStack());
                    pushSetRollbackOnlyCauseSave(clearSetRollbackOnlyCause());
                    Transaction trans = txMgr.suspend();
                    pushSuspendedTransaction(trans);
                    return trans;
                } else {
                    return null;
                }
            } else {
                logger.warn("No transaction in place, so not suspending.");
                return null;
            }
        } catch (SystemException e) {
            throw new GenericTransactionException("System error, could not suspend transaction", e);
        }
    }

    public static void resume(Transaction parentTx) throws GenericTransactionException {
        if (parentTx == null) {
            return;
        }
        TransactionManager txMgr = TransactionFactory.getTransactionManager();
        try {
            if (txMgr != null) {
                setTransactionBeginStack(popTransactionBeginStackSave());
                setSetRollbackOnlyCause(popSetRollbackOnlyCauseSave());
                txMgr.resume(parentTx);
                removeSuspendedTransaction(parentTx);
            }
        } catch (InvalidTransactionException e) {
            /*
             * NOTE: uncomment this for Weblogic Application Server // this is a
             * work-around for non-standard Weblogic functionality; for more
             * information see:
             * http://www.onjava.com/pub/a/onjava/2005/07/20/transactions.html?page=3
             * if (txMgr instanceof weblogic.transaction.ClientTransactionManager) {
             * // WebLogic 8 and above
             * ((weblogic.transaction.ClientTransactionManager)
             * txMgr).forceResume(parentTx); } else if (txMgr instanceof
             * weblogic.transaction.TransactionManager) { // WebLogic 7
             * ((weblogic.transaction.TransactionManager)
             * txMgr).forceResume(parentTx); } else { throw new
             * GenericTransactionException
             * ("System error, could not resume transaction", e); }
             */
            throw new GenericTransactionException("System error, could not resume transaction", e);
        } catch (SystemException e) {
            throw new GenericTransactionException("System error, could not resume transaction", e);
        }
    }

    /**
     * Sets the timeout of the transaction in the current thread IF transactions
     * are available
     */
    public static void setTransactionTimeout(int seconds) throws GenericTransactionException {
        UserTransaction ut = TransactionFactory.getUserTransaction();
        if (ut != null) {
            try {
                ut.setTransactionTimeout(seconds);
            } catch (SystemException e) {
                throw new GenericTransactionException("System error, could not set transaction timeout", e);
            }
        }
    }

    /**
     * Enlists the given XAConnection and if a transaction is active in the
     * current thread, returns a plain JDBC Connection
     */
    public static Connection enlistConnection(XAConnection xacon) throws GenericTransactionException {
        if (xacon == null) {
            return null;
        }
        try {
            XAResource resource = xacon.getXAResource();
            TransactionUtil.enlistResource(resource);
            return xacon.getConnection();
        } catch (SQLException e) {
            throw new GenericTransactionException(
                    "SQL error, could not enlist connection in transaction even though transactions are available",
                    e);
        }
    }

    public static void enlistResource(XAResource resource) throws GenericTransactionException {
        if (resource == null) {
            return;
        }

        try {
            TransactionManager tm = TransactionFactory.getTransactionManager();
            if (tm != null && tm.getStatus() == STATUS_ACTIVE) {
                Transaction tx = tm.getTransaction();
                if (tx != null) {
                    tx.enlistResource(resource);
                }
            }
        } catch (RollbackException e) {
            // This is Java 1.4 only, but useful for certain debuggins: Throwable t =
            // e.getCause() == null ? e : e.getCause();
            throw new GenericTransactionException(
                    "Roll Back error, could not enlist resource in transaction even though transactions are available, current transaction rolled back",
                    e);
        } catch (SystemException e) {
            // This is Java 1.4 only, but useful for certain debuggins: Throwable t =
            // e.getCause() == null ? e : e.getCause();
            throw new GenericTransactionException(
                    "System error, could not enlist resource in transaction even though transactions are available",
                    e);
        }
    }

    public static String getTransactionStateString(int state) {
        /*
         * javax.transaction.Status STATUS_ACTIVE 0 STATUS_MARKED_ROLLBACK 1
         * STATUS_PREPARED 2 STATUS_COMMITTED 3 STATUS_ROLLEDBACK 4 STATUS_UNKNOWN 5
         * STATUS_NO_TRANSACTION 6 STATUS_PREPARING 7 STATUS_COMMITTING 8
         * STATUS_ROLLING_BACK 9
         */
        switch (state) {
        case Status.STATUS_ACTIVE:
            return "Transaction Active (" + state + ")";
        case Status.STATUS_COMMITTED:
            return "Transaction Committed (" + state + ")";
        case Status.STATUS_COMMITTING:
            return "Transaction Committing (" + state + ")";
        case Status.STATUS_MARKED_ROLLBACK:
            return "Transaction Marked Rollback (" + state + ")";
        case Status.STATUS_NO_TRANSACTION:
            return "No Transaction (" + state + ")";
        case Status.STATUS_PREPARED:
            return "Transaction Prepared (" + state + ")";
        case Status.STATUS_PREPARING:
            return "Transaction Preparing (" + state + ")";
        case Status.STATUS_ROLLEDBACK:
            return "Transaction Rolledback (" + state + ")";
        case Status.STATUS_ROLLING_BACK:
            return "Transaction Rolling Back (" + state + ")";
        case Status.STATUS_UNKNOWN:
            return "Transaction Status Unknown (" + state + ")";
        default:
            return "Not a valid state code (" + state + ")";
        }
    }

    public static void logRunningTx() {
        if (debugResources) {
            if (UtilValidate.isNotEmpty(debugResMap)) {
                for (DebugXaResource dxa : debugResMap.values()) {
                    dxa.log();
                }
            }
        }
    }

    public static void registerSynchronization(Synchronization sync) throws GenericTransactionException {
        if (sync == null) {
            return;
        }

        try {
            TransactionManager tm = TransactionFactory.getTransactionManager();
            if (tm != null && tm.getStatus() == STATUS_ACTIVE) {
                Transaction tx = tm.getTransaction();
                if (tx != null) {
                    tx.registerSynchronization(sync);
                }
            }
        } catch (RollbackException e) {
            // This is Java 1.4 only, but useful for certain debuggins: Throwable t =
            // e.getCause() == null ? e : e.getCause();
            throw new GenericTransactionException(
                    "Roll Back error, could not register synchronization in transaction even though transactions are available, current transaction rolled back",
                    e);
        } catch (SystemException e) {
            // This is Java 1.4 only, but useful for certain debuggins: Throwable t =
            // e.getCause() == null ? e : e.getCause();
            throw new GenericTransactionException(
                    "System error, could not register synchronization in transaction even though transactions are available",
                    e);
        }
    }

    // =======================================
    // SUSPENDED TRANSACTIONS
    // =======================================
    /** BE VERY CARFUL WHERE YOU CALL THIS!! */
    public static int cleanSuspendedTransactions() throws GenericTransactionException {
        Transaction trans = null;
        int num = 0;
        while ((trans = popSuspendedTransaction()) != null) {
            resume(trans);
            rollback();
            num++;
        }
        // no transaction stamps to remember anymore ;-)
        clearTransactionStartStampStack();
        return num;
    }

    public static boolean suspendedTransactionsHeld() {
        List<Transaction> tl = suspendedTxStack.get();
        return UtilValidate.isNotEmpty(tl);
    }

    public static List<Transaction> getSuspendedTxStack() {
        List<Transaction> tl = suspendedTxStack.get();
        if (tl == null) {
            tl = new LinkedList<Transaction>();
            suspendedTxStack.set(tl);
        }
        return tl;
    }

    public static List<Exception> getSuspendedTxLocationsStack() {
        List<Exception> tl = suspendedTxLocationStack.get();
        if (tl == null) {
            tl = new LinkedList<Exception>();
            suspendedTxLocationStack.set(tl);
        }
        return tl;
    }

    protected static void pushSuspendedTransaction(Transaction t) {
        List<Transaction> tl = getSuspendedTxStack();
        tl.add(0, t);
        List<Exception> stls = getSuspendedTxLocationsStack();
        stls.add(0, new Exception("TX Suspend Location"));
        // save the current transaction start stamp
        pushTransactionStartStamp(t);
    }

    protected static Transaction popSuspendedTransaction() {
        List<Transaction> tl = suspendedTxStack.get();
        if (UtilValidate.isNotEmpty(tl)) {
            // restore the transaction start stamp
            popTransactionStartStamp();
            List<Exception> stls = suspendedTxLocationStack.get();
            if (UtilValidate.isNotEmpty(stls)) {
                stls.remove(0);
            }
            return tl.remove(0);
        } else {
            return null;
        }
    }

    protected static void removeSuspendedTransaction(Transaction t) {
        List<Transaction> tl = suspendedTxStack.get();
        if (UtilValidate.isNotEmpty(tl)) {
            tl.remove(t);
            List<Exception> stls = suspendedTxLocationStack.get();
            if (UtilValidate.isNotEmpty(stls)) {
                stls.remove(0);
            }
            popTransactionStartStamp(t);
        }
    }

    // =======================================
    // TRANSACTION BEGIN STACK
    // =======================================
    private static void pushTransactionBeginStackSave(Exception e) {
        // use the ThreadLocal one because it is more reliable than the all threads
        // Map
        List<Exception> el = transactionBeginStackSave.get();
        if (el == null) {
            el = FastList.newInstance();
            transactionBeginStackSave.set(el);
        }
        el.add(0, e);

        Long curThreadId = Thread.currentThread().getId();
        List<Exception> ctEl = allThreadsTransactionBeginStackSave.get(curThreadId);
        if (ctEl == null) {
            ctEl = FastList.newInstance();
            allThreadsTransactionBeginStackSave.put(curThreadId, ctEl);
        }
        ctEl.add(0, e);
    }

    private static Exception popTransactionBeginStackSave() {
        // do the unofficial all threads Map one first, and don't do a real return
        Long curThreadId = Thread.currentThread().getId();
        List<Exception> ctEl = allThreadsTransactionBeginStackSave.get(curThreadId);
        if (UtilValidate.isNotEmpty(ctEl)) {
            ctEl.remove(0);
        }

        // then do the more reliable ThreadLocal one
        List<Exception> el = transactionBeginStackSave.get();
        if (UtilValidate.isNotEmpty(el)) {
            return el.remove(0);
        } else {
            return null;
        }
    }

    public static int getTransactionBeginStackSaveSize() {
        List<Exception> el = transactionBeginStackSave.get();
        if (el != null) {
            return el.size();
        } else {
            return 0;
        }
    }

    public static List<Exception> getTransactionBeginStackSave() {
        List<Exception> el = transactionBeginStackSave.get();
        List<Exception> elClone = FastList.newInstance();
        elClone.addAll(el);
        return elClone;
    }

    public static Map<Long, List<Exception>> getAllThreadsTransactionBeginStackSave() {
        Map<Long, List<Exception>> attbssMap = allThreadsTransactionBeginStackSave;
        Map<Long, List<Exception>> attbssMapClone = FastMap.newInstance();
        attbssMapClone.putAll(attbssMap);
        return attbssMapClone;
    }

    public static void printAllThreadsTransactionBeginStacks() {
        if (!logger.isInfoEnabled()) {
            return;
        }

        for (Map.Entry<Long, Exception> attbsMapEntry : allThreadsTransactionBeginStack.entrySet()) {
            Long curThreadId = attbsMapEntry.getKey();
            Exception transactionBeginStack = attbsMapEntry.getValue();
            List<Exception> txBeginStackList = allThreadsTransactionBeginStackSave.get(curThreadId);

            logger.info(
                    "===================================================\n===================================================\n Current tx begin stack for thread ["
                            + curThreadId + "]:",
                    transactionBeginStack);

            if (UtilValidate.isNotEmpty(txBeginStackList)) {
                int stackLevel = 0;
                for (Exception stack : txBeginStackList) {
                    logger.info(
                            "===================================================\n===================================================\n Tx begin stack history for thread ["
                                    + curThreadId + "] history number [" + stackLevel + "]:",
                            stack);
                    stackLevel++;
                }
            } else {
                logger.info(
                        "========================================== No tx begin stack history found for thread ["
                                + curThreadId + "]");
            }
        }
    }

    private static void setTransactionBeginStack() {
        Exception e = new Exception("Tx Stack Placeholder");
        setTransactionBeginStack(e);
    }

    private static void setTransactionBeginStack(Exception newExc) {
        if (transactionBeginStack.get() != null) {
            Exception e = transactionBeginStack.get();
            logger.warn(
                    "WARNING: In setTransactionBeginStack a stack placeholder was already in place, here is where the transaction began: ",
                    e);
            Exception e2 = new Exception("Current Stack Trace");
            logger.warn(
                    "WARNING: In setTransactionBeginStack a stack placeholder was already in place, here is the current location: ",
                    e2);
        }
        transactionBeginStack.set(newExc);
        Long curThreadId = Thread.currentThread().getId();
        allThreadsTransactionBeginStack.put(curThreadId, newExc);
    }

    private static Exception clearTransactionBeginStack() {
        Long curThreadId = Thread.currentThread().getId();
        allThreadsTransactionBeginStack.remove(curThreadId);

        Exception e = transactionBeginStack.get();
        if (e == null) {
            Exception e2 = new Exception("Current Stack Trace");
            logger.warn(
                    "WARNING: In clearTransactionBeginStack no stack placeholder was in place, here is the current location: ",
                    e2);
            return null;
        } else {
            transactionBeginStack.set(null);
            return e;
        }
    }

    public static Exception getTransactionBeginStack() {
        Exception e = transactionBeginStack.get();
        if (e == null) {
            Exception e2 = new Exception("Current Stack Trace");
            logger.warn(
                    "WARNING: In getTransactionBeginStack no stack placeholder was in place, here is the current location: ",
                    e2);
        }
        return e;
    }

    // =======================================
    // ROLLBACK ONLY CAUSE
    // =======================================
    private static class RollbackOnlyCause {
        protected String causeMessage;

        protected Throwable causeThrowable;

        public RollbackOnlyCause(String causeMessage, Throwable causeThrowable) {
            this.causeMessage = causeMessage;
            this.causeThrowable = causeThrowable;
        }

        public String getCauseMessage() {
            return this.causeMessage + (this.causeThrowable == null ? "" : this.causeThrowable.toString());
        }

        public Throwable getCauseThrowable() {
            return this.causeThrowable;
        }

        public void logError(String message) {
            logger.debug((message == null ? "" : message) + this.getCauseMessage(), this.getCauseThrowable());
        }

        public boolean isEmpty() {
            return (UtilValidate.isEmpty(this.getCauseMessage()) && this.getCauseThrowable() == null);
        }
    }

    private static void pushSetRollbackOnlyCauseSave(RollbackOnlyCause e) {
        List<RollbackOnlyCause> el = setRollbackOnlyCauseSave.get();
        if (el == null) {
            el = new LinkedList<RollbackOnlyCause>();
            setRollbackOnlyCauseSave.set(el);
        }
        el.add(0, e);
    }

    private static RollbackOnlyCause popSetRollbackOnlyCauseSave() {
        List<RollbackOnlyCause> el = setRollbackOnlyCauseSave.get();
        if (UtilValidate.isNotEmpty(el)) {
            return el.remove(0);
        } else {
            return null;
        }
    }

    private static void setSetRollbackOnlyCause(String causeMessage, Throwable causeThrowable) {
        RollbackOnlyCause roc = new RollbackOnlyCause(causeMessage, causeThrowable);
        setSetRollbackOnlyCause(roc);
    }

    private static void setSetRollbackOnlyCause(RollbackOnlyCause newRoc) {
        if (setRollbackOnlyCause.get() != null) {
            RollbackOnlyCause roc = setRollbackOnlyCause.get();
            roc.logError(
                    "WARNING: In setSetRollbackOnlyCause a stack placeholder was already in place, here is the original rollbackOnly cause: ");
            Exception e2 = new Exception("Current Stack Trace");
            logger.warn(
                    "WARNING: In setSetRollbackOnlyCause a stack placeholder was already in place, here is the current location: ",
                    e2);
        }
        setRollbackOnlyCause.set(newRoc);
    }

    private static RollbackOnlyCause clearSetRollbackOnlyCause() {
        RollbackOnlyCause roc = setRollbackOnlyCause.get();
        if (roc == null) {
            /*
             * this is an obnoxious message, leaving out for now; could be added
             * manually if a problem with this is suspected if (Debug.verboseOn()) {
             * // for this in particular, unlike the begin location, normally there
             * will not be a setRollbackOnlyCause, so don't complain about it except
             * in verbose Debug.logVerbose(new Exception("Current Stack Trace"),
             * "In clearSetRollbackOnlyCause no stack placeholder was in place, here is the current location: "
             * , module); }
             */
            return null;
        } else {
            setRollbackOnlyCause.set(null);
            return roc;
        }
    }

    public static RollbackOnlyCause getSetRollbackOnlyCause() {
        if (setRollbackOnlyCause.get() == null) {
            Exception e = new Exception("Current Stack Trace");
            logger.warn(
                    "WARNING: In getSetRollbackOnlyCause no stack placeholder was in place, here is the current location: ",
                    e);
        }
        return setRollbackOnlyCause.get();
    }

    // =======================================
    // SUSPENDED TRANSACTIONS START TIMESTAMPS
    // =======================================

    /**
     * Maintain the suspended transactions together with their timestamps
     */
    private static ThreadLocal<Map<Transaction, Timestamp>> suspendedTxStartStamps = new ThreadLocal<Map<Transaction, Timestamp>>() {
        @Override
        public Map<Transaction, Timestamp> initialValue() {
            return UtilGenerics.checkMap(new ListOrderedMap());
        }
    };

    /**
     * Put the stamp to remember later
     * 
     * @param t transaction just suspended
     */
    private static void pushTransactionStartStamp(Transaction t) {
        Map<Transaction, Timestamp> map = suspendedTxStartStamps.get();
        Timestamp stamp = transactionStartStamp.get();
        if (stamp != null) {
            map.put(t, stamp);
        } else {
            logger.debug("Error in transaction handling - no start stamp to push.");
        }
    }

    /**
     * Method called when the suspended stack gets cleaned by
     * {@link #cleanSuspendedTransactions()}.
     */
    private static void clearTransactionStartStampStack() {
        suspendedTxStartStamps.get().clear();
    }

    /**
     * Remove the stamp of the specified transaction from stack (when resuming)
     * and set it as current start stamp.
     * 
     * @param t transaction just resumed
     */
    private static void popTransactionStartStamp(Transaction t) {
        Map<Transaction, Timestamp> map = suspendedTxStartStamps.get();
        if (map.size() > 0) {
            Timestamp stamp = map.remove(t);
            if (stamp != null) {
                transactionStartStamp.set(stamp);
            } else {
                logger.debug("Error in transaction handling - no saved start stamp found - using NOW.");
                transactionStartStamp.set(UtilDateTime.nowTimestamp());
            }
        }
    }

    /**
     * Remove the stamp from stack (when resuming)
     */
    private static void popTransactionStartStamp() {
        ListOrderedMap map = (ListOrderedMap) suspendedTxStartStamps.get();
        if (map.size() > 0) {
            transactionStartStamp.set((Timestamp) map.remove(map.lastKey()));
        } else {
            logger.debug("Error in transaction handling - no saved start stamp found - using NOW.");
            transactionStartStamp.set(UtilDateTime.nowTimestamp());
        }
    }

    public static Timestamp getTransactionStartStamp() {
        Timestamp curStamp = transactionStartStamp.get();
        if (curStamp == null) {
            curStamp = UtilDateTime.nowTimestamp();
            transactionStartStamp.set(curStamp);

            // we know this is the first time set for this transaction, so make sure
            // the StampClearSync is registered
            try {
                registerSynchronization(new StampClearSync());
            } catch (GenericTransactionException e) {
                logger.debug(
                        "Error registering StampClearSync synchronization, stamps will still be reset if begin/commit/rollback are call through TransactionUtil, but not if otherwise",
                        e);
            }
        }
        return curStamp;
    }

    public static Timestamp getTransactionUniqueNowStamp() {
        Timestamp lastNowStamp = transactionLastNowStamp.get();
        Timestamp nowTimestamp = UtilDateTime.nowTimestamp();

        // check for an overlap with the lastNowStamp, or if the lastNowStamp is in
        // the future because of incrementing to make each stamp unique
        if (lastNowStamp != null && (lastNowStamp.equals(nowTimestamp) || lastNowStamp.after(nowTimestamp))) {
            nowTimestamp = new Timestamp(lastNowStamp.getTime() + 1);
        }

        transactionLastNowStamp.set(nowTimestamp);
        return nowTimestamp;
    }

    protected static void clearTransactionStamps() {
        transactionStartStamp.set(null);
        transactionLastNowStamp.set(null);
    }

    public static class StampClearSync implements Synchronization {
        public void afterCompletion(int status) {
            TransactionUtil.clearTransactionStamps();
        }

        public void beforeCompletion() {
        }
    }

    public static final class NoTransaction<V> implements Callable<V> {
        private final Callable<V> callable;

        protected NoTransaction(Callable<V> callable) {
            this.callable = callable;
        }

        public V call() throws Exception {
            Transaction suspended = TransactionUtil.suspend();
            try {
                return callable.call();
            } finally {
                TransactionUtil.resume(suspended);
            }
        }
    }

    public static final class InTransaction<V> implements Callable<V> {
        private final Callable<V> callable;

        private final String ifErrorMessage;

        private final int timeout;

        private final boolean printException;

        protected InTransaction(Callable<V> callable, String ifErrorMessage, int timeout, boolean printException) {
            this.callable = callable;
            this.ifErrorMessage = ifErrorMessage;
            this.timeout = timeout;
            this.printException = printException;
        }

        public V call() throws GenericEntityException {
            boolean tx = TransactionUtil.begin(timeout);
            Throwable transactionAbortCause = null;
            try {
                try {
                    return callable.call();
                } catch (Throwable t) {
                    while (t.getCause() != null) {
                        t = t.getCause();
                    }
                    throw t;
                }
            } catch (Error e) {
                transactionAbortCause = e;
                throw e;
            } catch (RuntimeException e) {
                transactionAbortCause = e;
                throw e;
            } catch (Throwable t) {
                transactionAbortCause = t;
                throw new GenericEntityException(t);
            } finally {
                if (transactionAbortCause == null) {
                    TransactionUtil.commit(tx);
                } else {
                    if (printException) {
                        transactionAbortCause.printStackTrace();
                    }
                    TransactionUtil.rollback(tx, ifErrorMessage, transactionAbortCause);
                }
            }
        }
    }
}