/* $Id$ */ /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.manifoldcf.core.jdbcpool; import java.sql.*; import javax.naming.*; import javax.sql.*; import java.util.*; import org.apache.manifoldcf.core.system.Logging; /** The class that defines a connection pool. */ public class ConnectionPool { public static final String _rcsid = "@(#)$Id$"; protected final String dbURL; protected final String userName; protected final String password; protected volatile int freePointer; protected volatile int activeConnections; protected volatile boolean closed; protected final Connection[] freeConnections; protected final long[] connectionCleanupTimeouts; protected final long expiration; protected final boolean debug; protected final Set<WrappedConnection> outstandingConnections = new HashSet<WrappedConnection>(); /** Constructor */ public ConnectionPool(String dbURL, String userName, String password, int maxConnections, long expiration, boolean debug) { this.dbURL = dbURL; this.userName = userName; this.password = password; this.freeConnections = new Connection[maxConnections]; this.connectionCleanupTimeouts = new long[maxConnections]; this.freePointer = 0; this.activeConnections = 0; this.closed = false; this.expiration = expiration; this.debug = debug; } /** Obtain a connection from the pool. * This will wait until a connection is free, if the pool is already completely tapped. * The connection is returned by the "close" operation, executed on the connection. * (This requires us to wrap the actual connection object). */ public WrappedConnection getConnection() throws SQLException, InterruptedException { Exception instantiationException; if (debug) instantiationException = new Exception("Possibly leaked db connection"); else instantiationException = null; Connection rval = null; boolean returnedValue = true; try { while (true) { synchronized (this) { if (freePointer > 0) { if (closed) throw new InterruptedException("Pool already closed"); rval = freeConnections[--freePointer]; freeConnections[freePointer] = null; boolean isValid = true; try { isValid = rval.isValid(1); } catch (SQLException e) { // Ignore this; we just can't check if handle is valid I guess. // (Postgresql doesn't implement this method so it fails always) } catch (java.lang.AbstractMethodError e) { // Ignore this; we just can't check if handle is valid I guess. // (SQLServer doesn't implement this method so it fails always) } if (!isValid) { // If the connection is invalid, drop it on the floor, and get a new one. // Note: Order of operations is terribly important here!! final Connection closeValue = rval; rval = null; activeConnections--; try { closeValue.close(); } catch (SQLException e) { // Ignore SQL errors on close, and drop the connection on the floor } continue; } break; } if (activeConnections == freeConnections.length) { // If properly configured, we really shouldn't be getting here. if (debug) { synchronized (outstandingConnections) { Logging.db.warn("Out of db connections, list of outstanding ones follows."); for (WrappedConnection c : outstandingConnections) { Logging.db.warn("Found a possibly leaked db connection",c.getInstantiationException()); } } } // Wait until kicked; we hope something will free up... this.wait(); continue; } // Increment active connection counter, because we're about to mint a new connection, and break out of our loop // Note: order is terribly important here! activeConnections++; if (userName != null) rval = DriverManager.getConnection(dbURL, userName, password); else rval = DriverManager.getConnection(dbURL); break; } } WrappedConnection wc = new WrappedConnection(this,rval,instantiationException); if (debug) { synchronized (outstandingConnections) { outstandingConnections.add(wc); } } return wc; } catch (Error e) { returnedValue = false; throw e; } catch (RuntimeException e) { returnedValue = false; throw e; } catch (SQLException e) { returnedValue = false; throw e; } finally { if (!returnedValue) { // We didn't finish. Restore the pool to the correct form. // Note: We should always be able to just return any current connection to the pool. This is // safe because we reserved a slot when we decided to create the connection (if that's what // we did), or we just used a connection that was already allocated. Either way, we can put // it into the pool. if (rval != null) { // We have a handle, so just free it and leave activeConnections alone release(rval); } else { // We didn't manage to create the handle, so decrement active connections. synchronized (this) { activeConnections--; } } } } } /** Flush the pool. */ public synchronized void flushPool() { for (int i = 0 ; i < freePointer ; i++) { try { freeConnections[i].close(); } catch (SQLException e) { Logging.db.warn("Error closing pooled connection: "+e.getMessage(),e); } freeConnections[i] = null; activeConnections--; } freePointer = 0; notifyAll(); } /** Close down the pool. */ public synchronized void closePool() { for (int i = 0 ; i < freePointer ; i++) { try { freeConnections[i].close(); } catch (SQLException e) { Logging.db.warn("Error closing pooled connection: "+e.getMessage(),e); } freeConnections[i] = null; } freePointer = 0; closed = true; notifyAll(); } /** Clean up expired connections. */ public synchronized void cleanupExpiredConnections(long currentTime) { int i = 0; while (i < freePointer) { if (connectionCleanupTimeouts[i] <= currentTime) { Connection c = freeConnections[i]; freeConnections[i] = null; freePointer--; activeConnections--; if (freePointer == i) { freeConnections[i] = null; } else { freeConnections[i] = freeConnections[freePointer]; connectionCleanupTimeouts[i] = connectionCleanupTimeouts[freePointer]; freeConnections[freePointer] = null; } try { c.close(); } catch (SQLException e) { Logging.db.warn("Error closing pooled connection: "+e.getMessage(),e); } } else i++; } } public void releaseConnection(WrappedConnection connection) { if (debug) { synchronized (outstandingConnections) { if (!outstandingConnections.contains(connection)) Logging.db.warn("Released a connection that wasn't tracked!!"); outstandingConnections.remove(connection); } } release(connection.getConnection()); } protected void release(Connection c) { synchronized (this) { freeConnections[freePointer] = c; connectionCleanupTimeouts[freePointer] = System.currentTimeMillis() + expiration; freePointer++; notifyAll(); } } }