/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */
package com.sun.enterprise.resource;

import java.util.*;

import javax.resource.ResourceException;
import javax.resource.spi.ManagedConnection;
import javax.transaction.*;
import java.util.logging.*;
import com.sun.logging.*;

import javax.naming.Context;
import javax.naming.NamingException;
import com.sun.enterprise.Switch;
import com.sun.enterprise.connectors.*;
import com.sun.enterprise.distributedtx.*;
import com.sun.enterprise.util.i18n.StringManager;

/**
 * this resource pool does not allow sharing
 * A resource is only given out if it is not used by
 * any enterprise bean and it does not have any pending transaction
 *
 * @author Aditya Gore
 * @since 9.0
 */
public abstract class AbstractResourcePool implements MonitorableResourcePool {

    protected static StringManager localStrings =
        StringManager.getManager(AbstractResourcePool.class);

    protected ArrayList resources;
    
    /**
     * list of ResourceHandles that are free and unenlisted
     */
    protected ArrayList free;
    
    // time (in ms) before destroying a free resource
    protected long idletime;
    
    protected String name;
    
    // adding resourceSpec and allocator
    protected ResourceSpec resourceSpec;
    // Note: This resource allocator may not be the same as the allocator
    //          passed in to getResource()
    protected ResourceAllocator allocator;
    
    //config properties
    
    protected int maxPoolSize;           // Max size of the pool
    
    protected int steadyPoolSize;        // Steady size of the pool
    
    protected int resizeQuantity;        // used by resizer to downsize the pool
    
    protected int maxWaitTime;           // The total time a thread is willing to wait
                                       //  for a resource object.
    
    protected boolean failAllConnections = false;

    protected boolean matchConnections = false;
    
    protected boolean poolInitialized = false;
 
    protected Timer timer;
    
    // hold on to the resizer task so we can cancel/reschedule it.
    protected TimerTask resizerTask;
    
    protected boolean monitoringEnabled; //Indicates if monitoring is on
    
    protected PoolCounters poolCounters = null;
    
    protected boolean validation = false;
    /**
     * given a Transaction, get a Set of resources enlisted in that
     * transaction (Transaction -> Set of ResourceHandle)
     */
    
    protected LinkedList waitQueue = new LinkedList();
   
    protected boolean lazyConnectionAssoc_;
    protected boolean lazyConnectionEnlist_;
    protected boolean associateWithThread_;
    protected boolean connectionLeakTracing_;

    private boolean selfManaged_;

    //To validate a Sun RA Pool Connection if it hasnot been validated in the past x sec. (x=idle-timeout)
    //The property will be set from system property - com.sun.enterprise.connectors.ValidateAtmostEveryIdleSecs=true
    private boolean validateAtmostEveryIdleSecs = false;


    private boolean inTx() {
        com.sun.enterprise.ComponentInvocation inv =
        Switch.getSwitch().getInvocationManager().getCurrentInvocation();
        if (inv == null) {
            throw new com.sun.enterprise.InvocationException();
        }
        Transaction tran = inv.getTransaction();
        return (tran != null);
    }

    public AbstractResourcePool(String poolName) throws PoolingException{
        this.name = poolName;
        setPoolConfiguration();
        monitoringEnabled = false;
        resources = new ArrayList(maxPoolSize);
        free = new ArrayList(maxPoolSize);
    }
    
    static Logger _logger = LogDomains.getLogger(LogDomains.RSR_LOGGER);
    
    private void setPoolConfiguration() throws PoolingException{
        Context ic = Switch.getSwitch().getNamingManager().getInitialContext();
        ConnectorConnectionPool poolResource;
        try {
            String jndiNameOfPool = ConnectorAdminServiceUtils.
                getReservePrefixedJNDINameForPool(name);
            poolResource = (ConnectorConnectionPool)ic.lookup( jndiNameOfPool );
        } catch (NamingException ex) {
            throw new PoolingException(ex);
        }
        idletime = Integer.parseInt(poolResource.getIdleTimeoutInSeconds()) * 1000;
        maxPoolSize = Integer.parseInt(poolResource.getMaxPoolSize());
        steadyPoolSize = Integer.parseInt(poolResource.getSteadyPoolSize());

        if (maxPoolSize < steadyPoolSize ) {
            maxPoolSize = steadyPoolSize;
        }
        resizeQuantity = Integer.parseInt(poolResource.getPoolResizeQuantity());
        
        maxWaitTime = Integer.parseInt(poolResource.getMaxWaitTimeInMillis());
        //Make sure it's not negative.
        if (maxWaitTime < 0) {
            maxWaitTime = 0;
        }
        
        failAllConnections = poolResource.isFailAllConnections();
        matchConnections = poolResource.matchConnections();
	validation = poolResource.isIsConnectionValidationRequired();
	lazyConnectionAssoc_ = poolResource.isLazyConnectionAssoc();
	lazyConnectionEnlist_ = poolResource.isLazyConnectionEnlist();
	associateWithThread_ = poolResource.isAssociateWithThread();
	connectionLeakTracing_ = poolResource.isConnectionLeakTracing();
    validateAtmostEveryIdleSecs = poolResource.isValidateAtmostEveryIdleSecs();
    }
    
    
    // This method does not need to be synchronized since all caller methods are,
    //  but it does not hurt. Just to be safe.
    protected synchronized void initPool(ResourceSpec resourceSpec,
    ResourceAllocator allocator)
    throws PoolingException {
        
        if (poolInitialized) {
            return;
        }
        
        this.resourceSpec = resourceSpec;
        this.allocator = allocator;

        createSteadyResources();
        
        // if the idle time out is 0, then don't schedule the resizer task
        if (idletime > 0) {
            scheduleResizerTask();
        }
        poolInitialized = true;
    }
    
    /**
     * Schedules the resizer timer task. If a task is currently scheduled,
     * it would be canceled and a new one is scheduled.
     */
    private void scheduleResizerTask() {
        if (resizerTask != null) {
            //cancel the current task
            resizerTask.cancel();
	    resizerTask = null;
        } 
       
        resizerTask = new Resizer();
        
        if (timer == null){ 
            timer = Switch.getSwitch().getTimer();
        }

        timer.scheduleAtFixedRate(resizerTask, idletime, idletime);
        if (_logger.isLoggable( Level.FINEST ) ) {
            _logger.finest("schduled resizer task");
        }
    }
    
    
    /**
     * add a resource with status busy and not enlisted
     */
    synchronized
    public void addResource(ResourceSpec spec,
    ResourceHandle h) {
        if (_logger.isLoggable( Level.FINE ) ) {
                _logger.log(Level.FINE,"Pool: resource added: " + spec + "," + h);
        }	
        // all resources in this pool have the same resource spec
        ResourceState state = new ResourceState();
        resources.add(h);
        h.setResourceState(state);
        state.setEnlisted(false);
        state.setBusy(true);

    }
    
    /**
     * returns resource from the pool.
     *
     * @return a free pooled resource object matching the ResourceSpec
     *
     * @throws PoolingException
     *          - if any error occurrs
     *          - or the pool has reached its max size and the
     *                  max-connection-wait-time-in-millis has expired.
     */
    public ResourceHandle getResource(ResourceSpec spec,
    ResourceAllocator alloc,
    Transaction tran) throws PoolingException {
        //Note: this method should not be synchronized or the
        //      startTime would be incorrect for threads waiting to enter
    
        /*
	 * Here are all the comments for the method put togethar for
	 * easy reference. 
	 *  1. 
            // - Try to get a free resource. Note: internalGetResource()
            // will create a new resource if none is free and the max has
            // not been reached.
            // - If can't get one, get on the wait queue.
            // - Repeat this until maxWaitTime expires.
            // - If maxWaitTime == 0, repeat indefinitely.
	   
	    2.
	    //the doFailAllConnectionsProcessing method would already
	    //have been invoked by now.
	    //We simply go ahead and create a new resource here
	    //from the allocator that we have and adjust the resources
	    //list accordingly so as to not exceed the maxPoolSize ever
	    //(i.e if steadyPoolSize == maxPoolSize )
	    ///Also since we are creating the resource out of the allocator
	    //that we came into this method with, we need not worry about
	    //matching
	 */
        ResourceHandle result = null;
        
        long startTime = 0;
        long elapsedWaitTime = 0;
        long remainingWaitTime = 0;

        Object waitMonitor = new Object();
        
        if (maxWaitTime > 0) {
            startTime = System.currentTimeMillis();
        }
    
        while (true) {
            //See comment #1 above
	    result = internalGetResource(spec, alloc, tran);

            if (result != null) {
                // got one, return it
                if (monitoringEnabled) {
                    poolCounters.incrementNumConnAcquired();
                    elapsedWaitTime = System.currentTimeMillis() - startTime;
                    poolCounters.setWaitTime(elapsedWaitTime);
                }


		//got one - seems we are not doing validation or matching
		//return it
	        break;
            } else {
                // did not get a resource.
                if (maxWaitTime > 0) {
                    elapsedWaitTime = System.currentTimeMillis() - startTime;
                    if (elapsedWaitTime < maxWaitTime) {
                        // time has not expired, determine remaining wait time.
                        remainingWaitTime = maxWaitTime - elapsedWaitTime;
                    } else {
                        // wait time has expired
                        if (monitoringEnabled) {
                            poolCounters.incrementNumConnTimedOut();
                        }
                        String msg = localStrings.getString(
                        "poolmgr.no.available.resource",
                        "No available resource. Wait-time expired.");
                        throw new PoolingException(msg);
                    }
                }
                
                synchronized (waitMonitor) {
                    synchronized (waitQueue) {
                        waitQueue.addLast(waitMonitor);
                    }
                    try {
                        if (_logger.isLoggable( Level.FINE) ) {
                            _logger.log(Level.FINE,"Resource Pool: getting on wait queue");
                        }
                        //numConnWaited++;   
                        waitMonitor.wait(remainingWaitTime);
                        
                    } catch (InterruptedException ex) {
                        //Could be system shutdown.
                        break;
                    }
                    
                    //try to remove in case that the monitor has timed
                    // out.  We dont expect the queue to grow to great numbers
                    // so the overhead for removing inexistant objects is low.
		    synchronized( waitQueue ) {
                        waitQueue.remove(waitMonitor);
		    }
                }
            }
        }

	alloc.fillInResourceObjects(result);
        return result;
    }
   
    //Overridden in AssocWithThreadResourcePool to fetch the resource
    //cached in the ThreadLocal
    //In SJSASResourcePool this simply returns null
    protected abstract ResourceHandle prefetch( ResourceSpec spec,
        ResourceAllocator alloc, Transaction tran );
        
    protected ResourceHandle internalGetResource(ResourceSpec spec,
    ResourceAllocator alloc,
    Transaction tran) throws PoolingException {
        if (!poolInitialized) {
            initPool(spec, alloc);
        }
        ResourceHandle result = null;
        
        result = prefetch( spec, alloc, tran );
        if ( result != null ) {
            return result;
        }

        try {
	    //comment-1: sharing is possible only if caller is marked 
	    //shareable, so abort right here if that's not the case
            if (tran != null && alloc.shareableWithinComponent() ) {
                J2EETransaction j2eetran = (J2EETransaction) tran;
                // case 1. look for free and enlisted in same tx
                Set set = (Set) j2eetran.getResources(name);
                if (set != null) {
                    Iterator iter = set.iterator();
                    while (iter.hasNext()) {
                        ResourceHandle h = (ResourceHandle) iter.next();
			if (h.hasConnectionErrorOccurred()) {
			    iter.remove();
			    continue;
			}

                        ResourceState state = h.getResourceState();
			/*
			 * One can share a resource only for the following conditions:
			 * 1. The caller resource is shareable (look at the outermost
			 *    if marked comment-1
			 * 2. The resource enlisted inside the transaction is shareable
			 * 3. We are dealing with XA resources OR
			 *    We are deling with a non-XA resource that's not in use
			 *    Note that sharing a non-xa resource that's in use involves
			 *    associating physical connections.
			 * 4. The credentials of the resources match
			 */
			if (h.getResourceAllocator().shareableWithinComponent() && 
			        ( state.isFree() || spec.isXA())) {
                            if (matchConnections) {
                                if ( !alloc.matchConnection( h )) {
                                    if (monitoringEnabled){
                                       poolCounters.incrementNumConnNotSuccessfullyMatched();
                                    }
                                    continue;
                                }
                                if (h.hasConnectionErrorOccurred() ) {
                                    if (  failAllConnections ) {
                                        //if failAllConnections has happened, we flushed the
                                        //pool, so we don't have to do iter.remove else we
                                        //will get a ConncurrentModificationException
                                        result = null;
                                        break;
                                    }
                                    iter.remove();
                                    continue;
                                }
                                if (monitoringEnabled){
                                    poolCounters.incrementNumConnSuccessfullyMatched();
                                }
                            }
                            state.setBusy(true);
                            result = h;
                            break;
                        }
                    }
                }
            }		
        } catch(ClassCastException e) {
            _logger.log(Level.FINE, "Pool: getResource : " + 
            "transaction is not J2EETransaction but a " + tran.getClass().getName() , e); 
        }

     
        // We didnt get a connections that is already enlisted in a transaction.
        if (result == null) {
            result = getUnenlistedResource(spec, alloc, tran);
            //Only getting of an unenlisted resource results in
            //an increment of the connection used stat
            if ((result != null) && monitoringEnabled) {
                poolCounters.incrementNumConnUsed();
            }
        }
        return result;

    }

    // this is the RI getResource() with some modifications
    /**
     * return resource in free list. If none is found, returns null
     * This method needs to be synchronized in the child class
     *
     */
    synchronized protected ResourceHandle getUnenlistedResource(ResourceSpec spec,
    ResourceAllocator alloc,
    Transaction tran) throws PoolingException {
        
        // the order of serving a resource request
        // 1. free and enlisted in the same transaction
        // 2. free and unenlisted
        // Do NOT give out a connection that is
        // free and enlisted in a different transaction
        ResourceHandle result = null;
        
        Iterator iter = free.iterator();
        while (iter.hasNext()) {
	    boolean matched = false;
            ResourceHandle h = (ResourceHandle) iter.next();
            if (h.hasConnectionErrorOccurred()) {
                iter.remove();
                continue;
            }

            //h.isAssociated can only be true for
            //AssocWithThreadResourcePool
            if ( h.isAssociated() ) {
                //Don't touch it then
                continue;
            }

            boolean connectionValid = true;

            long timeSinceValidation = System.currentTimeMillis() - h.getLastValidated();
            if (validation || (validateAtmostEveryIdleSecs && timeSinceValidation > idletime)) {
                if (!alloc.isConnectionValid(h)) {
                    connectionValid = false;
                } else {
                    h.setLastValidated(System.currentTimeMillis());
                }
            }

            if (connectionValid) {
                if (matchConnections) {
                    matched = alloc.matchConnection(h);
                    if (monitoringEnabled) {
                        if (matched) {
                            poolCounters.incrementNumConnSuccessfullyMatched();
                        } else {
                            poolCounters.incrementNumConnNotSuccessfullyMatched();
                        }
                    }
                } else {
                    matched = true; //always
                }
            }

            if (h.hasConnectionErrorOccurred() || !connectionValid) {
                if (failAllConnections) {
                    result = createSingleResourceAndAdjustPool(alloc, spec);
                    //no need to match since we came from the same allocator as the caller
                    break;
                } else {
                    iter.remove();
                    continue;
                }
            }

            if (matched) {
                result = h;
                break;
            }

        }
        
        if (result != null) {
            // set correct state
            ResourceState state = result.getResourceState();
            state.setBusy(true);
            free.remove(result);
        } else {
            if(resources.size() < steadyPoolSize ){
                // May be all invalid resources are destroyed as
                // a result no free resource found and no. of resources is less than steady-pool-size
                createSteadyResources();
                if(free.size() != 0){
                    result = (ResourceHandle)free.remove(0);
                    ResourceState state = result.getResourceState();
                    state.setBusy(true);
                }
            }
            else if (resources.size() < maxPoolSize) {
                // Either no free resource found or an invalid resource has
                // been destroyed. Let's see if we can add one.
                // changed to use resources.size instead of independent counter
                result = alloc.createResource();
                addResource(spec, result);
                if ( monitoringEnabled ) {
                    poolCounters.incrementNumConnCreated();
                }
            }
        }

        return result;
    }

    /**
     * This method will be called from the getUnenlistedResource method if
     * we detect a failAllConnection flag.
     * Here we simply create a new resource and replace a free resource in
     * the pool by this resource and then give it out. 
     * This replacement is required since the steadypoolsize might equal
     * maxpoolsize and in that case if we were not to remove a resource
     * from the pool, our resource would be above maxPoolSize
     */ 
    protected ResourceHandle createSingleResourceAndAdjustPool(
        ResourceAllocator alloc, ResourceSpec spec) 
	throws PoolingException 
    {
        if ( free.size() != 0 ) {
	    //resources.size() could be 0 if we were to run into
            //trouble while createSteadyResources(0 was called in 
            //doFailAllConnectionsProcessing
            ResourceHandle rHandle = (ResourceHandle) free.get(0);
            resources.remove( rHandle );
                free.remove( rHandle );
        }
	ResourceHandle result = alloc.createResource();
	addResource( spec, result );
	alloc.fillInResourceObjects( result );
	if ( monitoringEnabled ) {
	    poolCounters.incrementNumConnCreated();
	}

	return result;

    }
    
    /*
     * Create resources upto steadyPoolSize
     */
    
    //synchronized method, eliminated block sync
    private synchronized void createSteadyResources() throws PoolingException {
        for (int i = 0; i < steadyPoolSize; i++) {
            createResourceAndAddToPool();
        }
    }

    
    protected void destroyResource(ResourceHandle resourceHandle) {
        try {
            resourceHandle.getResourceAllocator().destroyResource(resourceHandle);
        } catch (Exception ex) {
            _logger.log(Level.WARNING,"poolmgr.destroy_resource_failed");
            if (_logger.isLoggable( Level.FINE ) ) {
                _logger.log(Level.FINE,"poolmgr.destroy_resource_failed",ex);
            }
        } finally {
            if (monitoringEnabled) {
                poolCounters.incrementNumConnDestroyed();
                //Only connections that are busy and destroyed
                //would be counted as a decrease in number of connections
                //used. Other connections would have their transactions completed
                //and then the resource would be destroyed. transactionComplete()
                //would capture the latter case.
                if (resourceHandle.getResourceState().isBusy()) 
                    poolCounters.decrementNumConnUsed();
            }
        }
    }

    /**
     * this method is called to indicate that the resource is
     * not used by a bean anymore
     */
    public void resourceClosed(ResourceHandle h)
    throws IllegalStateException {
        if (_logger.isLoggable( Level.FINE ) ) {
            _logger.log(Level.FINE,"Pool: resourceClosed: " + h);
        }
        
        ResourceState state = getResourceState(h);
        if (state == null || state.isBusy() == false) {
            throw new IllegalStateException("state is null : " + (state == null)
	        + " ::: state.isBusy() : " + state.isBusy() );
        }

        state.setBusy(false);  // mark as not busy
        state.touchTimestamp();

        if (state.isUnenlisted()) {
            freeUnenlistedResource(h);
            //only unenlisted 
            if(monitoringEnabled){
                poolCounters.decrementNumConnUsed();
            }
        }
        
        if (monitoringEnabled){
            poolCounters.incrementNumConnReleased();
        }

        if (_logger.isLoggable( Level.FINE ) ) {
            _logger.log(Level.FINE,"Pool: resourceFreed: " + h);
        }
    }

    protected synchronized void freeUnenlistedResource(ResourceHandle h) {

        // Put it back to the free collection.
        free.add(h);
        notifyWaitingThreads();
    }
    
    synchronized
    public void resourceErrorOccurred(ResourceHandle h)
    throws IllegalStateException {
        logFine("Pool: resourceErrorOccurred: " + h);

	if ( failAllConnections ) {
	    doFailAllConnectionsProcessing();
	    return;
	}
	
        if (monitoringEnabled) {
            poolCounters.incrementNumConnFailedValidation(1);
        }
        ResourceState state = getResourceState(h);
        //GJCINT - commenting out below
        /** 
         * The reason is that normally connection error is expected
         * to occur only when the connection is in use by the application.
         * When there is connection validation involved, the connection
         * can be checked for validity "before" it is passed to the
         * application i.e. when the resource is still free. Since,
         * the connection error can occur when the resource
         * is free, the following is being commented out.
         */
        /*
        if (state == null ||
        state.isBusy() == false) {
            throw new IllegalStateException();
        }
        */
        if (state == null) {
            throw new IllegalStateException();
        }
        
        // changed order of commands
        
	//Commenting resources.remove() out since we will call an iter.remove()
	//in the getUnenlistedResource method in the if check after
	//matchManagedConnections or in the internalGetResource method
	//If we were to call remove directly here there is always the danger
	//of a ConcurrentModificationExceptionbeing thrown when we return
	//
	//In case of this method being called asynchronously, since
	//the resource has been marked as "errorOccured", it will get
	//removed in the next iteration of getUnenlistedResource
	//or internalGetResource
	resources.remove(h);
        destroyResource(h);
    }
    
    private void doFailAllConnectionsProcessing() {
	logFine(" @@@@ doFailAllConnectionsProcessing entered");
	cancelResizerTask();
        if ( monitoringEnabled ) {
	    poolCounters.incrementNumConnFailedValidation(resources.size());
	}
	
        emptyPool();
	try {
	    createSteadyResources();
	} catch(PoolingException pe) {
	    //Ignore and hope the resizer does its stuff
	    logFine( "in doFailAllConnectionsProcessing couldn't create steady resources");
	}
	scheduleResizerTask();
	logFine(" @@@@ doFailAllConnectionsProcessing done - created new resources");
	
    }
    
    /**
     * this method is called when a resource is enlisted in
     * transation tran
     */
    public void resourceEnlisted(Transaction tran, ResourceHandle resource)
    throws IllegalStateException {
        try {
            J2EETransaction j2eetran = (J2EETransaction) tran;
            Set set = (Set) j2eetran.getResources(name);
            if (set == null) {
                set = new HashSet();
                j2eetran.setResources(set, name);
            }
            set.add(resource);
        } catch(ClassCastException e) {
            _logger.log(Level.FINE, "Pool: resourceEnlisted:" + 
            "transaction is not J2EETransaction but a " + tran.getClass().getName() , e); 
        }
        ResourceState state = getResourceState(resource);
        state.setEnlisted(true);
        if (_logger.isLoggable( Level.FINE ) ) {
            _logger.log(Level.FINE,"Pool: resourceEnlisted: " + resource);
        }
    }
    
    /**
     * this method is called when transaction tran is completed
     */
    synchronized
    public void transactionCompleted(Transaction tran, int status)
    throws IllegalStateException {
        try {
            J2EETransaction j2eetran = (J2EETransaction) tran;
            Set set = (Set) j2eetran.getResources(name);
            if (set == null) return;
    
            Iterator iter = set.iterator();
            while (iter.hasNext()) {
                ResourceHandle resource = (ResourceHandle) iter.next();
                ResourceState state = getResourceState(resource);
                state.setEnlisted(false);
                // Application might not have closed the connection.
                if ( isResourceUnused(resource) ){
                    free.add(resource);
                    if(monitoringEnabled){
                        poolCounters.decrementNumConnUsed();
                    }
                }
                iter.remove();
                notifyWaitingThreads();
                if (_logger.isLoggable( Level.FINE ) ) {
                    _logger.log(Level.FINE,"Pool: transactionCompleted: " + resource);
                }    
            }
        } catch (ClassCastException e) {
            _logger.log(Level.FINE, "Pool: transactionCompleted: " +
            "transaction is not J2EETransaction but a " + tran.getClass().getName() , e); 
        }
    }
  
    protected boolean isResourceUnused(ResourceHandle h){
        return getResourceState(h).isFree();
    }

    protected void notifyWaitingThreads() {
        // notify the first thread in the waitqueue
        Object waitMonitor = null;
        synchronized (waitQueue) {
            if (waitQueue.size() > 0) {
                waitMonitor = waitQueue.removeFirst();
            }
        }
        if (waitMonitor != null) {
            synchronized (waitMonitor) {
                waitMonitor.notify();
            }
        }
    }
    
    // Start of methods related to Pool Monitoring
    /**
     * Return the number of threads that are waiting
     * to obtain a connection from the pool
     */
    public int getNumThreadWaiting() {
        return waitQueue.size();
    }
    
    /**
     * Return the number of connections that have failed validation
     */
    public long getNumConnFailedValidation() {
        return poolCounters.numConnFailedValidation;
    }
    
    /**
     * Return the number of threads that have time out after
     * waiting to obtain a connection from the pool.
     */
    public long getNumConnTimedOut() {
        return poolCounters.numConnTimedOut;
    }
    
    /**
     * Return the number of free connections in the pool
     */
    public synchronized long getNumConnFree() {
        return poolCounters.currNumConnFree;        
    }
    
    public long getMaxNumConnFree(){
        return poolCounters.maxNumConnFree;
    }
    
    public long getMinNumConnFree(){
        if (poolCounters.minNumConnFree != Long.MAX_VALUE) {
            return poolCounters.minNumConnFree;
        } else {
            return 0;
        }
    }
    
    /**
     * Return the number of connections in use 
     */
    public synchronized long getNumConnInUse() {
        return poolCounters.currNumConnUsed;
    }
    
    /**
     * Return the maximum number of connections ever used in
     * this pool
     */
    public long getMaxNumConnUsed() {
        return poolCounters.maxNumConnUsed;
    }
    
    
    //8.1 monitoring
    public long getCurrentConnRequestWaitTime(){
    	return poolCounters.currConnectionRequestWait;
    }
    
    public long getMaxConnRequestWaitTime(){
        return poolCounters.maxConnectionRequestWait;
    }
    
    public long getMinConnRequestWaitTime(){
        if(poolCounters.minConnectionRequestWait != Long.MAX_VALUE) {
            return  poolCounters.minConnectionRequestWait;
        } else {
            return 0;
        }
    }

    public long getTotalConnectionRequestWaitTime() {
       return poolCounters.totalConnectionRequestWait;
    }
    
    public long getMinNumConnUsed(){
        if (poolCounters.minNumConnUsed != Long.MAX_VALUE) {
            return poolCounters.minNumConnUsed;
        } else {
            return 0;
        }
    }
    
    public long getNumConnCreated(){
        return poolCounters.numConnCreated;
    }

    public long getNumConnDestroyed(){
        return poolCounters.numConnDestroyed;
    }

    public long getNumConnAcquired() {
        return poolCounters.numConnAcquired;
    }

    public long getNumConnReleased() {
        return poolCounters.numConnReleased;
    }

    public long getNumConnSuccessfullyMatched(){
       return poolCounters.numConnSuccessfullyMatched;
    }

    public long getNumConnNotSuccessfullyMatched(){
       return poolCounters.numConnNotSuccessfullyMatched;
    }

    
    // end of methods related to pool monitoring
    
    // Modifications on resizePool() from RI:
    //  - Use destroyResource()
    //  - Reuse-variable optimization.
    //  - Add steady pool size check.
    
    synchronized public void resizePool(boolean forced) {
        
        // Mods:
        //    - If the wait queue is NOT empty, don't do anything.
        //    - Only destroy resources if current-size > steady-pool-size
        //    - Only destroy the lesser of:
        //        - pool-resize-quantity
        //        - numResoucesInPool - steady-pool-size
        
        synchronized (waitQueue) {
            if (waitQueue.size() > 0) {
                return;
            }
        }

        if(validation){
            _logger.log(Level.FINE, "CP.Resizer.Validation-Turned-On");
            removeInvalidResources();
        }
        
        // changed to use resources.size() instead of counter
        // eliminated block sync
        int overSize = resources.size() - steadyPoolSize;
        
        if (overSize > 0) {
            int numResourcesToBeDestroyed =
            (resizeQuantity < overSize ? resizeQuantity : overSize);
            
            ResourceHandle h;
            ResourceState state;
            
            Iterator iter = free.iterator();
            // let's cache the current time since precision is not required here.
            long currentTime = System.currentTimeMillis();
            
            while ( (numResourcesToBeDestroyed > 0) && iter.hasNext()) {
                h = (ResourceHandle) iter.next();
                state = getResourceState(h);
                if (forced || (currentTime - state.getTimestamp() > idletime)) {
                    // changed order of instructions
                    resources.remove(h);
                    destroyResource(h);
                    iter.remove();
                    numResourcesToBeDestroyed--;
                }
            }
        }
        // Now make sure that the steady pool size is met.
        
        // changed to use resources.size() instead of counter
        // eliminated block sync
        if (resources.size() < steadyPoolSize) {
            // Create resources to match the steady pool size
            // changed to use resources.size() instead of counter
            for (int i = resources.size(); i < steadyPoolSize; i++) {
                try {
                    createResourceAndAddToPool();
                } catch (PoolingException ex) {
                    _logger.log(Level.WARNING,
                    "resource_pool.resize_pool_error", 
		        (ex.getMessage() != null ? ex.getMessage() : " ") );
                    continue;
                }
            }
        }
        
        if (_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE,"Pool: Name = " + name);
            _logger.log(Level.FINE,"Pool: Resource held: " 
	        + resources.size());
        }
    }
    
    /**
     * Removes invalid resource handles in the pool while resizing the pool.
     * Uses the Connector 1.5 spec 6.5.3.4 optional RA feature to obtain 
     * invalid ManagedConnections
     */
    private void removeInvalidResources() {
        try {
            //Find all ManagedConnections that are free/not-in-use
            Set freeManagedConnections = new HashSet();
            for (Iterator iter = free.iterator(); iter.hasNext();) {
                ResourceHandle element = (ResourceHandle) iter.next();
                freeManagedConnections.add(element.getResource());
            }
            
            _logger.log(Level.FINE, "Sending to RA a set of free connections " +
                    "of size", new Integer(freeManagedConnections.size()));
            
            //get Invalid ManagedConnections from the resource-adapter
            Set invalidManagedConnections = 
                this.allocator.getInvalidConnections(freeManagedConnections);
            
            //Find the appropriate ResourceHandle for a returned invalid 
            //ManagedConnection and destroy the Resourcehandle and references to
            //it in resources and free list.
            if (invalidManagedConnections != null) {
                _logger.log(Level.FINE, "Received from RA invalid connections # ", 
                                new Integer(invalidManagedConnections.size()));
                
                for (Iterator iter = invalidManagedConnections.iterator(); 
                                              iter.hasNext();) {
                    ManagedConnection invalidManagedConnection = 
                                            (ManagedConnection)iter.next();
                    for (Iterator freeResourcesIter = free.iterator(); 
                                            freeResourcesIter.hasNext();) {
                        ResourceHandle handle = 
                            (ResourceHandle) freeResourcesIter.next();
                        if (invalidManagedConnection.equals
                                        (handle.getResource())) {
                            resources.remove(handle);
                            destroyResource(handle);
                            freeResourcesIter.remove();
                        }
                    }
                }
            } else {
                _logger.log(Level.FINE, "RA does not support " +
                        "ValidatingManagedConnectionFactory");
            }
        } catch (ResourceException re) {
            _logger.log(Level.FINE, "ResourceException while trying to " +
                    "get invalid connections from MCF", re);
        } catch (Exception e) {
            _logger.log(Level.FINE, "Exception while trying " +
                    "to get invalid connections from MCF", e);
        }
    }

    private ResourceState getResourceState(ResourceHandle h) {
        return h.getResourceState();
    }
    
    synchronized public void emptyPool() {
        logFine("EmptyPool: Name = " + name);
        
        Iterator iter = resources.iterator();
        while(iter.hasNext()) {
            ResourceHandle h = (ResourceHandle) iter.next();
            destroyResource(h);
        }
        free.clear();
        resources.clear();
    }

    synchronized public void emptyFreeConnectionsInPool() {
	if (_logger.isLoggable(Level.FINE)) {
	    _logger.fine( "Emptying free connections in pool : " + name );
	}
        Iterator iter = free.iterator();
	while( iter.hasNext() ) {
	    ResourceHandle h = (ResourceHandle) iter.next();
	    resources.remove( h );
	    destroyResource( h );
	}
	free.clear();
    }
    
    class Resizer extends TimerTask {
        public void run() {

	    if (_logger.isLoggable( Level.FINE ) ) {
                _logger.log(Level.FINE,"AbstractResourcePool: resize pool " 
		    + name);
            }
            resizePool(false);
        }
    }
    
    public String toString() {
        StringBuffer sb = new StringBuffer("Pool [");
        sb.append(name);
        sb.append("] PoolSize=");
        sb.append(resources.size());
        sb.append("  FreeResources=");
        sb.append(free.size());
        sb.append("  QueueSize=");
        sb.append(waitQueue.size());
	sb.append(" matching=");
	sb.append( (matchConnections ? "on" : "off") );
	sb.append(" validation=");
	sb.append( (validation ? "on" : "off") );
        return sb.toString();
    }


    public boolean isMonitoringEnabled() {
        return monitoringEnabled;
    }
    public void disableMonitoring() {
        monitoringEnabled = false;
    }

    public void setMonitoringEnabledHigh() {
        logFine("Enabling monitoring to level : HIGH");
        int numConnFree = (this.poolInitialized) ? this.free.size() : this.steadyPoolSize;  
        if ( poolCounters == null ){
            poolCounters = new HighPoolCounters(numConnFree);
        }
        poolCounters.reset(numConnFree);
        monitoringEnabled = true;
    }
    
    public void setMonitoringEnabledLow() {
        logFine("Enabling monitoring to level : LOW");
        if ( poolCounters == null ){
            poolCounters = new LowPoolCounters(0);
        }
        poolCounters.reset(0);
        monitoringEnabled = true;
    }

    /**
     * Reconfigure the Pool's properties. The reconfigConnectorConnectionPool
     * method in the ConnectorRuntime will use this method (through PoolManager)
     * if it needs to just change pool properties and not recreate the pool
     *
     * @param poolResource - the ConnectorConnectionPool javabean that holds 
     * the new pool properties
     * @throws PoolingException if the pool resizing fails
     */
    public synchronized void reconfigPoolProperties( ConnectorConnectionPool poolResource ) 
            throws PoolingException
    {
        int _idleTime = Integer.parseInt(poolResource.getIdleTimeoutInSeconds()) 
                * 1000;
        if (_idleTime != idletime && _idleTime != 0) {
            scheduleResizerTask();
        }
        if (_idleTime == 0 ) {
            //resizerTask.cancel();
	    cancelResizerTask();
        }
        
        idletime = _idleTime;
        

        resizeQuantity = Integer.parseInt(poolResource.getPoolResizeQuantity());
        
        maxWaitTime = Integer.parseInt(poolResource.getMaxWaitTimeInMillis());
        //Make sure it's not negative.
        if (maxWaitTime < 0) {
            maxWaitTime = 0;
        }
    
	validation = poolResource.isIsConnectionValidationRequired();
        failAllConnections = poolResource.isFailAllConnections();
	matchConnections = poolResource.matchConnections();
	lazyConnectionAssoc_ = poolResource.isLazyConnectionAssoc();
	lazyConnectionEnlist_ = poolResource.isLazyConnectionEnlist();
	associateWithThread_ = poolResource.isAssociateWithThread();
	connectionLeakTracing_ = poolResource.isConnectionLeakTracing();
        
        //Self managed quantities. These are ignored if self management
        //is on
        if ( ! isSelfManaged() ) {
            int _maxPoolSize = Integer.parseInt(poolResource.getMaxPoolSize());
            if (_maxPoolSize < maxPoolSize ) {
                //In this case we need to kill extra connections in the pool
                //For the case where the value is increased, we need not 
                //do anything
	        //num resources to kill is decided by the resources in the pool.
	        //if we have less than current maxPoolSize resources, we need to
	        //kill less.
	        int toKill =  resources.size() - _maxPoolSize;
	    	
                killExtraResources( toKill );
            }

            if (_maxPoolSize < steadyPoolSize ) {
                maxPoolSize = steadyPoolSize;
            } else {
                maxPoolSize = _maxPoolSize;
            }
    
            int _steadyPoolSize = Integer.parseInt(
                poolResource.getSteadyPoolSize());
            if ( _steadyPoolSize > steadyPoolSize ) {
                increaseSteadyPoolSize( _steadyPoolSize );        
            }

            if (_steadyPoolSize > maxPoolSize ) {
                steadyPoolSize = maxPoolSize;
            } else {   
                steadyPoolSize = _steadyPoolSize;
            }
        }

    }
    
    /*
     * Kill the extra resources at the end of the Hashtable
     * The maxPoolSize being reduced causes this method to
     * be called
     */
    private void killExtraResources(int numToKill) {
        cancelResizerTask();

        Iterator iter = free.iterator();
        for( int i = 0; iter.hasNext() && i < numToKill ; i++ ) {
            ResourceHandle h = (ResourceHandle) iter.next();
            ResourceState s = getResourceState(h);
            resources.remove(h);
            destroyResource( h );
            iter.remove();
        }

        scheduleResizerTask();
    }

    /*
     * Increase the number of steady resources in the pool
     * if we detect that the steadyPoolSize has been increased
     */
    private void increaseSteadyPoolSize( int newSteadyPoolSize ) 
            throws PoolingException
    {
        cancelResizerTask();
        for (int i = resources.size(); i < newSteadyPoolSize; i++) {
            createResourceAndAddToPool();
        }
        scheduleResizerTask();
    }

    /**
     * @throws PoolingException
     */
    private void createResourceAndAddToPool() throws PoolingException {
        ResourceHandle resourceHandle = allocator.createResource();
        //addResource() will also increment numResourcesInPool	    
        addResource(resourceSpec, resourceHandle);
        
        // addResource() does not add the resource to the free pool!!!
        // so we need to do that.
        getResourceState(resourceHandle).setBusy(false);
        free.add(resourceHandle);
        
        if ( monitoringEnabled ) {
            poolCounters.incrementNumConnCreated();
        }
    }

    /**
     * Switch on matching of connections in the pool.
     */
    public void switchOnMatching() {
        matchConnections = true;
    }

    /**
     * query the name of this pool. Required by monitoring
     *
     * @return the name of this pool
     */
    public String getPoolName() {
        return name;
    }

    public synchronized void cancelResizerTask() {
	
	if (_logger.isLoggable( Level.FINE ) ) {
            _logger.finest("Cancelling resizer");    
	}
	if (resizerTask != null ) {
            resizerTask.cancel();
	}
        resizerTask = null;

       if (timer != null){
	    timer.purge();
       }
    }


    public synchronized void dumpPoolStatus() {
        _logger.log(Level.INFO, "Name of pool :" + name);
        _logger.log(Level.INFO, "Free connections :" + free.size());
        _logger.log(Level.INFO, "Total connections :" + resources.size());
        _logger.log(Level.INFO, "Pool's matching is :" + matchConnections);
        _logger.log(Level.INFO, "Free Table is :" + free);
        _logger.log(Level.INFO, "Resource Table is :" + resources);
    }
    
    //Accessor to aid debugging
    public PoolCounters getPoolCounters(){
        return this.poolCounters;
    }
    
    private void logFine( String msg ) {
        if ( _logger.isLoggable( Level.FINE) ) {
	    _logger.fine( msg );
	}
    }
    
    //Self management methods
    public int getMaxPoolSize() {
        return maxPoolSize;
    }

    public int getSteadyPoolSize() {
        return steadyPoolSize;
    }


    public void setMaxPoolSize( int size ) {
        if ( size < resources.size() ) {
            synchronized( this ) {
                int toKill =  resources.size() - size;
                if ( toKill > 0 ) {
                    try {
                        killExtraResources( toKill );
                    } catch( Exception re ) {
                        //ignore for now
                        if (_logger.isLoggable(Level.FINE) ) {
                            _logger.fine( "setMaxPoolSize:: killExtraResources " +
                                "throws exception: " + re.getMessage() );
                        }
                    }
                }
            }
        }
        maxPoolSize = size;
    }

    public void setSteadyPoolSize( int size ) {
        steadyPoolSize = size;
    }
    
    public void setSelfManaged( boolean selfManaged ) {
        logFine( "Setting selfManaged to : " +selfManaged+" in pool : "+name );
        selfManaged_ = selfManaged;
    }

    protected boolean isSelfManaged() {
        return selfManaged_;
    }
    
    class PoolCounters {
        // No need to synchronize these counters, since it is not vital
        // information , and we avoid performance penalties
        long numConnFailedValidation = 0;
        long numConnTimedOut = 0;
        long numConnAcquired = 0;
        long numConnReleased = 0;

        long maxNumConnUsed = 0;  //The max number of connections ever used
        long minNumConnUsed = Long.MAX_VALUE;
        long currNumConnUsed = 0;
        
        //8.1 new monitoring statistics
        long currConnectionRequestWait = 0;
        long maxConnectionRequestWait = 0;
        long minConnectionRequestWait = Long.MAX_VALUE;
        long totalConnectionRequestWait = 0; //to compute Avg Conn Req wait time
        
        long maxNumConnFree = 0;
        long minNumConnFree = Long.MAX_VALUE;
        long currNumConnFree = 0;
        
        long numConnCreated = 0;
        long numConnDestroyed = 0;

	long numConnSuccessfullyMatched = 0;
	long numConnNotSuccessfullyMatched = 0;

        PoolCounters(int freePoolSize) {
            currNumConnFree = freePoolSize;
        }


        protected void reset(int freePoolSize) {
            maxNumConnUsed = 0;
            minNumConnUsed = Long.MAX_VALUE;
            currNumConnUsed = 0;
            
            numConnFailedValidation = 0;
            numConnTimedOut = 0;

            maxConnectionRequestWait = 0;
            minConnectionRequestWait = Long.MAX_VALUE;
            totalConnectionRequestWait = 0;

            maxNumConnFree = 0;
            minNumConnFree = Long.MAX_VALUE;
            currNumConnFree = freePoolSize;
                
            numConnCreated = 0;
            numConnDestroyed = 0;
	
            numConnSuccessfullyMatched = 0;
            numConnNotSuccessfullyMatched = 0;
        }
        
        protected synchronized void setWaitTime(long elapsedWaitTime) {}
        protected synchronized void incrementNumConnUsed(){}
        protected synchronized void decrementNumConnUsed(){}
        protected void setNumConnUsed(long numConnInUse){}
        protected void setNumConnFree(long numConnFree){}
        protected void incrementNumConnCreated(){}
        protected void incrementNumConnDestroyed(){}
        protected void incrementNumConnMatched() {}
        protected void incrementNumConnNotMatched() {}
        protected void incrementNumConnAcquired() {}
        protected void incrementNumConnTimedOut() {}
        protected void incrementNumConnReleased() {}
        protected void incrementNumConnFailedValidation(int incr) {}
        protected void incrementNumConnSuccessfullyMatched() {}
        protected void incrementNumConnNotSuccessfullyMatched() {}
        
        public String toString(){
            String str = "PoolCounters: ";
            str += "\n numConnFailedValidation = " + numConnFailedValidation;
            str += "\n numConnTimedOut = " + numConnTimedOut;
            str += "\n numConnAcquired = " + numConnAcquired;
            str += "\n numConnReleased = " + numConnReleased;
            
            str += "\n maxNumConnUsed = " + maxNumConnUsed;
            str += "\n minNumConnUsed = " + minNumConnUsed;
            str += "\n currNumConnUsed = " + currNumConnUsed;
            
            str += "\n maxConnectionRequestWait = " + maxConnectionRequestWait;
            str += "\n minConnectionRequestWait = " + minConnectionRequestWait;
            str += "\n maxNumConnFree =  = " + maxNumConnFree;
            str += "\n minNumConnFree = " + minNumConnFree;

            str += "\n currNumConnFree = " + currNumConnFree;
            str += "\n numConnCreated = " + numConnCreated;
            str += "\n numConnDestroyed = " + numConnDestroyed;
            
            str += "\n numConnSuccessfullyMatched = " + numConnSuccessfullyMatched;		 
            str += "\n numConnNotSuccessfullyMatched = " + numConnNotSuccessfullyMatched;		 
            return str;
        }
    }

    class LowPoolCounters extends PoolCounters {

        LowPoolCounters( int freePoolSize ) {
            super( freePoolSize );
        }

        protected void incrementNumConnCreated(){
            numConnCreated++;
        }

        protected void incrementNumConnDestroyed(){
            numConnDestroyed++;
        }

        protected synchronized void incrementNumConnUsed(){
            this.setNumConnUsed(++this.currNumConnUsed);
            this.setNumConnFree(--this.currNumConnFree);
        }

        protected synchronized void decrementNumConnUsed(){
            this.setNumConnUsed(--this.currNumConnUsed);
            this.setNumConnFree(++this.currNumConnFree);
        }

        protected void setNumConnUsed(long numConnInUse){
            //latch max
            if ( numConnInUse > maxNumConnUsed ) {
                maxNumConnUsed = numConnInUse;
            }
            //latch min
            if ( numConnInUse < minNumConnUsed ) {
                if (numConnInUse <= 0) {
                    minNumConnUsed = 0;
                } else {
                    minNumConnUsed = numConnInUse;
                }
            }
            //set current
            this.currNumConnUsed = numConnInUse;
        }
        
        protected void setNumConnFree(long numConnFree){
            //latch max
            if ( numConnFree > maxNumConnFree ) {
                maxNumConnFree = numConnFree;
            }
            
            //latch min
            if ( numConnFree < minNumConnFree ) {
                if (numConnFree <= 0) {
                    minNumConnFree = 0;
                } else { 
                    minNumConnFree = numConnFree;
                }
            }
            
            //set current - number of connections free cannot be less than zero
            this.currNumConnFree = (numConnFree >= 0) ? numConnFree : 0;
        }
    }

    class HighPoolCounters extends LowPoolCounters {

        HighPoolCounters( int freePoolSize ) {
            super( freePoolSize );
        }
        
        public synchronized void setWaitTime(long elapsedWaitTime) {
            //latch max
        	currConnectionRequestWait = elapsedWaitTime;
        	if (elapsedWaitTime > maxConnectionRequestWait)
                maxConnectionRequestWait= elapsedWaitTime;
            //latch min
            if (elapsedWaitTime < minConnectionRequestWait)
                minConnectionRequestWait = elapsedWaitTime;
            totalConnectionRequestWait += elapsedWaitTime;
        }
        
        /**
        protected synchronized void incrementNumConnUsed(){
            this.setNumConnUsed(++this.currNumConnUsed);
            this.setNumConnFree(--this.currNumConnFree);
        }

        protected synchronized void decrementNumConnUsed(){
            this.setNumConnUsed(--this.currNumConnUsed);
            this.setNumConnFree(++this.currNumConnFree);
        }

        protected void setNumConnUsed(long numConnInUse){
            //latch max
            if ( numConnInUse > maxNumConnUsed ) {
                maxNumConnUsed = numConnInUse;
            }
            //latch min
            if ( numConnInUse < minNumConnUsed ) {
                if (numConnInUse <= 0) {
                    minNumConnUsed = 0;
                } else {
                    minNumConnUsed = numConnInUse;
                }
            }
            //set current
            this.currNumConnUsed = numConnInUse;
        }
        
        protected void setNumConnFree(long numConnFree){
            //latch max
            if ( numConnFree > maxNumConnFree ) {
                maxNumConnFree = numConnFree;
            }
            
            //latch min
            if ( numConnFree < minNumConnFree ) {
                if (numConnFree <= 0) {
                    minNumConnFree = 0;
                } else { 
                    minNumConnFree = numConnFree;
                }
            }
            
            //set current - number of connections free cannot be less than zero
            this.currNumConnFree = (numConnFree >= 0) ? numConnFree : 0;
        }
        */

        protected void incrementNumConnAcquired() {
            numConnAcquired++;
        }

        protected void incrementNumConnTimedOut() {
            numConnTimedOut++;
        }

        protected void incrementNumConnReleased() {
            numConnReleased++;
        }

        protected void incrementNumConnFailedValidation( int incr ) {
            numConnFailedValidation += incr;
        }
	
	protected void incrementNumConnSuccessfullyMatched() {
            numConnSuccessfullyMatched++;
	}
	
	protected void incrementNumConnNotSuccessfullyMatched() {
            numConnNotSuccessfullyMatched++;
	}
			
    }

}
