/* * JBoss, Home of Professional Open Source. * Copyright 2000 - 2008, Red Hat Middleware LLC, 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 GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * 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 GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General 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.jboss.cache.transaction; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.cache.CacheException; import org.jboss.cache.InvocationContext; import org.jboss.cache.RPCManager; import org.jboss.cache.factories.annotations.Inject; import org.jboss.cache.factories.annotations.NonVolatile; import org.jboss.cache.factories.context.ContextFactory; import org.jboss.cache.jmx.annotations.ManagedAttribute; import org.jgroups.Address; import javax.transaction.Status; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Maintains the mapping between a local {@link Transaction} and a {@link GlobalTransaction}. * Also stores {@link TransactionContext} instances under a given transaction. * * @author Bela Ban Apr 14, 2003 * @version $Revision: 1.1 $ */ @NonVolatile public class TransactionTable { private static final Log log = LogFactory.getLog(TransactionTable.class); private static final boolean trace = log.isTraceEnabled(); /** * Mapping between local (javax.transaction.Transaction) * and GlobalTransactions. * New: a local TX can have a number of GTXs */ protected final Map tx2gtxMap = new ConcurrentHashMap(); /** * Mappings between GlobalTransactions and modifications. */ protected final Map gtx2ContextMap = new ConcurrentHashMap(); protected final Map gtx2TxMap = new ConcurrentHashMap(); private TransactionManager transactionManager = null; private RPCManager rpcManager; private ContextFactory contextFactory; @Inject public void initialize(TransactionManager transactionManager, RPCManager rpcManager, ContextFactory contextFactory) { this.transactionManager = transactionManager; this.rpcManager = rpcManager; this.contextFactory = contextFactory; } /** * Returns the number of local transactions. */ public int getNumLocalTransactions() { return tx2gtxMap.size(); } /** * Returns the number of global transactions. */ public int getNumGlobalTransactions() { return gtx2ContextMap.size(); } /** * Returns the global transaction associated with the local transaction. * Returns null if tx is null or it was not found. */ public GlobalTransaction get(Transaction tx) { if (tx == null) { return null; } return tx2gtxMap.get(tx); } /** * Returns the local transaction associated with a GlobalTransaction. * * @param gtx The GlobalTransaction * @return Transaction. The local transaction associated with a given * GlobalTransaction). This will be null if no local transaction is * associated with a given GTX */ public Transaction getLocalTransaction(GlobalTransaction gtx) { if (gtx == null) return null; return gtx2TxMap.get(gtx); } /** * If assers exists is true and the coresponding local transaction is null an IllegalStateExcetpion is being thrown. */ public Transaction getLocalTransaction(GlobalTransaction gtx, boolean assertExists) { Transaction ltx = getLocalTransaction(gtx); if (!assertExists) { return ltx; } if (ltx != null) { if (log.isDebugEnabled()) log.debug("Found local TX=" + ltx + ", global TX=" + gtx); return ltx; } else { throw new IllegalStateException(" found no local TX for global TX " + gtx); } } /** * Associates the global transaction with the local transaction. */ public void put(Transaction tx, GlobalTransaction gtx) { if (tx == null) { log.error("key (Transaction) is null"); return; } tx2gtxMap.put(tx, gtx); gtx2TxMap.put(gtx, tx); } /** * Returns the local transaction entry for the global transaction. * Returns null if tx is null or it was not found. */ public TransactionContext get(GlobalTransaction gtx) { return gtx != null ? gtx2ContextMap.get(gtx) : null; } /** * Associates the global transaction with a transaction context. */ public void put(GlobalTransaction tx, TransactionContext transactionContext) { if (tx == null) { log.error("key (GlobalTransaction) is null"); return; } gtx2ContextMap.put(tx, transactionContext); } /** * Removes a global transation, returns the old transaction entry. */ public TransactionContext remove(GlobalTransaction tx) { if (tx == null) return null; gtx2TxMap.remove(tx); return gtx2ContextMap.remove(tx); } /** * Removes a local transation, returns the global transaction entry. */ public GlobalTransaction remove(Transaction tx) { if (tx == null) { return null; } return tx2gtxMap.remove(tx); } public void remove(GlobalTransaction gtx, Transaction tx) { gtx2TxMap.remove(gtx); gtx2ContextMap.remove(gtx); tx2gtxMap.remove(tx); } /** * Returns summary debug information. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(tx2gtxMap.size()).append(" mappings, "); sb.append(gtx2ContextMap.size()).append(" transactions"); return sb.toString(); } /** * Returns detailed debug information. */ public String toString(boolean printDetails) { if (!printDetails) { return toString(); } StringBuilder sb = new StringBuilder(); sb.append("LocalTransactions: ").append(tx2gtxMap.size()).append("\n"); sb.append("GlobalTransactions: ").append(gtx2ContextMap.size()).append("\n"); sb.append("tx2gtxMap:\n"); for (Map.Entry entry : tx2gtxMap.entrySet()) { sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } sb.append("gtx2EntryMap:\n"); for (Map.Entry transactionContextEntry : gtx2ContextMap.entrySet()) { sb.append(transactionContextEntry.getKey()).append(": ").append(transactionContextEntry.getValue()).append("\n"); } return sb.toString(); } /** * Returns the transaction associated with the current thread. * If a local transaction exists, but doesn't yet have a mapping to a * GlobalTransaction, a new GlobalTransaction will be created and mapped to * the local transaction. Note that if a local transaction exists, but is * not ACTIVE or PREPARING, null is returned. * * @return A GlobalTransaction, or null if no (local) transaction was associated with the current thread */ public GlobalTransaction getCurrentTransaction() { return getCurrentTransaction(true); } /** * Returns the transaction associated with the thread; optionally creating * it if is does not exist. */ public GlobalTransaction getCurrentTransaction(boolean createIfNotExists) { Transaction tx; if ((tx = getLocalTransaction()) == null) {// no transaction is associated with the current thread return null; } if (!isValid(tx)) {// we got a non-null transaction, but it is not active anymore int status = -1; try { status = tx.getStatus(); } catch (SystemException e) { } // JBCACHE-982 -- don't complain if COMMITTED if (status != Status.STATUS_COMMITTED) { log.warn("status is " + status + " (not ACTIVE or PREPARING); returning null)"); } else { log.trace("status is COMMITTED; returning null"); } return null; } return getCurrentTransaction(tx, createIfNotExists); } /** * Returns the transaction associated with the current thread. We get the * initial context and a reference to the TransactionManager to get the * transaction. This method is used by {@link #getCurrentTransaction()} */ protected Transaction getLocalTransaction() { if (transactionManager == null) { return null; } try { return transactionManager.getTransaction(); } catch (Throwable t) { return null; } } /** * Returns true if transaction is ACTIVE, false otherwise */ public static boolean isActive(Transaction tx) { if (tx == null) return false; int status; try { status = tx.getStatus(); return status == Status.STATUS_ACTIVE; } catch (SystemException e) { return false; } } /** * Returns true if transaction is PREPARING, false otherwise */ public static boolean isPreparing(Transaction tx) { if (tx == null) return false; int status; try { status = tx.getStatus(); return status == Status.STATUS_PREPARING; } catch (SystemException e) { return false; } } /** * Returns true if transaction is STATUS_MARKED_ROLLBACK, false otherwise */ public static boolean isMarkedAsRollback(Transaction tx) { if (tx == null) return false; int status; try { status = tx.getStatus(); return status == Status.STATUS_MARKED_ROLLBACK; } catch (SystemException e) { return false; } } /** * Returns true of tx's status is ACTIVE or PREPARING or MARKED_ROLLBACK * * @param tx * @return true if the tx is active or preparing */ public static boolean isValid(Transaction tx) { return isActive(tx) || isPreparing(tx) || isMarkedAsRollback(tx); } /** * Tests whether the caller is in a valid transaction. If not, will throw a CacheException. */ public static void assertTransactionValid(InvocationContext ctx) { Transaction tx = ctx.getTransaction(); if (!isValid(tx)) { try { throw new CacheException("Invalid transaction " + tx + ", status = " + (tx == null ? null : tx.getStatus())); } catch (SystemException e) { throw new CacheException("Exception trying to analyse status of transaction " + tx, e); } } } /** * Returns the global transaction for this local transaction. */ public GlobalTransaction getCurrentTransaction(Transaction tx) { return getCurrentTransaction(tx, true); } /** * Returns the global transaction for this local transaction. * * @param createIfNotExists if true, if a global transaction is not found; one is created */ public GlobalTransaction getCurrentTransaction(Transaction tx, boolean createIfNotExists) { // removed synchronization on txTable because underlying implementation is thread safe // and JTA spec (section 3.4.3 Thread of Control, par 2) says that only one thread may // operate on the transaction at one time so no concern about 2 threads trying to call // this method for the same Transaction instance at the same time // GlobalTransaction gtx = get(tx); if (gtx == null && createIfNotExists) { Address addr = rpcManager.getLocalAddress(); gtx = GlobalTransaction.create(addr); put(tx, gtx); TransactionContext transactionContext; try { transactionContext = contextFactory.createTransactionContext(tx); } catch (Exception e) { throw new CacheException("Unable to create a transaction entry!", e); } put(gtx, transactionContext); if (trace) { log.trace("created new GTX: " + gtx + ", local TX=" + tx); } } return gtx; } @ManagedAttribute(name = "numberOfRegisteredTransactions", description = "Number of registered transactions") public int getNumberOfRegisteredTransactions() { return tx2gtxMap.size(); } @ManagedAttribute(name = "transactionMap", description = "A String representation of the transaction map") public String getTransactionMap() { return tx2gtxMap.toString(); } }