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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.CacheException;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.notifications.annotation.CacheListener;
import org.jboss.cache.notifications.annotation.NodeModified;
import org.jboss.cache.notifications.annotation.NodeRemoved;
import org.jboss.cache.notifications.event.NodeEvent;
import org.jboss.cache.transaction.DummyTransactionManager;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

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

/**
 * Test out the TreeCacheListener
 *
 * @version $Revision: 5546 $
 */
@Test(groups = {"functional"})
public class SyncCacheListenerTest
{
   private CacheSPI<Object, Object> cache1, cache2;
   private final static Log log_ = LogFactory.getLog(SyncCacheListenerTest.class);
   private String old_factory = null;
   private final static String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";

   @BeforeMethod(alwaysRun = true)
   public void setUp() throws Exception
   {
      System.out.println("*** starting setUp()");
      old_factory = System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
      System.setProperty(Context.INITIAL_CONTEXT_FACTORY, FACTORY);

      initCaches();
      System.out.println("*** finished setUp()");
   }

   @AfterMethod(alwaysRun = true)
   public void tearDown() throws Exception
   {
      System.out.println("*** starting tearDown()");
      DummyTransactionManager.destroy();
      destroyCaches();
      if (old_factory != null)
      {
         System.setProperty(Context.INITIAL_CONTEXT_FACTORY, old_factory);
         old_factory = null;
      }
      System.out.println("*** finished tearDown()");
   }

   private void initCaches()
   {
      cache1 = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(false);
      cache2 = (CacheSPI<Object, Object>) new DefaultCacheFactory().createCache(false);
      cache1.getConfiguration().setCacheMode(Configuration.CacheMode.REPL_SYNC);
      cache2.getConfiguration().setCacheMode(Configuration.CacheMode.REPL_SYNC);
      cache1.getConfiguration().setIsolationLevel(IsolationLevel.SERIALIZABLE);
      cache2.getConfiguration().setIsolationLevel(IsolationLevel.SERIALIZABLE);

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

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

   public void testSyncTxRepl() throws Exception
   {
      Integer age;
      TransactionManager tm = cache1.getTransactionManager();

      tm.begin();
      Transaction tx = tm.getTransaction();
      Listener lis = new Listener();
      cache1.getNotifier().addCacheListener(lis);
      lis.put("/a/b/c", "age", 38);

      tm.suspend();
      assertNull("age on cache2 must be null as the TX has not yet been committed", cache2.get("/a/b/c", "age"));
      tm.resume(tx);
      tm.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 == 38);
   }

   public void testRemoteCacheListener() throws Exception
   {
      Integer age;
      RemoteListener lis = new RemoteListener();
      cache2.getNotifier().addCacheListener(lis);
      cache1.put("/a/b/c", "age", 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 == 38);
      cache1.remove("/a/b/c", "age");
   }

   public void testSyncRepl() throws Exception
   {
      Integer age;
      Listener lis = new Listener();
      cache1.addCacheListener(lis);
      lis.put("/a/b/c", "age", 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 == 38);
   }

   public void testSyncTxReplMap() throws Exception
   {
      Integer age;
      TransactionManager tm = cache1.getConfiguration().getRuntimeConfig().getTransactionManager();
      tm.begin();
      Transaction tx = tm.getTransaction();
      Listener lis = new Listener();
      cache1.getNotifier().addCacheListener(lis);
      Map<String, Comparable> map = new HashMap<String, Comparable>();
      map.put("age", 38);
      map.put("name", "Ben");
      lis.put("/a/b/c", map);
      tm.suspend();
      assertNull("age on cache2 must be null as the TX has not yet been committed", cache2.get("/a/b/c", "age"));
      tm.resume(tx);
      tm.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 == 38);
   }

   public void testSyncReplMap() throws Exception
   {
      Integer age;

      Listener lis = new Listener();
      cache1.getNotifier().addCacheListener(lis);
      Map<String, Comparable> map = new HashMap<String, Comparable>();
      map.put("age", 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 == 38);
   }

   @CacheListener
   public class Listener
   {
      Object key_ = null;

      public void put(String fqn, Object key, Object val)
      {
         key_ = key;
         cache1.put(fqn, key, val);
      }

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

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

   }

   @CacheListener
   public class RemoteListener
   {

      @NodeRemoved
      @NodeModified
      public void callback(NodeEvent e)
      {
         System.out.println("Callback got event " + e);
         log_.debug("Callback got event " + e);
         assertFalse("node was removed on remote cache so isLocal should be false", e.isOriginLocal());
      }
   }

}
