/**
 * 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.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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ValidatingManagedConnectionFactory;
import javax.resource.ResourceException;
import javax.transaction.Transaction;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
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;

/**
 * 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. not used today.
     */
    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. Default = no limit.
     */
    private int maxSize = NO_LIMIT;

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

    /**
     * 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
     * @param jndiname jndi name of the RA
     */
    public HArrayPool(final Logger logger, final 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(final boolean obs) {
        observable = obs;
    }

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

    /**
     * @return int number of physically 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(final int v) throws Exception {
        if (initSize >= 0) {
            logger.log(BasicLevel.WARN, "Already set");
            return;
        }
        initSize = v;
        if (initSize == 0 || maxSize == NO_POOLING) {
            return;
        }
        // Check coherence of values
        if (maxSize > 0 && initSize > maxSize) {
            logger.log(BasicLevel.ERROR, "initSize="+initSize+" should be less than maxSize="+maxSize);
            initSize = maxSize;
        }
        // Create initial set of resources
        ManagedConnection res;
        for (int i = 0; i < initSize; i++) {
            res = createResource(null);
            synchronized (this) {
                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(final 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(final 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(final 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(final 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 void setMaxSize(final int val) throws Exception {
        if (val == maxSize) {
            return;
        }
        if (val < minSize) {
            logger.log(BasicLevel.WARN, "Bad arg maxsize:" + val);
            return;
        }
        // Make adjustement only if too many Connections
        boolean shrink = (val < getSize());
        maxSize = val;
        if (shrink) {
            shrinkPool();
        }
    }

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

    /**
     * @param nb max nb of waiters
     */
    public void setMaxWaiters(final 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(final 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 void setMinSize(final int val) throws Exception {
        if (val == minSize) {
            return;
        }
        if ((val < 0) || ((val > maxSize) && (maxSize > 0))) {
            logger.log(BasicLevel.WARN, "Bad arg minsize:" + val);
            return;
        }
        // make adjustment only if not enough connections in freeList
        boolean feed = (val > freeList.size());
        minSize = val;
        if (feed) {
            feedFreeList();
        }
    }

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

    /**
     * @param sec sampling period in sec.
     */
    public void setSamplingPeriod(final 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
     * not used
     */
    public void setTimeout(final 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

    /**
     * Get a Resource from the freelist, matching hints.
     * the matchFactory is used to check the hints.
     * If freelist is empty, create a new resource
     * @param hints
     * @see org.ow2.jonas.resource.internal.pool.Pool#getResource
     * @return the matching resource
     * @throws Exception cannot get a Resource
     */
    public Object getResource(final Object hints)
        throws Exception {
        if (matchFactory == null) {
            throw new Exception("The matchFactory is mandatory!!");
        }
        ManagedConnection res = null;
        long timetowait = maxWaitTimeout;
        long starttime = 0;
        synchronized(this) {
            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);
                            break;
                        }
                    } catch (Exception ex) {
                        logger.log(BasicLevel.WARN, "Error from matchResource", ex);
                    }
                }

                int curSize = activeList.size() + freeList.size();
                if (maxSize < 0 || curSize < maxSize) {
                    // we can create more resources
                    res = createResource(hints);
                    activeList.add(res);
                } else if (freeList.size() > 0) {
                    // remove one from freelist and recreate a good one instead
                    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();
        if (conLogger.isLoggable(BasicLevel.DEBUG)) {
            conLogger.log(BasicLevel.DEBUG, "Returned Resource: " + res);
        }
        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(final Object hints) throws Exception {
        if (matchFactory == null) {
            throw new Exception("The matchFactory is mandatory!!");
        }
        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);
        }
        return res;
    }

    /**
     * Remove Resource from activeList
     * @param resource ManagedConnection to be released
     * @param destroy true if do not keep it in freeList
     * @see org.ow2.jonas.resource.internal.pool.Pool#releaseResource
     */
    public void releaseResource(final Object resource, final boolean destroy)
        throws Exception {

        ManagedConnection res = (ManagedConnection) resource;
        synchronized(this) {
            if (!activeList.contains(res)) {
                conLogger.log(BasicLevel.WARN, "Attempt to release inactive resource");
                return;
            }
            activeList.remove(res);
            if (maxSize == NO_POOLING || destroy) {
                try {
                    // Destroy underlying physical Connection
                    res.destroy();
                    if (conLogger.isLoggable(BasicLevel.DEBUG)) {
                        conLogger.log(BasicLevel.DEBUG, "Destroyed Resource: " + res);
                    }
                    infoList.remove(res);
                } catch (IllegalStateException e) {
                    // cannot destroy resource being used.
                    activeList.add(res);
                    conLogger.log(BasicLevel.WARN, "Attempt to release resource being used:" + e);
                    throw e;
                } catch (ResourceException e) {
                    activeList.add(res);
                    conLogger.log(BasicLevel.WARN, "Could not release resource:" + e);
                    throw e;
                }
            } else {
                // Keep the Connection in the freeList
                freeList.add(res);
                PoolItemStats pis = (PoolItemStats) infoList.get(res);
                if (pis != null) {
                    // update total connection time
                    pis.setTotalConnectionTime(System.currentTimeMillis() - pis.getStartTime());
                }
            }
            // Notify threads waiting for a Connection.
            if (currentWaiters > 0) {
                notifyAll();
            }
        }
        shrinkPool();
        recomputeBusy();
    }

    /**
     * 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 from freeList
        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();

        // Close physically all connections from activeList
        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.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(final PoolMatchFactory pmf) {
        matchFactory = pmf;
    }

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

    /**
     * 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);
                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(final 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(final ManagedConnection res, final Transaction tx) {
        PoolItemStats pis = (PoolItemStats) infoList.get(res);
        Map details = new HashMap();
        long curTime = System.currentTimeMillis();

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

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

        // 2: status : Boolean
        boolean inactive = false;
        if (pis.getMaxOpenTimeout() > 0 && curTime > pis.getMaxOpenTimeout()) {
            inactive = true;
        }
        details.put("inactive", Boolean.valueOf(inactive));

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

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

        // 5: connection-age (ms)
        long age = curTime - pis.getCreationTime();
        details.put("age", new Long(age));

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

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

        return details;
    }

    /**
     * @see org.ow2.jonas.resource.internal.pool.Pool#validateMCs
     */
    public void validateMCs() throws Exception {
    	if (poolClosed) {
    		return;
    	}
        if (matchFactory == null) {
            throw new Exception("The matchFactory is mandatory!!");
        }
        ValidatingManagedConnectionFactory vmcf = matchFactory.getValidatingMCFactory();
        if (vmcf == null) {
            return;
        }
        conLogger.log(BasicLevel.DEBUG, "");

        // Get MC out of the freeList
        int count = 0;
        HashSet checkList = new HashSet();
        synchronized(this) {
            for (Iterator it = freeList.iterator(); it.hasNext();) {
                ManagedConnection res = (ManagedConnection) it.next();
                it.remove();
                checkList.add(res);
                count++;
                if (count >= MAX_REMOVE_FREELIST) {
                    break;
                }
            }
        }
        // Check these Managed Connections (outside the lock)
        Set invMcs = vmcf.getInvalidConnections(checkList);
        for (Iterator it = invMcs.iterator(); it.hasNext();) {
            ManagedConnection res = (ManagedConnection) it.next();
            // remove from the list, and forget the connection
            checkList.remove(res);
            synchronized(this) {
                infoList.remove(res);
            }
            res.destroy();  // physical close
            matchFactory.releaseResource(res);
        }
        // Put back good Connections in freeList
        synchronized(this) {
            for (Iterator it = checkList.iterator(); it.hasNext();) {
                ManagedConnection res = (ManagedConnection) it.next();
                freeList.add(res);
            }
        }

        feedFreeList();
    }

    /**
     * Remove max aged elements in freelist
     * - Not more than MAX_REMOVE_FREELIST
     * @return true if something has been done
     */
    private boolean pruneMaxAged() throws Exception {
    	if (poolClosed) {
    		return false;
    	}
        if (matchFactory == null) {
            throw new Exception("The matchFactory is mandatory!!");
        }
        boolean ret = false;
		long curTime = System.currentTimeMillis();
        // count is used to remove not more than MAX_REMOVE_FREELIST
        int count = 0;
        LinkedList removeList = new LinkedList();
        synchronized(this) {
            for (Iterator it = freeList.iterator(); 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);
                    }
                    it.remove();
                    infoList.remove(res);
                    removeList.add(res);
                    count++;
                    if (count >= MAX_REMOVE_FREELIST) {
                        break;
                    }
                }
            }
        }
        // Physically close these connections, outside the lock,
        // because in some cases, closing may be very long or even block.
        for (Iterator it = removeList.iterator(); it.hasNext();) {
            ManagedConnection res = (ManagedConnection) it.next();
            try {
                ret = true;
                matchFactory.releaseResource(res);
                res.destroy();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return ret;
    }

    /**
     * Close (physically) connections lost (opened for too long time)
     * @return true if something has been done
     */
    private boolean pruneLostConnections() throws Exception {
    	if (poolClosed) {
    		return false;
    	}
        if (matchFactory == null) {
            throw new Exception("The matchFactory is mandatory!!");
        }

        boolean ret = false;
        long curTime = System.currentTimeMillis();
        LinkedList removeList = new LinkedList();
        synchronized(this) {
            for (Iterator 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()) {
                    removeList.add(res);
                }
            }
        }
        // Physically close these connections, outside the lock,
        // because in some cases, closing may be very long or even block.
        for (Iterator it = removeList.iterator(); it.hasNext();) {
            ManagedConnection item = (ManagedConnection) it.next();
            try {
                releaseResource(item, true);
                matchFactory.releaseResource(item);
                logger.log(BasicLevel.WARN, "close a timed out active connection");
                logger.log(BasicLevel.WARN, "MC = " + item);
                connectionLeaks++;
                ret = true;
            } catch (Exception e) {
                logger.log(BasicLevel.WARN, "Cannot destroy resource being used:" +e);
                logger.log(BasicLevel.WARN, "MC = " + item);
            }
        }
        return ret;
    }

    /**
     * Shrink the pool in case of max pool size
     * This occurs when max pool size has been reduced by jonas admin.
     * @return true if something has been done
     */
    private boolean shrinkPool() throws Exception {
        int curSize = getSize();
        boolean ret = false;
        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) {
                LinkedList removeList = new LinkedList();
                synchronized(this) {
                    while (!freeList.isEmpty() && nbRemove > 0) {
                        ManagedConnection res = (ManagedConnection) freeList.iterator().next();
                        freeList.remove(res);
                        removeList.add(res);
                        nbRemove--;
                        curSize--;
                    }
                }
                // Physically close these connections, outside the lock,
                // because in some cases, closing may be very long or even block.
                for (Iterator i = removeList.iterator(); i.hasNext();) {
                    ManagedConnection res = (ManagedConnection) i.next();
                    i.remove();
                    matchFactory.releaseResource(res);
                    res.destroy();
                    infoList.remove(res);
                    ret = true;
                }
            }
        }
        return ret;
    }

    /**
     * Recreate more Connections while minSize is not reached
     * @return true if something has been done
     */
    private boolean feedFreeList() throws Exception {
        boolean ret = false;
        if (maxSize != NO_POOLING) {
            synchronized(this) {
                while (minSize > getSize()) {
                    ManagedConnection 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);
                    ret = true;
                }
            }
        }
        return ret;
    }

    /**
     * Adjust the pool size, according to poolMax and minSize values.
     * Also remove old connections in the freeList.
     * do not synchronize this method: the lock should not be kept too long
     * especially during the connection close that could block.
     * @throws Exception if an exception occurs
     */
    public void adjust() throws Exception {
        if (conLogger.isLoggable(BasicLevel.DEBUG)) {
            conLogger.log(BasicLevel.DEBUG, jndiName + " active= "+activeList.size() +" min= "+minSize);
            this.printLists();
        }
        boolean pma = pruneMaxAged();
        boolean plc = pruneLostConnections();
        shrinkPool();
        if (pma || plc) {
            feedFreeList();
        }
        recomputeBusy();
    }

    /**
     * compute current min/max busyConnections
     */
    public synchronized 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 synchronized void sampling() throws Exception {

    	if (poolClosed) {
    		return;
    	}

        waitingHighRecent = waitingTime;
        if (waitingHigh < waitingTime) {
            waitingHigh = waitingTime;
        }
        waitingTime = 0;

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

        // Keep old values and init new ones with current busy
        busyMaxRecent = busyMax;
        busyMax = getCurrentBusy();
        busyMinRecent = busyMin;
        busyMin = getCurrentBusy();
    }

    /**
     * Set the item statistics
     * @param res Object of the resource
     */
    private void setItemStats(final 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() {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            int count = 0;
            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());
                }
            }
        }
    }

}
