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

import org.jboss.cache.Fqn;
import org.jboss.cache.TreeCache;
import org.jboss.cache.config.Option;
import org.jboss.cache.eviction.LRUPolicy;
import org.jboss.cache.loader.CacheLoader;
import org.jboss.cache.misc.TestingUtil;
import org.jboss.cache.xml.XmlHelper;

import java.io.File;

/**
 * Tests use of the data gravitator alongside other cache loaders as well as data gravitator options such as removeOnFind.
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
public class BuddyReplicationWithCacheLoaderTest extends BuddyReplicationTestsBase
{
    protected Fqn fqn = Fqn.fromString("/test");
    protected String key = "key";
    protected String value = "value";
    protected boolean passivation = false;

    private CacheLoader[] getLoaders(TreeCache[] caches)
    {
        CacheLoader[] retVal = new CacheLoader[caches.length];

        for (int i=0; i<retVal.length; i++)
        {
            retVal[i] = caches[i].getCacheLoader();
        }

        return retVal;
    }

    public void testWithDataGravitationDefault() throws Exception
    {
       dataGravitationDefaultTest(true);
    }

    public void testNoAutoDataGravitationDefault() throws Exception
    {
       dataGravitationDefaultTest(false);
    }
    
    private void dataGravitationDefaultTest(boolean autoGravitate) throws Exception
    {
        TreeCache[] caches = null;
        try
        {
            // create 3 caches
            caches = createCachesWithCacheLoader(3, autoGravitate, true, passivation);

            TestingUtil.sleepThread(1000);

            CacheLoader[] loaders = getLoaders(caches);

            // cleanup
            for (int i=0; i<3; i++) loaders[i].remove(Fqn.ROOT);

            for (int i=0;i<3; i++) System.out.println(i + ": " + loaders[i].get(fqn));

            // put stuff in cache0
            caches[0].put(fqn, key, value);

            // make sure there are no locks.
            assertNoLocks(caches);
            for (int i=0;i<3; i++) System.out.println(i + ": " + loaders[i].get(fqn));

            dumpCacheContents(caches);

            // request data from cache2
            if (autoGravitate)
            {
               assertEquals(value, caches[2].get(fqn, key));
            }
            else
            {
               Option opt = new Option();
               opt.setForceDataGravitation(true);
               assertEquals(value, caches[2].get(fqn, key, opt));
            }
            assertNoLocks(caches);

            for (int i=0;i<3; i++) System.out.println(i + ": " + loaders[i].get(fqn));

            dumpCacheContents(caches);


            // test that data does not exist in cache0
            assertTrue("should not exist in cache0", !caches[0].exists(fqn));

            // test that data does not exist in cache1
            assertTrue("should not exist in cache1", !caches[1].exists(fqn));

            // test that data does exist in cache2
            assertTrue("should exist in cache2", caches[2].exists(fqn));

            // test that data does not exist in loader0
            assertTrue("should not exist in loader0", !loaders[0].exists(fqn));

            // test that data does not exist in loader1
            assertTrue("should not exist in loader1", !loaders[1].exists(fqn));

            // test that data does exist in loader2
            assertTrue("should exist in loader2", passivation ? !loaders[2].exists(fqn) : loaders[2].exists(fqn));

            Fqn b1 = new Fqn( new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN, BuddyManager.getGroupNameFromAddress(caches[0].getLocalAddress())), fqn.peekElements() );
            Fqn b2 = new Fqn( new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN, BuddyManager.getGroupNameFromAddress(caches[2].getLocalAddress())), fqn.peekElements() );

            // test that bkup does exist in cache0
            assertTrue("should not exist in cache0", !caches[0].exists(b1));
            assertTrue("should exist in cache0", caches[0].exists(b2));

            // test that bkup does not exist in cache1
            assertTrue("should not exist in cache1", !caches[1].exists(b1));
            assertTrue("should not exist in cache1", !caches[1].exists(b2));

            // test that bkup does not exist in cache2
            assertTrue("should not exist in cache2", !caches[2].exists(b1));
            assertTrue("should not exist in cache2", !caches[2].exists(b2));

            // test that bkup does exist in loader0
            assertTrue("should not exist in loader0", !loaders[0].exists(b1));
            assertTrue("should exist in loader0", passivation ? !loaders[0].exists(b2) : loaders[0].exists(b2));

            // test that bkup does not exist in loader1
            assertTrue("should not exist in loaders1", !loaders[1].exists(b1));
            assertTrue("should not exist in loaders1", !loaders[1].exists(b2));

            // test that bkup does not exist in loader2
            assertTrue("should not exist in loaders2", !loaders[2].exists(b1));
            assertTrue("should not exist in loaders2", !loaders[2].exists(b2));


        }
        finally
        {
            cleanup(caches);
        }

    }

    public void testWithDataGravitationEviction() throws Exception
    {
       dataGravitationEvictionTest(true);
    }

    public void testNoAutoDataGravitationEviction() throws Exception
    {
       dataGravitationEvictionTest(false);
    }
    
    private void dataGravitationEvictionTest(boolean autoGravitate) throws Exception
    {
        TreeCache[] caches = null;
        try
        {
            // create 3 caches
            caches = createCachesWithCacheLoader(3, autoGravitate, false, passivation);
            CacheLoader[] loaders = getLoaders(caches);

            // put stuff in cache0
            caches[0].put(fqn, key, value);

            // request data from cache2
            if (autoGravitate)
            {
               assertEquals(value, caches[2].get(fqn, key));
            }
            else
            {
               Option opt = new Option();
               opt.setForceDataGravitation(true);
               assertEquals(value, caches[2].get(fqn, key, opt));
            }

            // test that data does not exist in cache0
            assertTrue("should not exist in cache0", !caches[0].exists(fqn));

            // test that data does not exist in cache1
            assertTrue("should not exist in cache1", !caches[1].exists(fqn));

            // test that data does exist in cache2
            assertTrue("should exist in cache2", caches[2].exists(fqn));

            // test that data does exist in loader0
            assertTrue("should exist in loader0", loaders[0].exists(fqn));

            // test that data does not exist in loader1
            assertTrue("should not exist in loader1", !loaders[1].exists(fqn));

            // test that data does exist in loader2
            assertTrue("should exist in loader2", passivation ? !loaders[2].exists(fqn) : loaders[2].exists(fqn));

            Fqn b1 = new Fqn( new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN, BuddyManager.getGroupNameFromAddress(caches[0].getLocalAddress())), fqn.peekElements() );
            Fqn b2 = new Fqn( new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN, BuddyManager.getGroupNameFromAddress(caches[2].getLocalAddress())), fqn.peekElements() );

            // test that bkup does exist in cache0
            assertTrue("should not exist in cache0", !caches[0].exists(b1));
            assertTrue("should exist in cache0", caches[0].exists(b2));

            // test that bkup does not exist in cache1
            assertTrue("should not exist in cache1", !caches[1].exists(b1));
            assertTrue("should not exist in cache1", !caches[1].exists(b2));

            // test that bkup does not exist in cache2
            assertTrue("should not exist in cache2", !caches[2].exists(b1));
            assertTrue("should not exist in cache2", !caches[2].exists(b2));

            // test that bkup does exist in loader0
            assertTrue("should not exist in loader0", !loaders[0].exists(b1));
            assertTrue("should exist in loader0", passivation ? !loaders[0].exists(b2) : loaders[0].exists(b2));

            // test that bkup does not exist in loader1
            assertTrue("should exist in loaders1", loaders[1].exists(b1));
            assertTrue("should not exist in loaders1", !loaders[1].exists(b2));

            // test that bkup does not exist in loader2
            assertTrue("should not exist in loaders2", !loaders[2].exists(b1));
            assertTrue("should not exist in loaders2", !loaders[2].exists(b2));
        }
        finally
        {
            cleanup(caches);
        }
    }

   /**
    * Tests whether nodes that have been evicted can successfully be
    * gravitated.
    *
    * @throws Exception
    */
   public void testLocalGravitationOfEvictedNodes() throws Exception
   {
      TreeCache[] caches = null;
      String tmpLocation = System.getProperty("java.io.tmpdir", "/tmp");
      tmpLocation = tmpLocation + File.separator + "JBossCacheBRWithCacheLoaderTest";

      try
      {
         TreeCache cache1 = createCacheWithCacheLoader(tmpLocation + File.separator + "1", true, true, passivation, true, false);
         configureEviction(cache1);
         TreeCache cache0 = createCacheWithCacheLoader(tmpLocation + File.separator + "2", true, true, passivation, true, false);
         configureEviction(cache0);

         caches = new TreeCache[2];
         caches[0] = cache0;
         caches[1] = cache1;

         cache0.start();
         cache1.start();

         TestingUtil.blockUntilViewsReceived(caches, VIEW_BLOCK_TIMEOUT * caches.length);
         TestingUtil.sleepThread(getSleepTimeout());


         Fqn foo = Fqn.fromString("/foo");         
         Fqn backupFoo = BuddyManager.getBackupFqn(cache0.getLocalAddress(), foo);
         cache0.put(foo, "key", "value");

         assertTrue("Data should exist in data owner", cache0.exists(foo));
         assertTrue("Buddy should have data", cache1.exists(backupFoo));

         // Sleep long enough for eviction to run twice plus a bit
         TestingUtil.sleepThread(3050);

         // test that the data we're looking for has been evicted in both the data owner and the buddy.
         assertFalse("Data should have evicted in data owner", cache0.exists(foo));
         assertFalse("Buddy should have data evicted", cache1.exists(backupFoo));

         // now test that this exists in both loaders.
         assertNotNull("Should exist in data owner's cache loader", cache0.getCacheLoader().get(foo));
         assertNotNull("Should exist in buddy's loader", cache1.getCacheLoader().get(backupFoo));

         // a local gravitation should occur since cache1 has foo in it's backup tree.
         assertEquals("Passivated value available from buddy", "value", cache1.get(foo, "key"));
      }
      finally
      {
         cleanup(caches);
         TestingUtil.recursiveRemove(new File(tmpLocation));
      }
   }

   /**
    * Tests whether nodes that have been evicted can successfully be
    * gravitated.
    *
    * @throws Exception
    */
   public void testRemoteGravitationOfEvictedNodes() throws Exception
   {
      TreeCache[] caches = null;
      String tmpLocation = System.getProperty("java.io.tmpdir", "/tmp");
      tmpLocation = tmpLocation + File.separator + "JBossCacheBRWithCacheLoaderTest";

      try
      {
         TreeCache cache0 = createCacheWithCacheLoader(tmpLocation + File.separator + "0", true, true, passivation, true, false);
         configureEviction(cache0);
         TreeCache cache1 = createCacheWithCacheLoader(tmpLocation + File.separator + "1", true, true, passivation, true, false);
         configureEviction(cache1);
         TreeCache cache2 = createCacheWithCacheLoader(tmpLocation + File.separator + "2", true, true, passivation, true, false);
         configureEviction(cache2);

         caches = new TreeCache[3];
         caches[0] = cache0;
         caches[1] = cache1;
         caches[2] = cache2;

         cache0.start();
         cache1.start();
         cache2.start();

         TestingUtil.blockUntilViewsReceived(caches, 600000);
         TestingUtil.sleepThread(getSleepTimeout());


         assertTrue("Cache1 should be cache0's buddy!", cache0.getBuddyManager().getBuddyAddresses().contains(cache1.getLocalAddress()));

         Fqn foo = Fqn.fromString("/foo");
         Fqn backupFoo = BuddyManager.getBackupFqn(cache0.getLocalAddress(), foo);
         cache0.put(foo, "key", "value");

         // test that the data exists in both the data owner and the buddy
         assertTrue("Data should exist in data owner", cache0.exists(foo));
         assertTrue("Buddy should have data", cache1.exists(backupFoo));

         // Sleep long enough for eviction to run twice plus a bit
         TestingUtil.sleepThread(3050);

         // test that the data we're looking for has been evicted in both the data owner and the buddy.
         assertFalse("Data should have evicted in data owner", cache0.exists(foo));
         assertFalse("Buddy should have data evicted", cache1.exists(backupFoo));

         // now test that this exists in both loaders.
         assertNotNull("Should exist in data owner's loader", cache0.getCacheLoader().get(foo));
         assertNotNull("Should exist in buddy's loader", cache1.getCacheLoader().get(backupFoo));

         // doing a get on cache2 will guarantee a remote data gravitation.
         assertEquals("Passivated value available from buddy", "value", cache2.get(foo, "key"));
      }
      finally
      {
         cleanup(caches);
         TestingUtil.recursiveRemove(new File(tmpLocation));
      }
   }

   private void configureEviction(TreeCache cache) throws Exception
   {
      String evictionConfig = "<config>\n" +
                              "              <attribute name=\"wakeUpIntervalSeconds\">1</attribute>\n" +
                              "              <region name=\"/_default_\">\n" +
                              "                  <attribute name=\"maxNodes\">1</attribute>\n" +
                              "                  <attribute name=\"timeToLiveSeconds\">1</attribute>\n" +
                              "                  <attribute name=\"maxAgeSeconds\">2</attribute>\n" +
                              "              </region>\n" +
                              "           </config>";

      cache.setEvictionPolicyClass(LRUPolicy.class.getName());
      cache.setEvictionPolicyConfig(XmlHelper.stringToElement(evictionConfig));
   }

}
