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