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");
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).getFactory();
107             Objects.requireNonNull(pcf, "this.pool.getFactory()");
108             if (pcf.getPool() != this.pool) {
109                 log.warn(Utils.getMessage("poolingDataSource.factoryConfig"));
110                 @SuppressWarnings("unchecked") // PCF must have a pool of PCs
111                 final ObjectPool<PoolableConnection> p = (ObjectPool<PoolableConnection>) this.pool;
112                 pcf.setPool(p);
113             }
114         }
115     }
116 
117     /**
118      * Closes and free all {@link Connection}s from the pool.
119      *
120      * @since 2.1
121      */
122     @Override
123     public void close() throws SQLException {
124         try {
125             pool.close();
126         } catch (final Exception e) {
127             throw new SQLException(Utils.getMessage("pool.close.fail"), e);
128         }
129     }
130 
131     /**
132      * Returns a {@link java.sql.Connection} from my pool, according to the contract specified by
133      * {@link ObjectPool#borrowObject}.
134      */
135     @Override
136     public Connection getConnection() throws SQLException {
137         try {
138             final C conn = pool.borrowObject();
139             if (conn == null) {
140                 return null;
141             }
142             return new PoolGuardConnectionWrapper<>(conn);
143         } catch (final NoSuchElementException e) {
144             throw new SQLException("Cannot get a connection, pool error " + e.getMessage(), e);
145         } catch (final SQLException | RuntimeException e) {
146             throw e;
147         } catch (final InterruptedException e) {
148             // Reset the interrupt status so it is visible to callers
149             Thread.currentThread().interrupt();
150             throw new SQLException("Cannot get a connection, general error", e);
151         } catch (final Exception e) {
152             throw new SQLException("Cannot get a connection, general error", e);
153         }
154     }
155 
156     /**
157      * Throws {@link UnsupportedOperationException}
158      *
159      * @throws UnsupportedOperationException
160      *             always thrown
161      */
162     @Override
163     public Connection getConnection(final String userName, final String password) throws SQLException {
164         throw new UnsupportedOperationException();
165     }
166 
167 
168     /**
169      * Throws {@link UnsupportedOperationException}.
170      *
171      * @throws UnsupportedOperationException
172      *             As this 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      * Returns my log writer.
181      *
182      * @return my log writer
183      * @see DataSource#getLogWriter
184      */
185     @Override
186     public PrintWriter getLogWriter() {
187         return logWriter;
188     }
189 
190     @Override
191     public Logger getParentLogger() throws SQLFeatureNotSupportedException {
192         throw new SQLFeatureNotSupportedException();
193     }
194 
195     /**
196      * Gets the backing object pool.
197      *
198      * @return the backing object pool.
199      */
200     protected ObjectPool<C> getPool() {
201         return pool;
202     }
203 
204     /**
205      * Returns the value of the accessToUnderlyingConnectionAllowed property.
206      *
207      * @return true if access to the underlying {@link Connection} is allowed, false otherwise.
208      */
209     public boolean isAccessToUnderlyingConnectionAllowed() {
210         return this.accessToUnderlyingConnectionAllowed;
211     }
212 
213     @Override
214     public boolean isWrapperFor(final Class<?> iface) throws SQLException {
215         return iface != null && iface.isInstance(this);
216     }
217 
218     /**
219      * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
220      * the underlying connection. (Default: false)
221      *
222      * @param allow
223      *            Access to the underlying connection is granted when true.
224      */
225     public void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
226         this.accessToUnderlyingConnectionAllowed = allow;
227     }
228 
229     /**
230      * Throws {@link UnsupportedOperationException}.
231      *
232      * @throws UnsupportedOperationException
233      *             As this implementation does not support this feature.
234      */
235     @Override
236     public void setLoginTimeout(final int seconds) {
237         throw new UnsupportedOperationException("Login timeout is not supported.");
238     }
239 
240     /**
241      * Sets my log writer.
242      *
243      * @see DataSource#setLogWriter
244      */
245     @Override
246     public void setLogWriter(final PrintWriter out) {
247         logWriter = out;
248     }
249 
250     @Override
251     public <T> T unwrap(final Class<T> iface) throws SQLException {
252         if (isWrapperFor(iface)) {
253             return iface.cast(this);
254         }
255         throw new SQLException(this + " is not a wrapper for " + iface);
256     }
257 }