    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    private static final Log log = LogFactory.getLog(PoolingDataSource.class);
    045
    046    /** Controls access to the underlying connection */
    047    private boolean accessToUnderlyingConnectionAllowed;
    048
    049    /**
    050     * Constructs a new instance backed by the given connection pool.
    051     *
    052     * @param pool
    053     *            the given connection pool.
    054     */
    055    public PoolingDataSource(final ObjectPool<C> pool) {
    056        Objects.requireNonNull(pool, "Pool must not be null.");
    057        this.pool = pool;
    058        // Verify that pool's factory refers back to it. If not, log a warning and try to fix.
    059        if (this.pool instanceof GenericObjectPool<?>) {
    060            final PoolableConnectionFactory pcf = (PoolableConnectionFactory) ((GenericObjectPool<?>) this.pool)
    061                    .getFactory();
    062            Objects.requireNonNull(pcf, "PoolableConnectionFactory must not be null.");
    063            if (pcf.getPool() != this.pool) {
    064                log.warn(Utils.getMessage("poolingDataSource.factoryConfig"));
    065                @SuppressWarnings("unchecked") // PCF must have a pool of PCs
    066                final ObjectPool<PoolableConnection> p = (ObjectPool<PoolableConnection>) this.pool;
    067                pcf.setPool(p);
    068            }
    069        }
    070    }
    071
    072    /**
    073     * Closes and free all {@link Connection}s from the pool.
    074     *
    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. It controls if the PoolGuard allows access to
    099     * the underlying connection. (Default: false)
    100     *
    101     * @param allow
    102     *            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     * Returns a {@link java.sql.Connection} from my pool, according to the contract specified by
    129     * {@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     *
    157     * @throws UnsupportedOperationException
    158     *             always thrown
    159     */
    160    @Override
    161    public Connection getConnection(final String uname, final String passwd) throws SQLException {
    162        throw new UnsupportedOperationException();
    163    }
    164
    165    /**
    166     * Returns my log writer.
    167     *
    168     * @return my log writer
    169     * @see DataSource#getLogWriter
    170     */
    171    @Override
    172    public PrintWriter getLogWriter() {
    173        return logWriter;
    174    }
    175
    176    /**
    177     * Throws {@link UnsupportedOperationException}.
    178     *
    179     * @throws UnsupportedOperationException
    180     *             As this implementation does not support this feature.
    181     */
    182    @Override
    183    public int getLoginTimeout() {
    184        throw new UnsupportedOperationException("Login timeout is not supported.");
    185    }
    186
    187    /**
    188     * Throws {@link UnsupportedOperationException}.
    189     *
    190     * @throws UnsupportedOperationException
    191     *             As this implementation does not support this feature.
    192     */
    193    @Override
    194    public void setLoginTimeout(final int seconds) {
    195        throw new UnsupportedOperationException("Login timeout is not supported.");
    196    }
    197
    198    /**
    199     * Sets my log writer.
    200     *
    201     * @see DataSource#setLogWriter
    202     */
    203    @Override
    204    public void setLogWriter(final PrintWriter out) {
    205        logWriter = out;
    206    }
    207
    208    /** My log writer. */
    209    private PrintWriter logWriter = null;
    210
    211    private final ObjectPool<C> pool;
    212
    213    protected ObjectPool<C> getPool() {
    214        return pool;
    215    }
    216
    217    /**
    218     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
    219     *
    220     * @since 2.0
    221     */
    222    private class PoolGuardConnectionWrapper<D extends Connection> extends DelegatingConnection<D> {
    223
    224        PoolGuardConnectionWrapper(final D delegate) {
    225            super(delegate);
    226        }
    227
    228        /**
    229         * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
    230         */
    231        @Override
    232        public D getDelegate() {
    233            return isAccessToUnderlyingConnectionAllowed() ? super.getDelegate() : null;
    234        }
    235
    236        /**
    237         * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
    238         */
    239        @Override
    240        public Connection getInnermostDelegate() {
    241            return isAccessToUnderlyingConnectionAllowed() ? super.getInnermostDelegate() : null;
    242        }
    243
    244        @Override
    245        public void close() throws SQLException {
    246            if (getDelegateInternal() != null) {
    247                super.close();
    248                super.setDelegate(null);
    249            }
    250        }
    251
    252        @Override
    253        public boolean isClosed() throws SQLException {
    254            return getDelegateInternal() == null ? true : super.isClosed();
    255        }
    256    }
    257}