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 RuntimeException, SQLException {
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}