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.logging.Logger;
025
026import javax.sql.DataSource;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.apache.commons.pool2.ObjectPool;
031import org.apache.commons.pool2.impl.GenericObjectPool;
032
033/**
034 * A simple {@link DataSource} implementation that obtains
035 * {@link Connection}s from the specified {@link ObjectPool}.
036 *
037 * @param <C> The connection type
038 *
039 * @author Rodney Waldhoff
040 * @author Glenn L. Nielsen
041 * @author James House
042 * @author Dirk Verbeeck
043 * @version $Id: PoolingDataSource.java 1677110 2015-05-01 11:15:22Z sebb $
044 * @since 2.0
045 */
046public class PoolingDataSource<C extends Connection> implements DataSource, AutoCloseable {
047
048    private static final Log log = LogFactory.getLog(PoolingDataSource.class);
049
050    /** Controls access to the underlying connection */
051    private boolean accessToUnderlyingConnectionAllowed = false;
052
053    public PoolingDataSource(ObjectPool<C> pool) {
054        if (null == pool) {
055            throw new NullPointerException("Pool must not be null.");
056        }
057        _pool = pool;
058        // Verify that _pool's factory refers back to it.  If not, log a warning and try to fix.
059        if (_pool instanceof GenericObjectPool<?>) {
060            PoolableConnectionFactory pcf = (PoolableConnectionFactory) ((GenericObjectPool<?>) _pool).getFactory();
061            if (pcf == null) {
062                throw new NullPointerException("PoolableConnectionFactory must not be null.");
063            }
064            if (pcf.getPool() != _pool) {
065                log.warn(Utils.getMessage("poolingDataSource.factoryConfig"));
066                @SuppressWarnings("unchecked") // PCF must have a pool of PCs
067                ObjectPool<PoolableConnection> p = (ObjectPool<PoolableConnection>) _pool;
068                pcf.setPool(p);
069            }
070        }
071    }
072
073    /**
074     * Close and free all {@link Connection}s from the pool.
075     * @since 2.1
076     */
077    @Override
078    public void close() throws Exception {
079        try {
080            _pool.close();
081        } catch(RuntimeException rte) {
082            throw new RuntimeException(Utils.getMessage("pool.close.fail"), rte);
083        } catch(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.
099     * It controls if the PoolGuard allows access to the underlying connection.
100     * (Default: false)
101     *
102     * @param allow Access to the underlying connection is granted when true.
103     */
104    public void setAccessToUnderlyingConnectionAllowed(boolean allow) {
105        this.accessToUnderlyingConnectionAllowed = allow;
106    }
107
108    /* JDBC_4_ANT_KEY_BEGIN */
109    @Override
110    public boolean isWrapperFor(Class<?> iface) throws SQLException {
111        return false;
112    }
113
114    @Override
115    public <T> T unwrap(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     * Return a {@link java.sql.Connection} from my pool,
129     * according to the contract specified by {@link ObjectPool#borrowObject}.
130     */
131    @Override
132    public Connection getConnection() throws SQLException {
133        try {
134            C conn = _pool.borrowObject();
135            if (conn == null) {
136                return null;
137            }
138            return new PoolGuardConnectionWrapper<>(conn);
139        } catch(SQLException e) {
140            throw e;
141        } catch(NoSuchElementException e) {
142            throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e);
143        } catch(RuntimeException e) {
144            throw e;
145        } catch(Exception e) {
146            throw new SQLException("Cannot get a connection, general error", e);
147        }
148    }
149
150    /**
151     * Throws {@link UnsupportedOperationException}
152     * @throws UnsupportedOperationException
153     */
154    @Override
155    public Connection getConnection(String uname, String passwd) throws SQLException {
156        throw new UnsupportedOperationException();
157    }
158
159    /**
160     * Returns my log writer.
161     * @return my log writer
162     * @see DataSource#getLogWriter
163     */
164    @Override
165    public PrintWriter getLogWriter() {
166        return _logWriter;
167    }
168
169    /**
170     * Throws {@link UnsupportedOperationException}.
171     * @throws UnsupportedOperationException As this
172     *   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     * Throws {@link UnsupportedOperationException}.
181     * @throws UnsupportedOperationException As this
182     *   implementation does not support this feature.
183     */
184    @Override
185    public void setLoginTimeout(int seconds) {
186        throw new UnsupportedOperationException("Login timeout is not supported.");
187    }
188
189    /**
190     * Sets my log writer.
191     * @see DataSource#setLogWriter
192     */
193    @Override
194    public void setLogWriter(PrintWriter out) {
195        _logWriter = out;
196    }
197
198    /** My log writer. */
199    private PrintWriter _logWriter = null;
200
201    private final ObjectPool<C> _pool;
202
203    protected ObjectPool<C> getPool() {
204        return _pool;
205    }
206
207    /**
208     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a
209     * closed connection cannot be used anymore.
210     * @since 2.0
211     */
212    private class PoolGuardConnectionWrapper<D extends Connection>
213            extends DelegatingConnection<D> {
214
215        PoolGuardConnectionWrapper(D delegate) {
216            super(delegate);
217        }
218
219        /**
220         * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
221         */
222        @Override
223        public D getDelegate() {
224            if (isAccessToUnderlyingConnectionAllowed()) {
225                return super.getDelegate();
226            }
227            return null;
228        }
229
230        /**
231         * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
232         */
233        @Override
234        public Connection getInnermostDelegate() {
235            if (isAccessToUnderlyingConnectionAllowed()) {
236                return super.getInnermostDelegate();
237            }
238            return null;
239        }
240
241        @Override
242        public void close() throws SQLException {
243            if (getDelegateInternal() != null) {
244                super.close();
245                super.setDelegate(null);
246            }
247        }
248
249        @Override
250        public boolean isClosed() throws SQLException {
251            if (getDelegateInternal() == null) {
252                return true;
253            }
254            return super.isClosed();
255        }
256    }
257}