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 RuntimeException, SQLException {
125        try {
126            pool.close();
127        } catch (final RuntimeException rte) {
128            throw new RuntimeException(Utils.getMessage("pool.close.fail"), rte);
129        } catch (final Exception e) {
130            throw new SQLException(Utils.getMessage("pool.close.fail"), e);
131        }
132    }
133
134    /**
135     * Returns a {@link java.sql.Connection} from my pool, according to the contract specified by
136     * {@link ObjectPool#borrowObject}.
137     */
138    @Override
139    public Connection getConnection() throws SQLException {
140        try {
141            final C conn = pool.borrowObject();
142            if (conn == null) {
143                return null;
144            }
145            return new PoolGuardConnectionWrapper<>(conn);
146        } catch (final NoSuchElementException e) {
147            throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e);
148        } catch (final SQLException | RuntimeException e) {
149            throw e;
150        } catch (final InterruptedException e) {
151            // Reset the interrupt status so it is visible to callers
152            Thread.currentThread().interrupt();
153            throw new SQLException("Cannot get a connection, general error", e);
154        } catch (final Exception e) {
155            throw new SQLException("Cannot get a connection, general error", e);
156        }
157    }
158
159    /**
160     * Throws {@link UnsupportedOperationException}
161     *
162     * @throws UnsupportedOperationException
163     *             always thrown
164     */
165    @Override
166    public Connection getConnection(final String uname, final String passwd) throws SQLException {
167        throw new UnsupportedOperationException();
168    }
169
170    // --- DataSource methods -----------------------------------------
171
172    /**
173     * Throws {@link UnsupportedOperationException}.
174     *
175     * @throws UnsupportedOperationException
176     *             As this implementation does not support this feature.
177     */
178    @Override
179    public int getLoginTimeout() {
180        throw new UnsupportedOperationException("Login timeout is not supported.");
181    }
182
183    /**
184     * Returns my log writer.
185     *
186     * @return my log writer
187     * @see DataSource#getLogWriter
188     */
189    @Override
190    public PrintWriter getLogWriter() {
191        return logWriter;
192    }
193
194    @Override
195    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
196        throw new SQLFeatureNotSupportedException();
197    }
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    /* JDBC_4_ANT_KEY_END */
257}