    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.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 java.sql.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    /**
    169     * Throws {@link UnsupportedOperationException}.
    170     *
    171     * @throws UnsupportedOperationException
    172     *             As this implementation does not support this feature.
    173     */
    174    @Override
    175    public int getLoginTimeout() {
    176        throw new UnsupportedOperationException("Login timeout is not supported.");
    177    }
    178
    179    /**
    180     * Returns my log writer.
    181     *
    182     * @return my log writer
    183     * @see DataSource#getLogWriter
    184     */
    185    @Override
    186    public PrintWriter getLogWriter() {
    187        return logWriter;
    188    }
    189
    190    @Override
    191    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    192        throw new SQLFeatureNotSupportedException();
    193    }
    194
    195    /**
    196     * Gets the backing object pool.
    197     *
    198     * @return the backing object pool.
    199     */
    200    protected ObjectPool<C> getPool() {
    201        return pool;
    202    }
    203
    204    /**
    205     * Returns the value of the accessToUnderlyingConnectionAllowed property.
    206     *
    207     * @return true if access to the underlying {@link Connection} is allowed, false otherwise.
    208     */
    209    public boolean isAccessToUnderlyingConnectionAllowed() {
    210        return this.accessToUnderlyingConnectionAllowed;
    211    }
    212
    213    @Override
    214    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
    215        return iface != null && iface.isInstance(this);
    216    }
    217
    218    /**
    219     * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
    220     * the underlying connection. (Default: false)
    221     *
    222     * @param allow
    223     *            Access to the underlying connection is granted when true.
    224     */
    225    public void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
    226        this.accessToUnderlyingConnectionAllowed = allow;
    227    }
    228
    229    /**
    230     * Throws {@link UnsupportedOperationException}.
    231     *
    232     * @throws UnsupportedOperationException
    233     *             As this implementation does not support this feature.
    234     */
    235    @Override
    236    public void setLoginTimeout(final int seconds) {
    237        throw new UnsupportedOperationException("Login timeout is not supported.");
    238    }
    239
    240    /**
    241     * Sets my log writer.
    242     *
    243     * @see DataSource#setLogWriter
    244     */
    245    @Override
    246    public void setLogWriter(final PrintWriter out) {
    247        logWriter = out;
    248    }
    249
    250    @Override
    251    public <T> T unwrap(final Class<T> iface) throws SQLException {
    252        if (isWrapperFor(iface)) {
    253            return iface.cast(this);
    254        }
    255        throw new SQLException(this + " is not a wrapper for " + iface);
    256    }
    257}