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

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.PropertyConfigurator;
import org.jboss.cache.TreeCache;
import org.jboss.cache.lock.IsolationLevel;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.UserTransaction;
import java.util.*;

/**
 * Unit test for local TreeCache. Use locking and multiple threads to test
 * concurrent access to the tree.
 *
 * @version $Id: ConcurrentTransactionalTest.java,v 1.2 2005/11/21 18:03:26 msurtani Exp $
 */
public class ConcurrentTransactionalTest extends TestCase
{
   TreeCache cache;
   private Log logger_=LogFactory.getLog(ConcurrentTransactionalTest.class);
   static Throwable thread_ex=null;
   final int NUM=10000;
   static Properties p = null;
   String old_factory = null;
   final String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";

   public ConcurrentTransactionalTest(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);
      DummyTransactionManager.getInstance();
      if (p == null) {
         p = new Properties();
         p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.cache.transaction.DummyContextFactory");
      }
   }

   private void createCache(IsolationLevel level) throws Exception
   {
      cache = new TreeCache();
      PropertyConfigurator config = new PropertyConfigurator();
      config.configure(cache, "META-INF/local-service.xml");

      cache.setCacheMode(TreeCache.LOCAL);
      cache.setIsolationLevel(level);
      cache.start();
      cache.put("/a/b/c", null);
   }

   private UserTransaction getTransaction() {
      UserTransaction tx = null;
      try {
         tx = (UserTransaction) new InitialContext(p).lookup("UserTransaction");
      } catch (NamingException e) {
         e.printStackTrace();
      }

      if(tx == null)
         throw new RuntimeException("Tx is null");

      return tx;
   }

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

   public void testConcurrentAccessWithRWLock() throws Throwable
   {
      createCache(IsolationLevel.REPEATABLE_READ);
      work_();
   }

   public void testConcurrentAccessWithExclusiveLock() throws Throwable
   {
      createCache(IsolationLevel.SERIALIZABLE);
      work_();
   }

   private void work_() throws Throwable {
      Updater one, two;
      try {
         one = new Updater("Thread one");
         two = new Updater("Thread two");
         long current = System.currentTimeMillis();
         one.start();
         two.start();
         one.join();
         two.join();
         if(thread_ex != null)
            throw thread_ex;

         long now = System.currentTimeMillis();
         log("*** Time elapsed: " + (now-current));

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

         if(keys.size() != NUM) {
            scanForNullValues(keys);

            try {
               System.out.println("size=" + keys.size());
               List l=new LinkedList(keys);
               Collections.sort(l);
               System.out.println("keys: " + l);
               for(int i=0; i < NUM; i++) {
                  if(!l.contains(new Integer(i)))
                     System.out.println("missing: " + i);
               }

               LinkedList duplicates=new LinkedList();
               for(Iterator it=l.iterator(); it.hasNext();) {
                  Integer integer=(Integer)it.next();
                  if(duplicates.contains(integer)) {
                     System.out.println(integer + " is a duplicate");
                  }
                  else
                     duplicates.add(integer);
               }
            }
            catch(Exception e1) {
               e1.printStackTrace();
            }
         }

         assertEquals(NUM, keys.size());
         log("lock info:\n" + cache.printLockInfo());

      } catch (Exception e) {
         e.printStackTrace();
         fail(e.toString());
      }
   }

   private void scanForNullValues(Set keys) {
      for(Iterator it=keys.iterator(); it.hasNext();) {
         Object o=it.next();
         if(o == null)
            System.err.println("found a null value in keys");
      }
   }

   void log(String msg)
   {
      logger_.debug(" [" + Thread.currentThread() + "]: " + msg);
   }


   class Updater extends Thread {
      String val=null;
      UserTransaction tx;

      Updater(String name) {
         this.val=name;
      }

      public void run() {
         try {
            log("adding data");
            tx=getTransaction();
            for(int i=0; i < NUM; i++) {
               log("adding data i=" + i);
               tx.begin();
               cache.put("/a/b/c", new Integer(i), val);
               tx.commit();
               yield();
            }
         }
         catch(Throwable t) {
            t.printStackTrace();
            thread_ex=t;
         }
      }
   }

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

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


}
