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    *      https://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  
18  package org.apache.commons.dbcp2;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertNull;
25  import static org.junit.jupiter.api.Assertions.assertSame;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  
29  import java.io.IOException;
30  import java.lang.management.ManagementFactory;
31  import java.lang.management.ThreadMXBean;
32  import java.sql.Connection;
33  import java.sql.SQLException;
34  import java.time.Duration;
35  import java.util.ArrayList;
36  import java.util.Arrays;
37  import java.util.Collection;
38  import java.util.HashSet;
39  import java.util.Properties;
40  import java.util.Set;
41  import java.util.concurrent.atomic.AtomicInteger;
42  import java.util.stream.Stream;
43  
44  import javax.management.AttributeNotFoundException;
45  import javax.management.MBeanAttributeInfo;
46  import javax.management.MBeanServer;
47  import javax.management.ObjectName;
48  import javax.sql.DataSource;
49  
50  import org.junit.jupiter.api.AfterEach;
51  import org.junit.jupiter.api.Assertions;
52  import org.junit.jupiter.api.BeforeEach;
53  import org.junit.jupiter.api.Disabled;
54  import org.junit.jupiter.api.Test;
55  
56  /**
57   * TestSuite for BasicDataSource
58   */
59  public class TestBasicDataSource extends TestConnectionPool {
60  
61      private static final String CATALOG = "test catalog";
62  
63      protected BasicDataSource ds;
64  
65      /**
66       * Cycle through idle connections and verify that they are all valid.
67       * Assumes we are the only client of the pool.
68       *
69       * @throws Exception if an error occurs
70       */
71      private void checkIdleValid() throws Exception {
72          final Set<Connection> idleConnections = new HashSet<>(); // idle connections
73          // Get idle connections by repeatedly making connection requests up to NumIdle
74          for (int i = 0; i < ds.getNumIdle(); i++) {
75              final Connection conn = ds.getConnection();
76              idleConnections.add(conn);
77          }
78          // Cycle through idle connections and verify that they are valid
79          for (final Connection conn : idleConnections) {
80              assertTrue(conn.isValid(2), "Connection should be valid");
81              conn.close();
82          }
83      }
84  
85      /**
86       * Check that maxTotal and maxIdle are not exceeded
87       *
88       * @throws Exception
89       */
90      private void checkLimits() throws Exception {
91          assertTrue(ds.getNumActive() + ds.getNumIdle() <= getMaxTotal(), "Total connections exceed maxTotal");
92          assertTrue(ds.getNumIdle() <= ds.getMaxIdle(), "Idle connections exceed maxIdle");
93      }
94  
95      protected BasicDataSource createDataSource() throws Exception {
96          return new BasicDataSource();
97      }
98  
99      @Override
100     protected Connection getConnection() throws Exception {
101         return ds.getConnection();
102     }
103 
104     @BeforeEach
105     public void setUp() throws Exception {
106         ds = createDataSource();
107         ds.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
108         ds.setUrl("jdbc:apache:commons:testdriver");
109         ds.setMaxTotal(getMaxTotal());
110         ds.setMaxWait(getMaxWaitDuration());
111         ds.setDefaultAutoCommit(Boolean.TRUE);
112         ds.setDefaultReadOnly(Boolean.FALSE);
113         ds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
114         ds.setDefaultCatalog(CATALOG);
115         ds.setUsername("userName");
116         ds.setPassword("password");
117         ds.setValidationQuery("SELECT DUMMY FROM DUAL");
118         ds.setConnectionInitSqls(Arrays.asList("SELECT 1", "SELECT 2"));
119         ds.setDriverClassLoader(new TesterClassLoader());
120         ds.setJmxName("org.apache.commons.dbcp2:name=test");
121     }
122 
123     @Override
124     @AfterEach
125     public void tearDown() throws Exception {
126         super.tearDown();
127         ds.close();
128         ds = null;
129     }
130 
131     @Test
132     void testAccessToUnderlyingConnectionAllowed() throws Exception {
133         ds.setAccessToUnderlyingConnectionAllowed(true);
134         assertTrue(ds.isAccessToUnderlyingConnectionAllowed());
135 
136         try (final Connection conn = getConnection()) {
137             Connection dconn = ((DelegatingConnection<?>) conn).getDelegate();
138             assertNotNull(dconn);
139 
140             dconn = ((DelegatingConnection<?>) conn).getInnermostDelegate();
141             assertNotNull(dconn);
142 
143             assertInstanceOf(TesterConnection.class, dconn);
144         }
145     }
146 
147     @Test
148     void testClose() throws Exception {
149         ds.setAccessToUnderlyingConnectionAllowed(true);
150 
151         // active connection is held open when ds is closed
152         final Connection activeConnection = getConnection();
153         final Connection rawActiveConnection = ((DelegatingConnection<?>) activeConnection).getInnermostDelegate();
154         assertFalse(activeConnection.isClosed());
155         assertFalse(rawActiveConnection.isClosed());
156 
157         // idle connection is in pool but closed
158         final Connection idleConnection = getConnection();
159         final Connection rawIdleConnection = ((DelegatingConnection<?>) idleConnection).getInnermostDelegate();
160         assertFalse(idleConnection.isClosed());
161         assertFalse(rawIdleConnection.isClosed());
162 
163         // idle wrapper should be closed but raw connection should be open
164         idleConnection.close();
165         assertTrue(idleConnection.isClosed());
166         assertFalse(rawIdleConnection.isClosed());
167 
168         ds.close();
169 
170         // raw idle connection should now be closed
171         assertTrue(rawIdleConnection.isClosed());
172 
173         // active connection should still be open
174         assertFalse(activeConnection.isClosed());
175         assertFalse(rawActiveConnection.isClosed());
176 
177         // now close the active connection
178         activeConnection.close();
179 
180         // both wrapper and raw active connection should be closed
181         assertTrue(activeConnection.isClosed());
182         assertTrue(rawActiveConnection.isClosed());
183 
184         // Verify SQLException on getConnection after close
185         assertThrows(SQLException.class, this::getConnection);
186 
187         // Redundant close is OK
188         ds.close();
189 
190     }
191 
192     @Test
193     void testConcurrentInitBorrow() throws Exception {
194         ds.setDriverClassName("org.apache.commons.dbcp2.TesterConnectionDelayDriver");
195         ds.setUrl("jdbc:apache:commons:testerConnectionDelayDriver:50");
196         ds.setInitialSize(8);
197 
198         // Launch a request to trigger pool initialization
199         final TestThread testThread = new TestThread(1, 0);
200         final Thread t = new Thread(testThread);
201         t.start();
202 
203         // Get another connection (should wait for pool init)
204         Thread.sleep(100); // Make sure t gets into init first
205         try (Connection conn = ds.getConnection()) {
206 
207             // Pool should have at least 6 idle connections now
208             // Use underlying pool getNumIdle to avoid waiting for ds lock
209             assertTrue(ds.getConnectionPool().getNumIdle() > 5);
210 
211             // Make sure t completes successfully
212             t.join();
213             assertFalse(testThread.failed());
214         }
215         ds.close();
216     }
217 
218     /**
219      * JIRA: DBCP-444 Verify that invalidate does not return closed connection to the pool.
220      */
221     @Test
222     void testConcurrentInvalidateBorrow() throws Exception {
223         ds.setDriverClassName("org.apache.commons.dbcp2.TesterConnRequestCountDriver");
224         ds.setUrl("jdbc:apache:commons:testerConnRequestCountDriver");
225         ds.setTestOnBorrow(true);
226         ds.setValidationQuery("SELECT DUMMY FROM DUAL");
227         ds.setMaxTotal(8);
228         ds.setLifo(true);
229         ds.setMaxWait(Duration.ofMillis(-1));
230 
231         // Threads just borrow and return - validation will trigger close check
232         final TestThread testThread1 = new TestThread(1000, 0);
233         final Thread t1 = new Thread(testThread1);
234         t1.start();
235         final TestThread testThread2 = new TestThread(1000, 0);
236         final Thread t2 = new Thread(testThread1);
237         t2.start();
238 
239         // Grab and invalidate connections
240         for (int i = 0; i < 1000; i++) {
241             final Connection conn = ds.getConnection();
242             ds.invalidateConnection(conn);
243         }
244 
245         // Make sure borrow threads complete successfully
246         t1.join();
247         t2.join();
248         assertFalse(testThread1.failed());
249         assertFalse(testThread2.failed());
250 
251         ds.close();
252     }
253 
254     /**
255      * Test disabling MBean registration for Connection objects. JIRA: DBCP-585
256      */
257     @Test
258     void testConnectionMBeansDisabled() throws Exception {
259         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
260         // Unregister leftovers from other tests (TODO: worry about concurrent test execution)
261         final ObjectName commons = new ObjectName("org.apache.commons.*:*");
262         final Set<ObjectName> results = mbs.queryNames(commons, null);
263         for (final ObjectName result : results) {
264             mbs.unregisterMBean(result);
265         }
266         ds.setRegisterConnectionMBean(false); // Should disable Connection MBean registration
267         try (Connection conn = ds.getConnection()) { // Trigger initialization
268             // No Connection MBeans shall be registered
269             final ObjectName connections = new ObjectName("org.apache.commons.*:connection=*,*");
270             assertEquals(0, mbs.queryNames(connections, null).size());
271         }
272     }
273 
274     /**
275      * JIRA: DBCP-547 Verify that ConnectionFactory interface in BasicDataSource.createConnectionFactory().
276      */
277     @Test
278     void testCreateConnectionFactoryWithConnectionFactoryClassName() throws Exception {
279         Properties properties = new Properties();
280         // set ConnectionFactoryClassName
281         properties = new Properties();
282         properties.put("initialSize", "1");
283         properties.put("driverClassName", "org.apache.commons.dbcp2.TesterDriver");
284         properties.put("url", "jdbc:apache:commons:testdriver");
285         properties.put("username", "foo");
286         properties.put("password", "bar");
287         properties.put("connectionFactoryClassName", "org.apache.commons.dbcp2.TesterConnectionFactory");
288         try (BasicDataSource ds = BasicDataSourceFactory.createDataSource(properties)) {
289             try (Connection conn = ds.getConnection()) {
290                 assertNotNull(conn);
291             }
292         }
293     }
294 
295     /**
296      * JIRA: DBCP-547 Verify that ConnectionFactory interface in BasicDataSource.createConnectionFactory().
297      */
298     @Test
299     void testCreateConnectionFactoryWithoutConnectionFactoryClassName() throws Exception {
300         // not set ConnectionFactoryClassName
301         final Properties properties = new Properties();
302         properties.put("initialSize", "1");
303         properties.put("driverClassName", "org.apache.commons.dbcp2.TesterDriver");
304         properties.put("url", "jdbc:apache:commons:testdriver");
305         properties.put("username", "foo");
306         properties.put("password", "bar");
307         try (BasicDataSource ds = BasicDataSourceFactory.createDataSource(properties)) {
308             try (Connection conn = ds.getConnection()) {
309                 assertNotNull(conn);
310             }
311         }
312     }
313 
314     /**
315      * JIRA: DBCP-342, DBCP-93 Verify that when errors occur during BasicDataSource initialization, GenericObjectPool Evictors are cleaned up.
316      */
317     @Test
318     void testCreateDataSourceCleanupEvictor() throws Exception {
319         ds.close();
320         ds = null;
321         ds = createDataSource();
322         ds.setDriverClassName("org.apache.commons.dbcp2.TesterConnRequestCountDriver");
323         ds.setUrl("jdbc:apache:commons:testerConnRequestCountDriver");
324         ds.setValidationQuery("SELECT DUMMY FROM DUAL");
325         ds.setUsername("userName");
326 
327         // Make password incorrect, so createDataSource will throw
328         ds.setPassword("wrong");
329         // Set timeBetweenEvictionRuns > 0, so evictor will be created
330         ds.setDurationBetweenEvictionRuns(Duration.ofMillis(100));
331         // Set min idle > 0, so evictor will try to make connection as many as idle count
332         ds.setMinIdle(2);
333 
334         // Prevent concurrent execution of threads executing test subclasses
335         synchronized (TesterConnRequestCountDriver.class) {
336             TesterConnRequestCountDriver.initConnRequestCount();
337 
338             // user request 10 times
339             for (int i = 0; i < 10; i++) {
340                 try {
341                     @SuppressWarnings("unused")
342                     final DataSource ds2 = ds.createDataSource();
343                 } catch (final SQLException e) {
344                     // Ignore
345                 }
346             }
347 
348             // sleep 1000ms. evictor will be invoked 10 times if running.
349             Thread.sleep(1000);
350 
351             // Make sure there have been no Evictor-generated requests (count should be 10, from requests above)
352             assertEquals(10, TesterConnRequestCountDriver.getConnectionRequestCount());
353         }
354 
355         // make sure cleanup is complete
356         assertNull(ds.getConnectionPool());
357     }
358 
359     /**
360      * JIRA DBCP-93: If an SQLException occurs after the GenericObjectPool is initialized in createDataSource, the evictor task is not cleaned up.
361      */
362     @Test
363     void testCreateDataSourceCleanupThreads() throws Exception {
364         ds.close();
365         ds = null;
366         ds = createDataSource();
367         ds.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
368         ds.setUrl("jdbc:apache:commons:testdriver");
369         ds.setMaxTotal(getMaxTotal());
370         ds.setMaxWait(getMaxWaitDuration());
371         ds.setDefaultAutoCommit(Boolean.TRUE);
372         ds.setDefaultReadOnly(Boolean.FALSE);
373         ds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
374         ds.setDefaultCatalog(CATALOG);
375         ds.setUsername("userName");
376         // Set timeBetweenEvictionRuns > 0, so evictor is created
377         ds.setDurationBetweenEvictionRuns(Duration.ofMillis(100));
378         // Make password incorrect, so createDataSource will throw
379         ds.setPassword("wrong");
380         ds.setValidationQuery("SELECT DUMMY FROM DUAL");
381         final int threadCount = Thread.activeCount();
382         for (int i = 0; i < 10; i++) {
383             try (Connection c = ds.getConnection()) {
384             } catch (final SQLException ex) {
385                 // ignore
386             }
387         }
388         // Allow one extra thread for JRockit compatibility
389         assertTrue(Thread.activeCount() <= threadCount + 1);
390     }
391 
392     @Test
393     void testDefaultCatalog() throws Exception {
394         final Connection[] c = new Connection[getMaxTotal()];
395         for (int i = 0; i < c.length; i++) {
396             c[i] = getConnection();
397             assertNotNull(c[i]);
398             assertEquals(CATALOG, c[i].getCatalog());
399         }
400 
401         for (final Connection element : c) {
402             element.setCatalog("error");
403             element.close();
404         }
405 
406         for (int i = 0; i < c.length; i++) {
407             c[i] = getConnection();
408             assertNotNull(c[i]);
409             assertEquals(CATALOG, c[i].getCatalog());
410         }
411 
412         for (final Connection element : c) {
413             element.close();
414         }
415     }
416 
417     @Test
418     void testDeprecatedAccessors() throws SQLException {
419         try (BasicDataSource bds = new BasicDataSource()) {
420             int i = 0;
421             //
422             i++;
423             bds.setDefaultQueryTimeout(i);
424             assertEquals(i, bds.getDefaultQueryTimeout());
425             assertEquals(Duration.ofSeconds(i), bds.getDefaultQueryTimeoutDuration());
426             //
427             i++;
428             bds.setMaxConnLifetimeMillis(i);
429             assertEquals(i, bds.getMaxConnLifetimeMillis());
430             assertEquals(Duration.ofMillis(i), bds.getMaxConnDuration());
431             //
432             i++;
433             bds.setMaxWaitMillis(i);
434             assertEquals(i, bds.getMaxWaitMillis());
435             assertEquals(Duration.ofMillis(i), bds.getMaxWaitDuration());
436             //
437             i++;
438             bds.setMinEvictableIdleTimeMillis(i);
439             assertEquals(i, bds.getMinEvictableIdleTimeMillis());
440             assertEquals(Duration.ofMillis(i), bds.getMinEvictableIdleDuration());
441             //
442             i++;
443             bds.setRemoveAbandonedTimeout(i);
444             assertEquals(i, bds.getRemoveAbandonedTimeout());
445             assertEquals(Duration.ofSeconds(i), bds.getRemoveAbandonedTimeoutDuration());
446             //
447             i++;
448             bds.setSoftMinEvictableIdleTimeMillis(i);
449             assertEquals(i, bds.getSoftMinEvictableIdleTimeMillis());
450             assertEquals(Duration.ofMillis(i), bds.getSoftMinEvictableIdleDuration());
451             //
452             i++;
453             bds.setTimeBetweenEvictionRunsMillis(i);
454             assertEquals(i, bds.getTimeBetweenEvictionRunsMillis());
455             assertEquals(Duration.ofMillis(i), bds.getDurationBetweenEvictionRuns());
456             //
457             i++;
458             bds.setValidationQueryTimeout(1);
459             assertEquals(1, bds.getValidationQueryTimeout());
460             assertEquals(Duration.ofSeconds(1), bds.getValidationQueryTimeoutDuration());
461         }
462     }
463 
464     @Test
465     void testDisconnectionIgnoreSqlCodes() throws Exception {
466         final ArrayList<String> disconnectionIgnoreSqlCodes = new ArrayList<>();
467         disconnectionIgnoreSqlCodes.add("XXXX");
468         ds.setDisconnectionIgnoreSqlCodes(disconnectionIgnoreSqlCodes);
469         ds.setFastFailValidation(true);
470         try (Connection conn = ds.getConnection()) { // Triggers initialization - pcf creation
471             // Make sure factory got the properties
472             final PoolableConnectionFactory pcf = (PoolableConnectionFactory) ds.getConnectionPool().getFactory();
473             assertTrue(pcf.isFastFailValidation());
474             assertTrue(pcf.getDisconnectionIgnoreSqlCodes().contains("XXXX"));
475             assertEquals(1, pcf.getDisconnectionIgnoreSqlCodes().size());
476         }
477     }
478 
479     /**
480      * JIRA: DBCP-437 Verify that BasicDataSource sets disconnect codes properties. Functionality is verified in pcf tests.
481      */
482     @Test
483     void testDisconnectSqlCodes() throws Exception {
484         final ArrayList<String> disconnectionSqlCodes = new ArrayList<>();
485         disconnectionSqlCodes.add("XXX");
486         ds.setDisconnectionSqlCodes(disconnectionSqlCodes);
487         ds.setFastFailValidation(true);
488         try (Connection conn = ds.getConnection()) { // Triggers initialization - pcf creation
489             // Make sure factory got the properties
490             final PoolableConnectionFactory pcf = (PoolableConnectionFactory) ds.getConnectionPool().getFactory();
491             assertTrue(pcf.isFastFailValidation());
492             assertTrue(pcf.getDisconnectionSqlCodes().contains("XXX"));
493             assertEquals(1, pcf.getDisconnectionSqlCodes().size());
494         }
495     }
496 
497     /**
498      * JIRA DBCP-333: Check that a custom class loader is used.
499      *
500      * @throws Exception
501      */
502     @Test
503     void testDriverClassLoader() throws Exception {
504         try (Connection conn = getConnection()) {
505             final ClassLoader cl = ds.getDriverClassLoader();
506             assertNotNull(cl);
507             assertInstanceOf(TesterClassLoader.class, cl);
508             assertTrue(((TesterClassLoader) cl).didLoad(ds.getDriverClassName()));
509         }
510     }
511 
512     @Test
513     void testEmptyInitConnectionSql() throws Exception {
514         // List
515         ds.setConnectionInitSqls(Arrays.asList("", "   "));
516         assertNotNull(ds.getConnectionInitSqls());
517         assertEquals(0, ds.getConnectionInitSqls().size());
518         // null
519         ds.setConnectionInitSqls(null);
520         assertNotNull(ds.getConnectionInitSqls());
521         assertEquals(0, ds.getConnectionInitSqls().size());
522         // Collection
523         ds.setConnectionInitSqls((Collection<String>) Arrays.asList("", "   "));
524         assertNotNull(ds.getConnectionInitSqls());
525         assertEquals(0, ds.getConnectionInitSqls().size());
526     }
527 
528     @Test
529     void testEmptyValidationQuery() throws Exception {
530         assertNotNull(ds.getValidationQuery());
531 
532         ds.setValidationQuery("");
533         assertNull(ds.getValidationQuery());
534 
535         ds.setValidationQuery("   ");
536         assertNull(ds.getValidationQuery());
537     }
538 
539     @Test
540     @Disabled
541     void testEvict() throws Exception {
542         final long delay = 1000;
543 
544         ds.setInitialSize(10);
545         ds.setMaxIdle(10);
546         ds.setMaxTotal(10);
547         ds.setMinIdle(5);
548         ds.setNumTestsPerEvictionRun(3);
549         ds.setMinEvictableIdle(Duration.ofMillis(100));
550         ds.setDurationBetweenEvictionRuns(Duration.ofMillis(delay));
551         ds.setPoolPreparedStatements(true);
552 
553         try (Connection conn = ds.getConnection()) {
554             // empty
555         }
556 
557         final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
558         while (Stream.of(threadBean.getThreadInfo(threadBean.getAllThreadIds())).anyMatch(t -> t.getThreadName().equals("commons-pool-evictor-thread"))) {
559             if (ds.getNumIdle() <= ds.getMinIdle()) {
560                 break;
561             }
562             Thread.sleep(delay);
563         }
564         assertFalse(ds.getNumIdle() > ds.getMinIdle(),
565                 () -> "EvictionTimer thread was destroyed with numIdle=" + ds.getNumIdle() + "(expected: less or equal than " + ds.getMinIdle() + ")");
566     }
567 
568     @Test
569     void testInitialSize() throws Exception {
570         ds.setMaxTotal(20);
571         ds.setMaxIdle(20);
572         ds.setInitialSize(10);
573 
574         try (Connection conn = getConnection()) {
575             assertNotNull(conn);
576         }
577 
578         assertEquals(0, ds.getNumActive());
579         assertEquals(10, ds.getNumIdle());
580     }
581 
582     /**
583      * JIRA: DBCP-482 Verify warning not logged if JMX MBean unregistered before close() called.
584      */
585     @Test
586     void testInstanceNotFoundExceptionLogSuppressed() throws Exception {
587         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
588         try (Connection c = ds.getConnection()) {
589             // nothing
590         }
591         final ObjectName objectName = new ObjectName(ds.getJmxName());
592         if (mbs.isRegistered(objectName)) {
593             mbs.unregisterMBean(objectName);
594         }
595         StackMessageLog.clear();
596         ds.close();
597         assertNull(StackMessageLog.popMessage());
598         assertNull(ds.getRegisteredJmxName());
599     }
600 
601 
602     @Test
603     void testInvalidateConnection() throws Exception {
604         ds.setMaxTotal(2);
605         try (final Connection conn1 = ds.getConnection()) {
606             try (final Connection conn2 = ds.getConnection()) {
607                 ds.invalidateConnection(conn1);
608                 assertTrue(conn1.isClosed());
609                 assertEquals(1, ds.getNumActive());
610                 checkIdleValid();
611                 checkLimits();
612                 try (final Connection conn3 = ds.getConnection()) {
613                     conn2.close();
614                     conn3.close();
615                 }
616             }
617         }
618     }
619 
620     @Test
621     void testInvalidConnectionInitSqlCollection() {
622         ds.setConnectionInitSqls((Collection<String>) Arrays.asList("SELECT 1", "invalid"));
623         final SQLException e = assertThrows(SQLException.class, ds::getConnection);
624         assertTrue(e.toString().contains("invalid"));
625     }
626 
627     @Test
628     void testInvalidConnectionInitSqlList() {
629         ds.setConnectionInitSqls(Arrays.asList("SELECT 1", "invalid"));
630         final SQLException e = assertThrows(SQLException.class, ds::getConnection);
631         assertTrue(e.toString().contains("invalid"));
632     }
633 
634     @Test
635     void testInvalidValidationQuery() {
636         ds.setValidationQuery("invalid");
637         final SQLException e = assertThrows(SQLException.class, ds::getConnection);
638         assertTrue(e.toString().contains("invalid"));
639     }
640 
641     // Bugzilla Bug 28251: Returning dead database connections to BasicDataSource
642     // isClosed() failure blocks returning a connection to the pool
643     @Test
644     void testIsClosedFailure() throws SQLException {
645         ds.setAccessToUnderlyingConnectionAllowed(true);
646         final Connection conn = ds.getConnection();
647         assertNotNull(conn);
648         assertEquals(1, ds.getNumActive());
649 
650         // set an IO failure causing the isClosed method to fail
651         final TesterConnection tconn = (TesterConnection) ((DelegatingConnection<?>) conn).getInnermostDelegate();
652         tconn.setFailure(new IOException("network error"));
653 
654         assertThrows(SQLException.class, () -> conn.close());
655 
656         assertEquals(0, ds.getNumActive());
657     }
658 
659     @Test
660     void testIsWrapperFor() throws Exception {
661         assertTrue(ds.isWrapperFor(BasicDataSource.class));
662         assertTrue(ds.isWrapperFor(AutoCloseable.class));
663         assertFalse(ds.isWrapperFor(String.class));
664         assertFalse(ds.isWrapperFor(null));
665     }
666 
667     /**
668      * Make sure setting jmxName to null suppresses JMX registration of connection and statement pools. JIRA: DBCP-434
669      */
670     @Test
671     void testJmxDisabled() throws Exception {
672         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
673         // Unregister leftovers from other tests (TODO: worry about concurrent test execution)
674         final ObjectName commons = new ObjectName("org.apache.commons.*:*");
675         final Set<ObjectName> results = mbs.queryNames(commons, null);
676         for (final ObjectName result : results) {
677             mbs.unregisterMBean(result);
678         }
679         ds.setJmxName(null); // Should disable JMX for both connection and statement pools
680         ds.setPoolPreparedStatements(true);
681         try (Connection conn = ds.getConnection()) { // Trigger initialization
682             // Nothing should be registered
683             assertEquals(0, mbs.queryNames(commons, null).size());
684         }
685     }
686 
687     /**
688      * Tests JIRA <a href="https://issues.apache.org/jira/browse/DBCP-562">DBCP-562</a>.
689      * <p>
690      * Make sure Password Attribute is not exported via JMXBean.
691      * </p>
692      */
693     @Test
694     void testJmxDoesNotExposePassword() throws Exception {
695         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
696         try (Connection c = ds.getConnection()) {
697             // nothing
698         }
699         final ObjectName objectName = new ObjectName(ds.getJmxName());
700         final MBeanAttributeInfo[] attributes = mbs.getMBeanInfo(objectName).getAttributes();
701         assertTrue(attributes != null && attributes.length > 0);
702         Arrays.asList(attributes).forEach(attrInfo -> assertFalse("password".equalsIgnoreCase(attrInfo.getName())));
703         assertThrows(AttributeNotFoundException.class, () -> mbs.getAttribute(objectName, "Password"));
704     }
705 
706     @Test
707     void testManualConnectionEvict() throws Exception {
708         ds.setMinIdle(0);
709         ds.setMaxIdle(4);
710         ds.setMinEvictableIdle(Duration.ofMillis(10));
711         ds.setNumTestsPerEvictionRun(2);
712 
713         try (Connection ds2 = ds.createDataSource().getConnection(); Connection ds3 = ds.createDataSource().getConnection()) {
714             assertEquals(0, ds.getNumIdle());
715         }
716 
717         // Make sure MinEvictableIdleTimeMillis has elapsed
718         Thread.sleep(100);
719 
720         // Ensure no connections evicted by eviction thread
721         assertEquals(2, ds.getNumIdle());
722 
723         // Force Eviction
724         ds.evict();
725 
726         // Ensure all connections evicted
727         assertEquals(0, ds.getNumIdle());
728     }
729 
730     @Test
731     void testMaxConnLifetimeExceeded() throws Exception {
732         try {
733             StackMessageLog.lock();
734             ds.setMaxConn(Duration.ofMillis(100));
735             try (Connection conn = ds.getConnection()) {
736                 assertEquals(1, ds.getNumActive());
737                 Thread.sleep(500);
738             }
739             assertEquals(0, ds.getNumIdle());
740             final String message = StackMessageLog.popMessage();
741             Assertions.assertNotNull(message);
742             assertTrue(message.indexOf("exceeds the maximum permitted value") > 0);
743         } finally {
744             StackMessageLog.clear();
745             StackMessageLog.unLock();
746         }
747     }
748 
749     @Test
750     void testMaxConnLifetimeExceededMutedLog() throws Exception {
751         try {
752             StackMessageLog.lock();
753             StackMessageLog.clear();
754             ds.setMaxConn(Duration.ofMillis(100));
755             ds.setLogExpiredConnections(false);
756             try (final Connection conn = ds.getConnection()) {
757                 assertEquals(1, ds.getNumActive());
758                 Thread.sleep(500);
759             }
760             assertEquals(0, ds.getNumIdle());
761             assertTrue(StackMessageLog.isEmpty(), StackMessageLog.getAll().toString());
762         } finally {
763             StackMessageLog.clear();
764             StackMessageLog.unLock();
765         }
766     }
767 
768     /**
769      * Bugzilla Bug 29832: Broken behavior for BasicDataSource.setMaxTotal(0) MaxTotal == 0 should throw SQLException on getConnection. Results from Bug 29863
770      * in commons-pool.
771      */
772     @Test
773     void testMaxTotalZero() throws Exception {
774         ds.setMaxTotal(0);
775         assertThrows(SQLException.class, ds::getConnection);
776     }
777 
778     /**
779      * JIRA: DBCP-457 Verify that changes made to abandoned config are passed to the underlying pool.
780      */
781     @Test
782     void testMutateAbandonedConfig() throws Exception {
783         final Properties properties = new Properties();
784         properties.put("initialSize", "1");
785         properties.put("driverClassName", "org.apache.commons.dbcp2.TesterDriver");
786         properties.put("url", "jdbc:apache:commons:testdriver");
787         properties.put("username", "foo");
788         properties.put("password", "bar");
789         try (BasicDataSource ds = BasicDataSourceFactory.createDataSource(properties)) {
790             final boolean original = ds.getConnectionPool().getLogAbandoned();
791             ds.setLogAbandoned(!original);
792             Assertions.assertNotEquals(original, ds.getConnectionPool().getLogAbandoned());
793         }
794     }
795 
796     @Test
797     void testNoAccessToUnderlyingConnectionAllowed() throws Exception {
798         // default: false
799         assertFalse(ds.isAccessToUnderlyingConnectionAllowed());
800 
801         try (Connection conn = getConnection()) {
802             Connection dconn = ((DelegatingConnection<?>) conn).getDelegate();
803             assertNull(dconn);
804 
805             dconn = ((DelegatingConnection<?>) conn).getInnermostDelegate();
806             assertNull(dconn);
807         }
808     }
809 
810     @Test
811     void testNoOverlapBetweenDisconnectionAndIgnoreSqlCodes() {
812         // Set disconnection SQL codes without overlap
813         final HashSet<String> disconnectionSqlCodes = new HashSet<>(Arrays.asList("XXX", "ZZZ"));
814         ds.setDisconnectionSqlCodes(disconnectionSqlCodes);
815 
816         // Set ignore SQL codes without overlap
817         final HashSet<String> disconnectionIgnoreSqlCodes = new HashSet<>(Arrays.asList("YYY", "AAA"));
818         ds.setDisconnectionIgnoreSqlCodes(disconnectionIgnoreSqlCodes);
819 
820         assertEquals(disconnectionSqlCodes, ds.getDisconnectionSqlCodes(), "Disconnection SQL codes should match the set values.");
821         assertEquals(disconnectionIgnoreSqlCodes, ds.getDisconnectionIgnoreSqlCodes(), "Disconnection Ignore SQL codes should match the set values.");
822     }
823 
824     @Test
825     void testOverlapBetweenDisconnectionAndIgnoreSqlCodes() {
826         // Set initial disconnection SQL codes
827         final HashSet<String> disconnectionSqlCodes = new HashSet<>(Arrays.asList("XXX", "ZZZ"));
828         ds.setDisconnectionSqlCodes(disconnectionSqlCodes);
829         // Try setting ignore SQL codes with overlap
830         final HashSet<String> disconnectionIgnoreSqlCodes = new HashSet<>(Arrays.asList("YYY", "XXX"));
831 
832         final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
833                 () -> ds.setDisconnectionIgnoreSqlCodes(disconnectionIgnoreSqlCodes));
834         assertEquals("[XXX] cannot be in both disconnectionSqlCodes and disconnectionIgnoreSqlCodes.", exception.getMessage());
835     }
836 
837     /**
838      * Verifies correct handling of exceptions generated by the underlying pool as it closes connections in response to BDS#close. Exceptions have to be either
839      * swallowed by the underlying pool and logged, or propagated and wrapped.
840      */
841     @Test
842     void testPoolCloseCheckedException() throws Exception {
843         ds.setAccessToUnderlyingConnectionAllowed(true); // Allow dirty tricks
844 
845         final TesterConnection tc;
846         // Get an idle connection into the pool
847         try (Connection conn = ds.getConnection()) {
848             tc = (TesterConnection) ((DelegatingConnection<?>) conn).getInnermostDelegate();
849         }
850 
851         // After returning the connection to the pool, bork it.
852         // Don't try this at home - bad violation of pool contract!
853         tc.setFailure(new SQLException("bang"));
854 
855         // Now close Datasource, which will cause tc to be closed, triggering SQLE
856         // Pool 2.x swallows and logs exceptions on pool close. Below verifies that
857         // Either exceptions get logged or wrapped appropriately.
858         try {
859             StackMessageLog.lock();
860             StackMessageLog.clear();
861             ds.close();
862             // Exception must have been swallowed by the pool - verify it is logged
863             final String message = StackMessageLog.popMessage();
864             Assertions.assertNotNull(message);
865             assertTrue(message.indexOf("bang") > 0);
866         } catch (final SQLException ex) {
867             assertTrue(ex.getMessage().indexOf("Cannot close") > 0);
868             assertTrue(ex.getCause().getMessage().indexOf("bang") > 0);
869         } finally {
870             StackMessageLog.unLock();
871         }
872     }
873 
874     @Test
875     void testPoolCloseRTE() throws Exception {
876         // RTE version of testPoolCloseCheckedException - see comments there.
877         ds.setAccessToUnderlyingConnectionAllowed(true);
878         final TesterConnection tc;
879         try (Connection conn = ds.getConnection()) {
880             tc = (TesterConnection) ((DelegatingConnection<?>) conn).getInnermostDelegate();
881         }
882         tc.setFailure(new IllegalStateException("boom"));
883         try {
884             StackMessageLog.lock();
885             StackMessageLog.clear();
886             ds.close();
887             final String message = StackMessageLog.popMessage();
888             Assertions.assertNotNull(message);
889             assertTrue(message.indexOf("boom") > 0);
890         } catch (final IllegalStateException ex) {
891             assertTrue(ex.getMessage().indexOf("boom") > 0); // RTE is not wrapped by BDS#close
892         } finally {
893             StackMessageLog.unLock();
894         }
895     }
896 
897     @Override
898     @Test
899     public void testPooling() throws Exception {
900         // this also needs access to the underlying connection
901         ds.setAccessToUnderlyingConnectionAllowed(true);
902         super.testPooling();
903     }
904 
905     /**
906      * Bugzilla Bug 29054: The BasicDataSource.setTestOnReturn(boolean) is not carried through to the GenericObjectPool variable _testOnReturn.
907      */
908     @Test
909     void testPropertyTestOnReturn() throws Exception {
910         ds.setValidationQuery("select 1 from dual");
911         ds.setTestOnBorrow(false);
912         ds.setTestWhileIdle(false);
913         ds.setTestOnReturn(true);
914 
915         try (Connection conn = ds.getConnection()) {
916             assertNotNull(conn);
917 
918             assertFalse(ds.getConnectionPool().getTestOnBorrow());
919             assertFalse(ds.getConnectionPool().getTestWhileIdle());
920             assertTrue(ds.getConnectionPool().getTestOnReturn());
921         }
922     }
923 
924     @Test
925     void testRestart() throws Exception {
926         ds.setMaxTotal(2);
927         ds.setDurationBetweenEvictionRuns(Duration.ofMillis(100));
928         ds.setNumTestsPerEvictionRun(2);
929         ds.setMinEvictableIdle(Duration.ofMinutes(1));
930         ds.setInitialSize(2);
931         ds.setDefaultCatalog("foo");
932         try (Connection conn1 = ds.getConnection()) {
933             Thread.sleep(200);
934             // Now set some property that will not have effect until restart
935             ds.setDefaultCatalog("bar");
936             ds.setInitialSize(1);
937             // restart will load new properties
938             ds.restart();
939             assertEquals("bar", ds.getDefaultCatalog());
940             assertEquals(1, ds.getInitialSize());
941             ds.getLogWriter(); // side effect is to init
942             assertEquals(0, ds.getNumActive());
943             assertEquals(1, ds.getNumIdle());
944         }
945         // verify old pool connection is not returned to pool
946         assertEquals(1, ds.getNumIdle());
947         ds.close();
948     }
949 
950     /**
951      * Bugzilla Bug 29055: AutoCommit and ReadOnly The DaffodilDB driver throws an SQLException if trying to commit or rollback a readOnly connection.
952      */
953     @Test
954     void testRollbackReadOnly() throws Exception {
955         ds.setDefaultReadOnly(Boolean.TRUE);
956         ds.setDefaultAutoCommit(Boolean.FALSE);
957 
958         try (Connection conn = ds.getConnection()) {
959             assertNotNull(conn);
960         }
961     }
962 
963     @Test
964     void testSetAutoCommitTrueOnClose() throws Exception {
965         ds.setAccessToUnderlyingConnectionAllowed(true);
966         ds.setDefaultAutoCommit(Boolean.FALSE);
967         final Connection dconn;
968         try (Connection conn = getConnection()) {
969             assertNotNull(conn);
970             assertFalse(conn.getAutoCommit());
971 
972             dconn = ((DelegatingConnection<?>) conn).getInnermostDelegate();
973             assertNotNull(dconn);
974             assertFalse(dconn.getAutoCommit());
975 
976         }
977 
978         assertTrue(dconn.getAutoCommit());
979     }
980 
981     @Test
982     void testSetProperties() throws Exception {
983         // normal
984         ds.setConnectionProperties("name1=value1;name2=value2;name3=value3");
985         assertEquals(3, ds.getConnectionProperties().size());
986         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
987         assertEquals("value2", ds.getConnectionProperties().getProperty("name2"));
988         assertEquals("value3", ds.getConnectionProperties().getProperty("name3"));
989 
990         // make sure all properties are replaced
991         ds.setConnectionProperties("name1=value1;name2=value2");
992         assertEquals(2, ds.getConnectionProperties().size());
993         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
994         assertEquals("value2", ds.getConnectionProperties().getProperty("name2"));
995         assertFalse(ds.getConnectionProperties().containsKey("name3"));
996 
997         // no value is empty string
998         ds.setConnectionProperties("name1=value1;name2");
999         assertEquals(2, ds.getConnectionProperties().size());
1000         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
1001         assertEquals("", ds.getConnectionProperties().getProperty("name2"));
1002 
1003         // no value (with equals) is empty string
1004         ds.setConnectionProperties("name1=value1;name2=");
1005         assertEquals(2, ds.getConnectionProperties().size());
1006         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
1007         assertEquals("", ds.getConnectionProperties().getProperty("name2"));
1008 
1009         // single value
1010         ds.setConnectionProperties("name1=value1");
1011         assertEquals(1, ds.getConnectionProperties().size());
1012         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
1013 
1014         // single value with trailing ;
1015         ds.setConnectionProperties("name1=value1;");
1016         assertEquals(1, ds.getConnectionProperties().size());
1017         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
1018 
1019         // single value wit no value
1020         ds.setConnectionProperties("name1");
1021         assertEquals(1, ds.getConnectionProperties().size());
1022         assertEquals("", ds.getConnectionProperties().getProperty("name1"));
1023 
1024         // null should throw a NullPointerException
1025         assertThrows(NullPointerException.class, () -> ds.setConnectionProperties(null));
1026     }
1027 
1028     @Test
1029     void testSetValidationTestProperties() {
1030         // defaults
1031         assertTrue(ds.getTestOnBorrow());
1032         assertFalse(ds.getTestOnReturn());
1033         assertFalse(ds.getTestWhileIdle());
1034 
1035         ds.setTestOnBorrow(true);
1036         ds.setTestOnReturn(true);
1037         ds.setTestWhileIdle(true);
1038         assertTrue(ds.getTestOnBorrow());
1039         assertTrue(ds.getTestOnReturn());
1040         assertTrue(ds.getTestWhileIdle());
1041 
1042         ds.setTestOnBorrow(false);
1043         ds.setTestOnReturn(false);
1044         ds.setTestWhileIdle(false);
1045         assertFalse(ds.getTestOnBorrow());
1046         assertFalse(ds.getTestOnReturn());
1047         assertFalse(ds.getTestWhileIdle());
1048     }
1049 
1050     @Test
1051     void testStart() throws Exception {
1052         ds.setAccessToUnderlyingConnectionAllowed(true);
1053         ds.setMaxTotal(2);
1054         final DelegatingConnection<?> conn1 = (DelegatingConnection<?>) ds.getConnection();
1055         final DelegatingConnection<?> conn2 = (DelegatingConnection<?>) ds.getConnection();
1056         final Connection inner1 = conn1.getInnermostDelegate();
1057         final Connection inner2 = conn2.getInnermostDelegate();
1058         assertFalse(inner2.isClosed());
1059         conn2.close();
1060         assertFalse(inner2.isClosed());
1061         // One active, one idle in the pool
1062         ds.close();
1063         // Idle connection should be physically closed, checked out unaffected
1064         assertFalse(conn1.isClosed());
1065         assertTrue(inner2.isClosed());
1066         assertEquals(0, ds.getNumIdle());
1067 
1068         // Reopen creates a new pool, so we can have three out
1069         ds.start();
1070         final Connection conn3 = ds.getConnection();
1071         final Connection conn4 = ds.getConnection();
1072         conn3.close();
1073         conn4.close();
1074 
1075         // Old pool's orphan should get physically closed on return
1076         conn1.close();
1077         assertTrue(inner1.isClosed());
1078     }
1079 
1080     @Test
1081     void testStartInitializes() throws Exception {
1082         ds.setInitialSize(2);
1083         // Note: if we ever move away from lazy init, next two will fail
1084         assertEquals(0, ds.getNumIdle());
1085         assertNull(ds.getRegisteredJmxName());
1086 
1087         // Start forces init
1088         ds.start();
1089         assertEquals(2, ds.getNumIdle());
1090         assertNotNull(ds.getRegisteredJmxName());
1091     }
1092 
1093     @Test
1094     void testTransactionIsolationBehavior() throws Exception {
1095         try (final Connection conn = getConnection()) {
1096             assertNotNull(conn);
1097             assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn.getTransactionIsolation());
1098             conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
1099         }
1100 
1101         final Connection conn2 = getConnection();
1102         assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn2.getTransactionIsolation());
1103 
1104         final Connection conn3 = getConnection();
1105         assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn3.getTransactionIsolation());
1106 
1107         conn2.close();
1108 
1109         conn3.close();
1110     }
1111 
1112     @Test
1113     void testUnwrap() throws Exception {
1114         assertSame(ds.unwrap(BasicDataSource.class), ds);
1115         assertSame(ds.unwrap(AutoCloseable.class), ds);
1116         assertThrows(SQLException.class, () -> ds.unwrap(String.class));
1117         assertThrows(SQLException.class, () -> ds.unwrap(null));
1118     }
1119 
1120     @Test
1121     void testValidationQueryTimeoutNegative() throws Exception {
1122         ds.setTestOnBorrow(true);
1123         ds.setTestOnReturn(true);
1124         ds.setValidationQueryTimeout(Duration.ofSeconds(-1));
1125         try (final Connection con = ds.getConnection()) {
1126             // close right away.
1127         }
1128     }
1129 
1130     @Test
1131     void testValidationQueryTimeoutSucceed() throws Exception {
1132         ds.setTestOnBorrow(true);
1133         ds.setTestOnReturn(true);
1134         ds.setValidationQueryTimeout(Duration.ofMillis(100)); // Works for TesterStatement
1135         try (final Connection con = ds.getConnection()) {
1136             // close right away.
1137         }
1138     }
1139 
1140     @Test
1141     void testValidationQueryTimeoutZero() throws Exception {
1142         ds.setTestOnBorrow(true);
1143         ds.setTestOnReturn(true);
1144         ds.setValidationQueryTimeout(Duration.ZERO);
1145         try (final Connection con = ds.getConnection()) {
1146             // close right away.
1147         }
1148     }
1149 
1150     @Test
1151     void testValidationQueryTimoutFail() {
1152         ds.setTestOnBorrow(true);
1153         ds.setValidationQueryTimeout(Duration.ofSeconds(3)); // Too fast for TesterStatement
1154         final SQLException e = assertThrows(SQLException.class, ds::getConnection);
1155         assertTrue(e.toString().contains("timeout"));
1156     }
1157 }
1158 
1159 /**
1160  * TesterDriver that adds latency to connection requests. Latency (in ms) is the last component of the URL.
1161  */
1162 final class TesterConnectionDelayDriver extends TesterDriver {
1163     private static final String CONNECT_STRING = "jdbc:apache:commons:testerConnectionDelayDriver";
1164 
1165     public TesterConnectionDelayDriver() {
1166         // DBCP expects an explicit no-arg constructor
1167     }
1168 
1169     @Override
1170     public boolean acceptsURL(final String url) throws SQLException {
1171         return url.startsWith(CONNECT_STRING);
1172     }
1173 
1174     @Override
1175     public Connection connect(final String url, final Properties info) throws SQLException {
1176         final String[] parsedUrl = url.split(":");
1177         final int delay = Integer.parseInt(parsedUrl[parsedUrl.length - 1]);
1178         try {
1179             Thread.sleep(delay);
1180         } catch (final InterruptedException ex) {
1181             Thread.currentThread().interrupt();
1182         }
1183         return super.connect(url, info);
1184     }
1185 
1186 }
1187 
1188 /**
1189  * TesterDriver that keeps a static count of connection requests.
1190  */
1191 final class TesterConnRequestCountDriver extends TesterDriver {
1192     private static final String CONNECT_STRING = "jdbc:apache:commons:testerConnRequestCountDriver";
1193     private static final AtomicInteger connectionRequestCount = new AtomicInteger();
1194 
1195     public static int getConnectionRequestCount() {
1196         return connectionRequestCount.get();
1197     }
1198 
1199     public static void initConnRequestCount() {
1200         connectionRequestCount.set(0);
1201     }
1202 
1203     public TesterConnRequestCountDriver() {
1204         // DBCP expects an explicit no-arg constructor
1205     }
1206 
1207     @Override
1208     public boolean acceptsURL(final String url) throws SQLException {
1209         return CONNECT_STRING.startsWith(url);
1210     }
1211 
1212     @Override
1213     public Connection connect(final String url, final Properties info) throws SQLException {
1214         connectionRequestCount.incrementAndGet();
1215         return super.connect(url, info);
1216     }
1217 }