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

package org.jboss.cache.marshall;


import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.jboss.cache.Fqn;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.TreeCache;
import org.jboss.cache.misc.TestingUtil;
import org.jboss.cache.transaction.DummyTransactionManager;
import org.apache.commons.logging.LogFactory;

import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import java.lang.reflect.Method;

/**
 * Test marshalling for async mode.
 * @author Ben Wang
 * @version $Revision: 3088 $
 */
public class AsyncReplTest extends TestCase {
   TreeCache cache1, cache2;
   String props=null;
   Person ben_;
   Address addr_;
   Throwable ex_;

   public AsyncReplTest(String name) {
      super(name);
   }

   public void setUp() throws Exception {
      super.setUp();

      log("creating cache1");
      cache1=createCache("TestCache");

      log("creating cache2");

      cache2=createCache("TestCache");
      
      addr_ = new Address();
      addr_.setCity("San Jose");
      ben_ = new Person();
      ben_.setName("Ben");
      ben_.setAddress(addr_);
      
      // Pause to give caches time to see each other
      TestingUtil.blockUntilViewsReceived(new TreeCache[] { cache1, cache2 }, 60000);
   }

   private TreeCache createCache(String name) throws Exception {
      TreeCache tree=new TreeCache();
      PropertyConfigurator config=new PropertyConfigurator();
      config.configure(tree, "META-INF/replAsync-service.xml"); // read in generic replAsync xml
      tree.setClusterName(name);
      // Use marshaller
      tree.setUseRegionBasedMarshalling(true);
      tree.createService();
      tree.startService();
      return tree;
   }

   public void tearDown() throws Exception {
      super.tearDown();
      cache1.remove("/");
      if(cache1 != null) {
         log("stopping cache1");
         cache1.stopService();
      }

      if(cache2 != null) {
         log("stopping cache2");
         cache2.stopService();
      }
   }

   /**
    * Test replication with classloaders.
    * @throws Exception
    */
   public void testCLSet2() throws Exception
   {
      ClassLoader cla = getClassLoader();
      cache1.registerClassLoader("/aop", cla);
      ClassLoader clb = getClassLoader();
      cache2.registerClassLoader("/aop", clb);

      cache1.put("/aop", "person", ben_);
      cache1.put("alias", "person", ben_);

      TestingUtil.sleepThread(1000);
      Object ben2 = null;
      try
      {
         // Can't cast it to Person. CCE will resutl.
         ben2 = cache2.get("/aop", "person");
         assertNotNull(ben2); 
         assertEquals(ben_.toString(), ben2.toString());
      } catch (Exception ex)
      {
         LogFactory.getLog("TEST").debug("Ex:", ex);
         fail("Test fails with exception " +ex);
      }

      Class claz = clb.loadClass("org.jboss.cache.marshall.Address");
      Object add = claz.newInstance();
      {
      Class[] types = {String.class};
      Method setValue = claz.getMethod("setCity", types);
      Object[] margs = {"Sunnyvale"};
      setValue.invoke(add, margs);
      }

      {
      Class clasz1 = clb.loadClass("org.jboss.cache.marshall.Person");
      Class[] types = {claz};
      Method setValue = clasz1.getMethod("setAddress", types);
      Object[] margs = {add};
      setValue.invoke(ben2, margs);
      }

      // Set it back to the cache
      try
      {
         // Can't cast it to Person. CCE will resutl.
         cache2.put("/aop", "person", ben2);
         TestingUtil.sleepThread(1000);
         Object ben3 = cache1.get("/aop", "person");
         assertEquals(ben2.toString(), ben3.toString());
      } catch (Exception ex)
      {
         ex.printStackTrace();
         fail("Test fails with exception " +ex);
      }

   }

   public void testPuts() throws Exception
   {
      ClassLoader cl = getClassLoader();
      cache1.registerClassLoader("/aop", cl);   
      // Create an empty Person loaded by this classloader
      Object scopedBen1 = getPersonFromClassloader(cl);
      
      cl = getClassLoader();
      cache2.registerClassLoader("/aop", cl);         
      // Create another empty Person loaded by this classloader
      Object scopedBen2 = getPersonFromClassloader(cl);

      cache1.put("/aop/1", "person", ben_);
      cache1.put("/aop/2", "person", scopedBen1);
      TestingUtil.sleepThread(1000);

      Object ben2 = null;
      try
      {
         // Can't cast it to Person. CCE will resutl.
         ben2 = cache2.get("/aop/1", "person");
         assertEquals(ben_.toString(), ben2.toString());
         
         ben2 = cache2.get("/aop/2", "person");
         assertFalse("cache2 deserialized with scoped classloader", ben2 instanceof Person);
         assertFalse("cache2 deserialized with cache2 classloader", scopedBen1.equals(ben2));
         assertEquals("scopedBen deserialized properly", scopedBen2, ben2);
      } catch (Exception ex)
      {
         fail("Test fails with exception " +ex);
      }

   }

   public void testTxPut() throws Exception
   {
      Transaction tx = beginTransaction();
      cache1.put("/aop", "person", ben_);
      cache1.put("/aop", "person1", ben_);
      tx.commit();
      TestingUtil.sleepThread(1000);
      Person ben2 = (Person)cache2.get("/aop", "person");
      assertNotNull("Person from 2nd cache should not be null ", ben2);
      assertEquals(ben_.toString(), ben2.toString());
   }

   public void testTxCLSet2() throws Exception
   {
      ClassLoader cla = getClassLoader();
      cache1.registerClassLoader("/aop", cla);
      ClassLoader clb = getClassLoader();
      cache2.registerClassLoader("/aop", clb);

      Transaction tx = beginTransaction();
      cache1.put("/aop", "person", ben_);
      tx.commit();
      TestingUtil.sleepThread(1000);

      Object ben2 = null;
      try
      {
         // Can't cast it to Person. CCE will resutl.
         ben2 = cache2.get("/aop", "person");
         assertEquals(ben_.toString(), ben2.toString());
      } catch (Exception ex)
      {
         fail("Test fails with exception " +ex);
      }

      Class claz = clb.loadClass("org.jboss.cache.marshall.Address");
      Object add = claz.newInstance();
      {
      Class[] types = {String.class};
      Method setValue = claz.getMethod("setCity", types);
      Object[] margs = {"Sunnyvale"};
      setValue.invoke(add, margs);
      }

      {
      Class clasz1 = clb.loadClass("org.jboss.cache.marshall.Person");
      Class[] types = {claz};
      Method setValue = clasz1.getMethod("setAddress", types);
      Object[] margs = {add};
      setValue.invoke(ben2, margs);
      }

      // Set it back to the cache
      try
      {
         // Can't cast it to Person. CCE will resutl.
         cache2.put("/aop", "person", ben2);
         TestingUtil.sleepThread(1000);
         Object ben3 = cache1.get("/aop", "person");
         assertEquals(ben2.toString(), ben3.toString());
      } catch (Exception ex)
      {
         fail("Test fails with exception " +ex);
      }

   }

   public void testStateTransfer() throws Exception
   {
      // Need to test out if app is not registered with beforehand??
   }
   
   public void testCustomFqn() throws Exception
   {
      FooClassLoader cl1 = new FooClassLoader(Thread.currentThread().getContextClassLoader());
      cache1.registerClassLoader("/aop", cl1);
      FooClassLoader cl2 = new FooClassLoader(Thread.currentThread().getContextClassLoader());
      cache2.registerClassLoader("/aop", cl2); 
      
      Class clazz = cl1.loadFoo();
      Object custom1 = clazz.newInstance();

      clazz = cl2.loadFoo();
      Object custom2 = clazz.newInstance();
      
      Fqn base = Fqn.fromString("/aop");
      cache1.put(new Fqn(base, custom1), "key", "value");
      TestingUtil.sleepThread(1000);

      try
      {
         Object val = cache2.get(new Fqn(base, custom2), "key");
         assertEquals("value", val);
      } catch (Exception ex)
      {
         fail("Test fails with exception " +ex);
      }      
   }

   Transaction beginTransaction() throws SystemException, NotSupportedException {
      DummyTransactionManager mgr=DummyTransactionManager.getInstance();
      mgr.begin();
      Transaction tx=mgr.getTransaction();
      return tx;
   }

   protected ClassLoader getClassLoader() throws Exception {
      String[] includesClasses = { "org.jboss.cache.marshall.Person",
                                   "org.jboss.cache.marshall.Address" };
      String [] excludesClasses = {};
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      return new SelectedClassnameClassLoader(includesClasses, excludesClasses, cl);
   }
   
   protected Object getPersonFromClassloader(ClassLoader cl) throws Exception
   {
      Class clazz = cl.loadClass("org.jboss.cache.marshall.Person");
      return clazz.newInstance();
   }

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


   public static Test suite() {
      return new TestSuite(AsyncReplTest.class);
   }

   public static void main(String[] args) {
      junit.textui.TestRunner.run(suite());
   }

}
