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

package org.jboss.cache.aop;

import junit.framework.TestCase;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.transaction.DummyTransactionManager;
import org.jboss.cache.misc.TestingUtil;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.aop.test.Person;
import org.jboss.cache.aop.test.Address;
import org.jboss.cache.aop.test.IdObject;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.InitialContext;
import javax.transaction.*;
import java.util.Properties;
import java.util.ArrayList;
import java.util.List;

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

public class NewReplicatedTxAopTest extends TestCase
{
   Log log= LogFactory.getLog(NewReplicatedTxAopTest.class);
   PojoCache cache_;
   PojoCache cache1_;
   final String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";
   DummyTransactionManager tx_mgr;
   Throwable t1_ex, t2_ex;
   long start=0;


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

   protected void setUp() throws Exception
   {
      super.setUp();
      log.info("setUp() ....");
      super.setUp();
      Properties prop = new Properties();
      prop.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.cache.transaction.DummyContextFactory");
      cache_ = new PojoCache();
      PropertyConfigurator config = new PropertyConfigurator(); // configure tree cache.
      config.configure(cache_, "META-INF/replSync-service.xml");

      cache1_ = new PojoCache();
      config.configure(cache1_, "META-INF/replSync-service.xml");
      cache_.start();
      cache1_.start();

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

   protected void tearDown() throws Exception
   {
      super.tearDown();
      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_.putObject("/person/joe", joe);
      tx.commit();
      Person p = (Person)cache1_.getObject("/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_.putObject("/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 lang = new ArrayList();
      lang.add("English");
      lang.add("Taiwanese");
      lang.add("Mandirin");
      joe.setLanguages(lang);

      tx.begin();
      cache_.putObject("/person/joe", joe);
      tx.commit();
      Person p = (Person)cache1_.getObject("/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_.putObject("/person/ben", ben);
      tx.rollback();
      assertEquals("Zip should be the same ", joe.getAddress().getZip(), p.getAddress().getZip());
      assertEquals("Langue 1 should be: ", "English", (String)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  lang = new ArrayList();
            lang.add("English");
            lang.add("Taiwanese");
            lang.add("Japanese");
            p.setLanguages(lang);
            try {
               cache_.putObject("/test/ben", p);
            }
            catch(Exception ex) {
               try {
                  cache_.putObject("/test/ben", p);
               } catch (Exception ex1)
               {
                  t1_ex = ex1;
               }
            }
         }
      };

      Thread t2 = new Thread()
      {
         Transaction tx;
         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_.putObject("/test/ben", p);
               TestingUtil.sleepThread(1000);
               tx.commit();
            }
            catch(RollbackException rollback) {
//               t2_ex = 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()
      {
         Transaction tx;

         public void run()
         {
            UserTransaction tx = null;
            Person p = new Person();
            p.setName("Ben");
            Address add = new Address();
            add.setCity("Taipei");
            p.setAddress(add);
            List  lang = new ArrayList();
            lang.add("English");
            lang.add("Taiwanese");
            lang.add("Japanese");
            p.setLanguages(lang);
            try {
               tx = getTransaction();
               tx.begin();
               cache_.putObject("/test/ben", p);
               TestingUtil.sleepThread(1000);
               tx.commit();
            }
            catch(RollbackException rollback) {
               try {
                  tx = getTransaction();
                  tx.begin();
                  cache_.putObject("/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()
      {
         Transaction tx;

         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_.putObject("/test/ben", p);
               TestingUtil.sleepThread(1000);
               tx.commit();
            }
            catch(RollbackException rollback) {
//               t2_ex = 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  lang = new ArrayList();
      IdObject id1 = new IdObject("1");
      lang.add(id1);
      p.setLanguages(lang);
      cache_.putObject("/test/ben", p);

      Thread t1 = new Thread()
      {
         Transaction tx;

         public void run()
         {
            UserTransaction tx = null;
            List lang = null;
            IdObject id2 = new IdObject("2");
            IdObject id3 = new IdObject("3");
            try {
               Person ben = (Person)cache_.getObject("/test/ben");
               lang = ben.getLanguages();

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

      Thread t2 = new Thread()
      {
         Transaction tx;

         public void run()
         {
            List lang = null;
            UserTransaction tx = null;
            IdObject id1 = null;
            try {
               Person ben = (Person)cache1_.getObject("/test/ben");
               lang = ben.getLanguages();

               tx = getTransaction();
               tx.begin();
               id1 = new IdObject("2");
               lang.add(id1);
               TestingUtil.sleepThread(1000);
               tx.commit();
            }
            catch(RollbackException rollback) {
               TestingUtil.sleepThread(1000);
               try {
                  tx = getTransaction();
                  tx.begin();
                     lang.add(id1);
                  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);
   }

   public static Test suite() throws Exception
   {
      return new TestSuite(NewReplicatedTxAopTest.class);
   }


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

}
