/*
 * 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&lt;ReversibleCommand&gt; 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();
   }
}
