    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 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 must not be null.");
    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)
    107                    .getFactory();
    108            Objects.requireNonNull(pcf, "PoolableConnectionFactory must not be null.");
    109            if (pcf.getPool() != this.pool) {
    110                log.warn(Utils.getMessage("poolingDataSource.factoryConfig"));
    111                @SuppressWarnings("unchecked") // PCF must have a pool of PCs
    112                final ObjectPool<PoolableConnection> p = (ObjectPool<PoolableConnection>) this.pool;
    113                pcf.setPool(p);
    114            }
    115        }
    116    }
    117
    118    /**
    119     * Closes and free all {@link Connection}s from the pool.
    120     *
    121     * @since 2.1
    122     */
    123    @Override
    124    public void close() throws SQLException {
    125        try {
    126            pool.close();
    127        } catch (final Exception e) {
    128            throw new SQLException(Utils.getMessage("pool.close.fail"), e);
    129        }
    130    }
    131
    132    /**
    133     * Returns a {@link java.sql.Connection} from my pool, according to the contract specified by
    134     * {@link ObjectPool#borrowObject}.
    135     */
    136    @Override
    137    public Connection getConnection() throws SQLException {
    138        try {
    139            final C conn = pool.borrowObject();
    140            if (conn == null) {
    141                return null;
    142            }
    143            return new PoolGuardConnectionWrapper<>(conn);
    144        } catch (final NoSuchElementException e) {
    145            throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e);
    146        } catch (final SQLException | RuntimeException e) {
    147            throw e;
    148        } catch (final InterruptedException e) {
    149            // Reset the interrupt status so it is visible to callers
    150            Thread.currentThread().interrupt();
    151            throw new SQLException("Cannot get a connection, general error", e);
    152        } catch (final Exception e) {
    153            throw new SQLException("Cannot get a connection, general error", e);
    154        }
    155    }
    156
    157    /**
    158     * Throws {@link UnsupportedOperationException}
    159     *
    160     * @throws UnsupportedOperationException
    161     *             always thrown
    162     */
    163    @Override
    164    public Connection getConnection(final String uname, final String passwd) throws SQLException {
    165        throw new UnsupportedOperationException();
    166    }
    167
    168    // --- DataSource methods -----------------------------------------
    169
    170    /**
    171     * Throws {@link UnsupportedOperationException}.
    172     *
    173     * @throws UnsupportedOperationException
    174     *             As this implementation does not support this feature.
    175     */
    176    @Override
    177    public int getLoginTimeout() {
    178        throw new UnsupportedOperationException("Login timeout is not supported.");
    179    }
    180
    181    /**
    182     * Returns my log writer.
    183     *
    184     * @return my log writer
    185     * @see DataSource#getLogWriter
    186     */
    187    @Override
    188    public PrintWriter getLogWriter() {
    189        return logWriter;
    190    }
    191
    192    @Override
    193    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    194        throw new SQLFeatureNotSupportedException();
    195    }
    196
    197    protected ObjectPool<C> getPool() {
    198        return pool;
    199    }
    200
    201    /**
    202     * Returns the value of the accessToUnderlyingConnectionAllowed property.
    203     *
    204     * @return true if access to the underlying {@link Connection} is allowed, false otherwise.
    205     */
    206    public boolean isAccessToUnderlyingConnectionAllowed() {
    207        return this.accessToUnderlyingConnectionAllowed;
    208    }
    209
    210    @Override
    211    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
    212        return iface != null && iface.isInstance(this);
    213    }
    214
    215    /**
    216     * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
    217     * the underlying connection. (Default: false)
    218     *
    219     * @param allow
    220     *            Access to the underlying connection is granted when true.
    221     */
    222    public void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
    223        this.accessToUnderlyingConnectionAllowed = allow;
    224    }
    225
    226    /**
    227     * Throws {@link UnsupportedOperationException}.
    228     *
    229     * @throws UnsupportedOperationException
    230     *             As this implementation does not support this feature.
    231     */
    232    @Override
    233    public void setLoginTimeout(final int seconds) {
    234        throw new UnsupportedOperationException("Login timeout is not supported.");
    235    }
    236
    237    /**
    238     * Sets my log writer.
    239     *
    240     * @see DataSource#setLogWriter
    241     */
    242    @Override
    243    public void setLogWriter(final PrintWriter out) {
    244        logWriter = out;
    245    }
    246
    247    @Override
    248    public <T> T unwrap(final Class<T> iface) throws SQLException {
    249        if (isWrapperFor(iface)) {
    250            return iface.cast(this);
    251        }
    252        throw new SQLException(this + " is not a wrapper for " + iface);
    253    }
    254}