/*
 * 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.generic;


import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jboss.cache.GlobalTransaction;
import org.jboss.cache.Fqn;
import org.jboss.cache.lock.*;
import org.jboss.logging.Logger;


/** Testing of different locking semantics.
 * @author Bela Ban
 * @author Ben Wang
 * @version $Revision: 1.5.2.7 $
 */
public class IdentityLockUnitTestCase extends TestCase
{
   IdentityLock lock_;
   Object other_ = new Object();
   Logger logger_ = Logger.getLogger(IdentityLockUnitTestCase.class);
   static Throwable thread_ex=null;
   final Fqn FQN=Fqn.fromString("/dummyfqn");


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

   protected void setUp() throws Exception
   {
      super.setUp();
      lock_ = new IdentityLock(null, FQN);

//        try { Thread.sleep(10000); } catch (Exception e) {
//        }
   }

   protected void tearDown() throws Exception
   {
      super.tearDown();
      lock_.releaseAll();
      lock_ = null;
      thread_ex=null;
   }

   protected void setLevelRW()
   {
      log("set lock level to RWUpgrade ...");
      LockStrategyFactory.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
      lock_ = new IdentityLock(null, FQN);
   }

   protected void setLevelSerial()
   {
      log("set lock level to SimpleLock ...");
      LockStrategyFactory.setIsolationLevel(IsolationLevel.SERIALIZABLE);
      lock_ = new IdentityLock(null, FQN);
   }

   protected GlobalTransaction getGlobalTransactionFromThread()
   {
      GlobalTransaction gtx = null;
      if (gtx == null) {
         gtx = GlobalTransaction.create(null);
      }

      return gtx;
   }

   public void testNullOwner_RWLock() throws InterruptedException {
      setLevelRW();
      nullOwner();
   }

   public void testNullOwner_SimpleLock() throws InterruptedException {
      setLevelSerial();
      nullOwner();
   }

   protected void nullOwner() throws InterruptedException {
      log("testNullOwner ...");
      try {
         GlobalTransaction gtx = getGlobalTransactionFromThread();
         lock_.acquireWriteLock(gtx, 50);
         lock_.release(gtx);

         lock_.acquireReadLock(gtx, 50);
         lock_.release(gtx);
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e) {
         fail(e.toString());
      }
   }

   public void testNullOwner2_RWLock() throws InterruptedException {
      setLevelRW();
      nullOwner2();
   }

   public void testNullOwner2_SimpleLock() throws InterruptedException {
      setLevelSerial();
      nullOwner2();
   }

   protected void nullOwner2() throws InterruptedException {
      log("testNullOwner2 ...");
      try {
         GlobalTransaction gtx = getGlobalTransactionFromThread();
         lock_.acquireReadLock(gtx, 50);
         lock_.acquireWriteLock(gtx, 50); // this should succeed
         lock_.release(gtx);
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         fail(e2.toString());
      }
   }

   public void testNullOwner3_RWLock() throws InterruptedException {
      setLevelRW();
      nullOwner3();
   }

   public void testNullOwner3_SimpleLock() throws InterruptedException {
      setLevelSerial();
      nullOwner3();
   }

   public void nullOwner3() throws InterruptedException {
      log("testNullOwner3 ...");
      try {
         GlobalTransaction gtx = getGlobalTransactionFromThread();
         lock_.acquireWriteLock(gtx, 50);
         lock_.acquireReadLock(gtx, 50); // this should succeed
         lock_.release(gtx);
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         fail(e2.toString());
      }
   }

   public void testAcquireAndRelease_RWLock() throws InterruptedException {
      setLevelRW();
      acquireAndRelease();
   }

   public void testAcquireAndRelease_SimpleLock() throws InterruptedException {
      setLevelSerial();
      acquireAndRelease();
   }

   public void acquireAndRelease() throws InterruptedException {
      log("testAcquireAndRelease ...");
      try {
         lock_.acquireReadLock(this, 50);
         assertTrue("Is the lock owner", lock_.isOwner(this));
         assertTrue(lock_.getReaderOwners().contains(this));

         lock_.acquireReadLock(this, 50); // this should succeed
         assertTrue("Is the lock owner", lock_.isOwner(this));
         assertTrue(lock_.getReaderOwners().contains(this));

         lock_.acquireWriteLock(this, 50); // this should succeed
         assertTrue("Is the lock owner", lock_.isOwner(this));
         assertTrue(!lock_.getReaderOwners().contains(this));
         assertTrue(lock_.getWriterOwner().equals(this));

         lock_.release(this);
         assertTrue(!lock_.isOwner(this));
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         fail(e2.toString());
      }
   }

   public void acquireAndRelease2_RWLock() throws InterruptedException {
      setLevelRW();
      log("testAcquireAndRelease2 ...");
      try {
         lock_.acquireReadLock(this, 10);
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         fail(e2.toString());
      }

      try {
         lock_.acquireReadLock(other_, 10); // should succeed
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         fail(e2.toString());
      }

      try {
         lock_.acquireWriteLock(other_, 50); // should fail
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         assertTrue(true);
      }

      lock_.release(this);

      try {
         lock_.acquireWriteLock(other_, 10); // should succeed
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         fail(e2.toString());
      }

      lock_.releaseAll();
   }


   public void acquireAndRelease2_SimpleLock() throws InterruptedException {
      setLevelSerial();
      log("testAcquireAndRelease2 ...");
      try {
         lock_.acquireReadLock(this, 10);
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         fail(e2.toString());
      }

      try {
         lock_.acquireReadLock(other_, 10); // should fail
         fail("Acquire read lock for other. Should fail.");
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         // Expected
         assertTrue(true);
      }

      try {
         lock_.acquireWriteLock(other_, 50); // should fail
         fail("Acquire read lock for other. Should fail.");
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         assertTrue(true);
      }

      lock_.release(this);

      try {
         lock_.acquireWriteLock(other_, 10); // should succeed
      } catch (LockingException e) {
         fail(e.toString());
      } catch (TimeoutException e2) {
         fail(e2.toString());
      }

      lock_.releaseAll();
   }

   public void testThreadedAccess_RWLock() throws Throwable {
      setLevelRW();
      log("testThreadedAccess_RWLock ...");
      final Object o1 = new Object();
      final Object o2 = new Object();

      System.out.println("");
      // 1. o1 acquires the lock -- succeeds
      Thread t1 = new Thread()
      {
         public void run()
         {
            try {
               log("o1 acquiring lock");
               lock_.acquireReadLock(o1, 50);
               log("o1: OK");
            } catch (Throwable e) {
               log("o1: FAIL");
               thread_ex=e;
            }
         }
      };

      // 2. o2 wants to acquire the lock -- this will fail and o2 will block for 2 secs
      Thread t2 = new Thread()
      {
         public void run()
         {
            try {
               log("o2 acquiring lock");
               lock_.acquireWriteLock(o2, 2000);
               log("o2: OK");
            } catch (Throwable e) {
               log("o2: FAIL");
               thread_ex=e;
            }
         }
      };

      // 3. o1 acquires the lock a second time -- succeeds
      Thread t3 = new Thread()
      {
         public void run()
         {
            try {
               log("o1 acquiring lock");
               lock_.acquireWriteLock(o1, 10);
               log("o1: OK");
            } catch (Throwable e) {
               log("o1: FAIL");
               thread_ex=e;
            }
         }
      };

      t1.start();
      t2.start();
      sleep(1000);

      // o1 must be the owner of the lock
      assertTrue(lock_.isOwner(o1));
      sleep(100);
      // o1 must still be the owner of the lock
      assertTrue(lock_.isOwner(o1));

      t3.start();
      sleep(100);
      // o1 must still be the owner of the lock
      assertTrue(lock_.isOwner(o1));

      // 4. o1 releases the lock; now o2 will succeed in acquiring the lock
      log("o1 releasing lock");
      lock_.release(o1);
      log("o1: OK");

      sleep(200);
      //log("o2: " + o2.hashCode() + ", lock_.getOwner()=" + lock_.getOwner());
//        assertTrue(lock_.isOwner(o2));
//        lock_.release(o2);

      t1.join(20000);
      t2.join(20000);
      t3.join(20000);
      if(thread_ex != null)
         throw thread_ex;
   }


   public void testThreadedAccess_SimpleLock() throws Throwable {
      setLevelSerial();
      log("testThreadedAccess_SimpleLock() ...");
      final Object o1 = new Object();
      final Object o2 = new Object();

      System.out.println("");
      // 1. o1 acquires the lock -- succeeds
      Thread t1 = new Thread()
      {
         public void run()
         {
            try {
               log("o1 acquiring lock");
               lock_.acquireReadLock(o1, 50);
               log("o1: OK");
            } catch (Throwable e) {
               log("o1: FAIL");
               thread_ex=e;
            }
         }
      };

      // 2. o2 wants to acquire the lock -- this will fail and o2 will block for 2 secs
      Thread t2 = new Thread()
      {
         public void run()
         {
            try {
               log("o2 acquiring lock");
               lock_.acquireWriteLock(o2, 2000);
               log("o2: OK");
            } catch (Throwable e) {
               log("o2: FAIL");
               thread_ex=e;
            }
         }
      };

      // 3. o1 acquires the lock a second time -- succeeds
      Thread t3 = new Thread()
      {
         public void run()
         {
            try {
               log("o1 acquiring lock");
               lock_.acquireWriteLock(o1, 10);
               log("o1: OK");
            } catch (Throwable e) {
               log("o1: FAIL");
               thread_ex=e;
            }
         }
      };

      t1.start();
      t2.start();
      sleep(1000);

      // o1 must be the owner of the lock
      assertTrue(lock_.isOwner(o1));
      sleep(100);
      // o1 must still be the owner of the lock
      assertTrue(lock_.isOwner(o1));

      t3.start();
      sleep(100);
      // o1 must still be the owner of the lock
      assertTrue(lock_.isOwner(o1));

      // 4. o1 releases the lock; now o2 will succeed in acquiring the lock
      log("o1 releasing lock");
      lock_.release(o1);
      log("o1: OK");

      sleep(200);
      //log("o2: " + o2.hashCode() + ", lock_.getOwner()=" + lock_.getOwner());
//        assertTrue(lock_.isOwner(o2));
//        lock_.release(o2);

      t1.join(20000);
      t2.join(20000);
      t3.join(20000);
      if(thread_ex != null)
         throw thread_ex;
   }


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

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

   public static Test suite()
   {
      TestSuite s = new TestSuite(IdentityLockUnitTestCase.class);
      return s;
   }

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

}
