/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * ObjectWeb Connector: an implementation of JCA Sun specification along
 *                      with some extensions of this specification.
 * Copyright (C) 2001-2002 France Telecom R&D - INRIA
 *
 * This library 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 of the License, or (at your option) any later version.
 *
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Based on LArrayPool in ObjectWeb common
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.resource.internal.pool.lib;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;

import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.resource.internal.cm.ConnectionManagerImpl;
import org.ow2.jonas.resource.internal.pool.Pool;
import org.ow2.jonas.resource.internal.pool.PoolItemStats;
import org.ow2.jonas.resource.internal.pool.PoolMatchFactory;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * The class <b>HArrayPool</b> implements a Pool as a HashSet of ManagedConnections,
 * managing free/active resources.
 *
 * Updated to use an LRU list of free resources
 *
 * Author: Eric HARDESTY
 */
public class HArrayPool implements Pool {

    /**
     *  The Logger instance where messages are written.
     */
    private Logger logger = null;
    private Logger conLogger = null;

    /**
     * timeout value
     */
    private long timeout = 0;
    /**
     * Pool match factory for this pool
     */
    private PoolMatchFactory matchFactory = null;
    /**
     * Free list of ManagedConnections
     */
    private HashSet freeList = null;
    /**
     * Active list of ManagedConnections
     */
    private HashSet activeList = null;
    /**
     * PoolItem information list
     */
    private Hashtable infoList = null;

    /**
     * No Pooling is desired.  Objects are just wrappers until any
     * transactional states have been updated as needed
     */
    private static final int NO_POOLING = -2;

    /**
     * High Value for no limit for the connection pool
     */
    private static final int NO_LIMIT = -1;

    /**
     * Nb of milliseconds in a day
     */
    private static final long ONE_DAY = 1440L * 60L * 1000L;

    /**
     * max number of remove at once in the freelist
     * We avoid removing too many items at once for perf reasons.
     */
    private static final int MAX_REMOVE_FREELIST = 10;

    /**
     * count min busy connection during current period.
     */
    private int busyMin = 0;

    /**
     * count max busy connection during current period.
     */
    private int busyMax = 0;

    /**
     * initial size of the pool
     */
    private int initSize = -1;

    /**
     * jdbcConnLevel of the pool
     */
    private int jdbcConnLevel = 0;

    /**
     * initial jdbcTestStatement
     */
    private String jdbcTestStatement = "";

    /**
     * max age a connection will be available for use
     */
    private int maxAge = 0;

    /**
     * max open time for a connection, in minutes
     */
    private int maxOpentime = 0;

    /**
     * max size of the pool
     */
    private int maxSize = -1;

    /**
     * max nb of waiters allowed to wait for a Connection
     */
    private int maxWaiters = 1000;

    /**
     * max nb of milliseconds to wait for a connection when pool is full
     */
    private long maxWaitTimeout = 10000;

    /**
     * minimum size of the pool
     */
    private int minSize = 0;

    /**
     * min limit has been reached
     */
    private boolean minLimit = false;

    /**
     * pool monitor
     */
    private HArrayPoolMonitor poolMonitor = null;

    /**
     * sampling period in sec.
     */
    private int samplingPeriod = 60; // defaultSamplingPeriod;

    /**
     * count max waiters during current period.
     */
    private int waiterCount = 0;

    /**
     * count max waiting time during current period.
     */
    private long waitingTime = 0;


    /**
     * HArrayPool constructor
     * @param logger Logger for the pool to use
     */
    public HArrayPool(Logger logger) {
        this.logger = logger;
        if (conLogger == null) {
            conLogger = Log.getLogger(Log.JONAS_JCA_PREFIX+".connection");
        }
        freeList = new HashSet();
        activeList = new HashSet();
        infoList = new Hashtable();
    }

    // ----------------------------------------------------------------
    // Config properties (Getters & Setters)
    // ----------------------------------------------------------------

    /**
     * @return int number of busy connections
     */
    public int getCurrentBusy() {
        return activeList.size();
    }

    /**
     * @return int number of opened connections
     */
    public int getCurrentOpened() {
        return getSize();
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#getInitSize
     */
    public int getInitSize() {
        return initSize;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#setInitSize
     */
    public synchronized void setInitSize(int initsize) throws Exception {
        if (matchFactory == null) {
            throw new Exception("The matchFactory is mandatory!");
        }
        if (initSize < 0 && initsize >= 0) {
            initSize = initsize;
            setInitSize();
        }
    }

    /**
     * Internal setInitSize
     * @throws Exception if an error is encountered
     */
    private void setInitSize() throws Exception {
        int initsize;

        if (initSize <= 0 || maxSize == NO_POOLING) {
            return;
        }

        if (maxSize < 0) {
            initsize = initSize;
        } else {
            initsize = initSize < maxSize ? initSize : maxSize;
        }
        if (initsize >= minSize) {
            minLimit = true;
        }

        ManagedConnection res;
        synchronized (this) {
            for (int i = 0; i < initsize; i++) {
                res = createResource(null);
                freeList.add(res);
                infoList.put(res, new PoolItemStats());
                setItemStats(res);
            }
        }
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#getJdbcConnLevel
     */
    public int getJdbcConnLevel() {
        return jdbcConnLevel;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#setJdbcConnLevel
     */
    public void setJdbcConnLevel(int jdbcConnLevel) {
        this.jdbcConnLevel = jdbcConnLevel;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#getJdbcTestStatement
     */
    public String getJdbcTestStatement() {
        return jdbcTestStatement;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#setJdbcTestStatement
     */
    public void setJdbcTestStatement(String jdbcTestStatement) {
        this.jdbcTestStatement = jdbcTestStatement;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#getMaxAge
     */
    public int getMaxAge() {
        return maxAge;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#setMaxAge
     */
    public void setMaxAge(int maxAge) {
        this.maxAge = maxAge;
    }

    /**
     * @return max age for connections (in mns)
     */
    public int getMaxOpentime() {
        return maxOpentime;
    }

    /**
     * @param mx max time of open connection in minutes
     */
    public void setMaxOpentime(int mx) {
        maxOpentime = mx;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#getMaxSize
     */
    public int getMaxSize() {
        return maxSize;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#setMaxSize
     */
    public synchronized void setMaxSize(int maxsize) throws Exception {
        if (matchFactory == null) {
            throw new Exception("The matchFactory mandatory!!");
        }
        if ((maxsize < minSize && maxsize > 0) || maxsize == maxSize) {
            return;
        }

        // Determine if we need to adjust the pool
        if (maxsize < 0) {
            if (currentWaiters > 0) {
                notify();
            }
            maxSize = maxsize;
        } else {
            if (currentWaiters > 0 && maxSize < maxsize) {
                notify();
            }
            maxSize = maxsize;
            adjust();
        }
    }

    /**
     * @return max nb of waiters
     */
    public int getMaxWaiters() {
        return maxWaiters;
    }

    /**
     * @param nb max nb of waiters
     */
    public void setMaxWaiters(int nb) {
        maxWaiters = nb;
    }

    /**
     * @return waiter timeout in seconds
     */
    public int getMaxWaitTime() {
        return (int) (maxWaitTimeout / 1000L);
    }

    /**
     * @param sec max time to wait for a connection, in seconds
     */
    public void setMaxWaitTime(int sec) {
        maxWaitTimeout = sec * 1000L;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#getMinSize
     */
    public int getMinSize() {
        return minSize;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#setMinSize
     */
    public synchronized void setMinSize(int minsize) throws Exception {
        if (matchFactory == null) {
            throw new Exception("A matchFactory is mandatory!");
        }
        if ((minsize < 0) || (minsize > maxSize) || (minsize == minSize)) {
            return;
        }
        if (minsize < minSize) {
            minSize = minsize;
            return;
        }
        minSize = minsize;
        adjust();
    }

    /**
     * @return sampling period in sec.
     */
    public int getSamplingPeriod() {
        return samplingPeriod;
    }

    /**
     * @param sec sampling period in sec.
     */
    public void setSamplingPeriod(int sec) {
        if (sec > 0) {
            samplingPeriod = sec;
            poolMonitor.setSamplingPeriod(sec);
        }
    }

    /**
     * Get the size of the pool
     * @return int size of the pool
     */
    public synchronized int getSize() {
        return (activeList.size() + freeList.size());
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#getTimeout
     */
    public long getTimeout() {
        return timeout;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#setTimeout
     */
    public synchronized void setTimeout(long crto) {
    }


    // ----------------------------------------------------------------
    // Monitoring Attributes
    // Each attribute should have a get accessor.
    // ----------------------------------------------------------------

    /**
     *  maximum nb of busy connections in last sampling period
     */
    private int busyMaxRecent = 0;

    /**
     * @return maximum nb of busy connections in last sampling period
     */
    public int getBusyMaxRecent() {
        return busyMaxRecent;
    }

    /**
     *  minimum nb of busy connections in last sampling period
     */
    private int busyMinRecent = 0;

    /**
     * @return minimum nb of busy connections in last sampling period
     */
    public int getBusyMinRecent() {
        return busyMinRecent;
    }

    /**
     * nb of threads waiting for a Connection
     */
    private int currentWaiters = 0;

    /**
     * @return current number of connection waiters
     */
    public int getCurrentWaiters() {
        return currentWaiters;
    }

    /**
     * total number of opened physical connections since the datasource creation.
     */
    private int openedCount = 0;

    /**
     * @return int number of physical jdbc connection opened
     */
    public int getOpenedCount() {
        return openedCount;
    }

    /**
     * total nb of physical connection failures
     */
    private int connectionFailures = 0;

    /**
     * @return int number of connection failures on open
     */
    public int getConnectionFailures() {
        return connectionFailures;
    }

    /**
     * total nb of connection leaks.
     * A connection leak occurs when the caller never issues a close method
     * on the connection.
     */
    private int connectionLeaks = 0;

    /**
     * @return int number of connection leaks
     */
    public int getConnectionLeaks() {
        return connectionLeaks;
    }

    /**
     * total number of opened connections since the datasource creation.
     */
    private int servedOpen = 0;

    /**
     * @return int number of connection served
     */
    public int getServedOpen() {
        return servedOpen;
    }

    /**
     * total nb of open connection failures because waiter overflow
     */
    private int rejectedFull = 0;

    /**
     * @return int number of open calls that were rejected due to waiter overflow
     */
    public int getRejectedFull() {
        return rejectedFull;
    }

    /**
     * total nb of open connection failures because timeout
     */
    private int rejectedTimeout = 0;

    /**
     * @return int number of open calls that were rejected by timeout
     */
    public int getRejectedTimeout() {
        return rejectedTimeout;
    }

    /**
     * total nb of open connection failures for any other reason.
     */
    private int rejectedOther = 0;

    /**
     * @return int number of open calls that were rejected
     */
    public int getRejectedOther() {
        return rejectedOther;
    }

    /**
     * @return int number of open calls that were rejected
     */
    public int getRejectedOpen() {
        return rejectedFull + rejectedTimeout + rejectedOther;
    }

    /**
     * maximum nb of waiters since datasource creation
     */
    private int waitersHigh = 0;

    /**
     * @return maximum nb of waiters since the datasource creation
     */
    public int getWaitersHigh() {
        return waitersHigh;
    }

    /**
     * maximum nb of waiters in last sampling period
     */
    private int waitersHighRecent = 0;

    /**
     * @return maximum nb of waiters in last sampling period
     */
    public int getWaitersHighRecent() {
        return waitersHighRecent;
    }

    /**
     * total nb of waiters since datasource creation
     */
    private int totalWaiterCount = 0;

    /**
     * @return total nb of waiters since the datasource creation
     */
    public int getWaiterCount() {
        return totalWaiterCount;
    }

    /**
     * total waiting time in milliseconds
     */
    private long totalWaitingTime = 0;

    /**
     * @return total waiting time since the datasource creation
     */
    public long getWaitingTime() {
        return totalWaitingTime;
    }

    /**
     * max waiting time in milliseconds
     */
    private long waitingHigh = 0;

    /**
     * @return max waiting time since the datasource creation
     */
    public long getWaitingHigh() {
        return waitingHigh;
    }

    /**
     * max waiting time in milliseconds in last sampling period
     */
    private long waitingHighRecent = 0;

    /**
     * @return max waiting time in last sampling period
     */
    public long getWaitingHighRecent() {
        return waitingHighRecent;
    }

    // IMPLEMENTATION OF METHODS FROM THE Pool INTERFACE

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#getResource
     */
    public synchronized Object getResource(Object hints)
        throws Exception {

        if (matchFactory == null) {
            throw new Exception("The matchFactory mandatory!!");
        }
        ManagedConnection res = null;
        long timetowait = maxWaitTimeout;
        long starttime = 0;
        while (res == null) {
            if (!freeList.isEmpty()) {
                try {
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Free entries available");
                    }
                    res = (ManagedConnection) matchFactory.matchResource(freeList, hints);
                    if (res != null) {
                        freeList.remove(res);
                        activeList.add(res);
                        if (conLogger.isLoggable(BasicLevel.DEBUG)) {
                            conLogger.log(BasicLevel.DEBUG, "Returned Resource: " + res);
                        }
                    }
                } catch (Exception ex) {
                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                        logger.log(BasicLevel.DEBUG, "Error from matchResource", ex);
                    }
                }
            }
            if (res == null) {
                int curSize = activeList.size() + freeList.size();
                if (maxSize < 0 || curSize < maxSize) {
                    res = createResource(hints);
                    activeList.add(res);
                } else if (freeList.size() > 0) {
                    res = (ManagedConnection) freeList.iterator().next();
                    matchFactory.releaseResource(res);
                    res.destroy();
                    freeList.remove(res);
                    infoList.remove(res);
                    // Create a new one and return it
                    res = createResource(hints);
                    activeList.add(res);
                } else {
                    boolean stoplooping = true;
                    // Determine if waiting is an option
                    if (timetowait > 0) {
                        if (currentWaiters < maxWaiters) {
                            currentWaiters++;
                            // Store the maximum concurrent waiters
                            if (waiterCount < currentWaiters) {
                                waiterCount = currentWaiters;
                            }
                            if (starttime == 0) {
                                starttime = System.currentTimeMillis();
                                if (logger.isLoggable(BasicLevel.DEBUG)) {
                                    logger.log(BasicLevel.DEBUG, "Wait for a free Connection");
                                }
                            }
                            try {
                                wait(timetowait);
                            } catch (InterruptedException ign) {
                                logger.log(BasicLevel.WARN, "Interrupted");
                            } finally {
                                currentWaiters--;
                            }
                            long stoptime = System.currentTimeMillis();
                            long stillwaited = stoptime - starttime;
                            timetowait = maxWaitTimeout - stillwaited;
                            stoplooping = (timetowait <= 0);
                            if (stoplooping) {
                                // We have been woken up by the timeout.
                                totalWaiterCount++;
                                totalWaitingTime += stillwaited;
                                if (waitingTime < stillwaited) {
                                    waitingTime = stillwaited;
                                }
                            } else {
                                if (!freeList.isEmpty()) {
                                    // We have been notified by a connection release.
                                    if (logger.isLoggable(BasicLevel.DEBUG)) {
                                        logger.log(BasicLevel.DEBUG, "Notified after " + stillwaited);
                                    }
                                    totalWaiterCount++;
                                    totalWaitingTime += stillwaited;
                                    if (waitingTime < stillwaited) {
                                        waitingTime = stillwaited;
                                    }
                                }
                                continue;
                            }
                        }
                    }
                    if (stoplooping && freeList.isEmpty()) {
                        if (starttime > 0) {
                            rejectedTimeout++;
                            logger.log(BasicLevel.WARN, "Cannot create a Connection - timeout");
                        } else {
                            rejectedFull++;
                            logger.log(BasicLevel.WARN, "Cannot create a Connection");
                        }
                        throw new Exception("No more connections");
                    }
                }
            }
        }
        infoList.put(res, new PoolItemStats());

        printLists();
        setItemStats(res);
        recomputeBusy();
        return res;
    }

    /**
     * Create a resource
     * @param hints Object to pass to the matchFactory
     * @return ManagedConnection object returned from the matchFactory
     * @throws Exception if an exception occured
     */
    private ManagedConnection createResource(Object hints) throws Exception {
        ManagedConnection res = null;
        try {
            res = (ManagedConnection) matchFactory.createResource(hints);
            if (res == null) {
                Exception exc = new Exception("A null ManagedConnection was returned.");
                throw exc;
            }
            openedCount++;
        } catch (Exception ex) {
            connectionFailures++;
            rejectedOther++;
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "Cannot create new Connection", ex);
            }
            throw ex;
        }
        if (conLogger.isLoggable(BasicLevel.DEBUG)) {
            conLogger.log(BasicLevel.DEBUG, "Created Resource: " + res);
        }
        if (!minLimit && getSize() >= minSize) {
            minLimit = true;
        }
        return res;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#releaseResource
     */
    public synchronized void releaseResource(Object resource, boolean destroy, boolean adjustment)
        throws Exception {

        ManagedConnection res = (ManagedConnection) resource;
        if (matchFactory == null) {
            throw new Exception("The matchFactory mandatory!!");
        }
        if (activeList == null) {
            throw new Exception("No active resources to releases!!");
        }
        if (!activeList.contains(res)) {
            //Temp fix making the assumption that the item is already released
            return;
            //throw new Exception("Attempt to release inactive resource(" + res + ")");
        }
        activeList.remove(res);
        if (conLogger.isLoggable(BasicLevel.DEBUG)) {
            conLogger.log(BasicLevel.DEBUG, "Freed Resource: " + res);
        }
        if (maxSize == NO_POOLING || destroy) {
            infoList.remove(res);
            res.destroy();
            if (conLogger.isLoggable(BasicLevel.DEBUG)) {
                conLogger.log(BasicLevel.DEBUG, "Destroyed Resource: " + res);
            }
        } else {
            freeList.add(res);
            PoolItemStats pis = (PoolItemStats) infoList.get(res);
            if (pis != null) {
                pis.setTotalConnectionTime(System.currentTimeMillis() - pis.getStartTime());
            }
        }
        // Notify 1 thread waiting for a Connection.
        if (currentWaiters > 0) {
            notify();
        }
        if (adjustment) {
            adjust();
        }
    }

    /**
     * Close all connections in the pool when server is shutting down.
     */
    public synchronized void closeAllConnections() {
        logger.log(BasicLevel.DEBUG, "");

        // Stop the pool keeper, since all connections will be closed.
        poolMonitor.stopit();

        // Close physically all connections
        Iterator it = freeList.iterator();
        while (it.hasNext()) {
            ManagedConnection res = (ManagedConnection) it.next();
            try {
                 res.destroy();
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "Error while closing a Connection:", e);
            }
        }

        it = activeList.iterator();
        while (it.hasNext()) {
            ManagedConnection res = (ManagedConnection) it.next();
            try {
                 res.destroy();
            } catch (Exception e) {
                logger.log(BasicLevel.ERROR, "Error while closing a Connection:", e);
            }
        }
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#getMatchFactory
     */
    public PoolMatchFactory getMatchFactory() {
        return matchFactory;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#setMatchFactory
     */
    public synchronized void setMatchFactory(PoolMatchFactory pmf) {
        matchFactory = pmf;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#startMonitor
     */
    public void startMonitor() {
        logger.log(BasicLevel.DEBUG, "");
        poolMonitor = new HArrayPoolMonitor(this);
        poolMonitor.start();
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#validateMCs
     */
    public synchronized void validateMCs() throws Exception {
        matchFactory.validateResource(freeList);
    }

    /**
     * Adjust the pool size, according to poolMax and minSize values.
     * Also remove old connections in the freeList.
     * @throws Exception if an exception occurs
     */
    public synchronized void adjust() throws Exception {
        // Remove max aged elements in freelist
        // - Not more than MAX_REMOVE_FREELIST
        // - Don't reduce pool size less than minSize

        long curTime = System.currentTimeMillis();
        if (conLogger.isLoggable(BasicLevel.DEBUG)) {
            conLogger.log(BasicLevel.DEBUG, " activeList.size()  = "+activeList.size() +" minSize= "+minSize);
        }
        this.printLists();
        // count is used to remove not more than MAX_REMOVE_FREELIST
        int count = 0;
        Vector rList = new Vector();
        Iterator it = freeList.iterator();

        while (it.hasNext()) {
            ManagedConnection res = (ManagedConnection) it.next();
            PoolItemStats pis = (PoolItemStats) infoList.get(res);
            if (maxAge > 0 && pis != null && pis.getMaxAgeTimeout() > 0
                    && curTime > pis.getMaxAgeTimeout()) {
                if (conLogger.isLoggable(BasicLevel.DEBUG)) {
                    conLogger.log(BasicLevel.DEBUG, "remove a timed out connection "+res);
                }
                rList.add(res);
                count++;

                if (count >= MAX_REMOVE_FREELIST) {
                    break;
                }
            }
        }
        // Bug: 300351 Use the list built above to remove the MCs
        it = rList.iterator();
        while (it.hasNext()) {
            ManagedConnection res = (ManagedConnection) it.next();
            try {
                matchFactory.releaseResource(res);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            freeList.remove(res);
            infoList.remove(res);
            try {
                res.destroy();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        // Close (physically) connections lost (opened for too long time)
        curTime = System.currentTimeMillis();
        Vector aList = new Vector();
        for (it = activeList.iterator(); it.hasNext();) {
            ManagedConnection res = (ManagedConnection) it.next();
            PoolItemStats pis = (PoolItemStats) infoList.get(res);
            if (maxOpentime > 0 && pis != null && pis.getMaxOpenTimeout() > 0
                    && curTime > pis.getMaxOpenTimeout()) {
                aList.add(res);
            }
        }
        it = aList.iterator();
        while (it.hasNext()) {
            ManagedConnection item = (ManagedConnection) it.next();
            logger.log(BasicLevel.WARN, "close a timed out active connection");
            logger.log(BasicLevel.WARN, "MC = " + item);
            releaseResource(item, true, false);
            connectionLeaks++;
        }


        int curSize = this.getSize();
        if ((maxSize > 0 && maxSize < curSize) || (maxSize == NO_POOLING)) {
            // Removes as many free entries as possible
            int nbRemove = curSize;
            if (maxSize > 0) {
                nbRemove -= maxSize;
            }
            if (freeList != null) {
                while (!freeList.isEmpty() && nbRemove > 0) {
                    ManagedConnection res = (ManagedConnection) freeList.iterator().next();
                    matchFactory.releaseResource(res);
                    res.destroy();
                    freeList.remove(res);
                    infoList.remove(res);
                    nbRemove--;
                    curSize--;
                }
            }
        }

        // Recreate more Connections while minSize is not reached
        ManagedConnection res;
        if (maxSize != NO_POOLING) {
            while (minSize > curSize) {
                res = createResource(null);
                if (logger.isLoggable(BasicLevel.DEBUG)) {
                    logger.log(BasicLevel.DEBUG, "recreate connection "+res);
                }
                freeList.add(res);
                infoList.put(res, new PoolItemStats());
                setItemStats(res);
                curSize++;
            }
        }

        recomputeBusy();
    }

    /**
     * compute current min/max busyConnections
     */
    public void recomputeBusy() {
        int busy = getCurrentBusy();
        if (busyMax < busy) {
            busyMax = busy;
        }
        if (busyMin > busy) {
            busyMin = busy;
        }
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#sampling
     */
    public void sampling() throws Exception {
        //matchFactory.sampling();
        waitingHighRecent = waitingTime;
        if (waitingHigh < waitingTime) {
            waitingHigh = waitingTime;
        }
        waitingTime = 0;

        waitersHighRecent = waiterCount;
        if (waitersHigh < waiterCount) {
            waitersHigh = waiterCount;
        }
        waiterCount = 0;

        busyMaxRecent = busyMax;
        busyMax = getCurrentBusy();
        busyMinRecent = busyMin;
        busyMin = getCurrentBusy();

        //Check JDBC Keep Alive state
        if (jdbcConnLevel == ConnectionManagerImpl.JDBC_KEEP_ALIVE &&
             jdbcTestStatement != null &&
             jdbcTestStatement.length() > 0) {
            // Can't use an iterator since it could be modified while we're testing
            Vector fList = new Vector(freeList);
            Connection connection;
            // FIXME Removed a new ConnectionRequestInfoImpl() which was useless, and never used.
            // BTW This seems crappy to have the Connection Pool relying on JDBC classes.
            // It should be generic in regards to type of Connection
            ConnectionRequestInfo cxri = null;
            for (int i = 0;i < fList.size(); i++) {
                ManagedConnection mc = (ManagedConnection) fList.elementAt(i);
                connection = (Connection) mc.getConnection(null, cxri);
                java.sql.Statement stmt = null;
                if (conLogger.isLoggable(BasicLevel.DEBUG)) {
                    conLogger.log(BasicLevel.DEBUG, "Sending " + jdbcTestStatement + " on MC " + mc);
                }
                try {
                    stmt = connection.createStatement();
                    stmt.execute(jdbcTestStatement);
                    stmt.close();
                } catch (SQLException se) {
                    // An error will cause us to remove this connection
                    if (stmt != null) {
                        stmt.close();
                    }
                    synchronized (this) {
                        if (conLogger.isLoggable(BasicLevel.DEBUG)) {
                            conLogger.log(BasicLevel.DEBUG, "Freeing MC " + mc);
                        }
                        try {
                            matchFactory.releaseResource(mc);
                        } catch (Exception ex) {
                        }
                        // If there is an error removing from the freeList, it could
                        // have been cleaned up while sending statements to the DB
                        try {
                            freeList.remove(mc);
                            infoList.remove(mc);
                            mc.destroy();
                        } catch (Exception ex) {
                        }
                    }
                }
            }
        }
    }

    /**
     * Set the item statistics
     * @param res Object of the resource
     */
    private void setItemStats(Object res) {
        PoolItemStats pis = (PoolItemStats) infoList.get(res);
        if (pis != null) {
            pis.incrementUses();
            long sTime = System.currentTimeMillis();
            pis.setStartTime(sTime);
            if (maxAge > 0 && pis.getMaxAgeTimeout() == 0) {
                pis.setMaxAgeTimeout(sTime + (maxAge * 60L * 1000));
            }
            if (maxOpentime > 0) {
                pis.setMaxOpenTimeout(sTime + (maxOpentime * 60L * 1000));
            }
        }
        servedOpen++;
    }

    /**
     * Print information about the pool
     *
     */
    void printLists() {
        int count = 0;
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "minSize=" + minSize + ", maxSize=" + maxSize
                                         + ",  freeSize=" + freeList.size());
            logger.log(BasicLevel.DEBUG, "activeList:");
            if (activeList == null) {
                logger.log(BasicLevel.DEBUG, " null");
            } else {
                Iterator it = activeList.iterator();
                while (it.hasNext() && ++count < 40) {
                    ManagedConnection mc = (ManagedConnection) it.next();
                    PoolItemStats pis = (PoolItemStats) infoList.get(mc);
                    logger.log(BasicLevel.DEBUG, " " + mc);
                    logger.log(BasicLevel.DEBUG, " " + pis.toString());
                }
            }
            logger.log(BasicLevel.DEBUG, "freeList:");
            if (freeList == null) {
                logger.log(BasicLevel.DEBUG, " null");
            } else {
                count = 0;
                Iterator it = freeList.iterator();
                while (it.hasNext() && ++count < 40) {
                    ManagedConnection mc = (ManagedConnection) it.next();
                    PoolItemStats pis = (PoolItemStats) infoList.get(mc);
                    logger.log(BasicLevel.DEBUG, " " + mc);
                    logger.log(BasicLevel.DEBUG, " " + pis.toString());
                }
            }
        }
    }

}