View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.dbcp2;
18  
19  import java.io.PrintWriter;
20  import java.sql.Connection;
21  import java.sql.SQLException;
22  import java.sql.SQLFeatureNotSupportedException;
23  import java.util.NoSuchElementException;
24  import java.util.Objects;
25  import java.util.logging.Logger;
26  
27  import javax.sql.DataSource;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.commons.pool2.ObjectPool;
32  import org.apache.commons.pool2.impl.GenericObjectPool;
33  
34  /**
35   * A simple {@link DataSource} implementation that obtains {@link Connection}s from the specified {@link ObjectPool}.
36   *
37   * @param <C>
38   *            The connection type
39   *
40   * @since 2.0
41   */
42  public class PoolingDataSource<C extends Connection> implements DataSource, AutoCloseable {
43  
44      /**
45       * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
46       *
47       * @since 2.0
48       */
49      private final class PoolGuardConnectionWrapper<D extends Connection> extends DelegatingConnection<D> {
50  
51          PoolGuardConnectionWrapper(final D delegate) {
52              super(delegate);
53          }
54  
55          @Override
56          public void close() throws SQLException {
57              if (getDelegateInternal() != null) {
58                  super.close();
59                  super.setDelegate(null);
60              }
61          }
62  
63          /**
64           * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
65           */
66          @Override
67          public D getDelegate() {
68              return isAccessToUnderlyingConnectionAllowed() ? super.getDelegate() : null;
69          }
70  
71          /**
72           * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
73           */
74          @Override
75          public Connection getInnermostDelegate() {
76              return isAccessToUnderlyingConnectionAllowed() ? super.getInnermostDelegate() : null;
77          }
78  
79          @Override
80          public boolean isClosed() throws SQLException {
81              return getDelegateInternal() == null || super.isClosed();
82          }
83      }
84  
85      private static final Log log = LogFactory.getLog(PoolingDataSource.class);
86  
87      /** Controls access to the underlying connection */
88      private boolean accessToUnderlyingConnectionAllowed;
89  
90      /** My log writer. */
91      private PrintWriter logWriter;
92  
93      private final ObjectPool<C> pool;
94  
95      /**
96       * Constructs a new instance backed by the given connection pool.
97       *
98       * @param pool
99       *            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 userName, final String password) 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 }