/* * Copyright (c) 2004, PostgreSQL Global Development Group * See the LICENSE file in the project root for more information. */ package org.postgresql.ds; import org.postgresql.ds.common.BaseDataSource; import org.postgresql.util.GT; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; import java.sql.Connection; import java.sql.SQLException; import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.naming.NamingException; import javax.naming.Reference; import javax.naming.StringRefAddr; import javax.sql.ConnectionEvent; import javax.sql.ConnectionEventListener; import javax.sql.DataSource; import javax.sql.PooledConnection; /** * DataSource which uses connection pooling. <span style="color: red;">Don't use this if your * server/middleware vendor provides a connection pooling implementation which interfaces with the * PostgreSQL ConnectionPoolDataSource implementation!</span> 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. * * <p> * 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 <i>only connections * for the default user will be pooled!</i> Connections for other users will be normal non-pooled * connections, and will not count against the maximum pool size limit. * </p> * * <p> * 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. * </p> * * <p> * This implementation supports JDK 1.5 and higher. * </p> * * @author Aaron Mulder (ammulder@chariotsolutions.com) * * @deprecated Since 42.0.0, instead of this class you should use a fully featured connection pool * like HikariCP, vibur-dbcp, commons-dbcp, c3p0, etc. */ @Deprecated public class PGPoolingDataSource extends BaseDataSource implements DataSource { protected static ConcurrentMap<String, PGPoolingDataSource> dataSources = new ConcurrentHashMap<String, PGPoolingDataSource>(); public static PGPoolingDataSource getDataSource(String name) { return 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<PooledConnection> available = new Stack<PooledConnection>(); private Stack<PooledConnection> used = new Stack<PooledConnection>(); private Object lock = new Object(); private PGConnectionPoolDataSource source; /** * Gets a description of this DataSource. */ public String getDescription() { return "Pooling DataSource '" + dataSourceName + " from " + org.postgresql.util.DriverInfo.DRIVER_FULL_NAME; } /** * Ensures the DataSource properties are not changed after the DataSource has been used. * * @throws 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 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 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 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 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. * * @return number of connections that will be created when this DataSource is initialized */ 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. * * @param initialConnections number of initial connections * @throws 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 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. * * @return name of this DataSource */ 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. * * @param dataSourceName datasource name * @throws IllegalStateException The Data Source Name cannot be changed after the DataSource has * been used. * @throws 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; } PGPoolingDataSource previous = dataSources.putIfAbsent(dataSourceName, this); if (previous != null) { throw new IllegalArgumentException( "DataSource with name '" + dataSourceName + "' already exists!"); } if (this.dataSourceName != null) { dataSources.remove(this.dataSourceName); } this.dataSourceName = 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 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. * * @return appropriate ConnectionPool to use for this DataSource */ protected PGConnectionPoolDataSource createConnectionPool() { return new PGConnectionPoolDataSource(); } /** * Gets a <b>non-pooled</b> 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.isEmpty()) { PooledConnection pci = available.pop(); try { pci.close(); } catch (SQLException e) { } } available = null; while (!used.isEmpty()) { PooledConnection pci = used.pop(); pci.removeConnectionEventListener(connectionEventListener); try { pci.close(); } catch (SQLException e) { } } used = null; } removeStoredDataSource(); } protected void removeStoredDataSource() { dataSources.remove(dataSourceName); } protected void addDataSource(String dataSourceName) { dataSources.put(dataSourceName, this); } /** * 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.isEmpty()) { pc = 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((PooledConnection) event.getSource()); // There's now a new connection available lock.notify(); } else { // a connection error occurred } } } /** * 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; } public boolean isWrapperFor(Class<?> iface) throws SQLException { return iface.isAssignableFrom(getClass()); } public <T> T unwrap(Class<T> iface) throws SQLException { if (iface.isAssignableFrom(getClass())) { return iface.cast(this); } throw new SQLException("Cannot unwrap to " + iface.getName()); } }