package org.opensource.clearpool.core; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.sql.PooledConnection; import org.opensource.clearpool.configuration.ConfigurationVO; import org.opensource.clearpool.core.chain.BinaryHeap; import org.opensource.clearpool.datasource.connection.CommonConnection; import org.opensource.clearpool.datasource.factory.DataSourceAbstractFactory; import org.opensource.clearpool.datasource.proxy.ConnectionProxy; import org.opensource.clearpool.exception.ConnectionPoolException; import org.opensource.clearpool.logging.PoolLogger; import org.opensource.clearpool.logging.PoolLoggerFactory; /** * This class save the connection to {@link #connectionChain},it's duty is to manage the pool. * * The pool will increment when there is no free connection in the pool's size is less than the max * pool size. * * @author xionghui * @date 26.07.2014 * @version 1.0 */ public class ConnectionPoolManager { private static final PoolLogger LOGGER = PoolLoggerFactory.getLogger(ConnectionPoolManager.class); private Lock lock = new ReentrantLock(); private Condition notEmpty = this.lock.newCondition(); private final BinaryHeap connectionChain = new BinaryHeap();; // save all the connection to close in case we shutdown the JVM private volatile Set<ConnectionProxy> connectionSet = new HashSet<ConnectionProxy>(); // this is the sign of if the pool is removed private volatile boolean closed; private ConfigurationVO cfgVO; private AtomicInteger poolSize = new AtomicInteger(); // record the peak of the connection in the pool. private int peakPoolSize; ConnectionPoolManager(ConfigurationVO cfgVO) { this.cfgVO = cfgVO; } /** * Init the pool by the corePoolsize of {@link #cfgVO} */ void initPool() { int coreSize = this.cfgVO.getCorePoolSize(); this.fillPool(coreSize); String tableName = this.cfgVO.getTestTableName(); if (tableName != null) { this.initTestTable(); } } /** * Return connection to the pool */ public void entryPool(ConnectionProxy conProxy) { if (conProxy == null) { throw new NullPointerException(); } this.lock.lock(); try { this.connectionChain.add(conProxy); this.notEmpty.signal(); } finally { this.lock.unlock(); } } /** * Get a connection from the pool */ public PooledConnection exitPool() throws SQLException { ConnectionProxy conProxy = null; for (;;) { this.lock.lock(); try { do { conProxy = this.connectionChain.removeFirst(); // if we couln't get a connection from the pool,we should get // new // connection. if (conProxy == null) { int maxIncrement = this.cfgVO.getMaxPoolSize() - this.poolSize.get(); // if pool is full,we shouldn't grow it if (maxIncrement == 0) { if (this.cfgVO.getUselessConnectionException()) { throw new ConnectionPoolException("there is no connection left in the pool"); } else { // wait for connection while (this.connectionChain.size() == 0) { this.notEmpty.await(); } } } else { this.fillPoolByAcquireIncrement(); } } } while (conProxy == null); } catch (InterruptedException e) { LOGGER.error("exitPool error: ", e); throw new ConnectionPoolException(e); } finally { this.lock.unlock(); } if (this.cfgVO.isTestBeforeUse()) { boolean isValid = this.checkTestTable(conProxy, false); if (!isValid) { this.decrementPoolSize(); this.closeConnection(conProxy); this.incrementOneConnection(); continue; } } break; } DataSourceAbstractFactory factory = this.cfgVO.getFactory(); PooledConnection pooledConnection = factory.createPooledConnection(conProxy); return pooledConnection; } public ConnectionProxy exitPool(long period) { this.lock.lock(); try { return this.connectionChain.removeIdle(period); } finally { this.lock.unlock(); } } /** * fill the pool by acquireIncrement */ private void fillPoolByAcquireIncrement() { int maxIncrement = this.cfgVO.getMaxPoolSize() - this.poolSize.get(); // double check if (maxIncrement != 0) { int increment = this.cfgVO.getAcquireIncrement(); if (increment > maxIncrement) { increment = maxIncrement; } this.fillPool(increment); } } /** * increment one connection */ public void incrementOneConnection() { this.lock.lock(); try { this.fillPool(1); } finally { this.lock.unlock(); } } /** * fill the pool by poolNum */ private void fillPool(int poolNum) { int retryTimes = this.cfgVO.getAcquireRetryTimes(); for (int i = 0; i < poolNum; i++) { // try to get a connection ConnectionProxy conProxy = this.tryGetConnection(retryTimes); if (this.closed) { this.remove(); return; } this.connectionChain.add(conProxy); this.handlePeakPoolSize(i + 1); } this.poolSize.addAndGet(poolNum); } /** * try retryTimes times to get a connection */ private ConnectionProxy tryGetConnection(int retryTimes) { int count = 0; CommonConnection cmnCon = null; do { try { cmnCon = this.cfgVO.getDataSource().getCommonConnection(); } catch (SQLException e) { LOGGER.error("try connect error(" + count++ + " time): ", e); if (count > retryTimes) { throw new ConnectionPoolException("get connection error" + e.getMessage()); } } } while (cmnCon == null); ConnectionProxy conProxy = new ConnectionProxy(this, cmnCon); this.connectionSet.add(conProxy); return conProxy; } /** * Init test table by testTableName in {@link #cfgVO} */ private void initTestTable() { int coreSize = this.cfgVO.getCorePoolSize(); ConnectionProxy conProxy; if (coreSize > 0) { conProxy = this.connectionChain.removeFirst(); } else { int retryTimes = this.cfgVO.getAcquireRetryTimes(); conProxy = this.tryGetConnection(retryTimes); } try { this.checkTestTable(conProxy, true); } catch (ConnectionPoolException e) { LOGGER.error("initTestTable test: ", e); throw e; } if (coreSize > 0) { this.connectionChain.add(conProxy); } else { this.closeConnection(conProxy); } } public BinaryHeap getConnectionChain() { return this.connectionChain; } public Set<ConnectionProxy> getConnectionSet() { return this.connectionSet; } public ConfigurationVO getCfgVO() { return this.cfgVO; } public boolean isClosed() { return this.closed; } public boolean isNeedCollected() { return this.poolSize.get() > this.cfgVO.getCorePoolSize(); } /** * Save the peak pool size */ private void handlePeakPoolSize(int poolNum) { int size = this.poolSize.get() + poolNum; if (size > this.peakPoolSize) { this.peakPoolSize = size; } } public void decrementPoolSize() { this.poolSize.decrementAndGet(); } public int getPoolSize() { return this.poolSize.get(); } public int getPeakPoolSize() { return this.peakPoolSize; } /** * check if test table existed */ public boolean checkTestTable(ConnectionProxy conProxy, boolean autoCreateTable) { PreparedStatement queryPreparedStatement = null; try { DataSourceAbstractFactory factory = this.cfgVO.getFactory(); PooledConnection pooledConnection = factory.createPooledConnection(conProxy); Connection con = pooledConnection.getConnection(); queryPreparedStatement = con.prepareStatement(this.cfgVO.getTestQuerySql()); queryPreparedStatement.execute(); } catch (SQLException e) { LOGGER.error(this.cfgVO.getTestQuerySql() + " error: ", e); if (autoCreateTable) { this.createTestTable(conProxy); } else { return false; } } finally { if (queryPreparedStatement != null) { try { queryPreparedStatement.close(); } catch (SQLException e) { LOGGER.error("close queryPreparedStatement error: ", e); } } } return true; } /** * create test table if test table is not existed */ private void createTestTable(ConnectionProxy conProxy) { PreparedStatement createPreparedStatement = null; try { DataSourceAbstractFactory factory = this.cfgVO.getFactory(); PooledConnection pooledConnection = factory.createPooledConnection(conProxy); Connection con = pooledConnection.getConnection(); createPreparedStatement = con.prepareStatement(this.cfgVO.getTestCreateSql()); createPreparedStatement.execute(); con.commit(); } catch (SQLException e) { LOGGER.error("createTestTable error: ", e); throw new ConnectionPoolException(e); } finally { if (createPreparedStatement != null) { try { createPreparedStatement.close(); } catch (SQLException e) { LOGGER.error("close createPreparedStatement error: ", e); } } } } /** * remove the connection of the free connection. note:we shouldn't close the using connection * because it may cause a exception when people is using it. */ public void remove() { this.closed = true; Set<ConnectionProxy> tempSet = this.connectionSet; // help gc this.connectionSet = new HashSet<ConnectionProxy>(); for (ConnectionProxy conProxy : tempSet) { this.closeConnection(conProxy); } } /** * close pool connection */ public void closeConnection(ConnectionProxy conProxy) { if (conProxy != null) { try { conProxy.getConnection().close(); } catch (SQLException e) { LOGGER.error("it cause a exception when we close a pool connection: ", e); } this.connectionSet.remove(conProxy); } } }