package org.jboss.cache.invocationcontext;

import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.transaction.TransactionEntry;
import org.jboss.cache.transaction.TransactionTable;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

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

/**
 * A test to ensure the transactional context is properly set up in the IC
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani</a>
 */
@Test(groups = {"functional", "transaction"})
public class TransactionTest
{
   private CacheSPI<Object, Object> cache;
   private TransactionManager tm;

   @BeforeMethod(alwaysRun = true)
   public void setUp()
   {
      cache = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache("META-INF/conf-test/local-tx-service.xml");
      tm = cache.getTransactionManager();
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown()
   {
      if (cache != null)
      {
         cache.stop();
         cache = null;
      }
   }

   public void testTxExistenceAfterWrite() throws Exception
   {
      // make sure we have a running transaction.
      tm.begin();

      assertNull("Tx should not have been set up yet", cache.getInvocationContext().getTransaction());
      assertNull("Gtx should not have been set up yet", cache.getInvocationContext().getGlobalTransaction());

      // now make a WRITE call into the cache (should start a tx)
      cache.getRoot().put("k", "v");
      Map data = cache.getRoot().getData();
      assertEquals("Data map should not empty", 1, data.size());

      // but now we should have a local tx registered in the invocation context
      assertNotNull("Tx should have been set up by now", cache.getInvocationContext().getTransaction());
      assertEquals("The same current transaction should be associated with this invocation ctx.", tm.getTransaction(), cache.getInvocationContext().getTransaction());
      assertNotNull("Gtx should have been set up by now", cache.getInvocationContext().getGlobalTransaction());

      tm.commit();
   }

   public void testTxExistenceAfterRead() throws Exception
   {
      // make sure we have a running transaction.
      tm.begin();

      assertNull("Tx should not have been set up yet", cache.getInvocationContext().getTransaction());
      assertNull("Gtx should not have been set up yet", cache.getInvocationContext().getGlobalTransaction());

      // now make a WRITE call into the cache (should start a tx)
      Object value = cache.get(Fqn.ROOT, "k");
      assertNull("Value should be null", value);

      // but now we should have a local tx registered in the invocation context
      assertNotNull("Tx should have been set up by now", cache.getInvocationContext().getTransaction());
      assertEquals("The same current transaction should be associated with this invocation ctx.", tm.getTransaction(), cache.getInvocationContext().getTransaction());
      assertNotNull("Gtx should have been set up by now", cache.getInvocationContext().getGlobalTransaction());

      tm.commit();
   }

   public void testScrubbingAfterCommit() throws Exception
   {
      doScrubbingTest(true);
   }

   public void testScrubbingAfterOnePhaseCommit() throws Exception
   {
      setUpOnePhaseCache();

      doScrubbingTest(true);
   }

   public void testScrubbingAfterRollback() throws Exception
   {
      doScrubbingTest(false);
   }

   public void testScrubbingAfterOnePhaseRollback() throws Exception
   {
      setUpOnePhaseCache();

      doScrubbingTest(true);
   }

   @SuppressWarnings("deprecation")
   private void doScrubbingTest(boolean commit) throws Exception
   {
      // Start clean
      cache.getInvocationContext().reset();

      tm.begin();
      TransactionTable tt = cache.getTransactionTable();
      cache.getRoot().put("key", "value");

      assertNotNull("Tx should have been set up by now", cache.getInvocationContext().getTransaction());
      assertEquals("The same current transaction should be associated with this invocation ctx.", tm.getTransaction(), cache.getInvocationContext().getTransaction());
      assertNotNull("Gtx should have been set up by now", cache.getInvocationContext().getGlobalTransaction());

      Transaction tx = tm.getTransaction();
      TransactionEntry entry = tt.get(tt.get(tx));

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

      assertNull("Tx should have been scrubbed", cache.getInvocationContext().getTransaction());
      assertNull("Gtx should have been scrubbed", cache.getInvocationContext().getGlobalTransaction());
      assertEquals("Method call should have been scrubbed", null, cache.getInvocationContext().getMethodCall());
      assertEquals("Cache command should have been scrubbed", null, cache.getInvocationContext().getCommand());

      // check that the transaction entry hasn't leaked stuff.
      assert entry.getModifications().isEmpty() : "Should have scrubbed modifications in transaction entry";
      assert entry.getLocks().isEmpty() : "Should have scrubbed modifications in transaction entry";
      assert entry.getOrderedSynchronizationHandler() == null : "Should have removed the ordered sync handler";
   }

   private void setUpOnePhaseCache()
   {
      if (cache != null)
      {
         cache.stop();
         cache = null;
      }

      cache = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache("META-INF/conf-test/replSync-service.xml", false);
      cache.getConfiguration().setCacheMode(CacheMode.REPL_ASYNC);
      cache.start();
      tm = cache.getTransactionManager();
   }
}
