com.caucho.sql.DBPoolImpl Maven / Gradle / Ivy
/*
* Copyright (c) 1998-2018 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.sql;
import com.caucho.config.ConfigException;
import com.caucho.config.types.InitParam;
import com.caucho.config.types.Period;
import com.caucho.env.meter.ActiveTimeSensor;
import com.caucho.env.meter.MeterService;
import com.caucho.loader.Environment;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.loader.EnvironmentListener;
import com.caucho.management.server.JdbcDriverMXBean;
import com.caucho.util.Alarm;
import com.caucho.util.AlarmListener;
import com.caucho.util.L10N;
import com.caucho.util.SQLExceptionWrapper;
import com.caucho.sql.spy.*;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.resource.spi.ManagedConnectionFactory;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.XADataSource;
import javax.transaction.TransactionManager;
import java.io.PrintWriter;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Manages a pool of database connections. In addition, DBPool configures
* the database connection from a configuration file.
*
* Like JDBC 2.0 pooling, DBPool returns a wrapped Connection.
* Applications can use that connection just like an unpooled connection.
* It is more important than ever to close()
the connection,
* because the close returns the connection to the connection pool.
*
*
Example using DataSource JNDI style (recommended)
*
*
* Context env = (Context) new InitialContext().lookup("java:comp/env");
* DataSource pool = (DataSource) env.lookup("jdbc/test");
* Connection conn = pool.getConnection();
* try {
* ... // normal connection stuff
* } finally {
* conn.close();
* }
*
*
* Configuration
*
*
* <database name='jdbc/test'>
* <init>
* <driver>postgresql.Driver</driver>
* <url>jdbc:postgresql://localhost/test</url>
* <user>ferg</user>
* <password>foobar</password>
* </init>
* </database>
*
*
* Pool limits and timeouts
*
* The pool will only allow getMaxConnections() connections alive at a time.
* If getMaxConnection
connections are already active,
* getPooledConnection
will block waiting for an available
* connection. The wait is timed. If connection-wait-time passes
* and there is still no connection, getPooledConnection
* create a new connection anyway.
*
* Connections will only stay in the pool for about 5 seconds. After
* that they will be removed and closed. This reduces the load on the DB
* and also protects against the database dropping old connections.
*/
public class DBPoolImpl implements AlarmListener, EnvironmentListener {
protected static final Logger log
= Logger.getLogger(DBPoolImpl.class.getName());
private static final L10N L = new L10N(DBPoolImpl.class);
/**
* The key used to look into the properties passed to the
* connect method to find the username.
*/
public static final String PROPERTY_USER = "user" ;
/**
* The key used to look into the properties passed to the
* connect method to find the password.
*/
public static final String PROPERTY_PASSWORD = "password" ;
// How long an unused connection can remain in the pool
private static final long MAX_IDLE_TIME = 30000;
private String _name;
private ArrayList _driverList
= new ArrayList();
private ArrayList _backupDriverList
= new ArrayList();
private ConnectionConfig _connectionConfig
= new ConnectionConfig();
// private ManagedFactoryImpl _mcf;
private ManagedConnectionFactory _mcf;
private String _user;
private String _password;
// total connections allowed in this pool
private int _maxConnections = 1024;
// time before an idle connection is closed
private long _maxIdleTime = MAX_IDLE_TIME;
// max time a connection is allowed to be active (6 hr)
private long _maxActiveTime = 6L * 3600L * 1000L;
// commit or rollback when max active time expires
private boolean _isCommitOnTimeout;
// max time a connection is allowed in the pool
private long _maxPoolTime = 24L * 3600L * 1000L;
// how long to wait for a connection, say 10 minutes
private long _connectionWaitTime = 600 * 1000;
private int _connectionWaitCount = (int) (_connectionWaitTime / 1000);
// connections to create even when the max-connections overflows.
private int _maxOverflowConnections = 0;
// true if the pool has started
private boolean _isStarted;
// true if the pool has closed
private boolean _isClosed;
// if true, the pool can't be closed.
private boolean _forbidClose;
// The JDBC table to be used to ping for connection liveness.
private String _pingTable;
// The Query used for connection liveness.
private String _pingQuery;
// Ping when the connection is reused.
private Boolean _isPing;
// How long between pings
private long _pingInterval = 60000;
// True if the pool is transactional
private boolean _isTransactional = true;
// True if the pool should never allow isSameRM
private boolean _isXAForbidSameRM = true;
// The transaction manager if the pool participates in transactions.
private TransactionManager _tm;
// how long before the transaction times out
private long _transactionTimeout = 0;
private boolean _isSpy;
private SpyDataSource _spyDataSource;
private int _maxCloseStatements = 256;
// The prepared statement cache size.
private int _preparedStatementCacheSize = 0;
private boolean _isWrapStatements = true;
// The connections currently in the pool.
// transient ArrayList _connections = new ArrayList();
// Count for debugging ids.
private int _idCount;
private ActiveTimeSensor _timeProbe;
/**
* Null constructor for the Driver interface; called by the JNDI
* configuration. Applications should not call this directly.
*/
public DBPoolImpl()
{
}
/**
* Returns the Pool's name
*/
public String getName()
{
return _name;
}
/**
* Sets the Pool's name. Also puts the pool in the classloader's
* list of pools.
*/
public void setName(String name)
{
_name = name;
}
public String getURL()
{
return _driverList.get(0).getURL();
}
/**
* Returns the driver config.
*/
public DriverConfig createDriver()
{
DriverConfig driver = new DriverConfig(this);
driver.setIndex(_driverList.size() + _backupDriverList.size());
_driverList.add(driver);
return driver;
}
/**
* Returns the driver config.
*/
public DriverConfig createBackupDriver()
{
DriverConfig driver = new DriverConfig(this);
driver.setIndex(_driverList.size() + _backupDriverList.size());
_backupDriverList.add(driver);
return driver;
}
/**
* Sets a driver parameter.
*/
public void setInitParam(InitParam init)
{
DriverConfig driver = _driverList.get(0);
HashMap params = init.getParameters();
Iterator iter = params.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
driver.setInitParam(key, params.get(key));
}
}
/**
* Sets the jdbc-driver config.
*/
public void setJDBCDriver(Driver jdbcDriver)
throws SQLException
{
DriverConfig driver;
if (_driverList.size() > 0)
driver = _driverList.get(0);
else
driver = createDriver();
driver.setDriver(jdbcDriver);
}
/**
* Returns the driver admin
*/
public JdbcDriverMXBean []getDriverAdmin()
{
JdbcDriverMXBean []drivers = new JdbcDriverMXBean[_driverList.size()];
for (int i = 0; i < _driverList.size(); i++) {
DriverConfig driver = _driverList.get(i);
drivers[i] = driver.getAdmin();
}
return drivers;
}
/**
* Creates the connection config.
*/
public ConnectionConfig createConnection()
{
return _connectionConfig;
}
/**
* Returns the connection config.
*/
public ConnectionConfig getConnectionConfig()
{
return _connectionConfig;
}
ActiveTimeSensor getTimeProbe()
{
if (_timeProbe == null) {
_timeProbe = MeterService.createActiveTimeMeter("Resin|Database|Query");
}
return _timeProbe;
}
/**
* Sets the jdbc-driver config.
*/
public void setPoolDataSource(ConnectionPoolDataSource poolDataSource)
throws SQLException
{
DriverConfig driver;
if (_driverList.size() > 0)
driver = _driverList.get(0);
else
driver = createDriver();
driver.setPoolDataSource(poolDataSource);
}
/**
* Sets the jdbc-driver config.
*/
public void setXADataSource(XADataSource xaDataSource)
throws SQLException
{
DriverConfig driver;
if (_driverList.size() > 0)
driver = _driverList.get(0);
else
driver = createDriver();
driver.setXADataSource(xaDataSource);
}
/**
* Sets the jdbc-driver config.
*/
public void setURL(String url)
throws ConfigException
{
DriverConfig driver;
if (_driverList.size() > 0)
driver = _driverList.get(0);
else
throw new ConfigException(L.l("The driver must be assigned before the URL."));
driver.setURL(url);
}
/**
* Returns the connection's user.
*/
public String getUser()
{
return _user;
}
/**
* Sets the connection's user.
*/
public void setUser(String user)
{
_user = user;
}
/**
* Returns the connection's password
*/
public String getPassword()
{
return _password;
}
/**
* Sets the connection's password
*/
public void setPassword(String password)
{
_password = password;
}
/**
* Get the maximum number of pooled connections.
*/
public int getMaxConnections()
{
return _maxConnections;
}
/**
* Sets the maximum number of pooled connections.
*/
public void setMaxConnections(int maxConnections)
{
_maxConnections = maxConnections;
}
/**
* Get the total number of connections
*/
public int getTotalConnections()
{
// return _connections.size();
return 0;
}
/**
* Sets the time to wait for a connection when all are used.
*/
public void setConnectionWaitTime(Period waitTime)
{
long period = waitTime.getPeriod();
_connectionWaitTime = period;
if (period < 0)
_connectionWaitCount = 3600; // wait for an hour == infinity
else {
_connectionWaitCount = (int) ((period + 999) / 1000);
if (_connectionWaitCount <= 0)
_connectionWaitCount = 1;
}
}
/**
* Gets the time to wait for a connection when all are used.
*/
public long getConnectionWaitTime()
{
return _connectionWaitTime;
}
/**
* The number of connections to overflow if the connection pool fills
* and there's a timeout.
*/
public void setMaxOverflowConnections(int maxOverflowConnections)
{
_maxOverflowConnections = maxOverflowConnections;
}
/**
* The number of connections to overflow if the connection pool fills
* and there's a timeout.
*/
public int getMaxOverflowConnections()
{
return _maxOverflowConnections;
}
/**
* Sets the transaction timeout.
*/
public void setTransactionTimeout(Period period)
{
_transactionTimeout = period.getPeriod();
}
/**
* Gets the transaction timeout.
*/
public long getTransactionTimeout()
{
return _transactionTimeout;
}
/**
* Sets the max statement.
*/
public void setMaxCloseStatements(int max)
{
_maxCloseStatements = max;
}
/**
* Gets the max statement.
*/
public int getMaxCloseStatements()
{
return _maxCloseStatements;
}
/**
* Sets true if statements should be wrapped.
*/
public void setWrapStatements(boolean isWrap)
{
_isWrapStatements = isWrap;
}
/**
* Sets true if statements should be wrapped.
*/
public boolean isWrapStatements()
{
return _isWrapStatements;
}
/**
* Returns the prepared statement cache size.
*/
public int getPreparedStatementCacheSize()
{
return _preparedStatementCacheSize;
}
/**
* Sets the prepared statement cache size.
*/
public void setPreparedStatementCacheSize(int size)
{
_preparedStatementCacheSize = size;
}
/**
* Get the time in milliseconds a connection will remain in the pool before
* being closed.
*/
public long getMaxIdleTime()
{
if (_maxIdleTime > Long.MAX_VALUE / 2)
return -1;
else
return _maxIdleTime;
}
/**
* Set the time in milliseconds a connection will remain in the pool before
* being closed.
*/
public void setMaxIdleTime(Period idleTime)
{
long period = idleTime.getPeriod();
if (period < 0)
_maxIdleTime = Long.MAX_VALUE / 2;
else if (period < 1000L)
_maxIdleTime = 1000L;
else
_maxIdleTime = period;
}
/**
* Get the time in milliseconds a connection will remain in the pool before
* being closed.
*/
public long getMaxPoolTime()
{
if (_maxPoolTime > Long.MAX_VALUE / 2)
return -1;
else
return _maxPoolTime;
}
/**
* Set the time in milliseconds a connection will remain in the pool before
* being closed.
*/
public void setMaxPoolTime(Period maxPoolTime)
{
long period = maxPoolTime.getPeriod();
if (period < 0)
_maxPoolTime = Long.MAX_VALUE / 2;
else if (period == 0)
_maxPoolTime = 1000L;
else
_maxPoolTime = period;
}
/**
* Get the time in milliseconds a connection can remain active.
*/
public long getMaxActiveTime()
{
if (_maxActiveTime > Long.MAX_VALUE / 2)
return -1;
else
return _maxActiveTime;
}
public void setCommitOnTimeout(boolean commitOnTimeout)
{
_isCommitOnTimeout = commitOnTimeout;
}
public boolean isCommitOnTimeout()
{
return _isCommitOnTimeout;
}
/**
* Set the time in milliseconds a connection can remain active.
*/
public void setMaxActiveTime(Period maxActiveTime)
{
long period = maxActiveTime.getPeriod();
if (period < 0)
_maxActiveTime = Long.MAX_VALUE / 2;
else if (period == 0)
_maxActiveTime = 1000L;
else
_maxActiveTime = period;
}
/**
* Get the table to 'ping' to see if the connection is still live.
*/
public String getPingTable()
{
return _pingTable;
}
/**
* Set the table to 'ping' to see if the connection is still live.
*
* @param pingTable name of the SQL table to ping.
*/
public void setPingTable(String pingTable)
{
_pingTable = pingTable;
if (pingTable != null)
_pingQuery = "select 1 from " + pingTable + " where 1=0";
else
_pingQuery = null;
if (_isPing == null)
_isPing = true;
}
/**
* Returns the ping query.
*/
public String getPingQuery()
{
return _pingQuery;
}
/**
* Sets the ping query.
*/
public void setPingQuery(String pingQuery)
{
_pingQuery = pingQuery;
if (_isPing == null)
_isPing = true;
}
/**
* If true, the pool will ping when attempting to reuse a connection.
*/
public boolean getPingOnReuse()
{
return isPing();
}
/**
* Set the table to 'ping' to see if the connection is still live.
*/
public void setPingOnReuse(boolean pingOnReuse)
{
_isPing = pingOnReuse;
}
/**
* If true, the pool will ping in the idle pool.
*/
public boolean getPingOnIdle()
{
return _isPing;
}
/**
* Set the table to 'ping' to see if the connection is still live.
*/
public void setPingOnIdle(boolean pingOnIdle)
{
_isPing = pingOnIdle;
}
/**
* Set true if pinging is enabled.
*/
public void setPing(boolean ping)
{
_isPing = ping;
}
/**
* Returns true if pinging is enabled.
*/
public boolean isPing()
{
if (_isPing != null)
return _isPing;
else
return false;
}
/**
* Sets the time to ping for ping-on-idle
*/
public void setPingInterval(Period interval)
{
_pingInterval = interval.getPeriod();
if (_pingInterval < 0)
_pingInterval = Long.MAX_VALUE / 2;
else if (_pingInterval < 1000)
_pingInterval = 1000;
if (_isPing == null)
_isPing = true;
}
/**
* Gets how often the ping for ping-on-idle
*/
public long getPingInterval()
{
return _pingInterval;
}
/**
* Set the transaction manager for this pool.
*/
public void setTransactionManager(TransactionManager tm)
{
_tm = tm;
}
/**
* Returns the transaction manager.
*/
/*
public TransactionManager getTransactionManager()
{
return _tm;
}
*/
/**
* Returns true if this is transactional.
*/
public boolean isXA()
{
return _isTransactional;
}
/**
* Returns true if this is transactional.
*/
public void setXA(boolean isTransactional)
{
_isTransactional = isTransactional;
}
/**
* Returns true if transactions should force isSameRM to be false.
*/
public boolean isXAForbidSameRM()
{
return _isXAForbidSameRM;
}
/**
* Returns true if transactions should force isSameRM to be false.
*/
public void setXAForbidSameRM(boolean isXAForbidSameRM)
{
_isXAForbidSameRM = isXAForbidSameRM;
}
/**
* Set the output for spying.
*/
public void setSpy(boolean isSpy)
{
_isSpy = isSpy;
}
/**
* Return true for a spy.
*/
public boolean isSpy()
{
return _isSpy;
}
/**
* Returns the next spy id.
*/
public SpyDataSource getSpyDataSource()
{
return _spyDataSource;
}
/**
* Returns the next spy id.
*/
public String newSpyId(DriverConfig driver)
{
return _spyDataSource.createConnectionId(driver);
}
/**
* Returns true if the pool supports transactions.
*/
public boolean isTransactional()
{
return _isTransactional;
}
/**
* Returns true if there is a valid XAResource associated
* with the database.
*/
public boolean isXATransaction()
{
if (_connectionConfig.isReadOnly())
return false;
else if (_driverList.size() > 0) {
DriverConfig driver = _driverList.get(0);
return driver.isXATransaction();
}
else
return false;
}
/**
* Returns true if there is a valid local transactino associated
* with the database.
*/
public boolean isLocalTransaction()
{
if (_connectionConfig.isReadOnly())
return false;
else if (_driverList.size() > 0) {
DriverConfig driver = _driverList.get(0);
return driver.isLocalTransaction();
}
else
return false;
}
int createPoolId()
{
return _idCount++;
}
/**
* Sets the timeout for a database login.
*/
public void setLoginTimeout(int seconds) throws SQLException
{
}
/**
* Gets the timeout for a database login.
*/
public int getLoginTimeout() throws SQLException
{
return 0;
}
/**
* Sets the debugging log for the connection.
*/
public void setLogWriter(PrintWriter out) throws SQLException
{
}
/**
* Sets the debugging log for the connection.
*/
public PrintWriter getLogWriter() throws SQLException
{
return null;
}
/**
* Initialize the pool.
*/
public void init()
throws Exception
{
Environment.addEnvironmentListener(this);
try {
if (_tm == null) {
Object obj = new InitialContext().lookup("java:comp/TransactionManager");
if (obj instanceof TransactionManager)
_tm = (TransactionManager) obj;
}
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
}
for (int i = 0; i < _driverList.size(); i++) {
DriverConfig driver = _driverList.get(i);
if (driver.getUser() == null)
driver.setUser(_user);
if (driver.getPassword() == null)
driver.setPassword(_password);
driver.initDriver();
driver.initDataSource(_isTransactional, _name, _isSpy);
if (_mcf == null)
_mcf = driver.getManagedConnectionFactory();
/*
if (driver.getXADataSource() == null)
_isTransactional = false;
*/
}
DriverConfig []drivers = new DriverConfig[_driverList.size()];
_driverList.toArray(drivers);
for (int i = 0; i < _backupDriverList.size(); i++) {
DriverConfig driver = _backupDriverList.get(i);
if (driver.getUser() == null)
driver.setUser(_user);
if (driver.getPassword() == null)
driver.setPassword(_password);
driver.initDriver();
driver.initDataSource(_isTransactional, _name, _isSpy);
/*
if (driver.getXADataSource() == null)
_isTransactional = false;
*/
}
DriverConfig []backupDrivers = new DriverConfig[_backupDriverList.size()];
_backupDriverList.toArray(backupDrivers);
if (_driverList.size() == 0 && _backupDriverList.size() == 0) {
throw new ConfigException(L.l(" configuration needs at least one , because it needs to know the database to connect."));
}
if (_mcf == null)
_mcf = new ManagedFactoryImpl(this, drivers, backupDrivers);
if (_name != null) {
String name = _name;
if (! name.startsWith("java:"))
name = "java:comp/env/" + name;
if (drivers.length == 0) {
log.config("database " + name + " starting");
}
else if (drivers[0].getURL() != null)
log.config("database " + name + " starting (URL:" + drivers[0].getURL() + ")");
else
log.config("database " + name + " starting");
// XXX: actually should be proxy
// Jndi.bindDeep(name, this);
}
_spyDataSource = new SpyDataSource(_name);
}
/**
* Returns the managed connection factory.
*/
ManagedConnectionFactory getManagedConnectionFactory()
{
return _mcf;
}
/**
* Initialize the pool's data source
*
*
* - If data-source is set, look it up in JNDI.
*
- Else if the driver is a pooled or xa data source, use it.
*
- Else create wrappers.
*
*/
synchronized void initDataSource()
throws SQLException
{
if (_isStarted)
return;
_isStarted = true;
for (int i = 0; i < _driverList.size(); i++) {
DriverConfig driver = _driverList.get(i);
driver.initDataSource(_isTransactional, _name, _isSpy);
}
try {
if (_isTransactional && _tm == null) {
Object obj = new InitialContext().lookup("java:comp/TransactionManager");
if (obj instanceof TransactionManager)
_tm = (TransactionManager) obj;
}
} catch (NamingException e) {
throw new SQLExceptionWrapper(e);
}
}
/**
* Closes the idle connections in the pool.
*/
public void closeIdleConnections()
{
}
/**
* At the alarm, close all connections which have been sitting in
* the pool for too long.
*
* @param alarm the alarm event.
*/
public void handleAlarm(Alarm alarm)
{
if (_isClosed)
return;
}
/**
* Callback when the environment configures.
*/
public void environmentConfigure(EnvironmentClassLoader loader)
{
}
/**
* Callback when the environment binds.
*/
public void environmentBind(EnvironmentClassLoader loader)
{
}
/**
* Callback when the environment starts.
*/
public void environmentStart(EnvironmentClassLoader loader)
{
}
/**
* Callback when the class loader dies.
*/
public void environmentStop(EnvironmentClassLoader loader)
{
forceClose();
}
/**
* Returns true if the pool is closed.
*/
public boolean isClosed()
{
return _isClosed;
}
/**
* Close the pool, closing the connections.
*/
public void close()
{
if (_forbidClose)
throw new IllegalStateException("illegal to call close() for this DBPool");
forceClose();
}
/**
* Close all the connections in the pool.
*/
public void forceClose()
{
if (_isClosed)
return;
_isClosed = true;
if (log.isLoggable(Level.FINE))
log.fine("closing pool " + getName());
}
/**
* Returns a string description of the pool.
*/
public String toString()
{
return getClass().getSimpleName() + "[" + _name + "]";
}
}