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      /** My URL prefix */
91      public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
92  
93      protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
94  
95      // version numbers
96      protected static final int MAJOR_VERSION = 1;
97  
98      protected static final int MINOR_VERSION = 0;
99  
100     /** Controls access to the underlying connection */
101     private final boolean accessToUnderlyingConnectionAllowed;
102 
103     /**
104      * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled.
105      */
106     public PoolingDriver() {
107         this(true);
108     }
109 
110     /**
111      * For unit testing purposes.
112      *
113      * @param accessToUnderlyingConnectionAllowed
114      *            Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
115      */
116     protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
117         this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
118     }
119 
120     @Override
121     public boolean acceptsURL(final String url) throws SQLException {
122         return url != null && url.startsWith(URL_PREFIX);
123     }
124 
125     /**
126      * Closes a named pool.
127      *
128      * @param name
129      *            The pool name.
130      * @throws SQLException
131      *             Thrown when a problem is caught closing the pool.
132      */
133     public synchronized void closePool(final String name) throws SQLException {
134         @SuppressWarnings("resource")
135         final ObjectPool<? extends Connection> pool = pools.get(name);
136         if (pool != null) {
137             pools.remove(name);
138             try {
139                 pool.close();
140             } catch (final Exception e) {
141                 throw new SQLException("Error closing pool " + name, e);
142             }
143         }
144     }
145 
146     @Override
147     public Connection connect(final String url, final Properties info) throws SQLException {
148         if (acceptsURL(url)) {
149             final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
150 
151             try {
152                 final Connection conn = pool.borrowObject();
153                 if (conn == null) {
154                     return null;
155                 }
156                 return new PoolGuardConnectionWrapper(pool, conn);
157             } catch (final NoSuchElementException e) {
158                 throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
159             } catch (final SQLException | RuntimeException e) {
160                 throw e;
161             } catch (final Exception e) {
162                 throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
163             }
164         }
165         return null;
166     }
167 
168     /**
169      * Gets the connection pool for the given name.
170      *
171      * @param name
172      *            The pool name
173      * @return The pool
174      * @throws SQLException
175      *             Thrown when the named pool is not registered.
176      */
177     public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
178         final ObjectPool<? extends Connection> pool = pools.get(name);
179         if (null == pool) {
180             throw new SQLException("Pool not registered: " + name);
181         }
182         return pool;
183     }
184 
185     @Override
186     public int getMajorVersion() {
187         return MAJOR_VERSION;
188     }
189 
190     @Override
191     public int getMinorVersion() {
192         return MINOR_VERSION;
193     }
194 
195     @Override
196     public Logger getParentLogger() throws SQLFeatureNotSupportedException {
197         throw new SQLFeatureNotSupportedException();
198     }
199 
200     /**
201      * Gets the pool names.
202      *
203      * @return the pool names.
204      */
205     public synchronized String[] getPoolNames() {
206         return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
207     }
208 
209     @Override
210     public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
211         return EMPTY_DRIVER_PROPERTY_INFO_ARRAY;
212     }
213     /**
214      * Invalidates the given connection.
215      *
216      * @param conn
217      *            connection to invalidate
218      * @throws SQLException
219      *             if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating
220      *             the connection
221      */
222     public void invalidateConnection(final Connection conn) throws SQLException {
223         if (!(conn instanceof PoolGuardConnectionWrapper)) {
224             throw new SQLException("Invalid connection class");
225         }
226         final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
227         @SuppressWarnings("unchecked")
228         final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
229         try {
230             pool.invalidateObject(pgconn.getDelegateInternal());
231         } catch (final Exception ignored) {
232             // Ignored.
233         }
234     }
235 
236     /**
237      * Returns the value of the accessToUnderlyingConnectionAllowed property.
238      *
239      * @return true if access to the underlying is allowed, false otherwise.
240      */
241     protected boolean isAccessToUnderlyingConnectionAllowed() {
242         return accessToUnderlyingConnectionAllowed;
243     }
244 
245     @Override
246     public boolean jdbcCompliant() {
247         return true;
248     }
249 
250     /**
251      * Registers a named pool.
252      *
253      * @param name
254      *            The pool name.
255      * @param pool
256      *            The pool.
257      */
258     public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
259         pools.put(name, pool);
260     }
261 }