/* $Id: ConnectionFactory.java 988245 2010-08-23 18:39:35Z kwright $ */ /** * 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.database; import org.apache.manifoldcf.core.interfaces.*; import org.apache.manifoldcf.core.jdbcpool.*; import org.apache.manifoldcf.core.system.Logging; import org.apache.manifoldcf.core.system.ManifoldCF; import java.util.*; import java.sql.*; import javax.naming.*; import javax.sql.*; /** This class creates a connection, and may at our discretion manage * a connection pool someday. */ public class ConnectionFactory { public static final String _rcsid = "@(#)$Id: ConnectionFactory.java 988245 2010-08-23 18:39:35Z kwright $"; private static HashMap checkedOutConnections = new HashMap(); private static PoolManager poolManager = new PoolManager(); private ConnectionFactory() { } public static WrappedConnection getConnection(String jdbcUrl, String jdbcDriver, String database, String userName, String password, int maxDBConnections, boolean debug) throws ManifoldCFException { // Make sure database driver is registered try { Class.forName(jdbcDriver); } catch (Exception e) { throw new ManifoldCFException("Unable to load database driver: "+e.getMessage(),e,ManifoldCFException.SETUP_ERROR); } ConnectionPoolManager cpm = poolManager.createPoolManager(debug); try { // Hope for a connection now WrappedConnection rval; ConnectionPool cp = cpm.getPool(database); if (cp == null) { cpm.addAlias(database, jdbcDriver, jdbcUrl, userName, password, maxDBConnections, 300000L); cp = cpm.getPool(database); } return getConnectionWithRetries(cp); } catch (InterruptedException e) { throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); } catch (SQLException e) { throw new ManifoldCFException("Error getting connection: "+e.getMessage(),e,ManifoldCFException.DATABASE_CONNECTION_ERROR); } catch (ClassNotFoundException e) { throw new ManifoldCFException("Fatal error getting connection: "+e.getMessage(),e,ManifoldCFException.SETUP_ERROR); } catch (InstantiationException e) { throw new ManifoldCFException("Fatal error getting connection: "+e.getMessage(),e,ManifoldCFException.SETUP_ERROR); } catch (IllegalAccessException e) { throw new ManifoldCFException("Fatal error getting connection: "+e.getMessage(),e,ManifoldCFException.SETUP_ERROR); } } public static void releaseConnection(WrappedConnection c) throws ManifoldCFException { c.release(); } public static void flush() { if (poolManager != null) poolManager.flush(); } public static void releaseAll() { if (poolManager != null) poolManager.releaseAll(); } protected static WrappedConnection getConnectionWithRetries(ConnectionPool cp) throws SQLException, InterruptedException { // If we have a problem, we will wait a grand total of 30 seconds int retryCount = 3; while (true) { try { return cp.getConnection(); } catch (SQLException e) { if (retryCount == 0) throw e; // Eat the exception and try again retryCount--; } // Ten seconds is a long time ManifoldCF.sleep(10000L); } } protected static void checkConnections(long currentTime) { synchronized (checkedOutConnections) { Iterator iter = checkedOutConnections.keySet().iterator(); while (iter.hasNext()) { Object key = iter.next(); ConnectionTracker ct = (ConnectionTracker)checkedOutConnections.get(key); if (ct.hasExpired(currentTime)) ct.printDetails(); } } } /** This class abstracts from a connection pool, such that a static reference * to an instance of this class will describe the entire body of connections. * The finalizer for this class attempts to free all connections that are outstanding, * so that class unloading, as it is practiced under tomcat 5.5, will not leave dangling * connections around. */ protected static class PoolManager { private Integer poolExistenceLock = new Integer(0); private ConnectionPoolManager _pool = null; private PoolManager() { } public ConnectionPoolManager createPoolManager(boolean debug) throws ManifoldCFException { synchronized (poolExistenceLock) { if (_pool != null) return _pool; _pool = new ConnectionPoolManager(100, debug); return _pool; } } public void releaseAll() { ConnectionPoolManager thisPool; synchronized (poolExistenceLock) { if (_pool == null) return; thisPool = _pool; _pool = null; } // Cleanup strategy: Some connections are still in use because they are being // used by non-worker threads that have been interrupted but haven't yet died. // Cleaning these up is a challenge. For now I won't address this. thisPool.shutdown(); } public void flush() { synchronized (poolExistenceLock) { if (_pool != null) { _pool.flush(); } } } /* // Cleanup strategy is to close everything that can easily be closed, but leave around connections that are so busy that they will not close within a certain amount of // time. To do that, we spin up a thread for each connection, which attempts to close that connection, and then wait until either 15 seconds passes, or all the threads // are finished. // // Under conditions of high load, or (more likely) when long-running exclusive operations like REINDEX are running, the 15 seconds may well be insufficient to acheive // thread shutdown. In that case a message "LOG: unexpected EOF on client connection" will appear for each dangling connection in the postgresql log. // This is not ideal, but is a compromise designed to permit speedy and relatively clean shutdown even under // difficult conditions. Enumeration enumeration = _pool.getPools(); ArrayList connectionShutdownThreads = new ArrayList(); while (enumeration.hasMoreElements()) { ConnectionPool pool = (ConnectionPool)enumeration.nextElement(); try { // The removeAllConnections() method did not work, probably because the cleanup was // delayed by their design. So instead, we have to do everything the hard way. // If the calling logic is poorly behaved, there is a chance that an open connection will be // left hanging around after this call happens. If so, postgresql log gets written // with: LOG: unexpected EOF on client connection // System.err.println("There are currently "+Integer.toString(pool.size())+" connections in this pool"); int count = pool.size(); int i = 0; while (i < count) { com.bitmechanic.sql.PooledConnection p = (com.bitmechanic.sql.PooledConnection)pool.getConnection(); ConnectionCloseThread t = new ConnectionCloseThread(p); t.start(); connectionShutdownThreads.add(t); i++; } // System.err.println("Done closing connections."); } catch (Exception e) { } } int k = 0; while (k < 15) { int j = 0; while (j < connectionShutdownThreads.size()) { ConnectionCloseThread t = (ConnectionCloseThread)connectionShutdownThreads.get(j); if (t.isAlive()) break; j++; } if (j < connectionShutdownThreads.size()) { try { ManifoldCF.sleep(1000L); k++; continue; } catch (InterruptedException e) { break; } } break; } // Some threads may still be running - but that can't be helped. } */ // Protected methods and classes /** Finalizer method should attempt to close open connections. * This should get called when tomcat 5.5 unloads a web application. * A shutdown thread will also be registered, which will attempt to do the same, but * will be blocked from proceeding under Tomcat 5.5. Between the two, however, * there's hope that the right things will take place. */ /* protected void finalize() throws Throwable { try { // Release all the connections we can within 15 seconds releaseAll(); } finally { super.finalize(); } } */ } /* protected static class ConnectionCloseThread extends Thread { protected com.bitmechanic.sql.PooledConnection connection; protected Throwable exception = null; public ConnectionCloseThread(com.bitmechanic.sql.PooledConnection connection) { super(); setDaemon(true); this.connection = connection; } public void run() { try { // Call the shutdown method connection.run(); } catch (Throwable e) { this.exception = e; } } public Throwable getException() { return exception; } } */ protected static class ConnectionTracker { protected Connection theConnection; protected long checkoutTime; protected Exception theTrace; public ConnectionTracker(Connection theConnection) { this.theConnection = theConnection; this.checkoutTime = System.currentTimeMillis(); this.theTrace = new Exception("Stack trace"); } public boolean hasExpired(long currentTime) { return (checkoutTime + 300000L < currentTime); } public void printDetails() { Logging.db.error("Connection handle may have been abandoned: "+theConnection.toString(),theTrace); } } }