    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 *      http://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 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    /** My URL prefix */
    091    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
    092
    093    protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
    094
    095    // version numbers
    096    protected static final int MAJOR_VERSION = 1;
    097
    098    protected static final int MINOR_VERSION = 0;
    099
    100    /** Controls access to the underlying connection */
    101    private final boolean accessToUnderlyingConnectionAllowed;
    102
    103    /**
    104     * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled.
    105     */
    106    public PoolingDriver() {
    107        this(true);
    108    }
    109
    110    /**
    111     * For unit testing purposes.
    112     *
    113     * @param accessToUnderlyingConnectionAllowed
    114     *            Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
    115     */
    116    protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
    117        this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
    118    }
    119
    120    @Override
    121    public boolean acceptsURL(final String url) throws SQLException {
    122        return url != null && url.startsWith(URL_PREFIX);
    123    }
    124
    125    /**
    126     * Closes a named pool.
    127     *
    128     * @param name
    129     *            The pool name.
    130     * @throws SQLException
    131     *             Thrown when a problem is caught closing the pool.
    132     */
    133    public synchronized void closePool(final String name) throws SQLException {
    134        @SuppressWarnings("resource")
    135        final ObjectPool<? extends Connection> pool = pools.get(name);
    136        if (pool != null) {
    137            pools.remove(name);
    138            try {
    139                pool.close();
    140            } catch (final Exception e) {
    141                throw new SQLException("Error closing pool " + name, e);
    142            }
    143        }
    144    }
    145
    146    @Override
    147    public Connection connect(final String url, final Properties info) throws SQLException {
    148        if (acceptsURL(url)) {
    149            final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
    150
    151            try {
    152                final Connection conn = pool.borrowObject();
    153                if (conn == null) {
    154                    return null;
    155                }
    156                return new PoolGuardConnectionWrapper(pool, conn);
    157            } catch (final NoSuchElementException e) {
    158                throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
    159            } catch (final SQLException | RuntimeException e) {
    160                throw e;
    161            } catch (final Exception e) {
    162                throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
    163            }
    164        }
    165        return null;
    166    }
    167
    168    /**
    169     * Gets the connection pool for the given name.
    170     *
    171     * @param name
    172     *            The pool name
    173     * @return The pool
    174     * @throws SQLException
    175     *             Thrown when the named pool is not registered.
    176     */
    177    public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
    178        final ObjectPool<? extends Connection> pool = pools.get(name);
    179        if (null == pool) {
    180            throw new SQLException("Pool not registered: " + name);
    181        }
    182        return pool;
    183    }
    184
    185    @Override
    186    public int getMajorVersion() {
    187        return MAJOR_VERSION;
    188    }
    189
    190    @Override
    191    public int getMinorVersion() {
    192        return MINOR_VERSION;
    193    }
    194
    195    @Override
    196    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    197        throw new SQLFeatureNotSupportedException();
    198    }
    199
    200    /**
    201     * Gets the pool names.
    202     *
    203     * @return the pool names.
    204     */
    205    public synchronized String[] getPoolNames() {
    206        return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
    207    }
    208
    209    @Override
    210    public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
    211        return EMPTY_DRIVER_PROPERTY_INFO_ARRAY;
    212    }
    213    /**
    214     * Invalidates the given connection.
    215     *
    216     * @param conn
    217     *            connection to invalidate
    218     * @throws SQLException
    219     *             if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating
    220     *             the connection
    221     */
    222    public void invalidateConnection(final Connection conn) throws SQLException {
    223        if (!(conn instanceof PoolGuardConnectionWrapper)) {
    224            throw new SQLException("Invalid connection class");
    225        }
    226        final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
    227        @SuppressWarnings("unchecked")
    228        final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
    229        try {
    230            pool.invalidateObject(pgconn.getDelegateInternal());
    231        } catch (final Exception ignored) {
    232            // Ignored.
    233        }
    234    }
    235
    236    /**
    237     * Returns the value of the accessToUnderlyingConnectionAllowed property.
    238     *
    239     * @return true if access to the underlying is allowed, false otherwise.
    240     */
    241    protected boolean isAccessToUnderlyingConnectionAllowed() {
    242        return accessToUnderlyingConnectionAllowed;
    243    }
    244
    245    @Override
    246    public boolean jdbcCompliant() {
    247        return true;
    248    }
    249
    250    /**
    251     * Registers a named pool.
    252     *
    253     * @param name
    254     *            The pool name.
    255     * @param pool
    256     *            The pool.
    257     */
    258    public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
    259        pools.put(name, pool);
    260    }
    261}