/* * Copyright 2001-2008 Geert Bevin <gbevin[remove] at uwyn dot com> * Licensed under the Apache License, Version 2.0 (the "License") * $Id: ConnectionPool.java 3918 2008-04-14 17:35:35Z gbevin $ */ package com.uwyn.rife.database; import com.uwyn.rife.database.DbConnection; import com.uwyn.rife.database.exceptions.DatabaseException; import java.util.ArrayList; import java.util.HashMap; /** * This is a class designed for database connection pooling. By storing * connections, along with the thread that they are assigned to, thread-aware * operations can be performed safely, securely, and more efficiently. * * @author JR Boyens (jboyens[remove] at uwyn dot com) * @author Geert Bevin (gbevin[remove] at uwyn dot com) * @version $Revision: 3918 $ * @since 1.0 */ public class ConnectionPool { private int mPoolsize = 0; private ArrayList<DbConnection> mConnectionPool = new ArrayList<DbConnection>(); private HashMap<Thread, DbConnection> mThreadConnections = new HashMap<Thread, DbConnection>(); /** * Create a new ConnectionPool * * @since 1.0 */ ConnectionPool() { } /** * Set the size of the connection pool * * @param poolsize the new size of the pool * @since 1.0 */ void setPoolsize(int poolsize) { synchronized (this) { if (mConnectionPool.size() > 0) { cleanup(); } mPoolsize = poolsize; } } /** * Get the size of the connection pool * * @return int the size of the connection pool * @since 1.0 */ int getPoolsize() { return mPoolsize; } /** * Check if the connection pool is initialized * * @return boolean true if initialized; false if not * @since 1.0 */ boolean isInitialized() { return mConnectionPool.size() > 0; } /** * Fill the pool with connections. Prepare the pool by filling it with * connections from the provided datasource * * @param datasource the {@link Datasource} to fill the pool with * connections from * @exception DatabaseException when an error occured during the * preparation of the pool * @since 1.0 */ void preparePool(Datasource datasource) throws DatabaseException { synchronized (this) { cleanup(); mConnectionPool.ensureCapacity(mPoolsize); for (int i = 0; i < mPoolsize; i++) { mConnectionPool.add(datasource.createConnection()); } assert mPoolsize == mConnectionPool.size(); this.notifyAll(); } } /** * Cleans up all connections that have been reserved by this * datasource. * * @exception DatabaseException when an error occured during the * clearing of the pool * @since 1.0 */ public void cleanup() throws DatabaseException { synchronized (this) { if (0 == mConnectionPool.size()) { return; } ArrayList<DbConnection> previous_pool = null; previous_pool = mConnectionPool; mConnectionPool = new ArrayList<DbConnection>(); if (previous_pool != null) { for (DbConnection connection : previous_pool) { connection.cleanup(); } previous_pool.clear(); } mThreadConnections.clear(); } } /** * Remembers which connection has been reserved for a particular * thread. This makes sequential operations within the same * transaction in the same thread be executed on the same connection. * Otherwise, transaction deadlocks might appear. * * @param thread the {@link Thread} to which the connection should be * registered to * @param connection the {@link DbConnection} that should be * registered to the thread * @since 1.0 */ void registerThreadConnection(Thread thread, DbConnection connection) { synchronized (this) { mThreadConnections.put(thread, connection); } } /** * Removes the dedication of a connection for a specific thread. * * @param thread the {@link Thread} whose {@link DbConnection} should * be unregistered. * @since 1.0 */ void unregisterThreadConnection(Thread thread) { synchronized (this) { mThreadConnections.remove(thread); this.notifyAll(); } } /** * Check if a connection reserved for a specific thread. * * @param thread the {@link Thread} to check for a reserved connection * @return true if the passed-in thread has a connection; false if not * @since 1.0 */ boolean hasThreadConnection(Thread thread) { synchronized (this) { return mThreadConnections.containsKey(thread); } } /** * Recreate the connection. * * @param connection the {@link DbConnection} to be recreated * @exception DatabaseException when there is a problem recreating the * connection or cleaning up the old connection * @since 1.0 */ void recreateConnection(DbConnection connection) throws DatabaseException { synchronized (this) { if (mConnectionPool.remove(connection)) { mConnectionPool.add(connection.getDatasource().createConnection()); } connection.cleanup(); } } /** * Retrieve this thread's connection. * <p>Connections are allocated from the pool and assigned to the * current calling thread. If the thread does not have a current * connection or the pool size is 0, then a new connection is created * and assigned to the calling thread. * * @param datasource the datasource to create the connection to * @exception DatabaseException when an error occurred retrieving or * creating the connection * @return the created or retrieved DbConnection * @since 1.0 */ DbConnection getConnection(Datasource datasource) throws DatabaseException { synchronized (this) { // check if the connection threads contains an entry for the // current thread so that transactions are treated in a // continuous fashion if (mThreadConnections.containsKey(Thread.currentThread())) { DbConnection connection = mThreadConnections.get(Thread.currentThread()); if (connection != null) { return connection; } } // if there's no pool, create a new connection if (0 == mPoolsize) { return datasource.createConnection(); } // otherwise, try to obtain a free connection in the pool else { DbConnection connection = null; // iterate over the available connections and try to obtain the // first free one DbConnection possible_connection = null; while (null == connection) { // prepare the pool if it's currently empty if (mConnectionPool.size() < mPoolsize) { preparePool(datasource); } for (int i = 0; i < mConnectionPool.size() && null == connection; i++) { possible_connection = mConnectionPool.get(i); if (null == possible_connection || possible_connection.isCleanedUp()) { connection = datasource.createConnection(); mConnectionPool.set(i, connection); break; } else if (null != possible_connection && possible_connection.isFree()) { connection = possible_connection; break; } } if (null == connection) { try { this.wait(); } catch (InterruptedException e) { Thread.yield(); } } } // move the obtained connection to the end of the connection // pool list mConnectionPool.remove(connection); mConnectionPool.add(connection); return connection; } } } /** * Ensures that the pool is cleared when this <code>Datasource</code> * is garbage collected. * * @exception Throwable when an error occured during the clearing of * the pool * @since 1.0 */ protected void finalize() throws Throwable { cleanup(); super.finalize(); } }