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

package org.jboss.cache.pojo;

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

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.pojo.test.Address;
import org.jboss.cache.pojo.test.Person;
import org.jboss.cache.transaction.DummyTransactionManager;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

/**
 * PojoCache replicated test cases with tx.
 *
 * @author Ben Wang
 */

@Test(groups = {"functional"})
public class NewReplicatedTxTest 
{
   Log log = LogFactory.getLog(NewReplicatedTxTest.class);
   PojoCache cache_;
   PojoCache cache1_;
   final String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";
   DummyTransactionManager tx_mgr;
   Throwable t1_ex, t2_ex;
   long start = 0;



   @BeforeMethod(alwaysRun = true)
   protected void setUp() throws Exception
   {
      log.info("setUp() ....");
      Properties prop = new Properties();
      prop.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.cache.transaction.DummyContextFactory");
      boolean toStart = false;
      cache_ = PojoCacheFactory.createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), toStart);
      cache1_ = PojoCacheFactory.createCache(UnitTestCacheConfigurationFactory.createConfiguration(CacheMode.REPL_SYNC), toStart);
      cache_.start();
      cache1_.start();

      tx_mgr = DummyTransactionManager.getInstance();
      t1_ex = t2_ex = null;
   }

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

   UserTransaction getTransaction() throws SystemException, NotSupportedException, NamingException
   {
      Properties prop = new Properties();
      prop.put(Context.INITIAL_CONTEXT_FACTORY,
              "org.jboss.cache.transaction.DummyContextFactory");
      return (UserTransaction) new InitialContext(prop).lookup("UserTransaction");
   }

   public void testSimpleTxWithRollback() throws Exception
   {
      log.info("testSimpleTxWithRollback() ....");
      UserTransaction tx = getTransaction();

      Person joe = new Person();
      joe.setName("Joe");
      Address add = new Address();
      add.setZip(104);
      add.setCity("Taipei");
      joe.setAddress(add);

      tx.begin();
      cache_.attach("/person/joe", joe);
      tx.commit();
      Person p = (Person) cache1_.find("/person/joe");
      assertEquals("Zip should be the same ", joe.getAddress().getZip(), p.getAddress().getZip());

      // test rollback
      Person ben = new Person();
      ben.setName("Ben");
      add = new Address();
      add.setZip(104);
      add.setCity("Taipei");
      joe.setAddress(add);
      tx.begin();
      cache_.attach("/person/ben", ben);
      tx.rollback();
      assertEquals("Zip should be the same ", joe.getAddress().getZip(), p.getAddress().getZip());
   }

   public void testCollectionTxWithRollback() throws Exception
   {
      log.info("testCollectionTxWithRollback() ....");
      UserTransaction tx = getTransaction();

      Person joe = new Person();
      joe.setName("Joe");
      Address add = new Address();
      add.setZip(104);
      add.setCity("Taipei");
      joe.setAddress(add);
      ArrayList<String> lang = new ArrayList<String>();
      lang.add("English");
      lang.add("Taiwanese");
      lang.add("Mandirin");
      joe.setLanguages(lang);

      tx.begin();
      cache_.attach("/person/joe", joe);
      tx.commit();
      Person p = (Person) cache1_.find("/person/joe");
      assertEquals("Zip should be the same ", joe.getAddress().getZip(), p.getAddress().getZip());

      // test rollback
      Person ben = new Person();
      ben.setName("Ben");
      add = new Address();
      add.setZip(104);
      add.setCity("Taipei");
      ben.setAddress(add);
      tx.begin();
      cache_.attach("/person/ben", ben);
      tx.rollback();
      assertEquals("Zip should be the same ", joe.getAddress().getZip(), p.getAddress().getZip());
      assertEquals("Langue 1 should be: ", "English", joe.getLanguages().get(0));
   }


   public void testConcurrentPutsWithRollback() throws Exception
   {
      Thread t1 = new Thread()
      {
         public void run()
         {
            Person p = new Person();
            p.setName("Ben");
            Address add = new Address();
            add.setCity("Taipei");
            p.setAddress(add);
            List<String> lang = new ArrayList<String>();
            lang.add("English");
            lang.add("Taiwanese");
            lang.add("Japanese");
            p.setLanguages(lang);
            try
            {
               cache_.attach("/test/ben", p);
            }
            catch (Exception ex)
            {
               try
               {
                  throw new RuntimeException("PojoCache is not rolling back properly for Collection yet.");
                  //                  cache_.attach("/test/ben", p);
               }
               catch (Exception ex1)
               {
                  t1_ex = ex1;
               }
            }
         }
      };

      Thread t2 = new Thread()
      {
         public void run()
         {
            try
            {
               UserTransaction tx = getTransaction();
               tx.begin();
               Person p = new Person();
               p.setName("Ben");
               Address add = new Address();
               add.setCity("Taipei");
               p.setAddress(add);
               cache1_.attach("/test/ben", p);
               TestingUtil.sleepThread(1000);
               tx.commit();
            }
            catch (RollbackException rollback)
            {
            }
            catch (Exception ex)
            {
               t2_ex = ex;
            }
         }
      };

      t1.start();
      t2.start();

      t1.join();
      t2.join();

      // t2 should rollback due to timeout while t2 should succeed
      if (t1_ex != null)
      {
         fail("Thread1 failed: " + t1_ex);
      }
      if (t2_ex != null)
      {
         fail("Thread2 failed: " + t2_ex);
      }
   }

   public void testConcurrentTxPutsWithRollback() throws Exception
   {
      Thread t1 = new Thread()
      {
         public void run()
         {
            UserTransaction tx = null;
            Person p = new Person();
            p.setName("Ben");
            Address add = new Address();
            add.setCity("Taipei");
            p.setAddress(add);
            List<String> lang = new ArrayList<String>();
            lang.add("English");
            lang.add("Taiwanese");
            lang.add("Japanese");
            p.setLanguages(lang);
            try
            {
               tx = getTransaction();
               tx.begin();
               cache_.attach("/test/ben", p);
               TestingUtil.sleepThread(500);
               tx.commit();
            }
            catch (RollbackException rollback)
            {
               try
               {
                  tx = getTransaction();
                  tx.begin();
                  //                  throw new RuntimeException("PojoCache is not rolling back properly for Collection yet.");
                  cache_.attach("/test/ben", p);
                  tx.commit();
               }
               catch (Exception ex)
               {
                  t1_ex = ex;
                  ex.printStackTrace();
               }
            }
            catch (Exception ex)
            {
               t1_ex = ex;
               ex.printStackTrace();
            }
         }
      };

      Thread t2 = new Thread()
      {
         public void run()
         {
            try
            {
               UserTransaction tx = getTransaction();
               tx.begin();
               Person p = new Person();
               p.setName("Ben");
               Address add = new Address();
               add.setCity("Taipei");
               p.setAddress(add);
               cache1_.attach("/test/ben", p);
               TestingUtil.sleepThread(1000);
               tx.commit();
            }
            catch (RollbackException rollback)
            {
            }
            catch (Exception ex)
            {
               t2_ex = ex;
            }
         }
      };

      t1.start();
      t2.start();

      t1.join();
      t2.join();

      // t2 should rollback due to timeout while t2 should succeed
      if (t1_ex != null)
      {
         fail("Thread1 failed: " + t1_ex);
      }
      if (t2_ex != null)
      {
         fail("Thread2 failed: " + t2_ex);
      }
   }

   public void testConcurrentTxPutsWithRollbackField() throws Exception
   {
      Person p = new Person();
      p.setName("Ben");
      Address add = new Address();
      add.setCity("Taipei");
      p.setAddress(add);
      List<String> lang = new ArrayList<String>();
      lang.add("1");
      p.setLanguages(lang);
      cache_.attach("/test/ben", p);

      Thread t1 = new Thread()
      {
         public void run()
         {
            UserTransaction tx = null;
            List<String> lang = null;
            try
            {
               Person ben = (Person) cache_.find("/test/ben");
               lang = ben.getLanguages();

               tx = getTransaction();
               tx.begin();
               lang.add("2");
               lang.add("3");
               lang.remove(0);
               TestingUtil.sleepThread(1000);
               tx.commit();
            }
            catch (RollbackException rollback)
            {
               try
               {
                  tx = getTransaction();
                  tx.begin();
                  lang.add("2");
                  lang.add("3");
                  lang.remove(0);
                  tx.commit();
               }
               catch (Exception ex)
               {
                  t1_ex = ex;
               }
            }
            catch (Exception ex)
            {
               t1_ex = ex;
            }
         }
      };

      Thread t2 = new Thread()
      {
         public void run()
         {
            List<String> lang = null;
            UserTransaction tx = null;
            try
            {
               Person ben = (Person) cache1_.find("/test/ben");
               lang = ben.getLanguages();

               tx = getTransaction();
               tx.begin();
               lang.add("2");
               TestingUtil.sleepThread(1000);
               tx.commit();
            }
            catch (RollbackException rollback)
            {
               TestingUtil.sleepThread(1000);
               try
               {
                  tx = getTransaction();
                  tx.begin();
                  lang.add("2");
                  tx.commit();
               }
               catch (Exception e)
               {
                  e.printStackTrace();
               }
            }
            catch (Exception ex)
            {
               t2_ex = ex;
            }
         }
      };

      t1.start();
      t2.start();

      t1.join();
      t2.join();

      // t2 should rollback due to timeout while t2 should succeed
      if (t1_ex != null)
      {
         fail("Thread1 failed: " + t1_ex);
      }
      if (t2_ex != null)
      {
         fail("Thread2 failed: " + t2_ex);
      }
   }




}
