PoolingDriver.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.dbcp2;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.HashMap;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.logging.Logger;
import org.apache.commons.pool2.ObjectPool;
/**
* A {@link Driver} implementation that obtains {@link Connection}s from a registered {@link ObjectPool}.
*
* @since 2.0
*/
public class PoolingDriver implements Driver {
/**
* PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
*
* @since 2.0
*/
private final class PoolGuardConnectionWrapper extends DelegatingConnection<Connection> {
private final ObjectPool<? extends Connection> pool;
PoolGuardConnectionWrapper(final ObjectPool<? extends Connection> pool, final Connection delegate) {
super(delegate);
this.pool = pool;
}
/**
* @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
*/
@Override
public Connection getDelegate() {
if (isAccessToUnderlyingConnectionAllowed()) {
return super.getDelegate();
}
return null;
}
/**
* @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
*/
@Override
public Connection getInnermostDelegate() {
if (isAccessToUnderlyingConnectionAllowed()) {
return super.getInnermostDelegate();
}
return null;
}
}
private static final DriverPropertyInfo[] EMPTY_DRIVER_PROPERTY_INFO_ARRAY = {};
/* Register myself with the {@link DriverManager}. */
static {
try {
DriverManager.registerDriver(new PoolingDriver());
} catch (final Exception ignored) {
// Ignored
}
}
/** The map of registered pools. */
protected static final HashMap<String, ObjectPool<? extends Connection>> pools = new HashMap<>();
/**
* The Apache Commons connection string prefix {@value}.
*/
public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
/**
* The String length of {@link #URL_PREFIX}.
*/
protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
/**
* Major version number.
*/
protected static final int MAJOR_VERSION = 1;
/**
* Minor version number.
*/
protected static final int MINOR_VERSION = 0;
/** Controls access to the underlying connection */
private final boolean accessToUnderlyingConnectionAllowed;
/**
* Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled.
*/
public PoolingDriver() {
this(true);
}
/**
* For unit testing purposes.
*
* @param accessToUnderlyingConnectionAllowed
* Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
*/
protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
}
@Override
public boolean acceptsURL(final String url) throws SQLException {
return url != null && url.startsWith(URL_PREFIX);
}
/**
* Closes a named pool.
*
* @param name
* The pool name.
* @throws SQLException
* Thrown when a problem is caught closing the pool.
*/
public synchronized void closePool(final String name) throws SQLException {
@SuppressWarnings("resource")
final ObjectPool<? extends Connection> pool = pools.get(name);
if (pool != null) {
pools.remove(name);
try {
pool.close();
} catch (final Exception e) {
throw new SQLException("Error closing pool " + name, e);
}
}
}
@Override
public Connection connect(final String url, final Properties info) throws SQLException {
if (acceptsURL(url)) {
final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
try {
final Connection conn = pool.borrowObject();
if (conn == null) {
return null;
}
return new PoolGuardConnectionWrapper(pool, conn);
} catch (final NoSuchElementException e) {
throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
} catch (final SQLException | RuntimeException e) {
throw e;
} catch (final Exception e) {
throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
}
}
return null;
}
/**
* Gets the connection pool for the given name.
*
* @param name
* The pool name
* @return The pool
* @throws SQLException
* Thrown when the named pool is not registered.
*/
public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
final ObjectPool<? extends Connection> pool = pools.get(name);
if (null == pool) {
throw new SQLException("Pool not registered: " + name);
}
return pool;
}
@Override
public int getMajorVersion() {
return MAJOR_VERSION;
}
@Override
public int getMinorVersion() {
return MINOR_VERSION;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
/**
* Gets the pool names.
*
* @return the pool names.
*/
public synchronized String[] getPoolNames() {
return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
}
@Override
public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
return EMPTY_DRIVER_PROPERTY_INFO_ARRAY;
}
/**
* Invalidates the given connection.
*
* @param conn
* connection to invalidate
* @throws SQLException
* if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating
* the connection
*/
public void invalidateConnection(final Connection conn) throws SQLException {
if (!(conn instanceof PoolGuardConnectionWrapper)) {
throw new SQLException("Invalid connection class");
}
final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
@SuppressWarnings("unchecked")
final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
try {
pool.invalidateObject(pgconn.getDelegateInternal());
} catch (final Exception ignored) {
// Ignored.
}
}
/**
* Returns the value of the accessToUnderlyingConnectionAllowed property.
*
* @return true if access to the underlying is allowed, false otherwise.
*/
protected boolean isAccessToUnderlyingConnectionAllowed() {
return accessToUnderlyingConnectionAllowed;
}
@Override
public boolean jdbcCompliant() {
return true;
}
/**
* Registers a named pool.
*
* @param name
* The pool name.
* @param pool
* The pool.
*/
public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
pools.put(name, pool);
}
}