com.foundationdb.sql.jdbc.ds.jdbc23.AbstractJdbc23PoolingDataSource Maven / Gradle / Ivy
/*-------------------------------------------------------------------------
*
* Copyright (c) 2004-2011, PostgreSQL Global Development Group
*
*
*-------------------------------------------------------------------------
*/
package com.foundationdb.sql.jdbc.ds.jdbc23;
import javax.sql.*;
import javax.naming.*;
import java.util.*;
import java.sql.Connection;
import java.sql.SQLException;
import com.foundationdb.sql.jdbc.ds.*;
import com.foundationdb.sql.jdbc.ds.common.*;
import com.foundationdb.sql.jdbc.util.GT;
import com.foundationdb.sql.jdbc.util.PSQLException;
import com.foundationdb.sql.jdbc.util.PSQLState;
/**
* DataSource which uses connection pooling. Don't use this if
* your server/middleware vendor provides a connection pooling implementation
* which interfaces with the PostgreSQL ConnectionPoolDataSource implementation!
* This class is provided as a convenience, but the JDBC Driver is really not
* supposed to handle the connection pooling algorithm. Instead, the server or
* middleware product is supposed to handle the mechanics of connection pooling,
* and use the PostgreSQL implementation of ConnectionPoolDataSource to provide
* the connections to pool.
*
* If you're sure you want to use this, then you must set the properties
* dataSourceName, databaseName, user, and password (if required for the user).
* The settings for serverName, portNumber, initialConnections, and
* maxConnections are optional. Note that only connections
* for the default user will be pooled! Connections for other users will
* be normal non-pooled connections, and will not count against the maximum pool
* size limit.
*
* If you put this DataSource in JNDI, and access it from different JVMs (or
* otherwise load this class from different ClassLoaders), you'll end up with one
* pool per ClassLoader or VM. This is another area where a server-specific
* implementation may provide advanced features, such as using a single pool
* across all VMs in a cluster.
*
* This implementation supports JDK 1.3 and higher.
*
* @author Aaron Mulder ([email protected])
*/
public abstract class AbstractJdbc23PoolingDataSource extends BaseDataSource
{
protected static Map dataSources = new HashMap();
public static FDBPoolingDataSource getDataSource(String name)
{
return (FDBPoolingDataSource)dataSources.get(name);
}
// Additional Data Source properties
protected String dataSourceName; // Must be protected for subclasses to sync updates to it
private int initialConnections = 0;
private int maxConnections = 0;
// State variables
private boolean initialized = false;
private Stack available = new Stack();
private Stack used = new Stack();
private Object lock = new Object()
;
private FDBConnectionPoolDataSource source;
/**
* Gets a description of this DataSource.
*/
public String getDescription()
{
return "Pooling DataSource '" + dataSourceName + " from " + com.foundationdb.sql.jdbc.Driver.getVersion();
}
/**
* Ensures the DataSource properties are not changed after the DataSource has
* been used.
*
* @throws java.lang.IllegalStateException
* The Server Name cannot be changed after the DataSource has been
* used.
*/
public void setServerName(String serverName)
{
if (initialized)
{
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
}
super.setServerName(serverName);
}
/**
* Ensures the DataSource properties are not changed after the DataSource has
* been used.
*
* @throws java.lang.IllegalStateException
* The Database Name cannot be changed after the DataSource has been
* used.
*/
public void setDatabaseName(String databaseName)
{
if (initialized)
{
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
}
super.setDatabaseName(databaseName);
}
/**
* Ensures the DataSource properties are not changed after the DataSource has
* been used.
*
* @throws java.lang.IllegalStateException
* The User cannot be changed after the DataSource has been
* used.
*/
public void setUser(String user)
{
if (initialized)
{
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
}
super.setUser(user);
}
/**
* Ensures the DataSource properties are not changed after the DataSource has
* been used.
*
* @throws java.lang.IllegalStateException
* The Password cannot be changed after the DataSource has been
* used.
*/
public void setPassword(String password)
{
if (initialized)
{
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
}
super.setPassword(password);
}
/**
* Ensures the DataSource properties are not changed after the DataSource has
* been used.
*
* @throws java.lang.IllegalStateException
* The Port Number cannot be changed after the DataSource has been
* used.
*/
public void setPortNumber(int portNumber)
{
if (initialized)
{
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
}
super.setPortNumber(portNumber);
}
/**
* Gets the number of connections that will be created when this DataSource
* is initialized. If you do not call initialize explicitly, it will be
* initialized the first time a connection is drawn from it.
*/
public int getInitialConnections()
{
return initialConnections;
}
/**
* Sets the number of connections that will be created when this DataSource
* is initialized. If you do not call initialize explicitly, it will be
* initialized the first time a connection is drawn from it.
*
* @throws java.lang.IllegalStateException
* The Initial Connections cannot be changed after the DataSource has been
* used.
*/
public void setInitialConnections(int initialConnections)
{
if (initialized)
{
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
}
this.initialConnections = initialConnections;
}
/**
* Gets the maximum number of connections that the pool will allow. If a request
* comes in and this many connections are in use, the request will block until a
* connection is available. Note that connections for a user other than the
* default user will not be pooled and don't count against this limit.
*
* @return The maximum number of pooled connection allowed, or 0 for no maximum.
*/
public int getMaxConnections()
{
return maxConnections;
}
/**
* Sets the maximum number of connections that the pool will allow. If a request
* comes in and this many connections are in use, the request will block until a
* connection is available. Note that connections for a user other than the
* default user will not be pooled and don't count against this limit.
*
* @param maxConnections The maximum number of pooled connection to allow, or
* 0 for no maximum.
*
* @throws java.lang.IllegalStateException
* The Maximum Connections cannot be changed after the DataSource has been
* used.
*/
public void setMaxConnections(int maxConnections)
{
if (initialized)
{
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
}
this.maxConnections = maxConnections;
}
/**
* Gets the name of this DataSource. This uniquely identifies the DataSource.
* You cannot use more than one DataSource in the same VM with the same name.
*/
public String getDataSourceName()
{
return dataSourceName;
}
/**
* Sets the name of this DataSource. This is required, and uniquely identifies
* the DataSource. You cannot create or use more than one DataSource in the
* same VM with the same name.
*
* @throws java.lang.IllegalStateException
* The Data Source Name cannot be changed after the DataSource has been
* used.
* @throws java.lang.IllegalArgumentException
* Another PoolingDataSource with the same dataSourceName already
* exists.
*/
public void setDataSourceName(String dataSourceName)
{
if (initialized)
{
throw new IllegalStateException("Cannot set Data Source properties after DataSource has been used");
}
if (this.dataSourceName != null && dataSourceName != null && dataSourceName.equals(this.dataSourceName))
{
return ;
}
synchronized (dataSources)
{
if (getDataSource(dataSourceName) != null)
{
throw new IllegalArgumentException("DataSource with name '" + dataSourceName + "' already exists!");
}
if (this.dataSourceName != null)
{
dataSources.remove(this.dataSourceName);
}
this.dataSourceName = dataSourceName;
addDataSource(dataSourceName);
}
}
/**
* Initializes this DataSource. If the initialConnections is greater than zero,
* that number of connections will be created. After this method is called,
* the DataSource properties cannot be changed. If you do not call this
* explicitly, it will be called the first time you get a connection from the
* DataSource.
* @throws java.sql.SQLException
* Occurs when the initialConnections is greater than zero, but the
* DataSource is not able to create enough physical connections.
*/
public void initialize() throws SQLException
{
synchronized (lock )
{
source = createConnectionPool();
try {
source.initializeFrom(this);
} catch (Exception e) {
throw new PSQLException(GT.tr("Failed to setup DataSource."),
PSQLState.UNEXPECTED_ERROR, e);
}
while (available.size() < initialConnections)
{
available.push(source.getPooledConnection());
}
initialized = true;
}
}
protected boolean isInitialized() {
return initialized;
}
/**
* Creates the appropriate ConnectionPool to use for this DataSource.
*/
protected FDBConnectionPoolDataSource createConnectionPool() {
return new FDBConnectionPoolDataSource();
}
/**
* Gets a non-pooled connection, unless the user and password are the
* same as the default values for this connection pool.
*
* @return A pooled connection.
* @throws SQLException
* Occurs when no pooled connection is available, and a new physical
* connection cannot be created.
*/
public Connection getConnection(String user, String password) throws SQLException
{
// If this is for the default user/password, use a pooled connection
if (user == null ||
(user.equals(getUser()) && ((password == null && getPassword() == null) || (password != null && password.equals(getPassword())))))
{
return getConnection();
}
// Otherwise, use a non-pooled connection
if (!initialized)
{
initialize();
}
return super.getConnection(user, password);
}
/**
* Gets a connection from the connection pool.
*
* @return A pooled connection.
* @throws SQLException
* Occurs when no pooled connection is available, and a new physical
* connection cannot be created.
*/
public Connection getConnection() throws SQLException
{
if (!initialized)
{
initialize();
}
return getPooledConnection();
}
/**
* Closes this DataSource, and all the pooled connections, whether in use or not.
*/
public void close()
{
synchronized (lock )
{
while (available.size() > 0)
{
FDBPooledConnection pci = (FDBPooledConnection)available.pop();
try
{
pci.close();
}
catch (SQLException e)
{
}
}
available = null;
while (used.size() > 0)
{
FDBPooledConnection pci = (FDBPooledConnection)used.pop();
pci.removeConnectionEventListener(connectionEventListener);
try
{
pci.close();
}
catch (SQLException e)
{
}
}
used = null;
}
removeStoredDataSource();
}
protected void removeStoredDataSource() {
synchronized (dataSources)
{
dataSources.remove(dataSourceName);
}
}
protected abstract void addDataSource(String dataSourceName);
/**
* Gets a connection from the pool. Will get an available one if
* present, or create a new one if under the max limit. Will
* block if all used and a new one would exceed the max.
*/
private Connection getPooledConnection() throws SQLException
{
PooledConnection pc = null;
synchronized (lock )
{
if (available == null)
{
throw new PSQLException(GT.tr("DataSource has been closed."),
PSQLState.CONNECTION_DOES_NOT_EXIST);
}
while (true)
{
if (available.size() > 0)
{
pc = (PooledConnection)available.pop();
used.push(pc);
break;
}
if (maxConnections == 0 || used.size() < maxConnections)
{
pc = source.getPooledConnection();
used.push(pc);
break;
}
else
{
try
{
// Wake up every second at a minimum
lock.wait(1000L);
}
catch (InterruptedException e)
{
}
}
}
}
pc.addConnectionEventListener(connectionEventListener);
return pc.getConnection();
}
/**
* Notified when a pooled connection is closed, or a fatal error occurs
* on a pooled connection. This is the only way connections are marked
* as unused.
*/
private ConnectionEventListener connectionEventListener = new ConnectionEventListener()
{
public void connectionClosed(ConnectionEvent event)
{
((PooledConnection)event.getSource()).removeConnectionEventListener(this);
synchronized (lock )
{
if (available == null)
{
return ; // DataSource has been closed
}
boolean removed = used.remove(event.getSource());
if (removed)
{
available.push(event.getSource());
// There's now a new connection available
lock.notify();
}
else
{
// a connection error occured
}
}
}
/**
* This is only called for fatal errors, where the physical connection is
* useless afterward and should be removed from the pool.
*/
public void connectionErrorOccurred(ConnectionEvent event)
{
((PooledConnection) event.getSource()).removeConnectionEventListener(this);
synchronized (lock )
{
if (available == null)
{
return ; // DataSource has been closed
}
used.remove(event.getSource());
// We're now at least 1 connection under the max
lock.notify();
}
}
};
/**
* Adds custom properties for this DataSource to the properties defined in
* the superclass.
*/
public Reference getReference() throws NamingException
{
Reference ref = super.getReference();
ref.add(new StringRefAddr("dataSourceName", dataSourceName));
if (initialConnections > 0)
{
ref.add(new StringRefAddr("initialConnections", Integer.toString(initialConnections)));
}
if (maxConnections > 0)
{
ref.add(new StringRefAddr("maxConnections", Integer.toString(maxConnections)));
}
return ref;
}
}