/*
 * 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.PropertyConfigurator;
import org.jboss.cache.TreeCache;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.transaction.DummyTransactionManager;
import org.jboss.logging.Logger;

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: ConcurrentTransactionalUnitTestCase.java,v 1.1.2.5 2005/12/01 19:50:26 dimitris Exp $
 */
public class ConcurrentTransactionalUnitTestCase extends TestCase
{
   TreeCache cache;
   private Logger logger_ = Logger.getLogger(ConcurrentTransactionalUnitTestCase.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 ConcurrentTransactionalUnitTestCase(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 sleep(long timeout)
   {
      try {
         Thread.sleep(timeout);
      } catch (InterruptedException e) {
      }
   }

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


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

      Updater(String name) {
         this.name=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), name);
               tx.commit();
               yield();
            }
         }
         catch(Throwable t) {
            t.printStackTrace();
            thread_ex=t;
         }
      }
   }

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

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


}
