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 $Revision: 1592119 $ $Date: 2014-05-02 16:32:43 -0700 (Fri, 02 May 2014) $
044 * @since 2.0
045 */
046public class PoolingDataSource<C extends Connection> implements DataSource {
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     * Returns the value of the accessToUnderlyingConnectionAllowed property.
075     *
076     * @return true if access to the underlying is allowed, false otherwise.
077     */
078    public boolean isAccessToUnderlyingConnectionAllowed() {
079        return this.accessToUnderlyingConnectionAllowed;
080    }
081
082    /**
083     * Sets the value of the accessToUnderlyingConnectionAllowed property.
084     * It controls if the PoolGuard allows access to the underlying connection.
085     * (Default: false)
086     *
087     * @param allow Access to the underlying connection is granted when true.
088     */
089    public void setAccessToUnderlyingConnectionAllowed(boolean allow) {
090        this.accessToUnderlyingConnectionAllowed = allow;
091    }
092
093    /* JDBC_4_ANT_KEY_BEGIN */
094    @Override
095    public boolean isWrapperFor(Class<?> iface) throws SQLException {
096        return false;
097    }
098
099    @Override
100    public <T> T unwrap(Class<T> iface) throws SQLException {
101        throw new SQLException("PoolingDataSource is not a wrapper.");
102    }
103    /* JDBC_4_ANT_KEY_END */
104
105    @Override
106    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
107        throw new SQLFeatureNotSupportedException();
108    }
109
110    //--- DataSource methods -----------------------------------------
111
112    /**
113     * Return a {@link java.sql.Connection} from my pool,
114     * according to the contract specified by {@link ObjectPool#borrowObject}.
115     */
116    @Override
117    public Connection getConnection() throws SQLException {
118        try {
119            C conn = _pool.borrowObject();
120            if (conn == null) {
121                return null;
122            }
123            return new PoolGuardConnectionWrapper<>(conn);
124        } catch(SQLException e) {
125            throw e;
126        } catch(NoSuchElementException e) {
127            throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e);
128        } catch(RuntimeException e) {
129            throw e;
130        } catch(Exception e) {
131            throw new SQLException("Cannot get a connection, general error", e);
132        }
133    }
134
135    /**
136     * Throws {@link UnsupportedOperationException}
137     * @throws UnsupportedOperationException
138     */
139    @Override
140    public Connection getConnection(String uname, String passwd) throws SQLException {
141        throw new UnsupportedOperationException();
142    }
143
144    /**
145     * Returns my log writer.
146     * @return my log writer
147     * @see DataSource#getLogWriter
148     */
149    @Override
150    public PrintWriter getLogWriter() {
151        return _logWriter;
152    }
153
154    /**
155     * Throws {@link UnsupportedOperationException}.
156     * @throws UnsupportedOperationException As this
157     *   implementation does not support this feature.
158     */
159    @Override
160    public int getLoginTimeout() {
161        throw new UnsupportedOperationException("Login timeout is not supported.");
162    }
163
164    /**
165     * Throws {@link UnsupportedOperationException}.
166     * @throws UnsupportedOperationException As this
167     *   implementation does not support this feature.
168     */
169    @Override
170    public void setLoginTimeout(int seconds) {
171        throw new UnsupportedOperationException("Login timeout is not supported.");
172    }
173
174    /**
175     * Sets my log writer.
176     * @see DataSource#setLogWriter
177     */
178    @Override
179    public void setLogWriter(PrintWriter out) {
180        _logWriter = out;
181    }
182
183    /** My log writer. */
184    private PrintWriter _logWriter = null;
185
186    private final ObjectPool<C> _pool;
187
188    protected ObjectPool<C> getPool() {
189        return _pool;
190    }
191
192    /**
193     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a
194     * closed connection cannot be used anymore.
195     * @since 2.0
196     */
197    private class PoolGuardConnectionWrapper<D extends Connection>
198            extends DelegatingConnection<D> {
199
200        PoolGuardConnectionWrapper(D delegate) {
201            super(delegate);
202        }
203
204        /**
205         * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
206         */
207        @Override
208        public D getDelegate() {
209            if (isAccessToUnderlyingConnectionAllowed()) {
210                return super.getDelegate();
211            }
212            return null;
213        }
214
215        /**
216         * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
217         */
218        @Override
219        public Connection getInnermostDelegate() {
220            if (isAccessToUnderlyingConnectionAllowed()) {
221                return super.getInnermostDelegate();
222            }
223            return null;
224        }
225
226        @Override
227        public void close() throws SQLException {
228            if (getDelegateInternal() != null) {
229                super.close();
230                super.setDelegate(null);
231            }
232        }
233
234        @Override
235        public boolean isClosed() throws SQLException {
236            if (getDelegateInternal() == null) {
237                return true;
238            }
239            return super.isClosed();
240        }
241    }
242}