package org.jboss.cache.transaction;

import junit.framework.TestCase;
import org.jboss.cache.Fqn;
import org.jboss.cache.TreeCache;

import javax.transaction.SystemException;
import javax.transaction.TransactionManager;

/**
 * Test behaviour of async rollback timeouted transaction
 *
 * @author <a href="mailto:jhalat@infovide.pl">Jacek Halat</a>
 * @since 1.4.0
 */
public class AsyncRollbackTxTest extends TestCase
{
   private TreeCache cache;
   private TransactionManager tm;
   private Fqn fqn = Fqn.fromString("/test");

   protected void setUp() throws Exception
   {
      cache = new TreeCache();

      cache.setTransactionManagerLookupClass("org.jboss.cache.transaction.AsyncRollbackTransactionManagerLookup");
      cache.startService();
      tm = cache.getTransactionManager();
      tm.setTransactionTimeout(2);
   }

   protected void tearDown()
   {
      try
      {
         if (tm != null && tm.getTransaction() != null)
         {
            try
            {
               tm.rollback();
            }
            catch (SystemException e)
            {
               // do nothing
            }
         }
      }
      catch (SystemException e)
      {
         // do nothing
      }
      if (cache != null) cache.stopService();
      cache = null;
      tm = null;
   }

   public void testCommitCreationInSameTx() throws Exception
   {
      assertEquals(0, cache.getNumberOfLocksHeld());
      tm.begin();
      cache.put(fqn, "k", "v");
      assertEquals(2, cache.getNumberOfLocksHeld());
      Thread.sleep(2500);
      tm.commit();
      assertEquals(0, cache.getNumberOfLocksHeld());
   }

   public void testRollbackCreationInSameTx() throws Exception
   {
      assertEquals(0, cache.getNumberOfLocksHeld());
      tm.begin();
      cache.put(fqn, "k", "v");
      assertEquals(2, cache.getNumberOfLocksHeld());
      Thread.sleep(2500);
      tm.rollback();
      assertEquals(0, cache.getNumberOfLocksHeld());
   }


   private void doTest(boolean commit, boolean writeLock) throws Exception
   {
      assertEquals(0, cache.getNumberOfLocksHeld());
      cache.put(fqn, "k", "v");//Executed in Not transactional context
      assertEquals(0, cache.getNumberOfLocksHeld());
      SeparateThread t = new SeparateThread(commit, writeLock);
      t.start();
      t.join();
      if (t.getException() != null)
      {
         throw t.getException();
      }
      assertEquals(0, cache.getNumberOfLocksHeld());
      assertEquals("v", cache.get(fqn, "k"));
   }

   public void testRollbackCreationInDifferentTxReadLock() throws Exception
   {
      doTest(false, false);
   }

   public void testCommitCreationInDifferentTxReadLock() throws Exception
   {
      doTest(true, false);
   }

   public void testRollbackCreationInDifferentTxWriteLock() throws Exception
   {
      doTest(false, true);
   }

   public void testCommitCreationInDifferentTxWriteLock() throws Exception
   {
      doTest(true, true);
   }

   public void testTxTimeoutAndPutAfter() throws Exception
   {
      assertEquals(0, cache.getNumberOfLocksHeld());
      tm.begin();
      cache.put(fqn, "k", "v");
      assertEquals(2, cache.getNumberOfLocksHeld());
      assertNotNull(tm.getTransaction());
      Thread.sleep(2500);
      tm.rollback();
      assertNull(tm.getTransaction());
      assertEquals(0, cache.getNumberOfLocksHeld());

      // make sure the node was NOT added!!
      assertFalse(cache.exists(fqn));
      // even in a "deleted" form
      assertNull(cache.peek(fqn));

      //Put not some data into cache. Because no transaction exist
      //no locks should be helds after this line, correct?

      cache.put(fqn, "k", "v");
      //On version 1.4.0SP1 cache.getNumberOfLocksHeld()==1
      assertEquals(0, cache.getNumberOfLocksHeld());
   }


   public void testTxTimeoutAndPutGetAfter() throws Exception
   {
      assertEquals(0, cache.getNumberOfLocksHeld());
      tm.begin();
      cache.put(fqn, "k", "v");
      assertEquals(2, cache.getNumberOfLocksHeld());
      assertNotNull(tm.getTransaction());
      Thread.sleep(2500);
      tm.rollback();
      assertNull(tm.getTransaction());
      assertEquals(0, cache.getNumberOfLocksHeld());
      //Put not some data into cache. Because no transaction exist
      //no locks should be helds after this line, correct?
      cache.put(fqn, "k", "v");
      cache.get(fqn, "k");
      //Because this thread is Owner of lock, read this node can be readed
      //but when we try read this value from another thread (another thread is of course NOT lock owner)
      //we got...
      SeparateThread t = new SeparateThread(false, false);
      t.start();
      t.join();
      if (t.getException() != null)
      {
         throw t.getException();
      }
      //...TimeoutException. This happend On version 1.4.0SP1.
      //org.jboss.cache.lock.TimeoutException: failure acquiring lock: fqn=/test, caller=GlobalTransaction:<null>:2, lock=write owner=GlobalTransaction:<null>:1 (activeReaders=0, activeWriter=Thread[main,5,main], waitingReaders=0, waitingWriters=0, waitingUpgrader=0)
      //at org.jboss.cache.Node.acquire(Node.java:407)

   }


   private class SeparateThread extends Thread
   {
      Exception e = null;
      boolean commit, writeLock;


      public SeparateThread(boolean commit, boolean writeLock)
      {
         this.commit = commit;
         this.writeLock = writeLock;
      }

      public Exception getException()
      {
         return e;
      }

      public void run()
      {
         try
         {
            tm.begin();
            if (writeLock)
            {
               cache.put(fqn, "k", "v2");// obtain write lock on node
            }
            else
            {
               cache.get(fqn, "k");// obtain read lock on node
            }

            // sleep to ensure tx times out.
            sleep(2500);

            if (commit)
            {
               tm.commit();
            }
            else
            {
               tm.rollback();
            }

            assertEquals(0, cache.getNumberOfLocksHeld());
         }
         catch (Exception e)
         {
            this.e = e;
         }
      }
   }

   ;
}

