Package org.logicalcobwebs.proxool

Source Code of org.logicalcobwebs.proxool.ConnectionPool

/*
* This software is released under a licence similar to the Apache Software Licence.
* See org.logicalcobwebs.proxool.package.html for details.
* The latest version is available at http://proxool.sourceforge.net
*/
package org.logicalcobwebs.proxool;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logicalcobwebs.concurrent.ReaderPreferenceReadWriteLock;
import org.logicalcobwebs.concurrent.WriterPreferenceReadWriteLock;
import org.logicalcobwebs.proxool.admin.Admin;
import org.logicalcobwebs.proxool.util.FastArrayList;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;

/**
* This is where most things happen. (In fact, probably too many things happen in this one
* class).
* @version $Revision: 1.86 $, $Date: 2007/01/25 23:38:24 $
* @author billhorsman
* @author $Author: billhorsman $ (current maintainer)
*/
class ConnectionPool implements ConnectionPoolStatisticsIF {

    /**
     * Use this for messages that aren't useful for the pool specific log
     */
    private static final Log LOG = LogFactory.getLog(ConnectionPool.class);

    /**
     * Here we deviate from the standard of using the classname for the log
     * name. Here we want to use the alias for the pool so that we can log
     * each pool to different places. So we have to instantiate the log later.
     */
    private Log log;

    private ReaderPreferenceReadWriteLock connectionStatusReadWriteLock = new ReaderPreferenceReadWriteLock();

    /**
     * If you want to shutdown the pool you should get a write lock on this. And if you use the pool then
     * get a read lock. This stops us trying to shutdown the pool whilst it is in use. Only, we don't want
     * to delay shutdown just because some greedy user has got a connection active. Shutdown should be
     * relatively immediate. So we don't ask for a read lock for the whole time that a connection is active.
     */
    private WriterPreferenceReadWriteLock primaryReadWriteLock = new WriterPreferenceReadWriteLock();

    private static final String[] STATUS_DESCRIPTIONS = {"NULL", "AVAILABLE", "ACTIVE", "OFFLINE"};

    private static final String MSG_MAX_CONNECTION_COUNT =
            "Couldn't get connection because we are at maximum connection count and there are none available";

    /** This is the pool itself */
    private List proxyConnections;

    /** This is the "round robin" that makes sure we use all the connections */
    private int nextAvailableConnection = 0;

    private long connectionsServedCount = 0;

    private long connectionsRefusedCount = 0;

    /** This keeps a count of how many connections there are in each state */
    private int[] connectionCountByState = new int[4];

    private ConnectionPoolDefinition definition;

    private CompositeConnectionListener compositeConnectionListener = new CompositeConnectionListener();

    private CompositeStateListener compositeStateListener = new CompositeStateListener();

    private long timeOfLastRefusal = 0;

    private int upState;

    private static boolean loggedLegend;

    private Admin admin;

    private boolean locked = false;

    private Date dateStarted = new Date();

    private boolean connectionPoolUp = false;

    /**
     * This gets set during {@link #shutdown}. We use it to notify shutdown
     * that all connections are now non-active.
     */
    private Thread shutdownThread;

    private Prototyper prototyper;

    /**
     * Initialised in {@link ConnectionPool#ConnectionPool constructor}.
     */
    private ConnectionResetter connectionResetter;

    private ConnectionValidatorIF connectionValidator;
   
   
    protected ConnectionPool(ConnectionPoolDefinition definition) throws ProxoolException {

        // Use the FastArrayList for performance and thread safe
        // behaviour. We set its behaviour to "fast"  (meaning reads are
        // unsynchronized, whilst writes are not).
        FastArrayList fal = new FastArrayList();
        fal.setFast(true);
        proxyConnections = fal;

        log = LogFactory.getLog("org.logicalcobwebs.proxool." + definition.getAlias());
        connectionResetter = new ConnectionResetter(log, definition.getDriver());
        setDefinition(definition);

        connectionValidator = new DefaultConnectionValidator();
       
        if (definition.getStatistics() != null) {
            try {
                admin = new Admin(definition);
            } catch (ProxoolException e) {
                log.error("Failed to initialise statistics", e);
            }
        }

        ShutdownHook.init();
    }

    /** Starts up house keeping and prototyper threads. */
    protected void start() throws ProxoolException {
        connectionPoolUp = true;
        prototyper = new Prototyper(this);
        HouseKeeperController.register(this);
    }

    /**
     * Get a connection from the pool.  If none are available or there was an Exception
     * then an exception is thrown and something written to the log
     */
    protected Connection getConnection() throws SQLException {

        String requester = Thread.currentThread().getName();

        /*
         *If we're busy, we need to return as quickly as possible. Because this is unsynchronized
         * we run the risk of refusing a connection when we might actually be able to. But that will
         * only happen when we're right at or near maximum connections anyway.
         */

        try {
            prototyper.quickRefuse();
        } catch (SQLException e) {
            connectionsRefusedCount++;
            if (admin != null) {
                admin.connectionRefused();
            }
            log.info(displayStatistics() + " - " + MSG_MAX_CONNECTION_COUNT);
            timeOfLastRefusal = System.currentTimeMillis();
            setUpState(StateListenerIF.STATE_OVERLOADED);
            throw e;
        }

        prototyper.checkSimultaneousBuildThrottle();      
       
        ProxyConnection proxyConnection = null;

        try {

            // We need to look at all the connections, but we don't want to keep looping round forever
            for (int connectionsTried = 0; connectionsTried < proxyConnections.size(); connectionsTried++) {
                // By doing this in a try/catch we avoid needing to synch on the size().  We need to do be
                // able to cope with connections being removed whilst we are going round this loop
                try {
                    proxyConnection = (ProxyConnection) proxyConnections.get(nextAvailableConnection);
                } catch (ArrayIndexOutOfBoundsException e) {
                    // This is thrown by a Vector (which we no longer use), but is
                    // kept here for a while.
                    nextAvailableConnection = 0;
                    proxyConnection = (ProxyConnection) proxyConnections.get(nextAvailableConnection);
                } catch (IndexOutOfBoundsException e) {
                    // This is thrown by a true List
                    nextAvailableConnection = 0;
                    proxyConnection = (ProxyConnection) proxyConnections.get(nextAvailableConnection);
                }
                // setActive() returns false if the ProxyConnection wasn't available.  You
                // can't set it active twice (at least, not without making it available again
                // in between)
                if (proxyConnection != null && proxyConnection.setStatus(ProxyConnectionIF.STATUS_AVAILABLE, ProxyConnectionIF.STATUS_ACTIVE)) {

                    // Okay. So we have it. But is it working ok?
                    if (getDefinition().isTestBeforeUse()) {
                        if (!testConnection(proxyConnection)) {
                            // Oops. No it's not. Let's choose another.
                            proxyConnection = null;
                        }
                    }
                    if (proxyConnection != null) {
                        nextAvailableConnection++;
                        break;
                    }
                } else {
                    proxyConnection = null;
                }
                nextAvailableConnection++;
            }
            // Did we get one?
            if (proxyConnection == null) {
                try {
                    // No!  Let's see if we can create one
                    proxyConnection = prototyper.buildConnection(ProxyConnection.STATUS_ACTIVE, "on demand");
                   
                    // Okay. So we have it. But is it working ok?
                    if (getDefinition().isTestBeforeUse()) {
                        if (!testConnection(proxyConnection)) {
                            // Oops. No it's not. There's not much more we can do for now
                            throw new SQLException("Created a new connection but it failed its test");
                        }
                    }
                } catch (SQLException e) {
                    throw e;
                } catch (ProxoolException e) {
                    log.debug("Couldn't get connection", e);
                    throw new SQLException(e.toString());
                } catch (Throwable e) {
                    log.error("Couldn't get connection", e);
                    throw new SQLException(e.toString());
                }
            }

        } catch (SQLException e) {
            throw e;
        } catch (Throwable t) {
            log.error("Problem getting connection", t);
            throw new SQLException(t.toString());
        } finally {
            if (proxyConnection != null) {
                connectionsServedCount++;
                proxyConnection.setRequester(requester);
            } else {
                connectionsRefusedCount++;
                if (admin != null) {
                    admin.connectionRefused();
                }
                timeOfLastRefusal = System.currentTimeMillis();
                setUpState(StateListenerIF.STATE_OVERLOADED);
            }
        }

        if (proxyConnection == null) {
            throw new SQLException("Unknown reason for not getting connection. Sorry.");
        }

        if (log.isDebugEnabled() && getDefinition().isVerbose()) {
            log.debug(displayStatistics() + " - Connection #" + proxyConnection.getId() + " served");
        }

        // This gives the proxy connection a chance to reset itself before it is served.
        proxyConnection.open();

        return ProxyFactory.getWrappedConnection(proxyConnection);
    }

    /**
     * Test the connection (if required)
     * If the connection fails the test, it is removed from the pool.
     * If no ConnectionValidatorIF is defined, then the test always succeed.
     *
     * @param proxyConnection the connection to test
     * @return TRUE if the connection pass the test, FALSE if it fails
     */
    private boolean testConnection(ProxyConnectionIF proxyConnection) {
        // is validation enabled ?
        if( connectionValidator == null ) {
            return true;
        }
       
        // validate the connection
        boolean success = connectionValidator.validate(getDefinition(), proxyConnection.getConnection());
       
        if( success ) {
            if (LOG.isDebugEnabled()) {
                LOG.debug(displayStatistics() + " - Connection #" + proxyConnection.getId() + " tested: OK");
            }
        }
        else {
            proxyConnection.setStatus(ProxyConnectionIF.STATUS_NULL);
            removeProxyConnection(proxyConnection, ConnectionListenerIF.VALIDATION_FAIL, "it didn't pass the validation", ConnectionPool.REQUEST_EXPIRY, true);
        }
       
        // return
        return success;
    }

    /**
     * Add a ProxyConnection to the pool
     * @param proxyConnection new connection
     * @return true if the connection was added or false if it wasn't (for instance, if the definition it
     * was built with is out of date).
     */
    protected boolean addProxyConnection(ProxyConnectionIF proxyConnection) {
        boolean added = false;
        try {
            acquireConnectionStatusWriteLock();
            if (proxyConnection.getDefinition() == getDefinition()) {
                proxyConnections.add(proxyConnection);
                connectionCountByState[proxyConnection.getStatus()]++;
                added = true;
            }
        } finally {
            releaseConnectionStatusWriteLock();
        }
        return added;
    }

    protected static String getStatusDescription(int status) {
        try {
            return STATUS_DESCRIPTIONS[status];
        } catch (ArrayIndexOutOfBoundsException e) {
            return "Unknown status: " + status;
        }
    }

    /**
     * When you have finished with a Connection you should put it back here.  That will make it available to others.
     * Unless it's due for expiry, in which case it will... expire
     */
    protected void putConnection(ProxyConnectionIF proxyConnection) {
     
        if (admin != null) {
            long now = System.currentTimeMillis();
            long start = proxyConnection.getTimeLastStartActive();
            if (now - start < 0) {
                log.warn("Future start time detected. #" + proxyConnection.getId() + " start = " + new Date(start)
                        + " (" + (now - start) + " milliseconds)");
            } else if (now - start > 1000000) {
                log.warn("Suspiciously long active time. #" + proxyConnection.getId() + " start = " + new Date(start));
            }
            admin.connectionReturned(now - start);
        }

        // It's possible that this connection is due for expiry
        if (proxyConnection.isMarkedForExpiry()) {
            if (proxyConnection.setStatus(ProxyConnectionIF.STATUS_ACTIVE, ProxyConnectionIF.STATUS_NULL)) {
                expireProxyConnection(proxyConnection, proxyConnection.getReasonCode(), proxyConnection.getReasonForMark(), REQUEST_EXPIRY);
            }
        } else {

            // Optionally, test it to see if it is ok
            if (getDefinition().isTestAfterUse()) {
                // It will get removed by this call if it is no good
                testConnection(proxyConnection);
            }

            // Let's make it available for someone else
            if (!proxyConnection.setStatus(ProxyConnectionIF.STATUS_ACTIVE, ProxyConnectionIF.STATUS_AVAILABLE)) {
                if (proxyConnection.getStatus() == ProxyConnectionIF.STATUS_AVAILABLE) {
                    // This is *probably* because the connection has been closed twice.
                    // Although we can't tell for sure. We'll have to refactor this to use
                    // throw away wrappers to avoid this problem.
                    log.warn("Unable to close connection " + proxyConnection.getId()
                            + " - I suspect that it has been closed already. Closing it more"
                            + " than once is unwise and should be avoided.");
                } else {
                    log.warn("Unable to set status of connection " + proxyConnection.getId()
                            + " from " + getStatusDescription(ProxyConnectionIF.STATUS_ACTIVE)
                            + " to " + getStatusDescription(ProxyConnectionIF.STATUS_AVAILABLE)
                            + " because it's state was " + getStatusDescription(proxyConnection.getStatus()));
                }
            }
        }

        if (log.isDebugEnabled() && getDefinition().isVerbose()) {
            log.debug(displayStatistics() + " - Connection #" + proxyConnection.getId() + " returned (now "
                    + getStatusDescription(proxyConnection.getStatus()) + ")");
        }

    }

    /** This means that there's something wrong the connection and it's probably best if no one uses it again. */
    protected void throwConnection(ProxyConnectionIF proxyConnection, int reasonCode, String reason) {
        expireConnectionAsSoonAsPossible(proxyConnection, reasonCode, reason, true);
    }

    /** Get a ProxyConnection by index */
    private ProxyConnectionIF getProxyConnection(int i) {
        return (ProxyConnectionIF) proxyConnections.get(i);
    }

    /**
     * Return an array of all the connections
     * @return array of connections
     */
    protected ProxyConnectionIF[] getProxyConnections() {
        return (ProxyConnectionIF[]) proxyConnections.toArray(new ProxyConnectionIF[proxyConnections.size()]);
    }

    /**
     * Remove a ProxyConnection by calling its {@link ConnectionListenerIF#onDeath onDeath} event,
     * closing it (for real) and then removing it from the list.
     * @param proxyConnection the connection to remove
     * @param reason for log audit
     * @param forceExpiry true means close now, whether it is active or not; false means if it is active then
     * merely mark it for expiry so that it is removed as soon as it finished being active
     * @param triggerSweep if true then this removal will trigger a prototype sweep
     */
    protected void removeProxyConnection(ProxyConnectionIF proxyConnection, int reasonCode, String reason, boolean forceExpiry, boolean triggerSweep) {
        // Just check that it is null
        if (forceExpiry || proxyConnection.isNull()) {

            proxyConnection.setStatus(ProxyConnectionIF.STATUS_NULL);

            /* Run some code everytime we destroy a connection */

            try {
                onDeath(proxyConnection.getConnection(), reasonCode);
            } catch (SQLException e) {
                log.error("Problem during onDeath (ignored)", e);
            }

            // The reallyClose() method also decrements the connectionCount.
            try {
                proxyConnection.reallyClose();
            } catch (SQLException e) {
                log.error(e);
            }

            try {
                // If we're shutting down then getting a write lock will cause a deadlock
                if (isConnectionPoolUp()) {
                    acquireConnectionStatusWriteLock();
                }
                proxyConnections.remove(proxyConnection);
            } finally {
                if (isConnectionPoolUp()) {
                    releaseConnectionStatusWriteLock();
                }
            }

            if (log.isDebugEnabled()) {
                log.debug(displayStatistics() + " - #" + FormatHelper.formatMediumNumber(proxyConnection.getId())
                        + " removed because " + reason + ".");
            }

            if (triggerSweep) {
                PrototyperController.triggerSweep(getDefinition().getAlias());
            }

        } else {
            log.error(displayStatistics() + " - #" + FormatHelper.formatMediumNumber(proxyConnection.getId())
                    + " was not removed because isNull() was false.");
        }
    }

    protected void expireProxyConnection(ProxyConnectionIF proxyConnection, int reasonCode, String reason, boolean forceExpiry) {
        removeProxyConnection(proxyConnection, reasonCode, reason, forceExpiry, true);
    }

    /**
     * Call this to shutdown gracefully.
     *  @param delay how long to wait for connections to become free before forcing them to close anyway
     */
    protected void shutdown(int delay, String finalizerName) throws Throwable {

        final String alias = getDefinition().getAlias();
        try {
            /* This will stop us giving out any more connections and may
            cause some of the threads to die. */

            acquirePrimaryWriteLock();

            if (connectionPoolUp) {

                connectionPoolUp = false;
                long startFinalize = System.currentTimeMillis();
                shutdownThread = Thread.currentThread();

                if (delay > 0) {
                    log.info("Shutting down '" + alias + "' pool started at "
                            + dateStarted + " - waiting for " + delay
                            + " milliseconds for everything to stop.  [ "
                            + finalizerName + "]");
                } else {
                    log.info("Shutting down '" + alias + "' pool immediately [" + finalizerName + "]");
                }

                /* Interrupt the threads (in case they're sleeping) */

                boolean connectionClosedManually = false;
                try {

                    try {
                        HouseKeeperController.cancel(alias);
                    } catch (ProxoolException e) {
                        log.error("Shutdown couldn't cancel house keeper", e);
                    }

                    // Cancel the admin thread (for statistics)
                    if (admin != null) {
                        admin.cancelAll();
                    }

                    /* Patience, patience. */

                    if (connectionCountByState[ProxyConnectionIF.STATUS_ACTIVE] != 0) {
                        long endWait = startFinalize + delay;
                        LOG.info("Waiting until " + new Date(endWait) + " for all connections to become inactive (active count is "
                                + connectionCountByState[ProxyConnectionIF.STATUS_ACTIVE] + ").");
                        while (true) {
                            long timeout = endWait - System.currentTimeMillis();
                            if (timeout > 0) {
                                synchronized (Thread.currentThread()) {
                                    try {
                                        Thread.currentThread().wait(timeout);
                                    } catch (InterruptedException e) {
                                        log.debug("Interrupted whilst sleeping.");
                                    }
                                }
                            }
                            int activeCount = connectionCountByState[ProxyConnectionIF.STATUS_ACTIVE];
                            if (activeCount == 0) {
                                break;
                            }
                            if (System.currentTimeMillis() < endWait) {
                                LOG.info("Still waiting for active count to reach zero (currently " + activeCount + ").");
                            } else {
                                // There are still connections active. Oh well, we're not _that_ patient
                                LOG.warn("Shutdown waited for "
                                        + (System.currentTimeMillis() - startFinalize) + " milliseconds for all "
                                        + "the connections to become inactive but the active count is still "
                                        + activeCount + ". Shutting down anyway.");
                                break;
                            }
                            Thread.sleep(100);
                        }
                    }

                    prototyper.cancel();
                   
                    // Silently close all connections
                    for (int i = proxyConnections.size() - 1; i >= 0; i--) {
                        long id = getProxyConnection(i).getId();
                        try {
                            connectionClosedManually = true;
                            removeProxyConnection(getProxyConnection(i), ConnectionListenerIF.SHUTDOWN, "of shutdown", true, false);
                            if (log.isDebugEnabled()) {
                                log.debug("Connection #" + id + " closed");
                            }
                        } catch (Throwable t) {
                            if (log.isDebugEnabled()) {
                                log.debug("Problem closing connection #" + id, t);
                            }

                        }
                    }

                } catch (Throwable t) {
                    log.error("Unknown problem finalizing pool", t);
                } finally {

                    ConnectionPoolManager.getInstance().removeConnectionPool(alias);

                    if (log.isDebugEnabled()) {
                        log.info("'" + alias + "' pool has been closed down by " + finalizerName
                                + " in " + (System.currentTimeMillis() - startFinalize) + " milliseconds.");
                        if (!connectionClosedManually) {
                            log.debug("No connections required manual removal.");
                        }
                    }
                    super.finalize();
                }
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("Ignoring duplicate attempt to shutdown '" + alias + "' pool by " + finalizerName);
                }
            }
        } catch (Throwable t) {
            log.error(finalizerName + " couldn't shutdown pool", t);
        } finally {
            releasePrimaryWriteLock();
        }
    }

    /**
     * You should {@link #acquireConnectionStatusReadLock acquire}
     * a read lock if you want this to be accurate (but that might have
     * an impact on the performance of your pool).
     * @see ConnectionPoolStatisticsIF#getAvailableConnectionCount
     */
    public int getAvailableConnectionCount() {
        return connectionCountByState[ConnectionInfoIF.STATUS_AVAILABLE];
    }

    /**
     * You should {@link #acquireConnectionStatusReadLock acquire}
     * a read lock if you want this to be accurate (but that might have
     * an impact on the performance of your pool).
     * @see ConnectionPoolStatisticsIF#getActiveConnectionCount
     */
    public int getActiveConnectionCount() {
        return connectionCountByState[ConnectionInfoIF.STATUS_ACTIVE];
    }

    /**
     * You should {@link #acquireConnectionStatusReadLock acquire}
     * a read lock if you want this to be accurate (but that might have
     * an impact on the performance of your pool).
     * @see ConnectionPoolStatisticsIF#getOfflineConnectionCount
     */
    public int getOfflineConnectionCount() {
        return connectionCountByState[ConnectionInfoIF.STATUS_OFFLINE];
    }

    protected String displayStatistics() {

        if (!loggedLegend) {
            log.info("Proxool statistics legend: \"s - r  (a/t/o)\" > s=served, r=refused (only shown if non-zero), a=active, t=total, o=offline (being tested)");
            loggedLegend = true;
        }

        StringBuffer statistics = new StringBuffer();
        statistics.append(FormatHelper.formatBigNumber(getConnectionsServedCount()));

        if (getConnectionsRefusedCount() > 0) {
            statistics.append(" -");
            statistics.append(FormatHelper.formatBigNumber(getConnectionsRefusedCount()));
        }

        statistics.append(" (");
        statistics.append(FormatHelper.formatSmallNumber(getActiveConnectionCount()));
        statistics.append("/");
        statistics.append(FormatHelper.formatSmallNumber(getAvailableConnectionCount() + getActiveConnectionCount()));
        statistics.append("/");
        statistics.append(FormatHelper.formatSmallNumber(getOfflineConnectionCount()));
        statistics.append(")");

        // Don't need this triple check any more.
        /*
        if (getDefinition().getDebugLevel() == ConnectionPoolDefinitionIF.DEBUG_LEVEL_LOUD) {
            statistics.append(", cc=");
            statistics.append(connectionCount);
            statistics.append(", ccc=");
            statistics.append(connectedConnectionCount);
        }
        */

        return statistics.toString();
    }

    protected void expireAllConnections(int reasonCode, String reason, boolean merciful) {

        // Do this in two stages because expiring a connection will trigger
        // the prototyper to make more. And that might mean we end up
        // killing a newly made connection;
        Set pcs = new HashSet();
        for (int i = proxyConnections.size() - 1; i >= 0; i--) {
            pcs.add(proxyConnections.get(i));
        }

        Iterator i = pcs.iterator();
        while (i.hasNext()) {
            ProxyConnectionIF pc = (ProxyConnectionIF) i.next();
            expireConnectionAsSoonAsPossible(pc, reasonCode, reason, merciful);
        }
    }

    protected void expireConnectionAsSoonAsPossible(ProxyConnectionIF proxyConnection, int reasonCode, String reason, boolean merciful) {
        if (proxyConnection.setStatus(ProxyConnectionIF.STATUS_AVAILABLE, ProxyConnectionIF.STATUS_OFFLINE)) {
            if (proxyConnection.setStatus(ProxyConnectionIF.STATUS_OFFLINE, ProxyConnectionIF.STATUS_NULL)) {
                // It is.  Expire it now .
                expireProxyConnection(proxyConnection, reasonCode, reason, REQUEST_EXPIRY);
            }
        } else {
            // Oh no, it's in use.

            if (merciful) {
                //Never mind, we'll mark it for expiry
                // next time it is available.  This will happen in the
                // putConnection() method.
                proxyConnection.markForExpiry(reason);
                if (log.isDebugEnabled()) {
                    log.debug(displayStatistics() + " - #" + FormatHelper.formatMediumNumber(proxyConnection.getId()) + " marked for expiry.");
                }
            } else {
                // So? Kill, kill, kill

                // We have to make sure it's null first.
                expireProxyConnection(proxyConnection, reasonCode, reason, FORCE_EXPIRY);
            }

        } // END if (proxyConnection.setOffline())
    }

    protected void registerRemovedConnection(int status) {
        prototyper.connectionRemoved();
        connectionCountByState[status]--;
    }

    /**
     * You should {@link #acquireConnectionStatusWriteLock acquire} a write lock
     * before calling this method
     * @param oldStatus so we know which count to decrement
     * @param newStatus so we know which count to increment
     */
    protected void changeStatus(int oldStatus, int newStatus) {
        // LOG.debug("About to change status");
        connectionCountByState[oldStatus]--;
        connectionCountByState[newStatus]++;
        // LOG.debug("Changing status from " + oldStatus + " to " + newStatus);
        // Check to see if shutdown is waiting for all connections to become
        // non-active
        if (shutdownThread != null && connectionCountByState[ProxyConnectionIF.STATUS_ACTIVE] == 0) {
            synchronized (shutdownThread) {
                shutdownThread.notify();
            }
        }

    }

    public long getConnectionsServedCount() {
        return connectionsServedCount;
    }

    public long getConnectionsRefusedCount() {
        return connectionsRefusedCount;
    }

    protected ConnectionPoolDefinition getDefinition() {
        return definition;
    }

    /**
     * Changes both the way that any new connections will be made, and the behaviour of the pool. Consider
     * calling expireAllConnections() if you're in a hurry.
     */
    protected synchronized void setDefinition(ConnectionPoolDefinition definition) throws ProxoolException {
        this.definition = definition;

        try {
            Class.forName(definition.getDriver());
        } catch (ClassNotFoundException e) {
            log.error("Couldn't load class " + definition.getDriver(), e);
            throw new ProxoolException("Couldn't load class " + definition.getDriver());
        } catch (NullPointerException e) {
            log.error("Definition did not contain driver", e);
            throw new ProxoolException("Definition did not contain driver");
        }

    }

    /**
     * @deprecated use {@link #addStateListener(StateListenerIF)} instead.
     */
    public void setStateListener(StateListenerIF stateListener) {
        addStateListener(stateListener);
    }

    public void addStateListener(StateListenerIF stateListener) {
        this.compositeStateListener.addListener(stateListener);
    }

    public boolean removeStateListener(StateListenerIF stateListener) {
        return this.compositeStateListener.removeListener(stateListener);
    }

    /**
     * @deprecated use {@link #addConnectionListener(ConnectionListenerIF)} instead.
     */
    public void setConnectionListener(ConnectionListenerIF connectionListener) {
        addConnectionListener(connectionListener);
    }

    public void addConnectionListener(ConnectionListenerIF connectionListener) {
        this.compositeConnectionListener.addListener(connectionListener);
    }

    public boolean removeConnectionListener(ConnectionListenerIF connectionListener) {
        return this.compositeConnectionListener.removeListener(connectionListener);
    }

    /** Call the onBirth() method on each StateListenerIF . */
    protected void onBirth(Connection connection) throws SQLException {
        this.compositeConnectionListener.onBirth(connection);
    }

    /** Call the onDeath() method on each StateListenerIF . */
    protected void onDeath(Connection connection, int reasonCode) throws SQLException {
        this.compositeConnectionListener.onDeath(connection, reasonCode);
    }

    /** Call the onExecute() method on each StateListenerIF . */
    protected void onExecute(String command, long elapsedTime, Exception exception) throws SQLException {
        if (exception == null) {
            this.compositeConnectionListener.onExecute(command, elapsedTime);
        } else {
            this.compositeConnectionListener.onFail(command, exception);
        }
    }

    /**
     * Is there a {@link ConnectionListenerIF listener} for connections
     * @return true if there is a listener registered.
     */
    protected boolean isConnectionListenedTo() {
        return !compositeConnectionListener.isEmpty();
    }

    public String toString() {
        return getDefinition().toString();
    }

    public int getUpState() {
        return upState;
    }


    public void setUpState(int upState) {
        if (this.upState != upState) {
            compositeStateListener.upStateChanged(upState);
            this.upState = upState;
        }
    }

    protected Collection getConnectionInfos() {
        Collection cis = null;
        cis = new TreeSet();
        Iterator i = proxyConnections.iterator();
        while (i.hasNext()) {
            ConnectionInfoIF connectionInfo = (ConnectionInfoIF) i.next();
            ConnectionInfo ci = new ConnectionInfo();
            ci.setAge(connectionInfo.getAge());
            ci.setBirthDate(connectionInfo.getBirthDate());
            ci.setId(connectionInfo.getId());
            ci.setMark(connectionInfo.getMark());
            ci.setRequester(connectionInfo.getRequester());
            ci.setStatus(connectionInfo.getStatus());
            ci.setTimeLastStartActive(connectionInfo.getTimeLastStartActive());
            ci.setTimeLastStopActive(connectionInfo.getTimeLastStopActive());
            ci.setDelegateUrl(connectionInfo.getDelegateUrl());
            ci.setProxyHashcode(connectionInfo.getProxyHashcode());
            ci.setDelegateHashcode(connectionInfo.getDelegateHashcode());
            String[] sqlCalls = connectionInfo.getSqlCalls();
            for (int j = 0; j < sqlCalls.length; j++) {
                ci.addSqlCall(sqlCalls[j]);
            }
            cis.add(ci);
        }
        return cis;
    }

    /**
     * Manually expire a connection.
     * @param id the id of the connection to kill
     * @param forceExpiry use true to expire even if it is in use
     * @return true if the connection was found and expired, else false
     */
    public boolean expireConnection(long id, boolean forceExpiry) {
        boolean success = false;
        ProxyConnection proxyConnection = null;

        // We need to look at all the connections, but we don't want to keep looping round forever
        for (int connectionsTried = 0; connectionsTried < proxyConnections.size(); connectionsTried++) {
            // By doing this in a try/catch we avoid needing to synch on the size().  We need to do be
            // able to cope with connections being removed whilst we are going round this loop
            try {
                proxyConnection = (ProxyConnection) proxyConnections.get(nextAvailableConnection);
            } catch (IndexOutOfBoundsException e) {
                nextAvailableConnection = 0;
                proxyConnection = (ProxyConnection) proxyConnections.get(nextAvailableConnection);
            }

            if (proxyConnection.getId() == id) {
                // This is the one
                proxyConnection.setStatus(ProxyConnectionIF.STATUS_AVAILABLE, ProxyConnectionIF.STATUS_OFFLINE);
                proxyConnection.setStatus(ProxyConnectionIF.STATUS_OFFLINE, ProxyConnectionIF.STATUS_NULL);
                removeProxyConnection(proxyConnection, ConnectionListenerIF.MANUAL_EXPIRY, "it was manually killed", forceExpiry, true);
                success = true;
                break;
            }

            nextAvailableConnection++;
        }

        if (!success) {
            if (log.isDebugEnabled()) {
                log.debug(displayStatistics() + " - couldn't find " + FormatHelper.formatMediumNumber(proxyConnection.getId())
                        + " and I've just been asked to expire it");
            }
        }

        return success;
    }

    public Log getLog() {
        return log;
    }

    /**
     * {@link ConnectionResetter#initialise Initialises} the ConnectionResetter.
     * @param connection sample Connection to use for default values
     */
    protected void initialiseConnectionResetter(Connection connection) {
        connectionResetter.initialise(connection);
    }

    /**
     * {@link ConnectionResetter#reset Resets} a Connection to its
     * original state.
     * @param connection the one to reset
     * @param id the id of the connection
     * @throws SQLException  if the call to {@link java.sql.Connection#isClosed()} fails
     * @return true if it was successfully reset, false if there was a problem (like the connection being already closed)
     */
    protected boolean resetConnection(Connection connection, String id) throws SQLException {
        if (connection.isClosed()) {
            return false;
        } else {
            return connectionResetter.reset(connection, id);
        }
    }

    /**
     * @see ConnectionPoolStatisticsIF#getDateStarted
     */
    public Date getDateStarted() {
        return dateStarted;
    }

    /**
     * Get the admin for this pool
     * @return admin
     */
    protected Admin getAdmin() {
        return admin;
    }

    protected boolean isLocked() {
        return locked;
    }

    protected void lock() {
        locked = true;
    }

    protected void unlock() {
        locked = false;
    }

    /**
     * Call this if you want to do something important to the pool. Like shut it down.
     * @throws InterruptedException if we couldn't
     */
    protected void acquirePrimaryReadLock() throws InterruptedException {
//        if (log.isDebugEnabled()) {
//            try {
//                throw new RuntimeException("TRACE ONLY");
//            } catch (RuntimeException e) {
//                log.debug("About to acquire primary read lock", e);
//            }
//            // log.debug("About to acquire primary read lock");
//        }
        primaryReadWriteLock.readLock().acquire();
//        if (log.isDebugEnabled()) {
//            try {
//                throw new RuntimeException("TRACE ONLY");
//            } catch (RuntimeException e) {
//                log.debug("Acquired primary read lock", e);
//            }
//            //log.debug("Acquired primary read lock");
//        }
    }

    /**
     * @see #acquirePrimaryReadLock
     */
    protected void releasePrimaryReadLock() {
//        try {
//            throw new RuntimeException("TRACE ONLY");
//        } catch (RuntimeException e) {
//            log.debug("Released primary read lock", e);
//        }
        //log.debug("Released primary read lock");
        primaryReadWriteLock.readLock().release();
    }

    /**
     * Call this everytime you build a connection. It ensures that we're not
     * trying to shutdown the pool whilst we are building a connection. So you
     * should check that the pool is still {@link #isConnectionPoolUp up}.
     * @throws InterruptedException if there was a problem.
     */
    protected void acquirePrimaryWriteLock() throws InterruptedException {
//        boolean success = false;
//        try {
//            if (log.isDebugEnabled()) {
//                try {
//                    throw new RuntimeException("TRACE ONLY");
//                } catch (RuntimeException e) {
//                    log.debug("About to acquire primary write lock", e);
//                }
//                //log.debug("About to acquire primary write lock");
//            }
            primaryReadWriteLock.writeLock().acquire();
//            success = true;
//            if (log.isDebugEnabled()) {
//                try {
//                    throw new RuntimeException("TRACE ONLY");
//                } catch (RuntimeException e) {
//                    log.debug("Acquired primary write lock", e);
//                }
//                //log.debug("Acquired primary write lock");
//            }
//        } finally {
//            if (log.isDebugEnabled() && !success) {
//                try {
//                    throw new RuntimeException("TRACE ONLY");
//                } catch (RuntimeException e) {
//                    log.debug("Failed to acquire primary write lock", e);
//                }
//                //log.debug("Failed to acquire primary write lock");
//            }
//        }
    }

    /**
     * @see #acquirePrimaryReadLock
     */
    protected void releasePrimaryWriteLock() {
        primaryReadWriteLock.writeLock().release();
//        try {
//            throw new RuntimeException("TRACE ONLY");
//        } catch (RuntimeException e) {
//            log.debug("Released primary write lock", e);
//        }
        //log.debug("Released primary write lock");
    }

    /**
     * Is the pool up?
     * @return false is the connection pool has been {@link #shutdown shutdown}
     * (or is in the process of being shutdown).
     */
    protected boolean isConnectionPoolUp() {
        return connectionPoolUp;
    }

    protected static final boolean FORCE_EXPIRY = true;

    protected static final boolean REQUEST_EXPIRY = false;

    /**
     * The time (in milliseconds) that we last refused a connection
     * @return timeOfLastRefusal
     */
    protected long getTimeOfLastRefusal() {
        return timeOfLastRefusal;
    }

    protected void acquireConnectionStatusWriteLock() {
        try {
//            try {
//                throw new RuntimeException("TRACE ONLY");
//            } catch (RuntimeException e) {
//                LOG.debug("About to acquire connectionStatus write lock", e);
//            }
            connectionStatusReadWriteLock.writeLock().acquire();
//            try {
//                throw new RuntimeException("TRACE ONLY");
//            } catch (RuntimeException e) {
//                LOG.debug("Acquired connectionStatus write lock", e);
//            }
        } catch (InterruptedException e) {
            log.error("Couldn't acquire connectionStatus write lock", e);
        }
    }

    protected void releaseConnectionStatusWriteLock() {
        connectionStatusReadWriteLock.writeLock().release();
//        try {
//            throw new RuntimeException("TRACE ONLY");
//        } catch (RuntimeException e) {
//            LOG.debug("Released connectionStatus write lock", e);
//        }
    }

    protected void acquireConnectionStatusReadLock() {
        try {
            connectionStatusReadWriteLock.readLock().acquire();
        } catch (InterruptedException e) {
            log.error("Couldn't acquire connectionStatus read lock", e);
        }
    }

    protected boolean attemptConnectionStatusReadLock(long msecs) {
        try {
            return connectionStatusReadWriteLock.readLock().attempt(msecs);
        } catch (InterruptedException e) {
            log.error("Couldn't acquire connectionStatus read lock", e);
            return false;
        }
    }

    protected void releaseConnectionStatusReadLock() {
        connectionStatusReadWriteLock.readLock().release();
//        LOG.debug("Released connectionStatus read lock");
    }

    protected Prototyper getPrototyper() {
        return prototyper;
    }

    public long getConnectionCount() {
        return getPrototyper().getConnectionCount();
    }

   
}

/*
Revision history:
$Log: ConnectionPool.java,v $
Revision 1.86  2007/01/25 23:38:24  billhorsman
Scrapped onAboutToDie and altered onDeath signature instead. Now includes reasonCode (see ConnectionListenerIF)

Revision 1.85  2007/01/25 00:10:24  billhorsman
New onAboutToDie event for ConnectionListenerIF that gets called if the maximum-active-time is exceeded.

Revision 1.84  2006/03/23 11:44:57  billhorsman
More information when quickly refusing

Revision 1.83  2006/01/18 14:40:01  billhorsman
Unbundled Jakarta's Commons Logging.

Revision 1.82  2005/10/07 08:19:05  billhorsman
New sqlCalls gives list of SQL calls rather than just he most recent (for when a connection makes more than one call before being returned to the pool)

Revision 1.81  2005/10/02 12:32:02  billhorsman
Make connectionCount available to statistics

Revision 1.80  2005/09/26 09:54:14  billhorsman
Avoid suspected deadlock when getting a detailed snapshot. Only attempt to get the concurrent lock for 10 seconds before giving up.

Revision 1.79  2005/05/04 16:26:31  billhorsman
Only add a new connection if the definition matches

Revision 1.78  2004/03/25 22:02:15  brenuart
First step towards pluggable ConnectionBuilderIF & ConnectionValidatorIF.
Include some minor refactoring that lead to deprecation of some PrototyperController methods.

Revision 1.76  2004/02/23 17:47:32  billhorsman
Improved message that gets logged if the state change of a connection fails.

Revision 1.75  2004/02/23 17:38:58  billhorsman
Improved message that gets logged if you close a connection more than once.

Revision 1.74  2004/02/12 13:02:17  billhorsman
Catch correct exception when iterating through list.

Revision 1.73  2003/12/09 18:54:55  billhorsman
Make closure of statement during connection test more robust - credit to John Hume

Revision 1.72  2003/11/04 13:52:01  billhorsman
Fixed warning message

Revision 1.71  2003/10/30 00:11:15  billhorsman
Debug info and error logged if unsuccessful attempt to put connection back in pool. Plus connectioninfo comparator changed

Revision 1.70  2003/09/30 18:39:08  billhorsman
New test-before-use, test-after-use and fatal-sql-exception-wrapper-class properties.

Revision 1.69  2003/09/30 07:50:04  billhorsman
Smarter throwing of caught SQLExceptions without wrapping them up inside another (and losing the stack trace)

Revision 1.68  2003/09/20 17:04:06  billhorsman
Fix for incorrect OFFLINE count when house keeper removed a connection if the test SQL failed. This
meant that the offline count went negative. The only consequence of that is that the logs look funny.

Revision 1.67  2003/08/30 14:54:04  billhorsman
Checkstyle

Revision 1.66  2003/04/10 08:23:54  billhorsman
removed very frequent debug

Revision 1.65  2003/03/11 23:58:04  billhorsman
fixed deadlock on connection expiry

Revision 1.64  2003/03/11 14:51:49  billhorsman
more concurrency fixes relating to snapshots

Revision 1.63  2003/03/11 01:16:29  billhorsman
removed misleasing debug

Revision 1.62  2003/03/11 00:32:13  billhorsman
fixed negative timeout

Revision 1.61  2003/03/10 23:39:51  billhorsman
shutdown is now notified when active count reaches
zero

Revision 1.60  2003/03/10 16:26:35  billhorsman
removed debug traces

Revision 1.59  2003/03/10 15:26:44  billhorsman
refactoringn of concurrency stuff (and some import
optimisation)

Revision 1.58  2003/03/05 18:42:32  billhorsman
big refactor of prototyping and house keeping to
drastically reduce the number of threads when using
many pools

Revision 1.57  2003/03/03 16:06:44  billhorsman
name house keeper and prototyper threads now includes alias

Revision 1.56  2003/03/03 11:11:57  billhorsman
fixed licence

Revision 1.55  2003/02/28 18:08:55  billhorsman
OVERLOAD state is now triggered immediately rather
than waiting for house keeper

Revision 1.54  2003/02/28 10:10:25  billhorsman
on death now gets called for connections killed during shutdown

Revision 1.53  2003/02/26 16:05:52  billhorsman
widespread changes caused by refactoring the way we
update and redefine pool definitions.

Revision 1.52  2003/02/26 12:57:30  billhorsman
added TO DO

Revision 1.51  2003/02/19 23:46:10  billhorsman
renamed monitor package to admin

Revision 1.50  2003/02/19 23:07:46  billhorsman
state changes are now only calculated every time the house
keeper runs, but it's more accurate

Revision 1.49  2003/02/19 22:38:33  billhorsman
fatal sql exception causes house keeper to run
immediately

Revision 1.48  2003/02/18 16:49:59  chr32
Added possibility to remove connection and state listeners.

Revision 1.47  2003/02/12 12:27:16  billhorsman
log proxy hashcode too

Revision 1.46  2003/02/07 17:26:04  billhorsman
deprecated removeAllConnectionPools in favour of
shutdown (and dropped unreliable finalize() method)

Revision 1.45  2003/02/07 14:19:01  billhorsman
fixed deprecated use of debugLevel property

Revision 1.44  2003/02/07 14:16:46  billhorsman
support for StatisticsListenerIF

Revision 1.43  2003/02/07 10:27:47  billhorsman
change in shutdown procedure to allow re-registration

Revision 1.42  2003/02/07 01:48:15  chr32
Started using new composite listeners.

Revision 1.41  2003/02/06 17:41:04  billhorsman
now uses imported logging

Revision 1.40  2003/02/04 17:18:30  billhorsman
move ShutdownHook init code

Revision 1.39  2003/02/04 15:59:50  billhorsman
finalize now shuts down StatsRoller timer

Revision 1.38  2003/02/02 23:35:48  billhorsman
removed ReloadMonitor to remove use of System properties

Revision 1.37  2003/01/31 16:53:16  billhorsman
checkstyle

Revision 1.36  2003/01/31 11:49:28  billhorsman
use Admin instead of Stats

Revision 1.35  2003/01/31 00:20:05  billhorsman
statistics is now a string to allow multiple,
comma-delimited values (plus better logging of errors
during destruction)

Revision 1.34  2003/01/30 17:22:21  billhorsman
add statistics support

Revision 1.33  2003/01/27 18:26:35  billhorsman
refactoring of ProxyConnection and ProxyStatement to
make it easier to write JDK 1.2 patch

Revision 1.32  2003/01/15 14:51:40  billhorsman
checkstyle

Revision 1.31  2003/01/15 12:01:37  billhorsman
added getDateStarted()

Revision 1.30  2003/01/15 00:07:43  billhorsman
now uses FastArrayList instead of Vector for thread safe
improvements

Revision 1.29  2002/12/18 12:16:22  billhorsman
double checking of connection state counts

Revision 1.28  2002/12/17 17:15:39  billhorsman
Better synchronization of status stuff

Revision 1.27  2002/12/17 16:52:51  billhorsman
synchronize part of removeProxyConnection to avoid
possible bug where connection by status count drifts.

Revision 1.26  2002/12/12 12:28:34  billhorsman
just in case: changed == 0 to < 1

Revision 1.25  2002/12/12 10:48:47  billhorsman
checkstyle

Revision 1.24  2002/12/03 12:24:00  billhorsman
fixed fatal sql exception

Revision 1.23  2002/11/12 20:24:12  billhorsman
checkstyle

Revision 1.22  2002/11/12 20:18:23  billhorsman
Made connection resetter a bit more friendly. Now, if it encounters any problems during
reset then that connection is thrown away. This is going to cause you problems if you
always close connections in an unstable state (e.g. with transactions open. But then
again, it's better to know about that as soon as possible, right?

Revision 1.21  2002/11/09 15:48:55  billhorsman
new isConnectionListenedTo() to stop unnecessary processing
if nobody is listening

Revision 1.20  2002/11/08 18:03:50  billhorsman
when connections are closed because they have been
active for too long then a log message is written
of level WARN, and it includes the name of the
thread responsible (reminder, name your threads).

Revision 1.19  2002/11/07 19:31:25  billhorsman
added sanity check against suspected situation where you
can make more connections than the maximumConnectionCount

Revision 1.18  2002/11/07 19:17:55  billhorsman
removed obsolete method

Revision 1.17  2002/11/06 20:27:30  billhorsman
supports the ConnectionResetter

Revision 1.16  2002/11/05 21:24:18  billhorsman
cosmetic: changed format of statistics dumped to log to make it less confusing for locales that use a space separator for thousands

Revision 1.15  2002/11/02 13:57:33  billhorsman
checkstyle

Revision 1.14  2002/10/30 21:19:17  billhorsman
make use of ProxyFactory

Revision 1.13  2002/10/29 23:00:33  billhorsman
fixed sign error in prototyper (that meant that protoyping never happened)

Revision 1.12  2002/10/29 22:58:22  billhorsman
added connection hashcode to debug

Revision 1.11  2002/10/28 19:44:03  billhorsman
small change to cleanup log

Revision 1.10  2002/10/27 13:02:45  billhorsman
change to prototyper logic to make it clearer (but no functional change)

Revision 1.9  2002/10/27 12:07:45  billhorsman
fix bug where prototyper kept making connections up to maximum. Log now gives reason why connection was prototyped. Fix bug where definition with no properties was not allowed (it is now).

Revision 1.8  2002/10/25 10:12:52  billhorsman
Improvements and fixes to the way connection pools close down. Including new ReloadMonitor to detect when a class is reloaded. Much better logging too.

Revision 1.7  2002/10/24 17:25:20  billhorsman
cleaned up logging and made it more informative

Revision 1.6  2002/10/23 21:04:36  billhorsman
checkstyle fixes (reduced max line width and lenient naming convention

Revision 1.5  2002/10/16 11:46:23  billhorsman
removed obsolete cleanupClob method and made onBirth call failsafe

Revision 1.4  2002/09/19 10:33:57  billhorsman
added ProxyConnection#toString

Revision 1.3  2002/09/18 13:48:56  billhorsman
checkstyle and doc

Revision 1.2  2002/09/13 12:13:50  billhorsman
added debug and fixed ClassCastException during housekeeping

Revision 1.1.1.1  2002/09/13 08:12:55  billhorsman
new

Revision 1.13  2002/08/24 19:57:15  billhorsman
checkstyle changes

Revision 1.12  2002/08/24 19:44:13  billhorsman
fixes for logging

Revision 1.11  2002/07/10 16:14:47  billhorsman
widespread layout changes and move constants into ProxoolConstants

Revision 1.10  2002/07/04 09:05:36  billhorsmaNn
Fixes

Revision 1.9  2002/07/02 11:19:08  billhorsman
layout code and imports

Revision 1.8  2002/07/02 11:14:26  billhorsman
added test (andbug fixes) for FileLogger

Revision 1.7  2002/07/02 08:39:55  billhorsman
getConnectionInfos now returns a Collection instead of an array. displayStatistics is
now available to ProxoolFacade. Prototyper no longer tries to make connections
when maximum is reached (stopping unnecessary log messages). bug fix.

Revision 1.6  2002/06/28 11:19:47  billhorsman
improved doc

*/ 
TOP

Related Classes of org.logicalcobwebs.proxool.ConnectionPool

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.