/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.cache.transaction;
import net.jcip.annotations.ThreadSafe;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheImpl;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.Fqn;
import org.jboss.cache.Modification;
import org.jboss.cache.config.Option;
import org.jboss.cache.interceptors.OrderedSynchronizationHandler;
import org.jboss.cache.invocation.CacheInvocationDelegate;
import org.jboss.cache.lock.IdentityLock;
import org.jboss.cache.lock.NodeLock;
import org.jboss.cache.marshall.MethodCall;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Information associated with a {@link GlobalTransaction} about the transaction state.
* <p/>
* A TransactionEntry maintains:
* <ul>
* <li>Handle to local Transactions: there can be more than 1 local TX associated with a GlobalTransaction
* <li>List of modifications ({@link Modification})
* <li>List of nodes that were created as part of lock acquisition. These nodes can be
* safely deleted when a transaction is rolled back
* <li>List of locks ({@link IdentityLock}) that have been acquired by
* this transaction so far
* </ul>
*
* @author <a href="mailto:bela@jboss.org">Bela Ban</a> Apr 14, 2003
* @version $Revision: 5691 $
*/
@ThreadSafe
public class TransactionEntry
{
private static Log log = LogFactory.getLog(TransactionEntry.class);
/**
* Local transaction
*/
private Transaction ltx = null;
private Option option;
private OrderedSynchronizationHandler orderedSynchronizationHandler;
private boolean forceAsyncReplication = false;
private boolean forceSyncReplication = false;
/**
* List<MethodCall> of modifications ({@link MethodCall}). They will be replicated on TX commit
*/
private List<MethodCall> modification_list = new LinkedList<MethodCall>();
// For some reason we see multiple threads accessing this list - even within the same tx. Could be due to reuse of
// tx identifiers in the DummyTM, which is where we see this problem.
private List<MethodCall> cl_mod_list = new CopyOnWriteArrayList<MethodCall>();
/**
* List<MethodCall>. List of compensating {@link org.jboss.cache.marshall.MethodCall} objects
* which revert the ones in <tt>modification_list</tt>. For each entry in the modification list,
* we have a corresponding entry in this list. A rollback will simply iterate over this list in
* reverse to undo the modifications. Note that these undo-ops will never be replicated.
*/
private final List<MethodCall> undo_list = new LinkedList<MethodCall>();
/**
* LinkedHashSet<IdentityLock> of locks acquired by the transaction. We use
* a LinkedHashSet because we need efficient Set semantics (same lock can
* be added multiple times) but also need guaranteed ordering for use
* by lock release code (see JBCCACHE-874).
*/
private LinkedHashSet<NodeLock> locks = new LinkedHashSet<NodeLock>();
/**
* A list of dummy uninitialised nodes created by the cache loader interceptor to load data for a
* given node in this tx.
*/
private List<Fqn> dummyNodesCreatedByCacheLoader;
/**
* List<Fqn> of nodes that have been removed by the transaction
*/
private List<Fqn> removedNodes = new LinkedList<Fqn>();
public TransactionEntry(Transaction tx) throws SystemException, RollbackException
{
ltx = tx;
orderedSynchronizationHandler = new OrderedSynchronizationHandler(tx);
}
/**
* Adds a modification to the modification list.
*/
public void addModification(MethodCall m)
{
if (m == null) return;
modification_list.add(m);
}
public void addCacheLoaderModification(MethodCall m)
{
if (m != null) cl_mod_list.add(m);
}
/**
* Returns all modifications.
*/
public List<MethodCall> getModifications()
{
return modification_list;
}
public List<MethodCall> getCacheLoaderModifications()
{
// make sure this isn't modified externally
return Collections.unmodifiableList(cl_mod_list);
}
/**
* Adds an undo operation to the undo list.
*
* @see #undoOperations
*/
public void addUndoOperation(MethodCall m)
{
undo_list.add(m);
}
/**
* Adds the node that has been removed.
*
* @param fqn
*/
public void addRemovedNode(Fqn fqn)
{
removedNodes.add(fqn);
}
/**
* Gets the list of removed nodes.
*/
public List<Fqn> getRemovedNodes()
{
return new ArrayList<Fqn>(removedNodes);
}
/**
* Returns the undo operations in use.
* Note: This list may be concurrently modified.
*/
public List<MethodCall> getUndoOperations()
{
return undo_list;
}
/**
* Sets the local transaction for this entry.
*/
public void setTransaction(Transaction tx)
{
ltx = tx;
}
/**
* Returns a local transaction associated with this TransactionEntry
*/
public Transaction getTransaction()
{
return ltx;
}
/**
* Adds a lock to the end of the lock list, if it isn't already present.
*/
public void addLock(NodeLock l)
{
if (l != null)
{
synchronized (locks)
{
locks.add(l);
}
}
}
/**
* Add multiple locks to the lock list.
*
* @param newLocks Collection<NodeLock>
*/
public void addLocks(Collection<NodeLock> newLocks)
{
if (newLocks != null)
{
synchronized (locks)
{
locks.addAll(newLocks);
}
}
}
/**
* Returns the locks in use.
*
* @return a defensive copy of the internal data structure.
*/
public List<NodeLock> getLocks()
{
synchronized (locks)
{
return new ArrayList<NodeLock>(locks);
}
}
/**
* Releases all locks held by the owner, in reverse order of creation.
* Clears the list of locks held.
*/
public void releaseAllLocksLIFO(Object owner)
{
boolean trace = log.isTraceEnabled();
synchronized (locks)
{
// Copying out to an array is faster than creating an ArrayList and iterating,
// since list creation will just copy out to an array internally
IdentityLock[] lockArray = locks.toArray(new IdentityLock[locks.size()]);
for (int i = lockArray.length - 1; i >= 0; i--)
{
if (trace)
{
log.trace("releasing lock for " + lockArray[i].getFqn() + " (" + lockArray[i] + ")");
}
lockArray[i].release(owner);
}
locks.clear();
}
}
/**
* Releases all locks held by the owner, in order of creation.
* Does not clear the list of locks held.
*/
public void releaseAllLocksFIFO(Object owner)
{
// I guess a copy would work as well
// This seems fairly safe though
synchronized (locks)
{
for (NodeLock lock : locks)
{
lock.release(owner);
if (log.isTraceEnabled())
{
log.trace("releasing lock for " + ((IdentityLock) lock).getFqn() + " (" + lock + ")");
}
}
}
}
/**
* Gets the value of the forceAsyncReplication flag. Used by ReplicationInterceptor and OptimisticReplicationInterceptor
* when dealing with {@link org.jboss.cache.Cache#putForExternalRead(org.jboss.cache.Fqn,Object,Object)} within
* a transactional context.
*
* @return true if the forceAsyncReplication flag is set to true.
*/
public boolean isForceAsyncReplication()
{
return forceAsyncReplication;
}
/**
* Sets the value of the forceAsyncReplication flag. Used by ReplicationInterceptor and OptimisticReplicationInterceptor
* when dealing with {@link org.jboss.cache.Cache#putForExternalRead(org.jboss.cache.Fqn,Object,Object)} within
* a transactional context. Also used by OptimisticReplicationInterceptor when dealing
* with {@link org.jboss.cache.config.Option#setForceAsynchronous(boolean)} in a
* non-transactional context (i.e. with an implicit transaction).
*
* @param forceAsyncReplication value of forceAsyncReplication
*/
public void setForceAsyncReplication(boolean forceAsyncReplication)
{
this.forceAsyncReplication = forceAsyncReplication;
if (forceAsyncReplication)
{
forceSyncReplication = false;
}
}
/**
* Gets the value of the forceSyncReplication flag. Used by ReplicationInterceptor and OptimisticReplicationInterceptor
* when dealing with {@link org.jboss.cache.Cache#putForExternalRead(org.jboss.cache.Fqn,Object,Object)} within
* a transactional context.
*
* @return true if the forceAsyncReplication flag is set to true.
*/
public boolean isForceSyncReplication()
{
return forceSyncReplication;
}
/**
* Sets the value of the forceSyncReplication flag. Used by ReplicationInterceptor and OptimisticReplicationInterceptor
* when dealing with {@link org.jboss.cache.Cache#putForExternalRead(org.jboss.cache.Fqn,Object,Object)} within
* a transactional context.
*
* @param forceSyncReplication value of forceSyncReplication
*/
public void setForceSyncReplication(boolean forceSyncReplication)
{
this.forceSyncReplication = forceSyncReplication;
if (forceSyncReplication)
{
forceAsyncReplication = false;
}
}
/**
* Posts all undo operations to the CacheImpl.
*/
public void undoOperations(CacheSPI cache)
{
if (log.isTraceEnabled())
{
log.trace("undoOperations " + undo_list);
}
ArrayList<MethodCall> l;
synchronized (undo_list)
{
l = new ArrayList<MethodCall>(undo_list);
}
CacheImpl ci = ((CacheInvocationDelegate) cache).getDelegationTarget();
for (ListIterator<MethodCall> i = l.listIterator(l.size()); i.hasPrevious();)
{
MethodCall undo_op = i.previous();
undo(undo_op, ci);
}
}
private void undo(MethodCall undo_op, CacheImpl cache)
{
try
{
Object retval = undo_op.invoke(cache);
if (retval instanceof Throwable)
{
throw (Throwable) retval;
}
}
catch (Throwable t)
{
log.error("undo operation failed, error=" + t);
log.trace(t, t);
}
}
/**
* Returns debug information about this transaction.
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("TransactionEntry\nmodification_list: ").append(modification_list);
synchronized (undo_list)
{
sb.append("\nundo_list: ").append(undo_list);
}
synchronized (locks)
{
sb.append("\nlocks: ").append(locks);
}
return sb.toString();
}
public void loadUninitialisedNode(Fqn fqn)
{
if (dummyNodesCreatedByCacheLoader == null)
dummyNodesCreatedByCacheLoader = new LinkedList<Fqn>();
dummyNodesCreatedByCacheLoader.add(fqn);
}
public List<Fqn> getDummyNodesCreatedByCacheLoader()
{
return dummyNodesCreatedByCacheLoader;
}
/**
* Sets a transaction-scope option override
*
* @param o
*/
public void setOption(Option o)
{
this.option = o;
}
/**
* Retrieves a transaction scope option override
*/
public Option getOption()
{
return this.option;
}
public OrderedSynchronizationHandler getOrderedSynchronizationHandler()
{
return orderedSynchronizationHandler;
}
public void setOrderedSynchronizationHandler(OrderedSynchronizationHandler orderedSynchronizationHandler)
{
this.orderedSynchronizationHandler = orderedSynchronizationHandler;
}
/**
* Returns true if modifications were registered to either modificationList or to class loader modifications list.
*/
public boolean existModifications()
{
return !modification_list.isEmpty() || !cl_mod_list.isEmpty();
}
/**
* Cleans up internal state
*/
public void reset()
{
orderedSynchronizationHandler = null;
modification_list.clear();
cl_mod_list.clear();
option = null;
locks.clear();
if (dummyNodesCreatedByCacheLoader != null) dummyNodesCreatedByCacheLoader.clear();
removedNodes.clear();
}
}