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

com.arjuna.ats.jbossatx.BaseTransactionManagerDelegate Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * 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,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *
 * (C) 2005-2006,
 * @author JBoss Inc.
 */
package com.arjuna.ats.jbossatx;

import jakarta.transaction.HeuristicMixedException;
import jakarta.transaction.HeuristicRollbackException;
import jakarta.transaction.InvalidTransactionException;
import jakarta.transaction.NotSupportedException;
import jakarta.transaction.RollbackException;
import jakarta.transaction.SystemException;
import jakarta.transaction.Transaction;
import jakarta.transaction.TransactionManager;
import jakarta.transaction.Status;

import org.jboss.tm.listener.TransactionTypeNotSupported;

import com.arjuna.ats.jbossatx.logging.jbossatxLogger;
import com.arjuna.ats.jta.common.JTAEnvironmentBean;
import com.arjuna.ats.jta.common.jtaPropertyManager;

import org.jboss.tm.TransactionLocal;
import org.jboss.tm.TransactionLocalDelegate;
import org.jboss.tm.TransactionTimeoutConfiguration;

import org.jboss.tm.listener.TransactionEvent;
import org.jboss.tm.listener.TransactionListener;
import org.jboss.tm.listener.TransactionListenerRegistry;
import org.jboss.tm.listener.EventType;

import java.util.EnumSet;
import java.util.Map;
import java.util.HashMap;
import java.util.Collection;

import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Delegate for JBoss TransactionManager/TransactionLocalDelegate.
 * @author kevin
 */
@Deprecated // org.jboss.tm.TransactionLocalDelegate, is Deprecated
public abstract class BaseTransactionManagerDelegate implements TransactionManager, TransactionLocalDelegate, TransactionTimeoutConfiguration, TransactionListenerRegistry
{
    private static final String LISTENER_MAP_KEY = "__TX_LISTENERS";

    /**
     * Delegate transaction manager.
     */
    private final TransactionManager transactionManager ;

    /**
     * Construct the delegate using the specified transaction manager.
     * @param transactionManager The delegate transaction manager.
     */
    protected BaseTransactionManagerDelegate(final TransactionManager transactionManager)
    {
        this.transactionManager = transactionManager ;
    }

    /**
     * Begin a transaction and associate it with the current thread.
     */
    public void begin()
        throws NotSupportedException, SystemException
    {
        transactionManager.begin() ;
    }

    /**
     * Commit the current transaction and disassociate from the thread.
     */
    public void commit()
        throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
        SecurityException, IllegalStateException, SystemException
    {
        notifyAssociationListeners(getTransaction(), EventType.DISASSOCIATING);
        transactionManager.commit() ;
    }

    /**
     * Get the transaction status.
     * @return the transaction status.
     */
    public int getStatus()
        throws SystemException
    {
        return transactionManager.getStatus() ;
    }

    /**
     * Get the transaction associated with the thread.
     * @return the transaction or null if none associated.
     */
    public Transaction getTransaction()
        throws SystemException
    {
        return transactionManager.getTransaction() ;
    }

    /**
     * Resume the specified transaction.
     * @param transaction The transaction to resume.
     */
    public void resume(final Transaction transaction)
        throws InvalidTransactionException, IllegalStateException, SystemException
    {
        if (transaction == null) {
            suspend(); // This is what AtomicAction does
        } else {
            transactionManager.resume(transaction) ;
            notifyAssociationListeners(transaction, EventType.ASSOCIATED);
        }
    }

    /**
     * Rollback the current transaction and disassociate from the thread.
     */
    public void rollback()
        throws IllegalStateException, SecurityException, SystemException
    {
        notifyAssociationListeners(getTransaction(), EventType.DISASSOCIATING);
        transactionManager.rollback() ;
    }

    /**
     * Set rollback only on the current transaction.
     */
    public void setRollbackOnly()
        throws IllegalStateException, SystemException
    {
        transactionManager.setRollbackOnly() ;
    }

    /**
     * Set the transaction timeout on the current thread.
     * @param timeout The transaction timeout.
     */
    public void setTransactionTimeout(final int timeout)
        throws SystemException
    {
        transactionManager.setTransactionTimeout(timeout) ;
    }

    /**
     * register a listener for transaction related events that effect the current thread
     * @param listener the callback for event notifications
     */
    @Override
    public void addListener (Transaction transaction, TransactionListener listener, EnumSet types) throws TransactionTypeNotSupported
    {
        if (transaction == null)
            throw new NullPointerException(); // we could interpret this as meaning register for all transactions

        if (!(transaction instanceof com.arjuna.ats.jta.transaction.Transaction))
            throw new TransactionTypeNotSupported(jbossatxLogger.i18NLogger.get_unsupported_transaction_type(transaction.getClass()));

        Collection listeners = getListeners(transaction, true);

        if (listeners != null) {
            listeners.add(listener);

            // if transaction is already associated with the current thread notify this listener
            try {
                if (transaction.equals(getTransaction()) && types.contains(EventType.ASSOCIATED))
                    listener.onEvent(new TransactionEvent(transaction, EnumSet.of(EventType.ASSOCIATED)));
            } catch (SystemException e) {
                // no transaction associated so do not trigger the ASSOCIATED callback
            }
        }
    }

    /**
     * Suspend the current transaction.
     * @return The suspended transaction.
     */
    public Transaction suspend()
        throws SystemException
    {
        if (getStatus() != Status.STATUS_NO_TRANSACTION)
            notifyAssociationListeners(getTransaction(), EventType.DISASSOCIATING);

        return transactionManager.suspend();
    }

    // TransactionListener implementation methods.
    // return all the event listeners associated with this thread
    private Collection getListeners(Transaction transaction, boolean create)
    {
        if(!jtaPropertyManager.getJTAEnvironmentBean().isTransactionToThreadListenersEnabled()) {
            if(create) {
                throw new IllegalStateException(jbossatxLogger.i18NLogger.get_transaction_listeners_disabled(
                		JTAEnvironmentBean.class.getName()+".transactionToThreadListenersEnabled"));
            }
            return null;
        }
        com.arjuna.ats.jta.transaction.Transaction txn = (com.arjuna.ats.jta.transaction.Transaction) transaction;
        Object resource;

        // protect against two concurrent listener registrations both trying to create the initial resource entry
        synchronized (transaction) {
            resource = txn.getTxLocalResource(LISTENER_MAP_KEY);

            if (resource == null && create) {
                Collection listeners = new ConcurrentLinkedQueue<>();

                txn.putTxLocalResource(LISTENER_MAP_KEY, listeners);

                return listeners;
            }
        }

        if (resource != null && !(resource instanceof ConcurrentLinkedQueue)) {
            // another container subsystem has inadvertently used our key
            throw new IllegalStateException(jbossatxLogger.i18NLogger.get_invalid_transaction_local_resource(resource, LISTENER_MAP_KEY));
        }

        return (Collection) resource;
    }

    // notify any listeners for this transaction that there has been an event
    private void notifyAssociationListeners(Transaction transaction, EventType reason)
    {
        if (transaction != null) {
            Collection listeners = getListeners(transaction, false);

            if (listeners != null && !listeners.isEmpty()) {

                TransactionEvent event = new TransactionEvent(transaction, EnumSet.of(reason));
                for (TransactionListener s : listeners)
                    s.onEvent(event);
            }
        }
    }

    /////////////////////////

    // TransactionLocalDelegate implementation methods. This part is basically
    // stateless, we just delegate down to the object storage on the TransactionImple

    // Note this has some interesting effects around Transaction termination. The TransactionImple instance
    // lifetime is up to tx termination only. After that getTransaction() on the tm returns a new instance
    // representing the same tx. Hence TransactionLocal state goes away magically at tx termination time
    // since it's part of the TransactionImple instance. That's what we want and saves writing cleanup code.
    // On the down side, since locks use the same storage they go away too. This causes us to have to
    // jump through some hoops to deal with locks vanishing and maybe never getting unlocked, see below.

    /**
     * Does the specified transaction contain a value for the transaction local.
     *
     * @param transactionLocal The associated transaction local.
     * @param transaction      The associated transaction.
     * @return true if a value exists within the specified transaction, false otherwise.
     */
    public boolean containsValue(final TransactionLocal transactionLocal, final Transaction transaction) {
        com.arjuna.ats.jta.transaction.Transaction transactionImple = (com.arjuna.ats.jta.transaction.Transaction) transaction;
        if(transactionImple.isAlive()) {
            return (transactionImple.getTxLocalResource(transactionLocal) != null ? true : false);
        } else {
            return false;
        }
    }

    /**
     * Get value of the transaction local in the specified transaction.
     *
     * @param transactionLocal The associated transaction local.
     * @param transaction      The associated transaction.
     * @return The value of the transaction local.
     */
    public Object getValue(final TransactionLocal transactionLocal, final Transaction transaction) {
        com.arjuna.ats.jta.transaction.Transaction transactionImple = (com.arjuna.ats.jta.transaction.Transaction) transaction;
        if(transactionImple.isAlive()) {
            return transactionImple.getTxLocalResource(transactionLocal);
        } else {
            return null;
        }
    }

    /**
     * Store the value of the transaction local in the specified transaction.
     *
     * @param transactionLocal The associated transaction local.
     * @param transaction      The associated transaction.
     * @param value            The value of the transaction local.
     */
    public void storeValue(final TransactionLocal transactionLocal, final Transaction transaction,
                           final Object value) {
        com.arjuna.ats.jta.transaction.Transaction transactionImple = (com.arjuna.ats.jta.transaction.Transaction) transaction;
        if(transactionImple.isAlive()) {
            transactionImple.putTxLocalResource(transactionLocal, value);
        } else {
            throw new IllegalStateException(jbossatxLogger.i18NLogger.get_cannot_store_transactionlocal(transactionImple));
        }
    }

    /**
     * Lock the transaction local in the context of this transaction.
     *
     * @throws IllegalStateException if the transaction is not active
     * @throws InterruptedException  if the thread is interrupted
     */
    public void lock(final TransactionLocal local, final Transaction transaction)
            throws InterruptedException {
        com.arjuna.ats.jta.transaction.Transaction transactionImple = (com.arjuna.ats.jta.transaction.Transaction) transaction;
        if(transactionImple.isAlive()) {
            // There is a race here, as the transaction may terminate between the check
            // and the lock attempt. See lock() implementation below.
            // It's still possible to lock a dead transaction, but that does no real harm.
            TransactionLocalLock lock = findLock(local, transaction);
            if(lock.lock(transactionImple)) {
                return;
            }
        }

        throw new IllegalStateException(jbossatxLogger.i18NLogger.get_cannot_lock_transactionlocal(transactionImple));
    }

    /**
     * Unlock the transaction local in the context of this transaction
     */
    public void unlock(final TransactionLocal local, final Transaction transaction) {
        TransactionLocalLock lock = findLock(local, transaction);
        lock.unlock();
    }

    // Lock implementation: This used to use a Synchronization for lock storage, but
    // we need to be able to lock things in some states where registration of
    // Synchronizations is not permitted. Besides, the JTA 1.1 work gives us a nice
    // general object storage mechanism on a TransactionImple, so we use that.

    // This is the key under which the map of TransactionLocals to locks is stored.
    // Bad things will probably happen if users ever use this key themselves
    private final String LOCKS_MAP = "__LOCKS_MAP";

    // Using string LOCKS_MAP directly as object of synchronization is not recommended
    // String literals are centrally interned and could also be locked on by a library,
    // potentially having deadlocks or lock collisions
    private static final Object locksMapLock = new Object();

    // locate and return the lock for a given TransactionLocal+Transaction tuple.
    // create it if it does not exist.
    private TransactionLocalLock findLock(final TransactionLocal local, final Transaction transaction) {

        com.arjuna.ats.jta.transaction.Transaction transactionImple = (com.arjuna.ats.jta.transaction.Transaction) transaction;
        Map locks; // 
        // ideally for performance we should sync on the tx instance itself but that may have nasty
        // side effects so we use something else as the lock object for the sync block
        locks = (Map) transactionImple.getTxLocalResource(LOCKS_MAP);
        // this is not a double-check locking anti-pattern, because locks
        // is a local variable and thus can not leak.
        if (locks == null) {
            synchronized (locksMapLock) {
                // ensure there is a holder for lock storage on the given tx instance.
                locks = (Map) transactionImple.getTxLocalResource(LOCKS_MAP);
                if (locks == null) {
                    locks = new HashMap(); // 
                    transactionImple.putTxLocalResource(LOCKS_MAP, locks);
                }
            }
        }

        TransactionLocalLock transactionLocalLock = (TransactionLocalLock) locks.get(local);
        if (transactionLocalLock == null) {
            synchronized (locks) {
                // ensure there is a lock for the specified local+tx tuple
                transactionLocalLock = (TransactionLocalLock)locks.get(local);
                if (transactionLocalLock == null) {
                    transactionLocalLock = new TransactionLocalLock();
                    locks.put(local, transactionLocalLock);
                }
            }
        }

        return transactionLocalLock;
    }

    // A class for the storage of individual lock state:

    private class TransactionLocalLock
    {
        /**
         * The locking thread.
         */
        private Thread lockingThread ;
        /**
         * The lock count.
         */
        private int lockCount ;
        /**
         * The lock.
         */
        private byte[] lock = new byte[0] ;

        /**
         * Lock the transaction local within the current thread context.
         * true on lock acquired, false otherwise
         */
        public boolean lock(com.arjuna.ats.jta.transaction.Transaction tx)
        {
            // The current code in the app server locks the transaction for all, we follow that practice
            synchronized(lock)
            {
                final Thread currentThread = Thread.currentThread() ;
                if (currentThread == lockingThread)
                {
                    lockCount++ ;
                    return true;
                }

                while (lockingThread != null)
                {
                    try
                    {
                        // lock object instances get thrown away at Transaction termination. That makes it impossible
                        // to call unlock() on them. Searching through them and unlocking them from the transaction
                        // termination code is a pain and finalizers suck.
                        // Hence we need to make sure we don't wait forever for a notify
                        // that will never come. Probably the only thing that will terminate a Transaction in another
                        // Thread is the transaction reaper, so we wait not longer than the tx timeout plus a fudge factor.
                        long timeout = 0;
                        try {
                            timeout = getTransactionTimeout();
                        } catch(SystemException e) {}

                        lock.wait(timeout+1000);
                        if(!tx.isAlive()) {
                            // transaction is dead, can't be locked, cleanup
                            lockingThread = null;
                            lockCount = 0;
                            return false;
                        }
                    }
                    catch (final InterruptedException ie) {}
                }

                lockingThread = currentThread ;
                lockCount ++ ;
                return true;
            }
        }

        /**
         * Unlock the transaction local within the current thread context.
         */
        public void unlock()
        {
            synchronized(lock)
            {
                if(lockCount == 0 && lockingThread == null) {
                    // the lock was probably reset by a transaction termination.
                    // we fail silent to save the caller having to deal with race condition.
                    return;
                }

                final Thread currentThread = Thread.currentThread() ;
                if (currentThread != lockingThread)
                {
                    throw new IllegalStateException(
                    		jbossatxLogger.i18NLogger.get_cannot_store_transactionlocal(lockingThread, currentThread)) ;
                }

                if (--lockCount == 0)
                {
                    lockingThread = null ;
                    lock.notify() ;
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy