package org.jboss.cache.loader;

import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.util.TestingUtil;
import org.jboss.cache.transaction.TransactionSetup;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.util.Set;

/**
 * @author Bela Ban
 * @version $Id: TxCacheLoaderTest.java 5906 2008-05-29 07:24:18Z mircea.markus $
 */
@Test(groups = {"functional", "transaction"})
public class TxCacheLoaderTest extends AbstractCacheLoaderTestBase
{
   CacheSPI<Object, Object> cache1, cache2;
   private Fqn<String> fqn = Fqn.fromString("/one/two/three");

   @BeforeMethod(alwaysRun = true)
   public void setUp() throws Exception
   {

      cache1 = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(false);
      cache1.getConfiguration().setCacheMode("repl_sync");
      cache1.getConfiguration().setTransactionManagerLookupClass(TransactionSetup.getManagerLookup());

      cache1.getConfiguration().setCacheLoaderConfig(getSingleCacheLoaderConfig("", DummyInMemoryCacheLoader.class.getName(), "", false, false, false));
      // cache1.setReplQueueInterval(3000);
      cache1.create();
      cache1.start();

      cache2 = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(false);
      cache2.getConfiguration().setCacheMode("repl_sync");
      cache2.getConfiguration().setTransactionManagerLookupClass(TransactionSetup.getManagerLookup());
      cache2.getConfiguration().setCacheLoaderConfig(getSingleCacheLoaderConfig("", DummyInMemoryCacheLoader.class.getName(), "", false, false, false));
      cache2.getConfiguration().setLockAcquisitionTimeout(2000);
      // cache2.setReplQueueInterval(3000);
      cache2.create();
      cache2.start();
   }

   @AfterMethod(alwaysRun = false)
   public void tearDown() throws Exception
   {
      // clean up cache loaders!!
      cache1.removeNode(Fqn.ROOT);

      cache1.stop();
      cache1.destroy();
      cache2.stop();
      cache2.destroy();
   }


   public void testTxPutCommit() throws Exception
   {
      TransactionManager mgr = cache1.getConfiguration().getRuntimeConfig().getTransactionManager();
      mgr.begin();


      cache1.put(fqn, "key1", "val1");
      cache1.put("/one/two/three/four", "key2", "val2");
      assertNull(cache2.get(fqn, "key1"));
      assertNull(cache2.get("/one/two/three/four", "key2"));
      mgr.commit();
      assertNotNull(cache1.getNode(fqn).getKeys());
      Set<?> children = cache1.getNode("/one").getChildrenNames();
      assertEquals(1, children.size());
      TestingUtil.sleepThread(2000);
      assertEquals("val1", cache2.get(fqn, "key1"));
      assertEquals("val2", cache2.get("/one/two/three/four", "key2"));
   }


   public void testTxPrepareAndRollback() throws Exception
   {
      final TransactionManager mgr = cache1.getConfiguration().getRuntimeConfig().getTransactionManager();
      mgr.begin();

      cache1.getConfiguration().setLockAcquisitionTimeout(1500);
      cache2.getConfiguration().setLockAcquisitionTimeout(1500);


      Thread locker = new Thread()
      {
         Transaction tx2 = null;

         public void run()
         {
            try
            {
               mgr.begin();
               tx2 = mgr.getTransaction();
               cache2.put(fqn, "block-key1", "block-val1");// acquires a lock on cache2./one/two/three
               TestingUtil.sleepThread(5000);
            }
            catch (Exception e)
            {
               e.printStackTrace();
            }
            finally
            {
               if (tx2 != null)
               {
                  try
                  {
                     mgr.rollback();
                  }
                  catch (SystemException e)
                  {
                     e.printStackTrace();
                  }
               }
            }
         }
      };

      locker.start();
      TestingUtil.sleepThread(1000);

      cache1.put(fqn, "key1", "val1");
      cache1.put("/one/two/three/four", "key2", "val2");

      try
      {
         mgr.commit();// prepare() on cache2 will fail due to lock held by locker thread
         fail("commit() should fail because we cannot acquire the lock on cache2");
      }
      catch (RollbackException rollback)
      {
         System.out.println("--- TX was rolled back (as expected)");
         assertTrue(true);
      }

      assertNull(cache1.get(fqn, "key1"));
      assertNull(cache1.get("/one/two/three/four", "key1"));

   }


   public void testPutAfterTxCommit() throws Exception
   {
      TransactionManager mgr = cache1.getConfiguration().getRuntimeConfig().getTransactionManager();
      mgr.begin();

      cache1.put(fqn, "key1", "val1");
      assertTrue(cache1.exists(fqn));
      mgr.commit();
      assertTrue(cache1.exists(fqn));
      cache1.put("/a/b/c", null);// should be run outside a TX !
      assertTrue(cache1.exists("/a/b/c"));
   }

   public void testPutAfterTxRollback() throws Exception
   {
      TransactionManager mgr = cache1.getConfiguration().getRuntimeConfig().getTransactionManager();
      mgr.begin();

      cache1.put(fqn, "key1", "val1");
      assertTrue(cache1.exists(fqn));
      mgr.rollback();
      assertFalse(cache1.getCacheLoaderManager().getCacheLoader().exists(fqn));
      assertFalse(cache1.exists(fqn));
      cache1.put("/a/b/c", null);// should be run outside a TX !
      assertTrue(cache1.exists("/a/b/c"));
   }

}
