/*
 *
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.cache.replicated;

import org.jboss.cache.Cache;
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.util.internals.ReplicationListener;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import javax.transaction.TransactionManager;

/**
 * Unit test for replicated async CacheSPI. Use locking and multiple threads to test
 * concurrent access to the tree.
 *
 * @version $Revision: 5908 $
 */
@Test(groups = {"functional", "jgroups"})
public class AsyncReplTest
{
   private CacheSPI<Object, Object> cache1, cache2;
   private ReplicationListener replListener1, replListener2;

   @BeforeMethod(alwaysRun = true)
   public void setUp() throws Exception
   {
      log("creating cache1");
      cache1 = createCache("CacheGroup");
      replListener1 = new ReplicationListener(cache1);

      log("creating cache2");
      cache2 = createCache("CacheGroup");
      replListener2 = new ReplicationListener(cache2);
   }


   private CacheSPI<Object, Object> createCache(String name) throws Exception
   {
      CacheSPI<Object, Object> cache = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.REPL_ASYNC), false);
      cache.getConfiguration().setClusterName(name);

      // Call the hook that allows mux integration
      configureMultiplexer(cache);

      cache.create();
      cache.start();

      validateMultiplexer(cache);

      return cache;
   }

   /**
    * Provides a hook for multiplexer integration. This default implementation
    * is a no-op; subclasses that test mux integration would override
    * to integrate the given cache with a multiplexer.
    * <p/>
    * param cache a cache that has been configured but not yet created.
    */
   protected void configureMultiplexer(Cache cache) throws Exception
   {
      // default does nothing
   }

   /**
    * Provides a hook to check that the cache's channel came from the
    * multiplexer, or not, as expected.  This default impl asserts that
    * the channel did not come from the multiplexer.
    *
    * @param cache a cache that has already been started
    */
   protected void validateMultiplexer(Cache cache)
   {
      assertFalse("Cache is not using multiplexer", cache.getConfiguration().isUsingMultiplexer());
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown() throws Exception
   {
      TestingUtil.killCaches(cache1, cache2);
   }

   public void testTxCompletion() throws Exception
   {
      // test a very simple replication.
      Fqn fqn = Fqn.fromString("/a");
      String key = "key";

      replListener2.expectAny();
      cache1.put(fqn, key, "value1");
      // allow for replication
      replListener2.waitForReplicationToOccur(500);
      assertEquals("value1", cache1.get(fqn, key));
      assertEquals("value1", cache2.get(fqn, key));

      TransactionManager mgr = cache1.getTransactionManager();
      mgr.begin();

      replListener2.expectAny();
      cache1.put(fqn, key, "value2");
      assertEquals("value2", cache1.get(fqn, key));
      assertEquals("value1", cache2.get(fqn, key));

      mgr.commit();

      replListener2.waitForReplicationToOccur(500);

      assertEquals("value2", cache1.get(fqn, key));
      assertEquals("value2", cache2.get(fqn, key));

      mgr.begin();
      cache1.put(fqn, key, "value3");
      assertEquals("value3", cache1.get(fqn, key));
      assertEquals("value2", cache2.get(fqn, key));

      mgr.rollback();

      assertEquals("value2", cache1.get(fqn, key));
      assertEquals("value2", cache2.get(fqn, key));
   }

   public void testPutShouldNotReplicateToDifferentCluster()
   {
      CacheSPI<Object, Object> cache3 = null, cache4 = null;
      try
      {
         cache3 = createCache("DifferentGroup");
         cache4 = createCache("DifferentGroup");
         replListener2.expectAny();
         cache1.put("/a/b/c", "age", 38);
         // because we use async repl, modfication may not yet have been propagated to cache2, so
         // we have to wait a little
         replListener2.waitForReplicationToOccur(500);
         assertNull("Should not have replicated", cache3.get("/a/b/c", "age"));
      }
      catch (Exception e)
      {
         fail(e.toString());
      }
      finally
      {
         if (cache3 != null)
         {
            cache3.stop();
         }
         if (cache4 != null)
         {
            cache4.stop();
         }
      }
   }

   public void testStateTransfer()
   {
      CacheSPI<Object, Object> cache4 = null;
      try
      {
         cache1.put("a/b/c", "age", 38);
         cache4 = createCache("CacheGroup");
         System.out.println("" + cache4.getMembers());
         assertEquals(3, cache4.getMembers().size());// cache1, cache2 and cache4
         assertEquals("\"age\" should be 38", 38, cache4.get("/a/b/c", "age"));
      }
      catch (Exception e)
      {
         fail(e.toString());
      }
      finally
      {
         if (cache4 != null)
         {
            System.out.println("cache4's view: " + cache4.getMembers());
            cache4.stop();
         }
      }
   }

   public void testAsyncReplDelay()
   {
      Integer age;

      try
      {
         cache1.put("/a/b/c", "age", 38);

         // value on cache2 may be 38 or not yet replicated
         age = (Integer) cache2.get("/a/b/c", "age");
         log("attr \"age\" of \"/a/b/c\" on cache2=" + age);
         assertTrue("should be either null or 38", age == null || age == 38);
      }
      catch (Exception e)
      {
         fail(e.toString());
      }
   }

   public void testAsyncReplTxDelay()
   {
      Integer age;

      try
      {
         TransactionManager tm = cache1.getTransactionManager();
         tm.begin();
         cache1.put("/a/b/c", "age", 38);
         tm.commit();

         // value on cache2 may be 38 or not yet replicated
         age = (Integer) cache2.get("/a/b/c", "age");
         log("attr \"age\" of \"/a/b/c\" on cache2=" + age);
         assertTrue("should be either null or 38", age == null || age == 38);
      }
      catch (Exception e)
      {
         fail(e.toString());
      }
   }

   private void log(String msg)
   {
      System.out.println("-- [" + Thread.currentThread() + "]: " + msg);
   }
}
