    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.Set;
    029import java.util.logging.Logger;
    030
    031import org.apache.commons.pool2.ObjectPool;
    032
    033/**
    034 * A {@link Driver} implementation that obtains {@link Connection}s from a registered {@link ObjectPool}.
    035 *
    036 * @since 2.0
    037 */
    038public class PoolingDriver implements Driver {
    039
    040    /** Register myself with the {@link DriverManager}. */
    041    static {
    042        try {
    043            DriverManager.registerDriver(new PoolingDriver());
    044        } catch (final Exception e) {
    045            // ignore
    046        }
    047    }
    048
    049    /** The map of registered pools. */
    050    protected static final HashMap<String, ObjectPool<? extends Connection>> pools = new HashMap<>();
    051
    052    /** Controls access to the underlying connection */
    053    private final boolean accessToUnderlyingConnectionAllowed;
    054
    055    /**
    056     * Constructs a new driver with <code>accessToUnderlyingConnectionAllowed</code> enabled.
    057     */
    058    public PoolingDriver() {
    059        this(true);
    060    }
    061
    062    /**
    063     * For unit testing purposes.
    064     *
    065     * @param accessToUnderlyingConnectionAllowed
    066     *            Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
    067     */
    068    protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
    069        this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
    070    }
    071
    072    /**
    073     * Returns the value of the accessToUnderlyingConnectionAllowed property.
    074     *
    075     * @return true if access to the underlying is allowed, false otherwise.
    076     */
    077    protected boolean isAccessToUnderlyingConnectionAllowed() {
    078        return accessToUnderlyingConnectionAllowed;
    079    }
    080
    081    /**
    082     * Gets the connection pool for the given name.
    083     *
    084     * @param name
    085     *            The pool name
    086     * @return The pool
    087     * @throws SQLException
    088     *             Thrown when the named pool is not registered.
    089     */
    090    public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
    091        final ObjectPool<? extends Connection> pool = pools.get(name);
    092        if (null == pool) {
    093            throw new SQLException("Pool not registered: " + name);
    094        }
    095        return pool;
    096    }
    097
    098    /**
    099     * Registers a named pool.
    100     *
    101     * @param name
    102     *            The pool name.
    103     * @param pool
    104     *            The pool.
    105     */
    106    public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
    107        pools.put(name, pool);
    108    }
    109
    110    /**
    111     * Closes a named pool.
    112     *
    113     * @param name
    114     *            The pool name.
    115     * @throws SQLException
    116     *             Thrown when a problem is caught closing the pool.
    117     */
    118    public synchronized void closePool(final String name) throws SQLException {
    119        @SuppressWarnings("resource")
    120        final ObjectPool<? extends Connection> pool = pools.get(name);
    121        if (pool != null) {
    122            pools.remove(name);
    123            try {
    124                pool.close();
    125            } catch (final Exception e) {
    126                throw new SQLException("Error closing pool " + name, e);
    127            }
    128        }
    129    }
    130
    131    /**
    132     * Gets the pool names.
    133     *
    134     * @return the pool names.
    135     */
    136    public synchronized String[] getPoolNames() {
    137        final Set<String> names = pools.keySet();
    138        return names.toArray(new String[names.size()]);
    139    }
    140
    141    @Override
    142    public boolean acceptsURL(final String url) throws SQLException {
    143        return url == null ? false : url.startsWith(URL_PREFIX);
    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 SQLException e) {
    158                throw e;
    159            } catch (final NoSuchElementException e) {
    160                throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
    161            } catch (final RuntimeException e) {
    162                throw e;
    163            } catch (final Exception e) {
    164                throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
    165            }
    166        }
    167        return null;
    168    }
    169
    170    @Override
    171    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    172        throw new SQLFeatureNotSupportedException();
    173    }
    174
    175    /**
    176     * Invalidates the given connection.
    177     *
    178     * @param conn
    179     *            connection to invalidate
    180     * @throws SQLException
    181     *             if the connection is not a <code>PoolGuardConnectionWrapper</code> or an error occurs invalidating
    182     *             the connection
    183     */
    184    public void invalidateConnection(final Connection conn) throws SQLException {
    185        if (conn instanceof PoolGuardConnectionWrapper) { // normal case
    186            final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
    187            @SuppressWarnings("unchecked")
    188            final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
    189            try {
    190                pool.invalidateObject(pgconn.getDelegateInternal());
    191            } catch (final Exception e) {
    192                // Ignore.
    193            }
    194        } else {
    195            throw new SQLException("Invalid connection class");
    196        }
    197    }
    198
    199    @Override
    200    public int getMajorVersion() {
    201        return MAJOR_VERSION;
    202    }
    203
    204    @Override
    205    public int getMinorVersion() {
    206        return MINOR_VERSION;
    207    }
    208
    209    @Override
    210    public boolean jdbcCompliant() {
    211        return true;
    212    }
    213
    214    @Override
    215    public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
    216        return new DriverPropertyInfo[0];
    217    }
    218
    219    /** My URL prefix */
    220    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
    221    protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
    222
    223    // version numbers
    224    protected static final int MAJOR_VERSION = 1;
    225    protected static final int MINOR_VERSION = 0;
    226
    227    /**
    228     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
    229     *
    230     * @since 2.0
    231     */
    232    private class PoolGuardConnectionWrapper extends DelegatingConnection<Connection> {
    233
    234        private final ObjectPool<? extends Connection> pool;
    235
    236        PoolGuardConnectionWrapper(final ObjectPool<? extends Connection> pool, final Connection delegate) {
    237            super(delegate);
    238            this.pool = pool;
    239        }
    240
    241        /**
    242         * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
    243         */
    244        @Override
    245        public Connection getDelegate() {
    246            if (isAccessToUnderlyingConnectionAllowed()) {
    247                return super.getDelegate();
    248            }
    249            return null;
    250        }
    251
    252        /**
    253         * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
    254         */
    255        @Override
    256        public Connection getInnermostDelegate() {
    257            if (isAccessToUnderlyingConnectionAllowed()) {
    258                return super.getInnermostDelegate();
    259            }
    260            return null;
    261        }
    262    }
    263}