/*
 * Created on 17-Feb-2005
 *
 *
 *
 */
package org.jboss.cache.optimistic;

import org.jboss.cache.CacheSPI;
import org.jboss.cache.util.TestingUtil;
import org.jboss.cache.commands.ReversibleCommand;
import org.jboss.cache.commands.tx.CommitCommand;
import org.jboss.cache.commands.tx.OptimisticPrepareCommand;
import org.jboss.cache.commands.tx.RollbackCommand;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.loader.SamplePojo;
import org.jboss.cache.transaction.DummyTransactionManager;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jboss.cache.transaction.OptimisticTransactionEntry;
import org.jboss.cache.transaction.TransactionTable;
import org.jgroups.Address;
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.Transaction;
import javax.transaction.TransactionManager;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author xenephon
 */
@SuppressWarnings("unchecked")
@Test(groups = "functional", enabled = false)
// disabling since this needs to be rewritten to cope with the new OptimisticReplicationInterceptor.  This really doesn't test very much right now.
public class OptimisticReplicationInterceptorTest extends AbstractOptimisticTestCase
{
   private CacheSPI cache;

   @BeforeMethod(alwaysRun = true)
   public void setUp() throws Exception
   {
      cache = createCache();
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown()
   {
      TestingUtil.killCaches(cache);
   }

   public void testLocalTransaction() throws Exception
   {
      MockInterceptor dummy = new MockInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      DummyTransactionManager mgr = DummyTransactionManager.getInstance();
      assertNull(mgr.getTransaction());

      mgr.begin();

      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());

      SamplePojo pojo = new SamplePojo(21, "test");

      cache.put("/one/two", "key1", pojo);

      mgr.commit();

      assertNull(mgr.getTransaction());
      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());

      //make sure all calls were done in right order

      List calls = dummy.getAllCalledIds();

      assertEquals(OptimisticPrepareCommand.METHOD_ID, calls.get(0));
      assertEquals(CommitCommand.METHOD_ID, calls.get(1));
   }

   public void testRollbackTransaction() throws Exception
   {
      MockInterceptor dummy = new MockInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      DummyTransactionManager mgr = DummyTransactionManager.getInstance();
      assertNull(mgr.getTransaction());
      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());

      SamplePojo pojo = new SamplePojo(21, "test");
      mgr.begin();
      cache.put("/one/two", "key1", pojo);
      mgr.rollback();
      assertNull(mgr.getTransaction());
      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());

      //make sure all calls were done in right order

      List calls = dummy.getAllCalledIds();

      assertEquals(1, calls.size());
      assertEquals(RollbackCommand.METHOD_ID, calls.get(0));
   }

   public void testRemotePrepareTransaction() throws Exception
   {
      MockInterceptor dummy = new MockInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      DummyTransactionManager mgr = DummyTransactionManager.getInstance();

      //start local transaction
      mgr.begin();
      Transaction tx = mgr.getTransaction();

      //this sets
      cache.getCurrentTransaction(tx, true);

      SamplePojo pojo = new SamplePojo(21, "test");

      cache.put("/one/two", "key1", pojo);

      GlobalTransaction gtx = cache.getCurrentTransaction(tx, true);
      TransactionTable table = cache.getTransactionTable();
      OptimisticTransactionEntry entry = (OptimisticTransactionEntry) table.get(gtx);
      assertNotNull(mgr.getTransaction());
      mgr.commit();


      GlobalTransaction remoteGtx = new GlobalTransaction();

      remoteGtx.setAddress(new TestAddress());
      //hack the method call to make it have the remote globalTransaction
      ReversibleCommand command = (ReversibleCommand) entry.getModifications().get(0);
      command.setGlobalTransaction(remoteGtx);

      //call our remote method
      OptimisticPrepareCommand optimisticPrepareCommand = new OptimisticPrepareCommand(remoteGtx, null, (Map) null, (Address) remoteGtx.getAddress(), false);
      try
      {
         TestingUtil.replicateCommand(cache, optimisticPrepareCommand); //getInvocationDelegate(cache)._replicate(prepareMethod);
      }
      catch (Throwable t)
      {
         fail();
      }

      //our thread should be null
      assertNull(mgr.getTransaction());

      //	 there should be a registration for the remote globalTransaction
      assertNotNull(table.get(remoteGtx));
      assertNotNull(table.getLocalTransaction(remoteGtx));
      //assert that this is populated
      assertEquals(1, table.get(remoteGtx).getModifications().size());

      //assert that the remote prepare has populated the local workspace
      assertEquals(3, entry.getTransactionWorkSpace().getNodes().size());
      assertEquals(1, entry.getModifications().size());
      List calls = dummy.getAllCalledIds();
      assertEquals(OptimisticPrepareCommand.METHOD_ID, calls.get(2));


      assertEquals(1, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(1, cache.getTransactionTable().getNumLocalTransactions());

   }


   public void testRemoteRollbackTransaction() throws Exception
   {
      MockInterceptor dummy = new MockInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      DummyTransactionManager mgr = DummyTransactionManager.getInstance();

      //start local transaction
      mgr.begin();
      Transaction tx = mgr.getTransaction();

      //this sets
      cache.getCurrentTransaction(tx, true);

      SamplePojo pojo = new SamplePojo(21, "test");

      cache.put("/one/two", "key1", pojo);

      GlobalTransaction gtx = cache.getCurrentTransaction(tx, true);
      TransactionTable table = cache.getTransactionTable();
      OptimisticTransactionEntry entry = (OptimisticTransactionEntry) table.get(gtx);
      assertNotNull(mgr.getTransaction());
      mgr.commit();


      GlobalTransaction remoteGtx = new GlobalTransaction();

      remoteGtx.setAddress(new TestAddress());
      //hack the method call to make it have the remote globalTransaction
      ReversibleCommand command = entry.getModifications().get(0);
      command.setGlobalTransaction(remoteGtx);
      //call our remote method
      OptimisticPrepareCommand prepareCommand = new OptimisticPrepareCommand(remoteGtx, null, null, (Address) remoteGtx.getAddress(), false);
      try
      {
         TestingUtil.replicateCommand(cache, prepareCommand);
      }
      catch (Throwable t)
      {
         fail();
      }

      //our thread should be null
      assertNull(mgr.getTransaction());

      //	 there should be a registration for the remote globalTransaction
      assertNotNull(table.get(remoteGtx));
      assertNotNull(table.getLocalTransaction(remoteGtx));
      //assert that this is populated
      assertEquals(1, table.get(remoteGtx).getModifications().size());

      assertEquals(3, entry.getTransactionWorkSpace().getNodes().size());
      assertEquals(1, entry.getModifications().size());
      List calls = dummy.getAllCalledIds();
      assertEquals(OptimisticPrepareCommand.METHOD_ID, calls.get(2));


      assertEquals(1, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(1, cache.getTransactionTable().getNumLocalTransactions());

      //	    call our remote method
      RollbackCommand cacheCommand = new RollbackCommand(null);
      try
      {
         TestingUtil.replicateCommand(cache, cacheCommand);
      }
      catch (Throwable t)
      {
         fail();
      }
      //we should have the commit as well now
      assertNull(mgr.getTransaction());
      assertEquals(RollbackCommand.METHOD_ID, calls.get(3));
      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());
   }


   public void testRemoteCommitNoPrepareTransaction() throws Exception
   {
      MockInterceptor dummy = new MockInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      DummyTransactionManager mgr = DummyTransactionManager.getInstance();

      //start local transaction
      mgr.begin();
      Transaction tx = mgr.getTransaction();

      //this sets
      cache.getCurrentTransaction(tx, true);

      SamplePojo pojo = new SamplePojo(21, "test");

      cache.put("/one/two", "key1", pojo);

      GlobalTransaction gtx = cache.getCurrentTransaction(tx, true);
      TransactionTable table = cache.getTransactionTable();
      OptimisticTransactionEntry entry = (OptimisticTransactionEntry) table.get(gtx);
      assertNotNull(mgr.getTransaction());
      mgr.commit();


      GlobalTransaction remoteGtx = new GlobalTransaction();

      remoteGtx.setAddress(new TestAddress());
      //hack the method call to make it have the remote globalTransaction
      ReversibleCommand command = entry.getModifications().get(0);
      command.setGlobalTransaction(remoteGtx);

      List calls = dummy.getAllCalledIds();
      assertEquals(2, calls.size());

      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());

      //	    call our remote method
      CommitCommand cacheCommand = new CommitCommand(gtx);

      try
      {
         TestingUtil.replicateCommand(cache, cacheCommand);
         fail();
      }
      catch (Throwable t)
      {
         assertTrue(t instanceof RuntimeException);
         //t.printStackTrace();
      }
      //we should have the commit as well now
      assertNull(mgr.getTransaction());
      assertEquals(2, calls.size());
      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());
   }


   public void testRemoteRollbackNoPrepareTransaction() throws Throwable
   {
      MockInterceptor dummy = new MockInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      DummyTransactionManager mgr = DummyTransactionManager.getInstance();

      //start local transaction
      mgr.begin();
      Transaction tx = mgr.getTransaction();

      //this sets
      cache.getCurrentTransaction(tx, true);

      SamplePojo pojo = new SamplePojo(21, "test");

      cache.put("/one/two", "key1", pojo);

      GlobalTransaction gtx = cache.getCurrentTransaction(tx, true);
      TransactionTable table = cache.getTransactionTable();
      OptimisticTransactionEntry entry = (OptimisticTransactionEntry) table.get(gtx);
      assertNotNull(mgr.getTransaction());
      mgr.commit();


      GlobalTransaction remoteGtx = new GlobalTransaction();

      remoteGtx.setAddress(new TestAddress());
      //hack the method call to make it have the remote globalTransaction
      ReversibleCommand command = entry.getModifications().get(0);

      command.setGlobalTransaction(remoteGtx);

      List calls = dummy.getAllCalledIds();
      assertEquals(2, calls.size());

      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());

      //	    call our remote method
      RollbackCommand cacheCommand = new RollbackCommand(remoteGtx);

      TestingUtil.replicateCommand(cache, cacheCommand);
      assertTrue("Should be handled on the remote end without barfing, in the event of a rollback without a prepare", true);

      //we should have the commit as well now
      assertNull(mgr.getTransaction());
      assertEquals(2, calls.size());
      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());
   }


   public void testRemoteCommitTransaction() throws Exception
   {
      MockInterceptor dummy = new MockInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      DummyTransactionManager mgr = DummyTransactionManager.getInstance();

      //start local transaction
      mgr.begin();
      Transaction tx = mgr.getTransaction();

      //this sets
      cache.getCurrentTransaction(tx, true);

      SamplePojo pojo = new SamplePojo(21, "test");

      cache.put("/one/two", "key1", pojo);

      GlobalTransaction gtx = cache.getCurrentTransaction(tx, true);
      TransactionTable table = cache.getTransactionTable();
      OptimisticTransactionEntry entry = (OptimisticTransactionEntry) table.get(gtx);
      assertNotNull(mgr.getTransaction());
      mgr.commit();


      GlobalTransaction remoteGtx = new GlobalTransaction();

      remoteGtx.setAddress(new TestAddress());
      //hack the method call to make it have the remote globalTransaction
      ReversibleCommand command = entry.getModifications().get(0);
      command.setGlobalTransaction(remoteGtx);
      OptimisticPrepareCommand prepareCommand = new OptimisticPrepareCommand(remoteGtx, null, (Map) null, (Address) remoteGtx.getAddress(), false);
      try
      {
         TestingUtil.replicateCommand(cache, prepareCommand);
      }
      catch (Throwable t)
      {
         fail();
      }

      //our thread should be null
      assertNull(mgr.getTransaction());

      //	 there should be a registration for the remote globalTransaction
      assertNotNull(table.get(remoteGtx));
      assertNotNull(table.getLocalTransaction(remoteGtx));
      //assert that this is populated
      assertEquals(1, table.get(remoteGtx).getModifications().size());

      //assert that the remote prepare has populated the local workspace
      assertEquals(3, entry.getTransactionWorkSpace().getNodes().size());
      assertEquals(1, entry.getModifications().size());
      List calls = dummy.getAllCalledIds();
      assertEquals(OptimisticPrepareCommand.METHOD_ID, calls.get(2));


      assertEquals(1, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(1, cache.getTransactionTable().getNumLocalTransactions());

      //	    call our remote method
      CommitCommand commitCommand = new CommitCommand(remoteGtx);
      try
      {
         TestingUtil.replicateCommand(cache, commitCommand);
      }
      catch (Throwable t)
      {
         fail();
      }
      //we should have the commit as well now
      assertNull(mgr.getTransaction());
      assertEquals(CommitCommand.METHOD_ID, calls.get(3));
      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());
   }


   public void testTwoWayRemoteCacheBroadcast() throws Exception
   {
      destroyCache(cache);
      cache = createReplicatedCache(Configuration.CacheMode.REPL_SYNC);

      MockInterceptor dummy = new MockInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      CacheSPI cache2 = createReplicatedCache(Configuration.CacheMode.REPL_SYNC);
      MockInterceptor dummy2 = new MockInterceptor();
      setAlteredInterceptorChain(dummy2, cache2);


      TransactionManager mgr = cache.getTransactionManager();

      //start local transaction
      mgr.begin();
      Transaction tx = mgr.getTransaction();

      //this sets
      cache.getCurrentTransaction(tx, true);

      SamplePojo pojo = new SamplePojo(21, "test");

      cache.put("/one/two", "key1", pojo);

      assertNotNull(mgr.getTransaction());
      mgr.commit();

      assertNull(mgr.getTransaction());

      //assert that the local cache is in the right state
      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());


      assertEquals(0, cache2.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache2.getTransactionTable().getNumLocalTransactions());


      List calls = dummy.getAllCalledIds();

      assertEquals(OptimisticPrepareCommand.METHOD_ID, calls.get(0));
      assertEquals(CommitCommand.METHOD_ID, calls.get(1));

      List calls2 = dummy2.getAllCalledIds();
      assertEquals(OptimisticPrepareCommand.METHOD_ID, calls2.get(0));
      assertEquals(CommitCommand.METHOD_ID, calls2.get(1));

      destroyCache(cache2);
   }


   public void testFailurePrepareRemoteCacheBroadcast() throws Exception
   {
      destroyCache(cache);
      cache = createReplicatedCache(Configuration.CacheMode.REPL_SYNC);

      MockInterceptor dummy = new MockInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      CacheSPI cache2 = createReplicatedCache(Configuration.CacheMode.REPL_SYNC);
      MockFailureInterceptor dummy2 = new MockFailureInterceptor();
      List failures = new ArrayList();
      failures.add(OptimisticPrepareCommand.class);
      dummy2.setFailurelist(failures);
      setAlteredInterceptorChain(dummy2, cache2);

      DummyTransactionManager mgr = DummyTransactionManager.getInstance();

      //start local transaction
      mgr.begin();
      Transaction tx = mgr.getTransaction();

      //this sets
      cache.getCurrentTransaction(tx, true);

      SamplePojo pojo = new SamplePojo(21, "test");

      cache.put("/one/two", "key1", pojo);

      assertNotNull(mgr.getTransaction());
      try
      {
         mgr.commit();
      }
      catch (Exception e)
      {
         assertTrue(e instanceof RollbackException);
      }


      assertNull(mgr.getTransaction());

      //assert that the local cache is in the right state
      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());


      assertEquals(0, cache2.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache2.getTransactionTable().getNumLocalTransactions());


      List calls = dummy.getAllCalledIds();
      assertEquals(OptimisticPrepareCommand.METHOD_ID, calls.get(0));
      assertEquals(RollbackCommand.METHOD_ID, calls.get(1));

      //we have no prepare - as it failed - but we have a commit
      List calls2 = dummy2.getAllCalledIds();
      assertEquals(RollbackCommand.METHOD_ID, calls2.get(0));

      destroyCache(cache2);
   }


   public void testFailurePrepareLocalCacheBroadcast() throws Exception
   {

      destroyCache(cache);
      cache = createReplicatedCache(Configuration.CacheMode.REPL_SYNC);

      MockFailureInterceptor dummy = new MockFailureInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      CacheSPI cache2 = createReplicatedCache(Configuration.CacheMode.REPL_SYNC);
      MockInterceptor dummy2 = new MockInterceptor();
      setAlteredInterceptorChain(dummy, cache);

      List failures = new ArrayList();
      failures.add(OptimisticPrepareCommand.class);
      dummy.setFailurelist(failures);

      DummyTransactionManager mgr = DummyTransactionManager.getInstance();

      //start local transaction
      mgr.begin();
      Transaction tx = mgr.getTransaction();

      //this sets
      cache.getCurrentTransaction(tx, true);

      SamplePojo pojo = new SamplePojo(21, "test");

      cache.put("/one/two", "key1", pojo);

      assertNotNull(mgr.getTransaction());
      try
      {
         mgr.commit();
      }
      catch (Exception e)
      {
         assertTrue(e instanceof RollbackException);
      }


      assertNull(mgr.getTransaction());

      //assert that the local cache is in the right state
      assertEquals(0, cache.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache.getTransactionTable().getNumLocalTransactions());


      assertEquals(0, cache2.getTransactionTable().getNumGlobalTransactions());
      assertEquals(0, cache2.getTransactionTable().getNumLocalTransactions());


      List calls = dummy.getAllCalledIds();
      assertEquals(RollbackCommand.METHOD_ID, calls.get(0));

      //we have no prepare - as it failed - but we have a commit
      List calls2 = dummy2.getAllCalledIds();
      assertEquals(0, calls2.size());

      destroyCache(cache2);
   }

   static class TestAddress implements Address
   {
      private static final long serialVersionUID = -8525272532201600656L;

      public boolean isMulticastAddress()
      {
         return false;
      }

      public void readExternal(ObjectInput arg0)
      {
      }

      public int size()
      {
         return 0;
      }

      public void writeExternal(ObjectOutput arg0)
      {
      }

      public void writeTo(DataOutputStream arg0)
      {
      }

      public void readFrom(DataInputStream arg0)
      {
      }

      public int compareTo(Object arg0)
      {
         return 0;
      }
   }

}
