    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.io.PrintWriter;
    020import java.sql.Connection;
    021import java.sql.SQLException;
    022import java.sql.SQLFeatureNotSupportedException;
    023import java.util.NoSuchElementException;
    024import java.util.logging.Logger;
    025
    026import javax.sql.DataSource;
    027
    028import org.apache.commons.logging.Log;
    029import org.apache.commons.logging.LogFactory;
    030import org.apache.commons.pool2.ObjectPool;
    031import org.apache.commons.pool2.impl.GenericObjectPool;
    032
    033/**
    034 * A simple {@link DataSource} implementation that obtains
    035 * {@link Connection}s from the specified {@link ObjectPool}.
    036 *
    037 * @param <C> The connection type
    038 *
    039 * @author Rodney Waldhoff
    040 * @author Glenn L. Nielsen
    041 * @author James House
    042 * @author Dirk Verbeeck
    043 * @since 2.0
    044 */
    045public class PoolingDataSource<C extends Connection> implements DataSource, AutoCloseable {
    046
    047    private static final Log log = LogFactory.getLog(PoolingDataSource.class);
    048
    049    /** Controls access to the underlying connection */
    050    private boolean accessToUnderlyingConnectionAllowed = false;
    051
    052    public PoolingDataSource(final ObjectPool<C> pool) {
    053        if (null == pool) {
    054            throw new NullPointerException("Pool must not be null.");
    055        }
    056        _pool = pool;
    057        // Verify that _pool's factory refers back to it.  If not, log a warning and try to fix.
    058        if (_pool instanceof GenericObjectPool<?>) {
    059            final PoolableConnectionFactory pcf = (PoolableConnectionFactory) ((GenericObjectPool<?>) _pool).getFactory();
    060            if (pcf == null) {
    061                throw new NullPointerException("PoolableConnectionFactory must not be null.");
    062            }
    063            if (pcf.getPool() != _pool) {
    064                log.warn(Utils.getMessage("poolingDataSource.factoryConfig"));
    065                @SuppressWarnings("unchecked") // PCF must have a pool of PCs
    066                final
    067                ObjectPool<PoolableConnection> p = (ObjectPool<PoolableConnection>) _pool;
    068                pcf.setPool(p);
    069            }
    070        }
    071    }
    072
    073    /**
    074     * Close and free all {@link Connection}s from the pool.
    075     * @since 2.1
    076     */
    077    @Override
    078    public void close() throws Exception {
    079        try {
    080            _pool.close();
    081        } catch(final RuntimeException rte) {
    082            throw new RuntimeException(Utils.getMessage("pool.close.fail"), rte);
    083        } catch(final Exception e) {
    084            throw new SQLException(Utils.getMessage("pool.close.fail"), e);
    085        }
    086    }
    087
    088    /**
    089     * Returns the value of the accessToUnderlyingConnectionAllowed property.
    090     *
    091     * @return true if access to the underlying {@link Connection} is allowed, false otherwise.
    092     */
    093    public boolean isAccessToUnderlyingConnectionAllowed() {
    094        return this.accessToUnderlyingConnectionAllowed;
    095    }
    096
    097    /**
    098     * Sets the value of the accessToUnderlyingConnectionAllowed property.
    099     * It controls if the PoolGuard allows access to the underlying connection.
    100     * (Default: false)
    101     *
    102     * @param allow Access to the underlying connection is granted when true.
    103     */
    104    public void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
    105        this.accessToUnderlyingConnectionAllowed = allow;
    106    }
    107
    108    /* JDBC_4_ANT_KEY_BEGIN */
    109    @Override
    110    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
    111        return false;
    112    }
    113
    114    @Override
    115    public <T> T unwrap(final Class<T> iface) throws SQLException {
    116        throw new SQLException("PoolingDataSource is not a wrapper.");
    117    }
    118    /* JDBC_4_ANT_KEY_END */
    119
    120    @Override
    121    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    122        throw new SQLFeatureNotSupportedException();
    123    }
    124
    125    //--- DataSource methods -----------------------------------------
    126
    127    /**
    128     * Return a {@link java.sql.Connection} from my pool,
    129     * according to the contract specified by {@link ObjectPool#borrowObject}.
    130     */
    131    @Override
    132    public Connection getConnection() throws SQLException {
    133        try {
    134            final C conn = _pool.borrowObject();
    135            if (conn == null) {
    136                return null;
    137            }
    138            return new PoolGuardConnectionWrapper<>(conn);
    139        } catch(final SQLException e) {
    140            throw e;
    141        } catch(final NoSuchElementException e) {
    142            throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e);
    143        } catch(final RuntimeException e) {
    144            throw e;
    145        } catch(final InterruptedException e) {
    146            // Reset the interrupt status so it is visible to callers
    147            Thread.currentThread().interrupt();
    148            throw new SQLException("Cannot get a connection, general error", e);
    149        } catch(final Exception e) {
    150            throw new SQLException("Cannot get a connection, general error", e);
    151        }
    152    }
    153
    154    /**
    155     * Throws {@link UnsupportedOperationException}
    156     * @throws UnsupportedOperationException
    157     */
    158    @Override
    159    public Connection getConnection(final String uname, final String passwd) throws SQLException {
    160        throw new UnsupportedOperationException();
    161    }
    162
    163    /**
    164     * Returns my log writer.
    165     * @return my log writer
    166     * @see DataSource#getLogWriter
    167     */
    168    @Override
    169    public PrintWriter getLogWriter() {
    170        return _logWriter;
    171    }
    172
    173    /**
    174     * Throws {@link UnsupportedOperationException}.
    175     * @throws UnsupportedOperationException As this
    176     *   implementation does not support this feature.
    177     */
    178    @Override
    179    public int getLoginTimeout() {
    180        throw new UnsupportedOperationException("Login timeout is not supported.");
    181    }
    182
    183    /**
    184     * Throws {@link UnsupportedOperationException}.
    185     * @throws UnsupportedOperationException As this
    186     *   implementation does not support this feature.
    187     */
    188    @Override
    189    public void setLoginTimeout(final int seconds) {
    190        throw new UnsupportedOperationException("Login timeout is not supported.");
    191    }
    192
    193    /**
    194     * Sets my log writer.
    195     * @see DataSource#setLogWriter
    196     */
    197    @Override
    198    public void setLogWriter(final PrintWriter out) {
    199        _logWriter = out;
    200    }
    201
    202    /** My log writer. */
    203    private PrintWriter _logWriter = null;
    204
    205    private final ObjectPool<C> _pool;
    206
    207    protected ObjectPool<C> getPool() {
    208        return _pool;
    209    }
    210
    211    /**
    212     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a
    213     * closed connection cannot be used anymore.
    214     * @since 2.0
    215     */
    216    private class PoolGuardConnectionWrapper<D extends Connection>
    217            extends DelegatingConnection<D> {
    218
    219        PoolGuardConnectionWrapper(final D delegate) {
    220            super(delegate);
    221        }
    222
    223        /**
    224         * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
    225         */
    226        @Override
    227        public D getDelegate() {
    228            if (isAccessToUnderlyingConnectionAllowed()) {
    229                return super.getDelegate();
    230            }
    231            return null;
    232        }
    233
    234        /**
    235         * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
    236         */
    237        @Override
    238        public Connection getInnermostDelegate() {
    239            if (isAccessToUnderlyingConnectionAllowed()) {
    240                return super.getInnermostDelegate();
    241            }
    242            return null;
    243        }
    244
    245        @Override
    246        public void close() throws SQLException {
    247            if (getDelegateInternal() != null) {
    248                super.close();
    249                super.setDelegate(null);
    250            }
    251        }
    252
    253        @Override
    254        public boolean isClosed() throws SQLException {
    255            if (getDelegateInternal() == null) {
    256                return true;
    257            }
    258            return super.isClosed();
    259        }
    260    }
    261}