/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.interceptors;

import org.jboss.cache.CacheException;
import org.jboss.cache.Fqn;
import org.jboss.cache.InvocationContext;
import org.jboss.cache.NodeFactory;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.interceptors.base.CommandInterceptor;
import org.jboss.cache.lock.LockManager;
import static org.jboss.cache.lock.LockType.READ;
import org.jboss.cache.lock.TimeoutException;
import org.jboss.cache.optimistic.TransactionWorkspace;
import org.jboss.cache.optimistic.WorkspaceNode;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jboss.cache.transaction.OptimisticTransactionEntry;
import org.jboss.cache.transaction.TransactionTable;

import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.util.List;

/**
 * Abstract interceptor for optimistic locking
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
public abstract class OptimisticInterceptor extends CommandInterceptor
{
   protected TransactionManager txManager;
   protected TransactionTable txTable;
   protected LockManager lockManager;

   @Inject
   private void injectDependencies(TransactionManager txManager, TransactionTable txTable, LockManager lockManager)
   {
      this.txManager = txManager;
      this.txTable = txTable;
      this.lockManager = lockManager;
   }

   protected TransactionWorkspace getTransactionWorkspace(InvocationContext ctx) throws CacheException
   {
      OptimisticTransactionEntry transactionEntry = (OptimisticTransactionEntry) ctx.getTransactionEntry();

      if (transactionEntry == null)
      {
         throw new CacheException("Unable to map global transaction " + ctx.getGlobalTransaction() + " to transaction entry when trying to retrieve transaction workspace.");
      }

      // try and get the workspace from the transaction
      return transactionEntry.getTransactionWorkSpace();
   }

   /**
    * Adds the Fqn of the node as well as all children and childrens children to the list.
    */
   protected void greedyGetFqns(List<Fqn> list, NodeSPI<?, ?> n, Fqn newBase)
   {
      list.add(n.getFqn());
      Fqn newFqn = Fqn.fromRelativeElements(newBase, n.getFqn().getLastElement());
      list.add(newFqn);

      for (NodeSPI child : n.getChildrenDirect())
      {
         greedyGetFqns(list, child, newFqn);
      }
   }

   /**
    * @return the {@link org.jboss.cache.transaction.GlobalTransaction}, extracted from the current {@link org.jboss.cache.InvocationContext}.
    * @throws CacheException if the {@link org.jboss.cache.transaction.GlobalTransaction} or {@link javax.transaction.Transaction} associated with the
    *                        {@link org.jboss.cache.InvocationContext} is null.
    */
   protected GlobalTransaction getGlobalTransaction(InvocationContext ctx) throws CacheException
   {
      Transaction tx = ctx.getTransaction();
      if (tx == null) throw new CacheException("Transaction associated with the current invocation is null!");
      GlobalTransaction gtx = ctx.getGlobalTransaction();
      if (gtx == null) throw new CacheException("GlobalTransaction associated with the current invocation is null!");
      return gtx;
   }

   protected void undeleteWorkspaceNode(WorkspaceNode nodeToUndelete, TransactionWorkspace workspace)
   {
      undeleteWorkspaceNode(nodeToUndelete, workspace.getNode(nodeToUndelete.getFqn().getParent()));
   }

   /**
    * Undeletes a node that already exists in the workspace, by setting appropriate flags and re-adding to parent's child map.
    *
    * @param nodeToUndelete WorkspaceNode to undelete
    * @param parent         parent of node to undelete
    */
   @SuppressWarnings("unchecked")
   protected void undeleteWorkspaceNode(WorkspaceNode nodeToUndelete, WorkspaceNode parent)
   {
      nodeToUndelete.markAsDeleted(false);
      nodeToUndelete.clearData();
      // add in parent again
      parent.addChild(nodeToUndelete);
      nodeToUndelete.markAsResurrected(true);
   }

   @SuppressWarnings("unchecked")
   protected WorkspaceNode lockAndCreateWorkspaceNode(NodeFactory nodeFactory, NodeSPI node, TransactionWorkspace workspace, GlobalTransaction gtx, long timeout)
   {
      boolean locked = lockManager.lock(node, READ, gtx, timeout);

      if (!locked)
         throw new TimeoutException("Unable to lock node " + node.getFqn() + " after timeout " + timeout + " for copying into workspace");

      WorkspaceNode wn = nodeFactory.createWorkspaceNode(node, workspace);

      lockManager.unlock(node, gtx);
      return wn;
   }


}
