/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.test.cache.test.local;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jboss.cache.TreeCache;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.transaction.DummyTransactionManager;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.transaction.UserTransaction;
import java.util.Properties;
import java.util.Set;

/**
 * Unit test for local TreeCache with transaction. Use locking and multiple threads to test
 * concurrent access to the tree.
 *
 * @version $Revision: 1.4.2.4 $
 */
public class TxConcurrentUnitTestCase extends TestCase
{
   TreeCache cache;
   static Properties p = null;
   String old_factory = null;
   final String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";
   static Throwable thread_ex=null;

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

   public void setUp() throws Exception
   {
      super.setUp();
      thread_ex=null;
      old_factory = System.getProperty(Context.INITIAL_CONTEXT_FACTORY);
      System.setProperty(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
      DummyTransactionManager.getInstance();
      if (p == null) {
         p = new Properties();
         p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.cache.transaction.DummyContextFactory");
      }

      cache = new TreeCache();
      cache.setCacheMode(TreeCache.LOCAL);
      cache.setTransactionManagerLookupClass("org.jboss.cache.JBossTransactionManagerLookup");
      cache.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
      cache.setLockAcquisitionTimeout(500);
      cache.createService();
      cache.startService();
   }

   public void tearDown() throws Exception
   {
      super.tearDown();
      cache.stopService();
      // BW. kind of a hack to destroy jndi binding and thread local tx before next run.
      DummyTransactionManager.destroy();
      if (old_factory != null) {
         System.setProperty(Context.INITIAL_CONTEXT_FACTORY, old_factory);
         old_factory = null;
      }
   }



   public void testConcurrentAccess() throws Throwable {
      RunCase1 one, two;
      try {
         one = new RunCase1("one", cache);
         two = new RunCase1("two", cache);

         one.start();
         sleep(100);
         two.start();
         one.join();
         two.join();
         if(thread_ex != null)
            throw thread_ex;

         System.out.println("cache content: " + cache.toString());
         Set keys = cache.getKeys("/a/b/c");
         log("number of keys=" + keys.size());
         assertEquals(keys.size(), 1000);

         log("lock info:\n" + cache.printLockInfo());
      } catch (Exception e) {
         e.printStackTrace();
         fail(e.toString());
      }
   }


   void sleep(long timeout)
   {
      try {
         Thread.sleep(timeout);
      } catch (InterruptedException e) {
      }
   }

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


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

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


   static class RunCase1 extends Thread
   {
      TreeCache cache;
      String threadName_;

      public RunCase1(String str, TreeCache cache)
      {
         super(str);
         this.cache = cache;
         threadName_ = str;
      }

      public void run()
      {
         UserTransaction tx = null;
         try {
            tx = (UserTransaction) new InitialContext(p).lookup("UserTransaction");
            log("adding data");
            for (int i = 0; i < 1000; i++) {


               // use 5 attempts; might fail due to concurrent lock (rollback if conflict)
               for (int j = 1; j < 5; j++) {
                  log("-- adding data i=" + i + " (attempt #" + j + ")");
                  try {
                     tx.begin();
                     cache.put("/a/b/c", "number #" + i, threadName_);
                     tx.commit();
                     break;
                  } catch (Throwable t) {
                     tx.rollback();
                  }
               }

               yield();
            }
         } catch (Throwable t) {
            t.printStackTrace();
            thread_ex=t;
         }
      }
   }

}
