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