package org.jboss.cache.options;

import org.jboss.cache.CacheFactory;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.lock.LockManager;
import org.jboss.cache.lock.LockType;
import org.jboss.cache.util.TestingUtil;
import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import javax.transaction.TransactionManager;

/**
 * Tests forcing a write lock to be obtained on a node
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani</a>
 * @since 2.0.0
 */
@Test(groups = {"functional"})
public class ForceWriteLockTest
{
   private CacheSPI<String, String> cache;
   private Fqn fqn = Fqn.fromString("/a/b");
   private TransactionManager tm;

   @BeforeMethod(alwaysRun = true)
   public void setUp()
   {
      Configuration c = new Configuration();
      c.setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
      CacheFactory<String, String> instance = new DefaultCacheFactory<String, String>();
      cache = (CacheSPI<String, String>) instance.createCache(c);
      tm = cache.getTransactionManager();
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown()
   {
      try
      {
         tm.rollback();
      }
      catch (Exception e)
      {
         // ignore
      }
      cache.stop();
   }

   public void testControl() throws Exception
   {
      cache.put(fqn, "k", "v");
      tm.begin();
      cache.get(fqn, "k");
      // parent should be read-locked!!
      assertLocked(cache.getInvocationContext().getGlobalTransaction(), fqn.getParent(), false);
      assertLocked(cache.getInvocationContext().getGlobalTransaction(), fqn, false);
      tm.commit();
      assertNotLocked(fqn);
   }

   public void testForceWithGetTx() throws Exception
   {
      cache.put(fqn, "k", "v");
      tm.begin();
      cache.getInvocationContext().getOptionOverrides().setForceWriteLock(true);
      cache.get(fqn, "k");
      // parent should be read-locked!!
      assertLocked(cache.getInvocationContext().getGlobalTransaction(), fqn.getParent(), false);
      assertLocked(cache.getInvocationContext().getGlobalTransaction(), fqn, true);
      tm.commit();
      assertNotLocked(fqn);

      // now test normal operation
      testControl();
   }

   public void testForceWithPutTx() throws Exception
   {
      cache.put(fqn, "k", "v");
      tm.begin();
      cache.getInvocationContext().getOptionOverrides().setForceWriteLock(true);
      cache.put(fqn, "k", "v2");
      // parent should be read-locked!!
      assertLocked(cache.getInvocationContext().getGlobalTransaction(), fqn.getParent(), false);
      assertLocked(cache.getInvocationContext().getGlobalTransaction(), fqn, true);
      tm.commit();
      assertNotLocked(fqn);

      // now test normal operation
      testControl();
   }

   public void testForceWithRemoveTx() throws Exception
   {
      cache.put(fqn, "k", "v");
      tm.begin();
      cache.getInvocationContext().getOptionOverrides().setForceWriteLock(true);
      cache.remove(fqn, "k");
      // parent should be read-locked!!
      assertLocked(cache.getInvocationContext().getGlobalTransaction(), fqn.getParent(), false);
      assertLocked(cache.getInvocationContext().getGlobalTransaction(), fqn, true);
      tm.commit();
      assertNotLocked(fqn);

      // now test normal operation
      testControl();
   }

   private void assertNotLocked(Fqn fqn)
   {
      assert !TestingUtil.extractLockManager(cache).isLocked(cache.peek(fqn, true)) : "Node " + fqn + " is locked!!";
   }

   private void assertLocked(Object owner, Fqn fqn, boolean write_locked)
   {
      LockManager lm = TestingUtil.extractLockManager(cache);
      NodeSPI<String, String> n = cache.peek(fqn, true);
      if (owner == null) owner = Thread.currentThread();
      assertTrue("node " + fqn + " is not locked", lm.isLocked(n));
      if (write_locked)
      {
         assertTrue("node " + fqn + " is not write-locked by owner " + owner, lm.ownsLock(fqn, LockType.WRITE, owner));
      }
      else
      {
         assertTrue("node " + fqn + " is not read-locked by owner " + owner, lm.ownsLock(fqn, LockType.READ, owner));
      }
   }

}
