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

import javax.resource.ResourceException;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnectionFactory;
import javax.security.auth.Subject;

import org.jboss.logging.Logger;
import org.jboss.resource.connectionmanager.BaseConnectionManager2;
import org.jboss.resource.connectionmanager.CachedConnectionManager;
import org.jboss.resource.connectionmanager.ConnectionListener;
import org.jboss.resource.connectionmanager.InternalManagedConnectionPool;
import org.jboss.resource.connectionmanager.JBossManagedConnectionPool;
import org.jboss.resource.connectionmanager.ManagedConnectionPool;
import org.jboss.resource.connectionmanager.NoTxConnectionManager;
import org.jboss.test.jca.adapter.TestConnectionRequestInfo;
import org.jboss.test.jca.adapter.TestManagedConnectionFactory;
import org.jboss.test.JBossTestCase;

/**
 *  Unit Test for class ManagedConnectionPool
 *
 *
 * Created: Wed Jan  2 00:06:35 2002
 *
 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
 * @version
 */
public class BaseConnectionManagerStressTestCase extends JBossTestCase
{

   Logger log = Logger.getLogger(getClass());

   boolean failed;
   ResourceException error;
   int startedThreadCount;
   final Object startedLock = new Object();
   int finishedThreadCount;
   final Object finishedLock = new Object();
   int connectionCount;
   int errorCount;
   float elapsed = 0;
   float held = 0;
   float getConnection = 0;
   float returnConnection = 0;

   Subject subject = new Subject();
   ConnectionRequestInfo cri = new TestConnectionRequestInfo();
   CachedConnectionManager ccm = new CachedConnectionManager();


   /**
    * Creates a new <code>BaseConnectionManagerStressTestCase</code> instance.
    *
    * @param name test name
    */
   public BaseConnectionManagerStressTestCase (String name)
   {
      super(name);
   }


   private BaseConnectionManager2 getCM(
      InternalManagedConnectionPool.PoolParams pp)
      throws Exception
   {
      ManagedConnectionFactory mcf = new TestManagedConnectionFactory();
      ManagedConnectionPool poolingStrategy = new TestPool(mcf, pp, false, log);
      BaseConnectionManager2 cm = new NoTxConnectionManager(ccm, poolingStrategy);
      poolingStrategy.setConnectionListenerFactory(cm);
      return cm;
   }


   private void shutdown(BaseConnectionManager2 cm)
   {
      TestPool pool = (TestPool) cm.getPoolingStrategy();
      pool.shutdown();
   }

   protected void setUp()
   {
      log.debug("================> Start " + getName());
   }

   protected void tearDown()
   {
      log.debug("================> End " + getName());
   }

   public void testShortBlockingNoFill()
      throws Exception
   {
      doShortBlocking(20, 0, 5000);
   }

   public void testShortBlockingFill()
      throws Exception
   {
      doShortBlocking(20, getBeanCount(), 5000);
   }

   public void testShortBlockingPartFill()
      throws Exception
   {
      doShortBlocking(20, getBeanCount()/2, 5000);
   }

   public void testShortBlockingNearlyFill()
      throws Exception
   {
      doShortBlocking(20, getBeanCount() - 1, 5000);
   }

   public void testShortBlockingAggressiveRemoval()
      throws Exception
   {
      doShortBlocking(20, 0, 10);
   }

   public void testShortBlockingAggressiveRemovalAndFill()
      throws Exception
   {
      doShortBlocking(20, getBeanCount(), 10);
   }

   /**
    * The testShortBlocking test tries to simulate extremely high load on the pool,
    * with a short blocking timeout.  It tests fairness in scheduling servicing
    * requests. The work time is modeled by sleepTime. Allowing overhead of
    * 15 ms/pool request, the blocking is calculated at
    * (worktime + overhead) * (threadsPerConnection)
    *
    * @exception Exception if an error occurs
    */
   public void doShortBlocking(long sleep, int min, long idle) throws Exception
   {  
      startedThreadCount = 0;
      finishedThreadCount = 0;
      connectionCount = 0;
      errorCount = 0;

      final int reps = getIterationCount();
      final int threadsPerConnection = getThreadCount();
      final long sleepTime = sleep;
      failed = false;
      InternalManagedConnectionPool.PoolParams pp = new InternalManagedConnectionPool.PoolParams();
      pp.minSize = min;
      pp.maxSize = getBeanCount();
      pp.blockingTimeout =  (threadsPerConnection) * ((int)sleepTime + 15);
      if (pp.blockingTimeout < 1000)
         pp.blockingTimeout = 1000;
      pp.idleTimeout = idle;
      final BaseConnectionManager2 cm = getCM(pp);
      try
      {
         int totalThreads = pp.maxSize * threadsPerConnection;
         log.info("ShortBlocking test with connections: " + pp.maxSize + " totalThreads: " + totalThreads + " reps: " + reps);
         for (int i = 0; i < totalThreads; i++)
         {
            Runnable t = new Runnable()
            {
               int id;
               public void run()
               {
                  synchronized (startedLock)
                  {
                     id = startedThreadCount;
                     startedThreadCount++;
                     startedLock.notify();
                  }
                  long duration = 0;
                  long getConnection = 0;
                  long returnConnection = 0;
                  long heldConnection = 0;
                  for (int j = 0; j < reps; j++)
                  {
                     try
                     {
                        long startGetConnection = System.currentTimeMillis();
                        ConnectionListener cl = cm.getManagedConnection(null, null);
                        long endGetConnection = System.currentTimeMillis();
                        //maybe should be synchronized
                        BaseConnectionManagerStressTestCase.this.connectionCount++;
                        Thread.sleep(sleepTime);
                        long startReturnConnection = System.currentTimeMillis();
                        cm.returnManagedConnection(cl, false);
                        long endReturnConnection = System.currentTimeMillis();
                        
                        duration += (endReturnConnection - startGetConnection);
                        getConnection += (endGetConnection - startGetConnection);
                        returnConnection += (endReturnConnection - startReturnConnection);
                        heldConnection += (startReturnConnection - endGetConnection);
                      }
                      catch (ResourceException re)
                      {
                         BaseConnectionManagerStressTestCase.this.log.info("error: iterationCount: " + j + ", connectionCount: " + BaseConnectionManagerStressTestCase.this.connectionCount + " " + re.getMessage());
                         BaseConnectionManagerStressTestCase.this.errorCount++;
                         BaseConnectionManagerStressTestCase.this.error = re;
                         BaseConnectionManagerStressTestCase.this.failed = true;
                      } // end of try-catch
                      catch (InterruptedException ie)
                      {
                         break;
                      } // end of catch


                   }
                   synchronized (BaseConnectionManagerStressTestCase.this)
                   {
                      BaseConnectionManagerStressTestCase.this.elapsed += duration;
                      BaseConnectionManagerStressTestCase.this.getConnection += getConnection;
                      BaseConnectionManagerStressTestCase.this.returnConnection += returnConnection;
                      BaseConnectionManagerStressTestCase.this.held += heldConnection;
                   }
                   synchronized (finishedLock)
                   {
                     finishedThreadCount++;
                     finishedLock.notify();
                  }
               }
            };
            new Thread(t).start();
            synchronized (startedLock)
            {
               while (startedThreadCount < i + 1)
               {
                  startedLock.wait();
               } // end of while ()
            }
         } // end of for ()
         synchronized (finishedLock)
         {
            while (finishedThreadCount < totalThreads)
            {
               finishedLock.wait();
            } // end of while ()
         }
         
         // Stop the pool/idle remover, otherwise the following checks will be random
         TestPool pool = (TestPool) cm.getPoolingStrategy();
         pool.shutdownWithoutClear();
         
         float expected = totalThreads * reps;
         float lessWaiting = getConnection - (threadsPerConnection - 1) * held;
         log.info("completed " + getName() + " with connectionCount: " + connectionCount + ", expected : " + expected);
         log.info("errorCount: " + errorCount + " %error=" + ((100 * errorCount) / expected));
         log.info("Total time elapsed: " + elapsed  + ", perRequest: " + (elapsed / connectionCount));
         log.info("Total time held   : " + held  + ", perRequest: " + (held / connectionCount));
         log.info("Time getConnection: " + getConnection  + ", perRequest: " + (getConnection / connectionCount));
         log.info("     lessWaiting  : " + lessWaiting  + ", perRequest: " + (lessWaiting / connectionCount));
         log.info("Time retConnection: " + returnConnection  + ", perRequest: " + (returnConnection / connectionCount));
         int available = (int) pool.getAvailableConnectionCount();
         assertTrue("Wrong number of connections counted: " + available, available == pp.maxSize);
         assertTrue("Blocking Timeout occurred in ShortBlocking test: " + error, !failed);
      }
      finally
      {
         shutdown(cm);
      }
   }
   
   public class TestPool extends JBossManagedConnectionPool.OnePool
   {
      public TestPool(final ManagedConnectionFactory mcf, final InternalManagedConnectionPool.PoolParams poolParams,
            final boolean noTxSeparatePools, final Logger log)
      {
         super(mcf, poolParams, noTxSeparatePools, log);
      }

      public void shutdownWithoutClear()
      {
         super.shutdownWithoutClear();
      }
   }
}//
