    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.dbcp2;
    018
    019import java.sql.Connection;
    020import java.sql.Driver;
    021import java.sql.DriverManager;
    022import java.sql.DriverPropertyInfo;
    023import java.sql.SQLException;
    024import java.sql.SQLFeatureNotSupportedException;
    025import java.util.HashMap;
    026import java.util.NoSuchElementException;
    027import java.util.Properties;
    028import java.util.logging.Logger;
    029
    030import org.apache.commons.pool2.ObjectPool;
    031
    032/**
    033 * A {@link Driver} implementation that obtains {@link Connection}s from a registered {@link ObjectPool}.
    034 *
    035 * @since 2.0
    036 */
    037public class PoolingDriver implements Driver {
    038
    039    /**
    040     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
    041     *
    042     * @since 2.0
    043     */
    044    private final class PoolGuardConnectionWrapper extends DelegatingConnection<Connection> {
    045
    046        private final ObjectPool<? extends Connection> pool;
    047
    048        PoolGuardConnectionWrapper(final ObjectPool<? extends Connection> pool, final Connection delegate) {
    049            super(delegate);
    050            this.pool = pool;
    051        }
    052
    053        /**
    054         * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
    055         */
    056        @Override
    057        public Connection getDelegate() {
    058            if (isAccessToUnderlyingConnectionAllowed()) {
    059                return super.getDelegate();
    060            }
    061            return null;
    062        }
    063
    064        /**
    065         * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
    066         */
    067        @Override
    068        public Connection getInnermostDelegate() {
    069            if (isAccessToUnderlyingConnectionAllowed()) {
    070                return super.getInnermostDelegate();
    071            }
    072            return null;
    073        }
    074    }
    075
    076    private static final DriverPropertyInfo[] EMPTY_DRIVER_PROPERTY_INFO_ARRAY = {};
    077
    078    /* Register myself with the {@link DriverManager}. */
    079    static {
    080        try {
    081            DriverManager.registerDriver(new PoolingDriver());
    082        } catch (final Exception ignored) {
    083            // Ignored
    084        }
    085    }
    086
    087    /** The map of registered pools. */
    088    protected static final HashMap<String, ObjectPool<? extends Connection>> pools = new HashMap<>();
    089
    090    /**
    091     * The Apache Commons connection string prefix {@value}.
    092     */
    093    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
    094
    095    /**
    096     * The String length of {@link #URL_PREFIX}.
    097     */
    098    protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
    099
    100    /**
    101     * Major version number.
    102     */
    103    protected static final int MAJOR_VERSION = 1;
    104
    105    /**
    106     * Minor version number.
    107     */
    108    protected static final int MINOR_VERSION = 0;
    109
    110    /** Controls access to the underlying connection */
    111    private final boolean accessToUnderlyingConnectionAllowed;
    112
    113    /**
    114     * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled.
    115     */
    116    public PoolingDriver() {
    117        this(true);
    118    }
    119
    120    /**
    121     * For unit testing purposes.
    122     *
    123     * @param accessToUnderlyingConnectionAllowed
    124     *            Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
    125     */
    126    protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
    127        this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
    128    }
    129
    130    @Override
    131    public boolean acceptsURL(final String url) throws SQLException {
    132        return url != null && url.startsWith(URL_PREFIX);
    133    }
    134
    135    /**
    136     * Closes a named pool.
    137     *
    138     * @param name
    139     *            The pool name.
    140     * @throws SQLException
    141     *             Thrown when a problem is caught closing the pool.
    142     */
    143    public synchronized void closePool(final String name) throws SQLException {
    144        @SuppressWarnings("resource")
    145        final ObjectPool<? extends Connection> pool = pools.get(name);
    146        if (pool != null) {
    147            pools.remove(name);
    148            try {
    149                pool.close();
    150            } catch (final Exception e) {
    151                throw new SQLException("Error closing pool " + name, e);
    152            }
    153        }
    154    }
    155
    156    @Override
    157    public Connection connect(final String url, final Properties info) throws SQLException {
    158        if (acceptsURL(url)) {
    159            final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
    160            try {
    161                final Connection conn = pool.borrowObject();
    162                if (conn == null) {
    163                    return null;
    164                }
    165                return new PoolGuardConnectionWrapper(pool, conn);
    166            } catch (final NoSuchElementException e) {
    167                throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
    168            } catch (final SQLException | RuntimeException e) {
    169                throw e;
    170            } catch (final Exception e) {
    171                throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
    172            }
    173        }
    174        return null;
    175    }
    176
    177    /**
    178     * Gets the connection pool for the given name.
    179     *
    180     * @param name
    181     *            The pool name
    182     * @return The pool
    183     * @throws SQLException
    184     *             Thrown when the named pool is not registered.
    185     */
    186    public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
    187        final ObjectPool<? extends Connection> pool = pools.get(name);
    188        if (null == pool) {
    189            throw new SQLException("Pool not registered: " + name);
    190        }
    191        return pool;
    192    }
    193
    194    @Override
    195    public int getMajorVersion() {
    196        return MAJOR_VERSION;
    197    }
    198
    199    @Override
    200    public int getMinorVersion() {
    201        return MINOR_VERSION;
    202    }
    203
    204    @Override
    205    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    206        throw new SQLFeatureNotSupportedException();
    207    }
    208
    209    /**
    210     * Gets the pool names.
    211     *
    212     * @return the pool names.
    213     */
    214    public synchronized String[] getPoolNames() {
    215        return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
    216    }
    217
    218    @Override
    219    public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
    220        return EMPTY_DRIVER_PROPERTY_INFO_ARRAY;
    221    }
    222    /**
    223     * Invalidates the given connection.
    224     *
    225     * @param conn
    226     *            connection to invalidate
    227     * @throws SQLException
    228     *             if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating
    229     *             the connection
    230     */
    231    public void invalidateConnection(final Connection conn) throws SQLException {
    232        if (!(conn instanceof PoolGuardConnectionWrapper)) {
    233            throw new SQLException("Invalid connection class");
    234        }
    235        final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
    236        @SuppressWarnings("unchecked")
    237        final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
    238        try {
    239            pool.invalidateObject(pgconn.getDelegateInternal());
    240        } catch (final Exception ignored) {
    241            // Ignored.
    242        }
    243    }
    244
    245    /**
    246     * Returns the value of the accessToUnderlyingConnectionAllowed property.
    247     *
    248     * @return true if access to the underlying is allowed, false otherwise.
    249     */
    250    protected boolean isAccessToUnderlyingConnectionAllowed() {
    251        return accessToUnderlyingConnectionAllowed;
    252    }
    253
    254    @Override
    255    public boolean jdbcCompliant() {
    256        return true;
    257    }
    258
    259    /**
    260     * Registers a named pool.
    261     *
    262     * @param name
    263     *            The pool name.
    264     * @param pool
    265     *            The pool.
    266     */
    267    public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
    268        pools.put(name, pool);
    269    }
    270}