/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.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.objectweb.jonas.resource.pool.lib;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.resource.spi.ManagedConnection;
import javax.transaction.Transaction;

import org.objectweb.jonas.common.Log;
import org.objectweb.jonas.resource.ConnectionManagerImpl;
import org.objectweb.jonas.resource.pool.api.Pool;
import org.objectweb.jonas.resource.pool.api.PoolItemStats;
import org.objectweb.jonas.resource.pool.api.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 LinkedHashSet 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;
    
    /**
     * The jndiname
     */
    private String jndiName = null;

    /**
     * Pool closed indicator
     */
    private boolean poolClosed = false;

    /**
     * Pool may be observable (default = not observable)
     */
    private boolean observable = false;

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

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

    /**
     * Set the pool observable
     * @param obs = true if keep stack traces at open
     */
    public void setObservable(boolean obs) {
        observable = obs;
    }

    /**
     * @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.objectweb.jonas.resource.pool.api.Pool#getInitSize
     */
    public int getInitSize() {
        return initSize;
    }

    /**
     * @see org.objectweb.jonas.resource.pool.api.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.objectweb.jonas.resource.pool.api.Pool#getJdbcConnLevel
     */
    public int getJdbcConnLevel() {
        return jdbcConnLevel;
    }

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

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

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

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

    /**
     * @see org.objectweb.jonas.resource.pool.api.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.objectweb.jonas.resource.pool.api.Pool#getMaxSize
     */
    public int getMaxSize() {
        return maxSize;
    }

    /**
     * @see org.objectweb.jonas.resource.pool.api.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.objectweb.jonas.resource.pool.api.Pool#getMinSize
     */
    public int getMinSize() {
        return minSize;
    }

    /**
     * @see org.objectweb.jonas.resource.pool.api.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.objectweb.jonas.resource.pool.api.Pool#getTimeout
     */
    public long getTimeout() {
        return timeout;
    }

    /**
     * @see org.objectweb.jonas.resource.pool.api.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.objectweb.jonas.resource.pool.api.Pool#getResource
     */
    public Object getResource(Object hints)
        throws Exception {
    	return getResource(hints, false);
    }

    /**
     * Get a Resource from the freelist, matching hints.
     * the matchFactory is used to check the hints.
     * * If freelist is empty, create a new resource except if freeListOnly is set.
     * @param hints
     * @param freeListOnly don't create new resource when freelist is empty.
     * @return  a ManagedConnection
     * @throws Exception
     */
    public synchronized Object getResource(Object hints, boolean freeListOnly)
        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 && freeListOnly) {
            	throw new Exception("No Free entry");
            }
            
            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");
                    }
                }
            }
        }
        if (infoList.get(res) == null) {
            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.objectweb.jonas.resource.pool.api.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, "Removed Resource: " + res);
        }
        if (maxSize == NO_POOLING || destroy) {
            try {
                res.destroy();
                if (conLogger.isLoggable(BasicLevel.DEBUG)) {
                    conLogger.log(BasicLevel.DEBUG, "Destroyed Resource: " + res);
                }
            } catch (IllegalStateException e) {
                // cannot destroy resource being used.
                activeList.add(res);
                throw e;
            }
            infoList.remove(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);
            }
        }
        freeList.clear();

        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);
            }
        }
        activeList.clear();
        
        poolClosed = true;
    }

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

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

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

    /**
     * @see org.objectweb.jonas.resource.pool.api.Pool#validateMCs
     */
    public synchronized void validateMCs() throws Exception {
    	if (poolClosed) {
    		return;
    	}
        matchFactory.validateResource(freeList);
    }

    /**
     * return a list of idents that represent the connections
     * opened for a given nb of seconds
     * @param usedTimeMs nb of milliseconds the Connection has been opened
     * @return array of idents representing the Connections
     */
    public synchronized int[] getOpenedConnections(final long usedTimeMs) {
        List connections = new ArrayList();
        for (Iterator it = activeList.iterator(); it.hasNext();) {
            ManagedConnection res = (ManagedConnection) it.next();
            PoolItemStats pis = (PoolItemStats) infoList.get(res);
            if (pis != null) {
                long duration = System.currentTimeMillis() - pis.getStartTime();
                if ((pis.getUses() > 0) && (duration >= usedTimeMs)) {
                    connections.add(new Integer(pis.getIdent()));
                }
            }
        }

        // Create the array of connection Ids
        int[] ids = new int[connections.size()];
        int idx = 0;
        for (Iterator i = connections.iterator(); i.hasNext();) {
            Integer id = (Integer) i.next();
            ids[idx++] = id.intValue();
        }

        return ids;
    }

    /**
     * force the close of the Connection identified by ots Id
     * @param connectionId int that represent the Connection
     */
    public synchronized void forceCloseConnection(final int connectionId) {
        ManagedConnection res = null;
        for (Iterator it = activeList.iterator(); it.hasNext();) {
            ManagedConnection res1 = (ManagedConnection) it.next();
            PoolItemStats pis = (PoolItemStats) infoList.get(res1);
            if (pis != null && pis.getIdent() == connectionId) {
                res = res1;
                break;
            }
        }
        if (res != null) {
            try {
                releaseResource(res, true, false);
                logger.log(BasicLevel.WARN, "Force close of active connection");
                logger.log(BasicLevel.WARN, "MC = " + res);
            } catch (Exception e) {
                logger.log(BasicLevel.WARN, "Could not close of active connection: " + e);
                logger.log(BasicLevel.WARN, "MC = " + res);
            }
        }  else {
            logger.log(BasicLevel.WARN, "Could not find this Connection " + connectionId);
        }
    }

    public synchronized ManagedConnection getConnectionById(int connectionId) {
        for (Iterator it = activeList.iterator(); it.hasNext();) {
            ManagedConnection res = (ManagedConnection) it.next();
            PoolItemStats pis = (PoolItemStats) infoList.get(res);
            if (pis != null && pis.getIdent() == connectionId) {
                return res;
            }
        }
        return null;
    }

    public Map getConnectionDetails(ManagedConnection res, Transaction tx) {
        PoolItemStats pis = (PoolItemStats) infoList.get(res);
        Map details = new HashMap();

        // connection-id : Integer
        details.put("id", new Integer(pis.getIdent()));

        // open-count : Integer
        details.put("uses-count", new Integer(pis.getUses()));

        // duration (ms) : Long
        long duration = System.currentTimeMillis() - pis.getStartTime();
        details.put("duration", new Long(duration));

        // connection-age (ms)
        long age = System.currentTimeMillis() - pis.getCreationTime();
        details.put("age", new Long(age));

        // connection-tx-timeout : String
        String xid = "null";
        if (tx != null) {
            xid = tx.toString();
        }
        details.put("transaction", xid);

        // Openers Thread infos : List<Map<thread-name, time, stack>>
        details.put("openers", pis.getOpenerThreadInfos());

        // Closers Thread infos : List<Map<thread-name, time, stack>>
        details.put("closers", pis.getCloserThreadInfos());

        return details;
    }

    /**
     * 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

    	if (poolClosed) {
    		return;
    	}
		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();
            try {
                releaseResource(item, true, false);
                logger.log(BasicLevel.WARN, "close a timed out active connection");
                logger.log(BasicLevel.WARN, "MC = " + item);
                connectionLeaks++;
            } catch (Exception e) {
                logger.log(BasicLevel.WARN, "Cannot destroy resource being used");
                logger.log(BasicLevel.WARN, "MC = " + item);
            }
        }


        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.objectweb.jonas.resource.pool.api.Pool#sampling
     */
    public void sampling() throws Exception {

    	if (poolClosed) {
    		return;
    	}
    	//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) {

	    	Vector mcs = new Vector();
	    	Connection connection = null;
            int fSize = freeList.size();
            boolean releaseRes = false;
            for (int i = 0;i < fSize; i++) {
                ManagedConnection mc = null;
            	try {
                    mc = (ManagedConnection) getResource(null, true);
            	} catch (Exception e) {
            		return;
            	}
                connection = (Connection) mc.getConnection(null, null);
                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();
                    connection.close();
                } catch (SQLException se) {
                	// An error will cause us to remove this connection
                	try {
                		releaseRes = true;
            	        if (stmt != null) {
                            stmt.close();
            	        }
                	} catch (Exception e) {
                	}
                	try {
            	        connection.close();
                	} catch (Exception e) {}
                }
                try {
                    releaseResource(mc, releaseRes, false);
                    logger.log(BasicLevel.WARN, "close an errored connection");
                } catch (Exception e) {
                    logger.log(BasicLevel.WARN, "Cannot destroy errored resource being used");
                }
            }
        }
    }

    /**
     * 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));
            }
            if (observable) {
                // Store the Stack trace
                pis.addOpenerThreadInfos();
            }
        }
        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());
                }
            }
        }
    }

}