/*
* 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.Fqn;
import org.jboss.cache.Modification;
import org.jboss.cache.commands.ReversibleCommand;
import org.jboss.cache.config.Option;
import org.jboss.cache.interceptors.OrderedSynchronizationHandler;
import org.jboss.cache.lock.IdentityLock;
import org.jboss.cache.lock.NodeLock;
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;
/**
* 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: 6072 $
*/
@ThreadSafe
public class TransactionEntry
{
private static final Log log = LogFactory.getLog(TransactionEntry.class);
private static final boolean trace = log.isTraceEnabled();
/**
* Local transaction
*/
private Transaction ltx = null;
private Option option;
private OrderedSynchronizationHandler orderedSynchronizationHandler;
private boolean forceAsyncReplication = false;
private boolean forceSyncReplication = false;
/**
* List<ReversibleCommand> of modifications ({@link ReversibleCommand}). They will be replicated on TX commit
*/
private List<ReversibleCommand> modificationList;
/**
* A list of modifications that have been encountered with a LOCAL mode option. These will be removed from the modification list during replication.
*/
private List<ReversibleCommand> localModifications;
/**
* 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 final 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 final 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(ReversibleCommand command)
{
if (command == null) return;
if (modificationList == null) modificationList = new LinkedList<ReversibleCommand>();
modificationList.add(command);
}
/**
* Returns all modifications.
*/
public List<ReversibleCommand> getModifications()
{
if (modificationList == null) return Collections.emptyList();
return modificationList;
}
/**
* Adds a modification to the local modification list.
*/
public void addLocalModification(ReversibleCommand command)
{
if (command == null) return;
if (localModifications == null) localModifications = new LinkedList<ReversibleCommand>();
localModifications.add(command);
}
/**
* Returns all modifications that have been invoked with the LOCAL cache mode option. These will also be in the standard modification list.
*/
public List<ReversibleCommand> getLocalModifications()
{
if (localModifications == null) return Collections.emptyList();
return localModifications;
}
/**
* 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);
}
/**
* 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);
}
}
/**
* 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()
{
if (modificationList == null)
{
if (trace) log.trace("Modification list is null, no modifications in this transaction!");
return;
}
if (trace) log.trace("undoOperations " + modificationList);
ArrayList<ReversibleCommand> copy;
// synchronized (modificationList)
// {
// no need to sync? Only one thread would access a transaction at any given time?
copy = new ArrayList<ReversibleCommand>(modificationList);
// }
for (ListIterator i = copy.listIterator(copy.size()); i.hasPrevious();)
{
Object undoOp = i.previous();
ReversibleCommand txCommand = (ReversibleCommand) undoOp;
if (log.isDebugEnabled()) log.debug("Calling rollback() on command " + undoOp);
txCommand.rollback();
}
}
/**
* Returns debug information about this transaction.
*/
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("TransactionEntry\nmodificationList: ").append(modificationList);
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 hasModifications()
{
return modificationList != null && !modificationList.isEmpty();
}
/**
* @return true if any modifications have been invoked with cache mode being LOCAL.
*/
public boolean hasLocalModifications()
{
return localModifications != null && !localModifications.isEmpty();
}
/**
* Cleans up internal state
*/
public void reset()
{
orderedSynchronizationHandler = null;
if (modificationList != null) modificationList = null;
if (localModifications != null) localModifications = null;
option = null;
locks.clear();
if (dummyNodesCreatedByCacheLoader != null) dummyNodesCreatedByCacheLoader.clear();
removedNodes.clear();
}
}