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