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