/*
 *
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.replicated;

import EDU.oswego.cs.dl.util.concurrent.FIFOSemaphore;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.AbstractTreeCacheListener;
import org.jboss.cache.CacheException;
import org.jboss.cache.Fqn;
import org.jboss.cache.TreeCache;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.transaction.DummyTransactionManager;
import org.jgroups.View;

import javax.naming.Context;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Test out the TreeCacheListener
 *
 * @version $Revision: 1.6 $
 */
public class SyncCacheListenerTest extends TestCase {
   TreeCache cache1, cache2;
   int caching_mode=TreeCache.REPL_SYNC;
   final String group_name="TreeCacheTestGroup";
   String props=
         "UDP(ip_mcast=true;ip_ttl=64;loopback=false;mcast_addr=228.1.2.3;" +
         "mcast_port=45566;mcast_recv_buf_size=80000;mcast_send_buf_size=150000;" +
         "ucast_recv_buf_size=80000;ucast_send_buf_size=150000):" +
         "PING(down_thread=true;num_initial_members=2;timeout=500;up_thread=true):" +
         "MERGE2(max_interval=20000;min_interval=10000):" +
         "FD(down_thread=true;shun=true;up_thread=true):" +
         "VERIFY_SUSPECT(down_thread=true;timeout=1500;up_thread=true):" +
         "pbcast.NAKACK(down_thread=true;gc_lag=50;retransmit_timeout=600,1200,2400,4800;" +
         "up_thread=true):" +
         "pbcast.STABLE(desired_avg_gossip=20000;down_thread=true;up_thread=true):" +
         "UNICAST(down_thread=true;min_threshold=10;timeout=600,1200,2400;window_size=100):" +
         "FRAG(down_thread=true;frag_size=8192;up_thread=true):" +
         "pbcast.GMS(join_retry_timeout=2000;join_timeout=5000;print_local_addr=true;shun=true):" +
         "pbcast.STATE_TRANSFER(down_thread=true;up_thread=true)";

   final static Log log_=LogFactory.getLog(SyncCacheListenerTest.class);
   String old_factory=null;
   final String FACTORY="org.jboss.cache.transaction.DummyContextFactory";
   FIFOSemaphore lock=new FIFOSemaphore(1);
   DummyTransactionManager tx_mgr;
   Throwable t1_ex, t2_ex, ex=null;



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

   public void setUp() throws Exception {
      super.setUp();
      old_factory=System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
      System.setProperty(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
      tx_mgr=DummyTransactionManager.getInstance();
      t1_ex=t2_ex=ex=null;
   }

   public void tearDown() throws Exception {
      super.tearDown();
      DummyTransactionManager.destroy();
      destroyCaches();
      if(old_factory != null) {
         System.setProperty(Context.INITIAL_CONTEXT_FACTORY, old_factory);
         old_factory=null;
      }
   }

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

   void initCaches(int caching_mode) throws Exception {
      this.caching_mode=caching_mode;
      cache1=new TreeCache();
      cache2=new TreeCache();
      cache1.setCacheMode(caching_mode);
      cache2.setCacheMode(caching_mode);
      cache1.setIsolationLevel(IsolationLevel.SERIALIZABLE);
      cache2.setIsolationLevel(IsolationLevel.SERIALIZABLE);

      cache1.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
      cache2.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
      /*
      cache1.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
      cache2.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
*/
      cache1.setLockAcquisitionTimeout(5000);
      cache2.setLockAcquisitionTimeout(5000);
      cache1.start();
      cache2.start();
   }

   void destroyCaches() throws Exception {
      if(cache1 != null)
         cache1.stop();
      if(cache2 != null)
         cache2.stop();
      cache1=null;
      cache2=null;
   }

   public void testSyncTxRepl() throws Exception {
      Integer age;
      Transaction tx;

      try {
         initCaches(TreeCache.REPL_SYNC);

         tx=beginTransaction();
         Listener lis = new Listener();
         cache1.addTreeCacheListener(lis);
         lis.put("/a/b/c", "age", new Integer(38));
         assertNull("age on cache2 must be null as the TX has not yet been committed", cache2.get("/a/b/c", "age"));
         tx.commit();

         // value on cache2 must be 38
         age=(Integer)cache2.get("/a/b/c", "age");
         assertNotNull("\"age\" obtained from cache2 must be non-null ", age);
         assertTrue("\"age\" must be 38", age.intValue() == 38);
      }
      catch(Exception e) {
         fail(e.toString());
      }
   }

   public void testRemoteCacheListener() throws Exception {
      Integer age;
      try {
         initCaches(TreeCache.REPL_SYNC);
         RemoteListener lis = new RemoteListener();
         cache2.addTreeCacheListener(lis);
         cache1.put("/a/b/c", "age", new Integer(38));

         // value on cache2 must be 38
         age=(Integer)cache2.get("/a/b/c", "age");
         assertNotNull("\"age\" obtained from cache2 must be non-null ", age);
         assertTrue("\"age\" must be 38", age.intValue() == 38);
	 cache1.remove("/a/b/c", "age");
      }
      catch(Exception e) {
         fail(e.toString());
      }
   }


   public void testSyncRepl() throws Exception {
      Integer age;
      try {
         initCaches(TreeCache.REPL_SYNC);

         Listener lis = new Listener();
         cache1.addTreeCacheListener(lis);
         lis.put("/a/b/c", "age", new Integer(38));

         // value on cache2 must be 38
         age=(Integer)cache2.get("/a/b/c", "age");
         assertNotNull("\"age\" obtained from cache2 must be non-null ", age);
         assertTrue("\"age\" must be 38", age.intValue() == 38);
      }
      catch(Exception e) {
         fail(e.toString());
      }
   }

   public void testSyncTxReplMap() throws Exception {
      Integer age;
      Transaction tx;

      try {
         initCaches(TreeCache.REPL_SYNC);

         tx=beginTransaction();
         Listener lis = new Listener();
         cache1.addTreeCacheListener(lis);
         Map map = new HashMap();
         map.put("age", new Integer(38));
         map.put("name", "Ben");
         lis.put("/a/b/c", map);
         assertNull("age on cache2 must be null as the TX has not yet been committed", cache2.get("/a/b/c", "age"));
         tx.commit();

         // value on cache2 must be 38
         age=(Integer)cache2.get("/a/b/c", "age");
         assertNotNull("\"age\" obtained from cache2 must be non-null ", age);
         assertTrue("\"age\" must be 38", age.intValue() == 38);
      }
      catch(Exception e) {
         fail(e.toString());
      }
   }


   public void testSyncReplMap() throws Exception {
      Integer age;
      try {
         initCaches(TreeCache.REPL_SYNC);

         Listener lis = new Listener();
         cache1.addTreeCacheListener(lis);
         Map map = new HashMap();
         map.put("age", new Integer(38));
         map.put("name", "Ben");
         lis.put("/a/b/c", map);

         // value on cache2 must be 38
         age=(Integer)cache2.get("/a/b/c", "age");
         assertNotNull("\"age\" obtained from cache2 must be non-null ", age);
         assertTrue("\"age\" must be 38", age.intValue() == 38);
      }
      catch(Exception e) {
         fail(e.toString());
      }
   }


   class Listener extends AbstractTreeCacheListener
   {
      Object key_ = null;
      public void put(String fqn, Object key, Object val) throws Exception
      {
         key_ = key;
         cache1.put(fqn, key, val);
      }

      public void put(String fqn, Map map) throws Exception
      {
         if(map.size() == 0) fail("put(): map size can't be 0");
         Set set = map.keySet();
         key_ = set.iterator().next(); // take anyone
         cache1.put(fqn, map);
      }


      public void nodeCreated(Fqn fqn) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void nodeRemoved(Fqn fqn) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void nodeLoaded(Fqn fqn) {
         //To change body of implemented methods use File | Settings | File Templates.
      }
      
      public void nodeEvicted(Fqn fqn) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void nodeModified(Fqn fqn)
      {
         log_.debug("nodeModified visited with fqn: " +fqn);
         try {
            // test out if we can get the read lock since there is a write lock going as well.
            cache1.get(fqn, key_);
         } catch (CacheException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            fail("nodeModified: test failed with exception: " +e);
         }
      }

      public void nodeVisited(Fqn fqn) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void cacheStarted(TreeCache cache) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void cacheStopped(TreeCache cache) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void viewChange(View new_view)  // might be MergeView after merging
      {
         //To change body of implemented methods use File | Settings | File Templates.
      }
   }

   class RemoteListener extends AbstractTreeCacheListener
   {

      public void nodeCreated(Fqn fqn) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void nodeRemoved(Fqn fqn) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void nodeLoaded(Fqn fqn) {
         //To change body of implemented methods use File | Settings | File Templates.
      }
      
      public void nodeEvicted(Fqn fqn) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void nodeRemove(Fqn fqn, boolean isPre, boolean isLocal)
      {
System.out.println("nodeRemove visited with fqn: " +fqn + ", isPre: "+ isPre + ", isLocal "+isLocal);
         log_.debug("nodeRemove visited with fqn: " +fqn + ", isPre: "+ isPre + ", isLocal "+isLocal);
	 assertFalse("node was removed on remote cache so isLocal should be false", isLocal);
      }

      public void nodeModify(Fqn fqn, boolean isPre, boolean isLocal)
      {
System.out.println("nodeModify visited with fqn: " +fqn + ", isPre: "+ isPre + ", isLocal "+isLocal);
         log_.debug("nodeModify visited with fqn: " +fqn + ", isPre: "+ isPre + ", isLocal "+isLocal);
	 assertFalse("node was modified on remote cache so isLocal should be false", isLocal);
      }

      public void nodeVisited(Fqn fqn) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void cacheStarted(TreeCache cache) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void cacheStopped(TreeCache cache) {
         //To change body of implemented methods use File | Settings | File Templates.
      }

      public void viewChange(View new_view)  // might be MergeView after merging
      {
         //To change body of implemented methods use File | Settings | File Templates.
      }
   }

    public static Test suite() throws Exception {
//        return getDeploySetup(SyncTxUnitTestCase.class, "cachetest.jar");
       return new TestSuite(SyncCacheListenerTest.class);
    }


}
