/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.cache.pojo.memory;

import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.fail;

import java.lang.ref.WeakReference;
import java.util.ArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.pojo.PojoCache;
import org.jboss.cache.pojo.PojoCacheFactory;
import org.jboss.cache.pojo.TestingUtil;
import org.jboss.cache.pojo.test.Address;
import org.jboss.cache.pojo.test.Person;
import org.jboss.cache.pojo.test.SerializedAddress;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * @author Ben Wang
 */

@Test(groups = {"functional"})
public class ReplicatedTest
{
   Log log_ = LogFactory.getLog(ReplicatedTest.class);
   PojoCache cache_;
   PojoCache cache1_;


   @BeforeMethod(alwaysRun = true)
   protected void setUp() throws Exception
   {
      boolean toStart = false;
      cache_ = PojoCacheFactory.createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), toStart);
      cache_.start();
      cache1_ = PojoCacheFactory.createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), toStart);
      cache1_.start();
   }

   @AfterMethod(alwaysRun = true)
   protected void tearDown() throws Exception
   {
      cache_.stop();
      cache1_.stop();
   }

//   public void testDummy() {}

   /**
    * Test replication with classloaders.
    *
    * @throws Exception
    */
   public void testCLLeakageBasic() throws Exception
   {
      SerializedAddress add = new SerializedAddress();
      add.setCity("Taipei");

      ClassLoader cla = getClassLoader();
      WeakReference<ClassLoader> refa = new WeakReference<ClassLoader>(cla);
      cache_.getCache().getRegion(new Fqn<String>("/aop"), true).registerContextClassLoader(cla);
      ClassLoader clb = getClassLoader();
      WeakReference<ClassLoader> refb = new WeakReference<ClassLoader>(clb);
      cache_.getCache().getRegion(new Fqn<String>("/aop"), true).registerContextClassLoader(clb);

      Fqn<String> fqn = new Fqn<String>("/aop");
      cache_.getCache().put(new Fqn<String>("/aop"), "add", add);

      TestingUtil.sleepThread(100);
      try
      {
         Object ben = cache1_.getCache().get(fqn, "add");
         assertEquals(add.toString(), ben.toString());
         ben = null;
      }
      catch (Exception ex)
      {
         fail("Test fails with exception " + ex);
      }

      cache_.getCache().remove(fqn, "add");

      ClassLoader clc = getClassLoader();
      cla = null;
      clb = null;
      cache_.getCache().getRegion(new Fqn<String>("/aop"), true).registerContextClassLoader(clc);
      cache1_.getCache().getRegion(new Fqn<String>("/aop"), true).registerContextClassLoader(clc);
      System.gc(); // force gc
      Thread.sleep(1000);
      assertNull("Classloader should be gced ", refa.get());
      assertNull("Classloader should be gced ", refb.get());
   }

   private static void forceOutOfMemoryError() throws Exception
   {
      ArrayList<String> list = new ArrayList<String>();
      try
      {

         long i = 0;
         while (true)
         {
            list.add("BigBigBigBigBigBigBigBigBigBigBigBigBigBigBigBigBigBigBigBigBigBigBigBig" + (i++));
         }
      }
      catch (Throwable ignored)
      {
      }
      list.clear();
      list = null;
      System.gc();
      Thread.sleep(1000);
   }

   /**
    * Test replication with classloaders.
    *
    * @throws Exception
    */
   public void testCLLeakage() throws Exception
   {
      Person p = new Person();
      p.setName("Ben");
      Address add = new Address();
      add.setCity("Taipei");

      ClassLoader cla = getClassLoader();
      WeakReference<ClassLoader> refa = new WeakReference<ClassLoader>(cla);
      cache_.getCache().getRegion(new Fqn<String>("/aop"), true).registerContextClassLoader(cla);
      ClassLoader clb = getClassLoader();
      cache1_.getCache().getRegion(new Fqn<String>("/aop"), true).registerContextClassLoader(clb);
      WeakReference<ClassLoader> refb = new WeakReference<ClassLoader>(clb);

      cache_.attach("/aop", p);

      TestingUtil.sleepThread(100);
      try
      {
         Object ben = cache1_.find("/aop");
         assertEquals(p.toString(), ben.toString());
         ben = null;
      }
      catch (Exception ex)
      {
         fail("Test fails with exception " + ex);
      }

      cache_.detach("/aop");
      ClassLoader clc = getClassLoader();
      cache_.getCache().getRegion(new Fqn<String>("/aop"), true).registerContextClassLoader(clc);
      cache1_.getCache().getRegion(new Fqn<String>("/aop"), true).registerContextClassLoader(clc);
      cla = null;
      clb = null;
      forceOutOfMemoryError();

      assertNull("Classloader should be gced ", refa.get());
      assertNull("Classloader should be gced ", refb.get());
   }

   protected ClassLoader getClassLoader() throws Exception
   {
      String[] includesClasses = {"org.jboss.cache.aop.test.Person",
                                  "org.jboss.cache.aop.test.Address"};
      String[] excludesClasses = {};
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      return new SelectedClassnameClassLoader(includesClasses, excludesClasses, cl);
   }




}
