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.sql.Connection;
20  import java.sql.Driver;
21  import java.sql.DriverManager;
22  import java.sql.DriverPropertyInfo;
23  import java.sql.SQLException;
24  import java.sql.SQLFeatureNotSupportedException;
25  import java.util.HashMap;
26  import java.util.NoSuchElementException;
27  import java.util.Properties;
28  import java.util.logging.Logger;
29  
30  import org.apache.commons.pool2.ObjectPool;
31  
32  /**
33   * A {@link Driver} implementation that obtains {@link Connection}s from a registered {@link ObjectPool}.
34   *
35   * @since 2.0
36   */
37  public class PoolingDriver implements Driver {
38  
39      /**
40       * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
41       *
42       * @since 2.0
43       */
44      private final class PoolGuardConnectionWrapper extends DelegatingConnection<Connection> {
45  
46          private final ObjectPool<? extends Connection> pool;
47  
48          PoolGuardConnectionWrapper(final ObjectPool<? extends Connection> pool, final Connection delegate) {
49              super(delegate);
50              this.pool = pool;
51          }
52  
53          /**
54           * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
55           */
56          @Override
57          public Connection getDelegate() {
58              if (isAccessToUnderlyingConnectionAllowed()) {
59                  return super.getDelegate();
60              }
61              return null;
62          }
63  
64          /**
65           * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
66           */
67          @Override
68          public Connection getInnermostDelegate() {
69              if (isAccessToUnderlyingConnectionAllowed()) {
70                  return super.getInnermostDelegate();
71              }
72              return null;
73          }
74      }
75  
76      private static final DriverPropertyInfo[] EMPTY_DRIVER_PROPERTY_INFO_ARRAY = {};
77  
78      /* Register myself with the {@link DriverManager}. */
79      static {
80          try {
81              DriverManager.registerDriver(new PoolingDriver());
82          } catch (final Exception ignored) {
83              // Ignored
84          }
85      }
86  
87      /** The map of registered pools. */
88      protected static final HashMap<String, ObjectPool<? extends Connection>> pools = new HashMap<>();
89  
90      /**
91       * The Apache Commons connection string prefix {@value}.
92       */
93      public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
94  
95      /**
96       * The String length of {@link #URL_PREFIX}.
97       */
98      protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
99  
100     /**
101      * Major version number.
102      */
103     protected static final int MAJOR_VERSION = 1;
104 
105     /**
106      * Minor version number.
107      */
108     protected static final int MINOR_VERSION = 0;
109 
110     /** Controls access to the underlying connection */
111     private final boolean accessToUnderlyingConnectionAllowed;
112 
113     /**
114      * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled.
115      */
116     public PoolingDriver() {
117         this(true);
118     }
119 
120     /**
121      * For unit testing purposes.
122      *
123      * @param accessToUnderlyingConnectionAllowed
124      *            Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
125      */
126     protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
127         this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
128     }
129 
130     @Override
131     public boolean acceptsURL(final String url) throws SQLException {
132         return url != null && url.startsWith(URL_PREFIX);
133     }
134 
135     /**
136      * Closes a named pool.
137      *
138      * @param name
139      *            The pool name.
140      * @throws SQLException
141      *             Thrown when a problem is caught closing the pool.
142      */
143     public synchronized void closePool(final String name) throws SQLException {
144         @SuppressWarnings("resource")
145         final ObjectPool<? extends Connection> pool = pools.get(name);
146         if (pool != null) {
147             pools.remove(name);
148             try {
149                 pool.close();
150             } catch (final Exception e) {
151                 throw new SQLException("Error closing pool " + name, e);
152             }
153         }
154     }
155 
156     @Override
157     public Connection connect(final String url, final Properties info) throws SQLException {
158         if (acceptsURL(url)) {
159             final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
160             try {
161                 final Connection conn = pool.borrowObject();
162                 if (conn == null) {
163                     return null;
164                 }
165                 return new PoolGuardConnectionWrapper(pool, conn);
166             } catch (final NoSuchElementException e) {
167                 throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
168             } catch (final SQLException | RuntimeException e) {
169                 throw e;
170             } catch (final Exception e) {
171                 throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
172             }
173         }
174         return null;
175     }
176 
177     /**
178      * Gets the connection pool for the given name.
179      *
180      * @param name
181      *            The pool name
182      * @return The pool
183      * @throws SQLException
184      *             Thrown when the named pool is not registered.
185      */
186     public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
187         final ObjectPool<? extends Connection> pool = pools.get(name);
188         if (null == pool) {
189             throw new SQLException("Pool not registered: " + name);
190         }
191         return pool;
192     }
193 
194     @Override
195     public int getMajorVersion() {
196         return MAJOR_VERSION;
197     }
198 
199     @Override
200     public int getMinorVersion() {
201         return MINOR_VERSION;
202     }
203 
204     @Override
205     public Logger getParentLogger() throws SQLFeatureNotSupportedException {
206         throw new SQLFeatureNotSupportedException();
207     }
208 
209     /**
210      * Gets the pool names.
211      *
212      * @return the pool names.
213      */
214     public synchronized String[] getPoolNames() {
215         return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
216     }
217 
218     @Override
219     public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
220         return EMPTY_DRIVER_PROPERTY_INFO_ARRAY;
221     }
222     /**
223      * Invalidates the given connection.
224      *
225      * @param conn
226      *            connection to invalidate
227      * @throws SQLException
228      *             if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating
229      *             the connection
230      */
231     public void invalidateConnection(final Connection conn) throws SQLException {
232         if (!(conn instanceof PoolGuardConnectionWrapper)) {
233             throw new SQLException("Invalid connection class");
234         }
235         final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
236         @SuppressWarnings("unchecked")
237         final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
238         try {
239             pool.invalidateObject(pgconn.getDelegateInternal());
240         } catch (final Exception ignored) {
241             // Ignored.
242         }
243     }
244 
245     /**
246      * Returns the value of the accessToUnderlyingConnectionAllowed property.
247      *
248      * @return true if access to the underlying is allowed, false otherwise.
249      */
250     protected boolean isAccessToUnderlyingConnectionAllowed() {
251         return accessToUnderlyingConnectionAllowed;
252     }
253 
254     @Override
255     public boolean jdbcCompliant() {
256         return true;
257     }
258 
259     /**
260      * Registers a named pool.
261      *
262      * @param name
263      *            The pool name.
264      * @param pool
265      *            The pool.
266      */
267     public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
268         pools.put(name, pool);
269     }
270 }