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

import junit.framework.TestCase;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.jboss.cache.TreeCache;
import org.jboss.cache.Fqn;
import org.jboss.cache.DummyTransactionManagerLookup;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.lock.TimeoutException;
import EDU.oswego.cs.dl.util.concurrent.Latch;

import javax.transaction.Transaction;
import javax.transaction.SystemException;
import javax.transaction.NotSupportedException;

/**
 * Tests READ_COMMITED isolation level.
 *
 * @author <a href="mailto:ovidiu@jboss.org">Ovidiu Feodorov</a>
 *
 * @version $Id: IsolationLevelReadCommittedNodeCreationRollbackTest.java 1300 2006-02-22 17:04:45Z msurtani $
 */

public class IsolationLevelReadCommittedNodeCreationRollbackTest extends TestCase
{

   private TreeCache cache = null;
   private final Fqn FQN = Fqn.fromString("/a/b/c");
   private final String KEY = "key";
   private final String VALUE = "value";

   private volatile boolean writerFailed;
   private volatile boolean readerFailed;
   private volatile AssertionFailedError writerError;
   private volatile AssertionFailedError readerError;

   protected void setUp() throws Exception
   {
      super.setUp();

      writerFailed = false;
      readerFailed = false;

      writerError = null;
      readerError = null;

      cache = new TreeCache();
      cache.setCacheMode(TreeCache.LOCAL);
      cache.setIsolationLevel(IsolationLevel.READ_COMMITTED);
      cache.setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName());
      cache.startService();
   }


   protected void tearDown() throws Exception
   {
      super.tearDown();

      cache.stopService();
      cache.destroyService();
      cache=null;
   }


   public void testNodeCreationRollback() throws Exception
   {
      final Latch secondCanWrite = new Latch();
      final Latch secondCanRead = new Latch();
      final Latch secondDone = new Latch();
      final Latch firstCanRollback = new Latch();
      final Latch firstDone = new Latch();

      final Fqn PARENT = Fqn.fromString("/a");

      // start a first thread and a transaction

      Thread firstThread = new Thread(new Runnable()
      {
         public void run()
         {
            try
            {
               Transaction tx = startTransaction();

               System.out.println("Writing /a/1");

               // Create an empty parent node and a node with data
               Fqn a1 = new Fqn(PARENT, "1");
               cache.put(a1, KEY, VALUE);

               // notify the second thread it can write
               secondCanWrite.release();

               // wait until the second thread writes and allows me to rollback or until I timeout
               firstCanRollback.attempt(3000);

               System.out.println("rolling back");

               tx.rollback();

               assertNull("a1 empty", cache.get(a1, KEY));

               // notify the reading thread
               secondCanRead.release();
            }
            catch (AssertionFailedError e)
            {
               writerError = e;
            }
            catch(Throwable t)
            {
               t.printStackTrace();
               writerFailed = true;
            }
            finally
            {
               System.out.println("first thread exits");
               secondCanWrite.release();
               secondCanRead.release();
               firstDone.release();
            }
         }
      }, "FIRST");
      firstThread.start();

      // start a second thread; no transaction is necessary here

      Thread secondThread = new Thread(new Runnable()
      {
         public void run()
         {
            try
            {
               // wait until the first thread has created PARENT and a child
               secondCanWrite.acquire();

               System.out.println("writing a2");

               // create a second child under parent
               Fqn a2 = new Fqn(PARENT, "2");
               try
               {
                  cache.put(a2, KEY, VALUE);
               }
               catch (TimeoutException good)
               {
                  // first thread locked us out of parent
                  System.out.println("Prevented from writing a2 -- " +
                                     good.getLocalizedMessage());
                  return;
               }

               // let the first thread know it can rollback
               firstCanRollback.release();

               // wait until the first thread rolls back.
               secondCanRead.acquire();

               // I should still see the value I put
               assertEquals("Known issue JBCACHE-407 -- write lock not acquired on " +
                            "creation of an empty node", VALUE, cache.get(a2, KEY));
            }
            catch (AssertionFailedError e)
            {
               readerError = e;
            }
            catch(Throwable t)
            {
               t.printStackTrace();
               readerFailed = true;
            }
            finally
            {
               System.out.println("second thread exits");
               firstCanRollback.release();
               secondDone.release();
            }
         }
      }, "SECOND");
      secondThread.start();

      // wait for both threads to finish
      secondDone.acquire();
      firstDone.acquire();

      // If any assertion failed, throw on the AssertionFailedError
      if (readerError != null)
      {
         throw readerError;
      }

      if (writerError != null)
      {
         throw writerError;
      }

      if (readerFailed)
      {
         fail("The second thread exited incorrectly. Watch the log for previous stack traces");
      }

      if (writerFailed)
      {
         fail("The first thread exited incorrectly. Watch the log for previous stack traces");
      }
   }


   private Transaction startTransaction() throws SystemException, NotSupportedException
   {
      DummyTransactionManager mgr=DummyTransactionManager.getInstance();
      mgr.begin();
      return mgr.getTransaction();
   }



   public static Test suite() {

      return new TestSuite(IsolationLevelReadCommittedNodeCreationRollbackTest.class);

   }







}
