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.hamcrest.MatcherAssert.assertThat;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
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.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.hamcrest.CoreMatchers;
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             assertTrue(dconn instanceof TesterConnection);
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     /**
443      * JIRA: DBCP-437
444      * Verify that BasicDataSource sets disconnect codes properties.
445      * Functionality is verified in pcf tests.
446      */
447     @Test
448     public void testDisconnectSqlCodes() throws Exception {
449         final ArrayList<String> disconnectionSqlCodes = new ArrayList<>();
450         disconnectionSqlCodes.add("XXX");
451         ds.setDisconnectionSqlCodes(disconnectionSqlCodes);
452         ds.setFastFailValidation(true);
453         try (Connection conn = ds.getConnection()) { // Triggers initialization - pcf creation
454             // Make sure factory got the properties
455             final PoolableConnectionFactory pcf = (PoolableConnectionFactory) ds.getConnectionPool().getFactory();
456             assertTrue(pcf.isFastFailValidation());
457             assertTrue(pcf.getDisconnectionSqlCodes().contains("XXX"));
458             assertEquals(1, pcf.getDisconnectionSqlCodes().size());
459         }
460     }
461 
462     /**
463      * JIRA DBCP-333: Check that a custom class loader is used.
464      * @throws Exception
465      */
466     @Test
467     public void testDriverClassLoader() throws Exception {
468         try (Connection conn = getConnection()) {
469             final ClassLoader cl = ds.getDriverClassLoader();
470             assertNotNull(cl);
471             assertTrue(cl instanceof TesterClassLoader);
472             assertTrue(((TesterClassLoader) cl).didLoad(ds.getDriverClassName()));
473         }
474     }
475 
476     @Test
477     public void testEmptyInitConnectionSql() throws Exception {
478         // List
479         ds.setConnectionInitSqls(Arrays.asList("", "   "));
480         assertNotNull(ds.getConnectionInitSqls());
481         assertEquals(0, ds.getConnectionInitSqls().size());
482         // null
483         ds.setConnectionInitSqls(null);
484         assertNotNull(ds.getConnectionInitSqls());
485         assertEquals(0, ds.getConnectionInitSqls().size());
486         // Collection
487         ds.setConnectionInitSqls((Collection<String>) Arrays.asList("", "   "));
488         assertNotNull(ds.getConnectionInitSqls());
489         assertEquals(0, ds.getConnectionInitSqls().size());
490     }
491 
492     @Test
493     public void testEmptyValidationQuery() throws Exception {
494         assertNotNull(ds.getValidationQuery());
495 
496         ds.setValidationQuery("");
497         assertNull(ds.getValidationQuery());
498 
499         ds.setValidationQuery("   ");
500         assertNull(ds.getValidationQuery());
501     }
502 
503     @Test
504     @Disabled
505     public void testEvict() throws Exception {
506         final long delay = 1000;
507 
508         ds.setInitialSize(10);
509         ds.setMaxIdle(10);
510         ds.setMaxTotal(10);
511         ds.setMinIdle(5);
512         ds.setNumTestsPerEvictionRun(3);
513         ds.setMinEvictableIdle(Duration.ofMillis(100));
514         ds.setDurationBetweenEvictionRuns(Duration.ofMillis(delay));
515         ds.setPoolPreparedStatements(true);
516 
517         try (Connection conn = ds.getConnection()) {
518             // empty
519         }
520 
521         final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
522         while (Stream.of(threadBean.getThreadInfo(threadBean.getAllThreadIds())).anyMatch(t -> t.getThreadName().equals("commons-pool-evictor-thread"))) {
523             if (ds.getNumIdle() <= ds.getMinIdle()) {
524                 break;
525             }
526             Thread.sleep(delay);
527         }
528         if (ds.getNumIdle() > ds.getMinIdle()) {
529             fail("EvictionTimer thread was destroyed with numIdle=" + ds.getNumIdle() + "(expected: less or equal than " + ds.getMinIdle() + ")");
530         }
531     }
532 
533     @Test
534     public void testInitialSize() throws Exception {
535         ds.setMaxTotal(20);
536         ds.setMaxIdle(20);
537         ds.setInitialSize(10);
538 
539         try (Connection conn = getConnection()) {
540             assertNotNull(conn);
541         }
542 
543         assertEquals(0, ds.getNumActive());
544         assertEquals(10, ds.getNumIdle());
545     }
546 
547     /**
548      * JIRA: DBCP-482
549      * Verify warning not logged if JMX MBean unregistered before close() called.
550      */
551     @Test
552     public void testInstanceNotFoundExceptionLogSuppressed() throws Exception {
553         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
554         try (Connection c = ds.getConnection()) {
555             // nothing
556         }
557         final ObjectName objectName = new ObjectName(ds.getJmxName());
558         if (mbs.isRegistered(objectName)) {
559             mbs.unregisterMBean(objectName);
560         }
561         StackMessageLog.clear();
562         ds.close();
563         assertThat(StackMessageLog.popMessage(),
564                 CoreMatchers.not(CoreMatchers.containsString("InstanceNotFoundException")));
565         assertNull(ds.getRegisteredJmxName());
566     }
567 
568     @Test
569     public void testInvalidateConnection() throws Exception {
570         ds.setMaxTotal(2);
571         try (final Connection conn1 = ds.getConnection()) {
572             try (final Connection conn2 = ds.getConnection()) {
573                 ds.invalidateConnection(conn1);
574                 assertTrue(conn1.isClosed());
575                 assertEquals(1, ds.getNumActive());
576                 assertEquals(0, ds.getNumIdle());
577                 try (final Connection conn3 = ds.getConnection()) {
578                     conn2.close();
579                 }
580             }
581         }
582     }
583 
584     @Test
585     public void testInvalidConnectionInitSqlList() {
586         ds.setConnectionInitSqls(Arrays.asList("SELECT 1", "invalid"));
587         final SQLException e = assertThrows(SQLException.class, ds::getConnection);
588         assertTrue(e.toString().contains("invalid"));
589     }
590 
591     @Test
592     public void testInvalidConnectionInitSqlCollection() {
593         ds.setConnectionInitSqls((Collection<String>) Arrays.asList("SELECT 1", "invalid"));
594         final SQLException e = assertThrows(SQLException.class, ds::getConnection);
595         assertTrue(e.toString().contains("invalid"));
596     }
597 
598     @Test
599     public void testInvalidValidationQuery() {
600         ds.setValidationQuery("invalid");
601         final SQLException e = assertThrows(SQLException.class, ds::getConnection);
602         assertTrue(e.toString().contains("invalid"));
603     }
604 
605     // Bugzilla Bug 28251:  Returning dead database connections to BasicDataSource
606     // isClosed() failure blocks returning a connection to the pool
607     @Test
608     public void testIsClosedFailure() throws SQLException {
609         ds.setAccessToUnderlyingConnectionAllowed(true);
610         final Connection conn = ds.getConnection();
611         assertNotNull(conn);
612         assertEquals(1, ds.getNumActive());
613 
614         // set an IO failure causing the isClosed method to fail
615         final TesterConnection tconn = (TesterConnection) ((DelegatingConnection<?>) conn).getInnermostDelegate();
616         tconn.setFailure(new IOException("network error"));
617 
618         assertThrows(SQLException.class, () -> conn.close());
619 
620         assertEquals(0, ds.getNumActive());
621     }
622 
623     @Test
624     public void testIsWrapperFor() throws Exception {
625         assertTrue(ds.isWrapperFor(BasicDataSource.class));
626         assertTrue(ds.isWrapperFor(AutoCloseable.class));
627         assertFalse(ds.isWrapperFor(String.class));
628         assertFalse(ds.isWrapperFor(null));
629     }
630 
631     /**
632      * Make sure setting jmxName to null suppresses JMX registration of connection and statement pools.
633      * JIRA: DBCP-434
634      */
635     @Test
636     public void testJmxDisabled() throws Exception {
637         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
638         // Unregister leftovers from other tests (TODO: worry about concurrent test execution)
639         final ObjectName commons = new ObjectName("org.apache.commons.*:*");
640         final Set<ObjectName> results = mbs.queryNames(commons, null);
641         for (final ObjectName result : results) {
642             mbs.unregisterMBean(result);
643         }
644         ds.setJmxName(null); // Should disable JMX for both connection and statement pools
645         ds.setPoolPreparedStatements(true);
646         try (Connection conn = ds.getConnection()) { // Trigger initialization
647             // Nothing should be registered
648             assertEquals(0, mbs.queryNames(commons, null).size());
649         }
650     }
651 
652     /**
653      * Tests JIRA <a href="https://issues.apache.org/jira/browse/DBCP-562">DBCP-562</a>.
654      * <p>
655      * Make sure Password Attribute is not exported via JMXBean.
656      * </p>
657      */
658     @Test
659     public void testJmxDoesNotExposePassword() throws Exception {
660         final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
661 
662         try (Connection c = ds.getConnection()) {
663             // nothing
664         }
665         final ObjectName objectName = new ObjectName(ds.getJmxName());
666 
667         final MBeanAttributeInfo[] attributes = mbs.getMBeanInfo(objectName).getAttributes();
668 
669         assertTrue(attributes != null && attributes.length > 0);
670 
671         Arrays.asList(attributes).forEach(attrInfo -> {
672             assertFalse("password".equalsIgnoreCase(attrInfo.getName()));
673         });
674 
675         assertThrows(AttributeNotFoundException.class, () -> {
676             mbs.getAttribute(objectName, "Password");
677         });
678     }
679 
680     @Test
681     public void testManualConnectionEvict() throws Exception {
682         ds.setMinIdle(0);
683         ds.setMaxIdle(4);
684         ds.setMinEvictableIdle(Duration.ofMillis(10));
685         ds.setNumTestsPerEvictionRun(2);
686 
687         try (Connection ds2 = ds.createDataSource().getConnection(); Connection ds3 = ds.createDataSource().getConnection()) {
688             assertEquals(0, ds.getNumIdle());
689         }
690 
691         // Make sure MinEvictableIdleTimeMillis has elapsed
692         Thread.sleep(100);
693 
694         // Ensure no connections evicted by eviction thread
695         assertEquals(2, ds.getNumIdle());
696 
697         // Force Eviction
698         ds.evict();
699 
700         // Ensure all connections evicted
701         assertEquals(0, ds.getNumIdle());
702     }
703 
704     @Test
705     public void testMaxConnLifetimeExceeded() throws Exception {
706         try {
707             StackMessageLog.lock();
708             ds.setMaxConn(Duration.ofMillis(100));
709             try (Connection conn = ds.getConnection()) {
710                 assertEquals(1, ds.getNumActive());
711                 Thread.sleep(500);
712             }
713             assertEquals(0, ds.getNumIdle());
714             final String message = StackMessageLog.popMessage();
715             Assertions.assertNotNull(message);
716             assertTrue(message.indexOf("exceeds the maximum permitted value") > 0);
717         } finally {
718             StackMessageLog.clear();
719             StackMessageLog.unLock();
720         }
721     }
722 
723     @Test
724     public void testMaxConnLifetimeExceededMutedLog() throws Exception {
725         try {
726             StackMessageLog.lock();
727             StackMessageLog.clear();
728             ds.setMaxConn(Duration.ofMillis(100));
729             ds.setLogExpiredConnections(false);
730             try (final Connection conn = ds.getConnection()) {
731                 assertEquals(1, ds.getNumActive());
732                 Thread.sleep(500);
733             }
734             assertEquals(0, ds.getNumIdle());
735             assertTrue(StackMessageLog.isEmpty(), StackMessageLog.getAll().toString());
736         } finally {
737             StackMessageLog.clear();
738             StackMessageLog.unLock();
739         }
740     }
741 
742     /**
743      * Bugzilla Bug 29832: Broken behavior for BasicDataSource.setMaxTotal(0)
744      * MaxTotal == 0 should throw SQLException on getConnection.
745      * Results from Bug 29863 in commons-pool.
746      */
747     @Test
748     public void testMaxTotalZero() throws Exception {
749         ds.setMaxTotal(0);
750         assertThrows(SQLException.class, ds::getConnection);
751     }
752 
753     /**
754      * JIRA: DBCP-457
755      * Verify that changes made to abandoned config are passed to the underlying
756      * pool.
757      */
758     @Test
759     public void testMutateAbandonedConfig() throws Exception {
760         final Properties properties = new Properties();
761         properties.put("initialSize", "1");
762         properties.put("driverClassName", "org.apache.commons.dbcp2.TesterDriver");
763         properties.put("url", "jdbc:apache:commons:testdriver");
764         properties.put("username", "foo");
765         properties.put("password", "bar");
766         try (BasicDataSource ds = BasicDataSourceFactory.createDataSource(properties)) {
767             final boolean original = ds.getConnectionPool().getLogAbandoned();
768             ds.setLogAbandoned(!original);
769             Assertions.assertNotEquals(original, ds.getConnectionPool().getLogAbandoned());
770         }
771     }
772 
773     @Test
774     public void testNoAccessToUnderlyingConnectionAllowed() throws Exception {
775         // default: false
776         assertFalse(ds.isAccessToUnderlyingConnectionAllowed());
777 
778         try (Connection conn = getConnection()) {
779             Connection dconn = ((DelegatingConnection<?>) conn).getDelegate();
780             assertNull(dconn);
781 
782             dconn = ((DelegatingConnection<?>) conn).getInnermostDelegate();
783             assertNull(dconn);
784         }
785     }
786 
787     /**
788      * Verifies correct handling of exceptions generated by the underlying pool as it closes
789      * connections in response to BDS#close. Exceptions have to be either swallowed by the
790      * underlying pool and logged, or propagated and wrapped.
791      */
792     @Test
793     public void testPoolCloseCheckedException() throws Exception {
794         ds.setAccessToUnderlyingConnectionAllowed(true);  // Allow dirty tricks
795 
796         final TesterConnection tc;
797         // Get an idle connection into the pool
798         try (Connection conn = ds.getConnection()) {
799             tc = (TesterConnection) ((DelegatingConnection<?>) conn).getInnermostDelegate();
800         }
801 
802         // After returning the connection to the pool, bork it.
803         // Don't try this at home - bad violation of pool contract!
804         tc.setFailure(new SQLException("bang"));
805 
806         // Now close Datasource, which will cause tc to be closed, triggering SQLE
807         // Pool 2.x swallows and logs exceptions on pool close.  Below verifies that
808         // Either exceptions get logged or wrapped appropriately.
809         try {
810             StackMessageLog.lock();
811             StackMessageLog.clear();
812             ds.close();
813             // Exception must have been swallowed by the pool - verify it is logged
814             final String message = StackMessageLog.popMessage();
815             Assertions.assertNotNull(message);
816             assertTrue(message.indexOf("bang") > 0);
817         } catch (final SQLException ex) {
818             assertTrue(ex.getMessage().indexOf("Cannot close") > 0);
819             assertTrue(ex.getCause().getMessage().indexOf("bang") > 0);
820         } finally {
821             StackMessageLog.unLock();
822         }
823     }
824 
825     @Test
826     public void testPoolCloseRTE() throws Exception {
827         // RTE version of testPoolCloseCheckedException - see comments there.
828         ds.setAccessToUnderlyingConnectionAllowed(true);
829         final TesterConnection tc;
830         try (Connection conn = ds.getConnection()) {
831             tc = (TesterConnection) ((DelegatingConnection<?>) conn).getInnermostDelegate();
832         }
833         tc.setFailure(new IllegalStateException("boom"));
834         try {
835             StackMessageLog.lock();
836             StackMessageLog.clear();
837             ds.close();
838             final String message = StackMessageLog.popMessage();
839             Assertions.assertNotNull(message);
840             assertTrue(message.indexOf("boom") > 0);
841         } catch (final IllegalStateException ex) {
842             assertTrue(ex.getMessage().indexOf("boom") > 0); // RTE is not wrapped by BDS#close
843         } finally {
844             StackMessageLog.unLock();
845         }
846     }
847 
848     @Override
849     @Test
850     public void testPooling() throws Exception {
851         // this also needs access to the underlying connection
852         ds.setAccessToUnderlyingConnectionAllowed(true);
853         super.testPooling();
854     }
855 
856     /**
857      * Bugzilla Bug 29054:
858      * The BasicDataSource.setTestOnReturn(boolean) is not carried through to
859      * the GenericObjectPool variable _testOnReturn.
860      */
861     @Test
862     public void testPropertyTestOnReturn() throws Exception {
863         ds.setValidationQuery("select 1 from dual");
864         ds.setTestOnBorrow(false);
865         ds.setTestWhileIdle(false);
866         ds.setTestOnReturn(true);
867 
868         try (Connection conn = ds.getConnection()) {
869             assertNotNull(conn);
870 
871             assertFalse(ds.getConnectionPool().getTestOnBorrow());
872             assertFalse(ds.getConnectionPool().getTestWhileIdle());
873             assertTrue(ds.getConnectionPool().getTestOnReturn());
874         }
875     }
876 
877     @Test
878     public void testRestart() throws Exception {
879         ds.setMaxTotal(2);
880         ds.setDurationBetweenEvictionRuns(Duration.ofMillis(100));
881         ds.setNumTestsPerEvictionRun(2);
882         ds.setMinEvictableIdle(Duration.ofMinutes(1));
883         ds.setInitialSize(2);
884         ds.setDefaultCatalog("foo");
885         try (Connection conn1 = ds.getConnection()) {
886             Thread.sleep(200);
887             // Now set some property that will not have effect until restart
888             ds.setDefaultCatalog("bar");
889             ds.setInitialSize(1);
890             // restart will load new properties
891             ds.restart();
892             assertEquals("bar", ds.getDefaultCatalog());
893             assertEquals(1, ds.getInitialSize());
894             ds.getLogWriter(); // side effect is to init
895             assertEquals(0, ds.getNumActive());
896             assertEquals(1, ds.getNumIdle());
897         }
898         // verify old pool connection is not returned to pool
899         assertEquals(1, ds.getNumIdle());
900         ds.close();
901     }
902 
903     /**
904      * Bugzilla Bug 29055: AutoCommit and ReadOnly
905      * The DaffodilDB driver throws an SQLException if
906      * trying to commit or rollback a readOnly connection.
907      */
908     @Test
909     public void testRollbackReadOnly() throws Exception {
910         ds.setDefaultReadOnly(Boolean.TRUE);
911         ds.setDefaultAutoCommit(Boolean.FALSE);
912 
913         try (Connection conn = ds.getConnection()) {
914             assertNotNull(conn);
915         }
916     }
917 
918     @Test
919     public void testSetAutoCommitTrueOnClose() throws Exception {
920         ds.setAccessToUnderlyingConnectionAllowed(true);
921         ds.setDefaultAutoCommit(Boolean.FALSE);
922         final Connection dconn;
923         try (Connection conn = getConnection()) {
924             assertNotNull(conn);
925             assertFalse(conn.getAutoCommit());
926 
927             dconn = ((DelegatingConnection<?>) conn).getInnermostDelegate();
928             assertNotNull(dconn);
929             assertFalse(dconn.getAutoCommit());
930 
931         }
932 
933         assertTrue(dconn.getAutoCommit());
934     }
935 
936     @Test
937     public void testSetProperties() throws Exception {
938         // normal
939         ds.setConnectionProperties("name1=value1;name2=value2;name3=value3");
940         assertEquals(3, ds.getConnectionProperties().size());
941         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
942         assertEquals("value2", ds.getConnectionProperties().getProperty("name2"));
943         assertEquals("value3", ds.getConnectionProperties().getProperty("name3"));
944 
945         // make sure all properties are replaced
946         ds.setConnectionProperties("name1=value1;name2=value2");
947         assertEquals(2, ds.getConnectionProperties().size());
948         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
949         assertEquals("value2", ds.getConnectionProperties().getProperty("name2"));
950         assertFalse(ds.getConnectionProperties().containsKey("name3"));
951 
952         // no value is empty string
953         ds.setConnectionProperties("name1=value1;name2");
954         assertEquals(2, ds.getConnectionProperties().size());
955         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
956         assertEquals("", ds.getConnectionProperties().getProperty("name2"));
957 
958         // no value (with equals) is empty string
959         ds.setConnectionProperties("name1=value1;name2=");
960         assertEquals(2, ds.getConnectionProperties().size());
961         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
962         assertEquals("", ds.getConnectionProperties().getProperty("name2"));
963 
964         // single value
965         ds.setConnectionProperties("name1=value1");
966         assertEquals(1, ds.getConnectionProperties().size());
967         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
968 
969         // single value with trailing ;
970         ds.setConnectionProperties("name1=value1;");
971         assertEquals(1, ds.getConnectionProperties().size());
972         assertEquals("value1", ds.getConnectionProperties().getProperty("name1"));
973 
974         // single value wit no value
975         ds.setConnectionProperties("name1");
976         assertEquals(1, ds.getConnectionProperties().size());
977         assertEquals("", ds.getConnectionProperties().getProperty("name1"));
978 
979         // null should throw a NullPointerException
980         assertThrows(NullPointerException.class, () -> ds.setConnectionProperties(null));
981     }
982 
983     @Test
984     public void testSetValidationTestProperties() {
985         // defaults
986         assertTrue(ds.getTestOnBorrow());
987         assertFalse(ds.getTestOnReturn());
988         assertFalse(ds.getTestWhileIdle());
989 
990         ds.setTestOnBorrow(true);
991         ds.setTestOnReturn(true);
992         ds.setTestWhileIdle(true);
993         assertTrue(ds.getTestOnBorrow());
994         assertTrue(ds.getTestOnReturn());
995         assertTrue(ds.getTestWhileIdle());
996 
997         ds.setTestOnBorrow(false);
998         ds.setTestOnReturn(false);
999         ds.setTestWhileIdle(false);
1000         assertFalse(ds.getTestOnBorrow());
1001         assertFalse(ds.getTestOnReturn());
1002         assertFalse(ds.getTestWhileIdle());
1003     }
1004 
1005     @Test
1006     public void testStart() throws Exception {
1007         ds.setAccessToUnderlyingConnectionAllowed(true);
1008         ds.setMaxTotal(2);
1009         final DelegatingConnection<?> conn1 = (DelegatingConnection<?>) ds.getConnection();
1010         final DelegatingConnection<?> conn2 = (DelegatingConnection<?>) ds.getConnection();
1011         final Connection inner1 = conn1.getInnermostDelegate();
1012         final Connection inner2 = conn2.getInnermostDelegate();
1013         assertFalse(inner2.isClosed());
1014         conn2.close();
1015         assertFalse(inner2.isClosed());
1016         // One active, one idle in the pool
1017         ds.close();
1018         // Idle connection should be physically closed, checked out unaffected
1019         assertFalse(conn1.isClosed());
1020         assertTrue(inner2.isClosed());
1021         assertEquals(0, ds.getNumIdle());
1022 
1023         // Reopen creates a new pool, so we can have three out
1024         ds.start();
1025         final Connection conn3 = ds.getConnection();
1026         final Connection conn4 = ds.getConnection();
1027         conn3.close();
1028         conn4.close();
1029 
1030         // Old pool's orphan should get physically closed on return
1031         conn1.close();
1032         assertTrue(inner1.isClosed());
1033     }
1034 
1035     @Test
1036     public void testStartInitializes() throws Exception {
1037         ds.setInitialSize(2);
1038         // Note: if we ever move away from lazy init, next two will fail
1039         assertEquals(0, ds.getNumIdle());
1040         assertNull(ds.getRegisteredJmxName());
1041 
1042         // Start forces init
1043         ds.start();
1044         assertEquals(2, ds.getNumIdle());
1045         assertNotNull(ds.getRegisteredJmxName());
1046     }
1047 
1048     @Test
1049     public void testTransactionIsolationBehavior() throws Exception {
1050         try (final Connection conn = getConnection()) {
1051             assertNotNull(conn);
1052             assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn.getTransactionIsolation());
1053             conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
1054         }
1055 
1056         final Connection conn2 = getConnection();
1057         assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn2.getTransactionIsolation());
1058 
1059         final Connection conn3 = getConnection();
1060         assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn3.getTransactionIsolation());
1061 
1062         conn2.close();
1063 
1064         conn3.close();
1065     }
1066 
1067     @Test
1068     public void testUnwrap() throws Exception {
1069         assertSame(ds.unwrap(BasicDataSource.class), ds);
1070         assertSame(ds.unwrap(AutoCloseable.class), ds);
1071         assertThrows(SQLException.class, () -> ds.unwrap(String.class));
1072         assertThrows(SQLException.class, () -> ds.unwrap(null));
1073     }
1074 
1075     @Test
1076     public void testValidationQueryTimeoutNegative() throws Exception {
1077         ds.setTestOnBorrow(true);
1078         ds.setTestOnReturn(true);
1079         ds.setValidationQueryTimeout(Duration.ofSeconds(-1));
1080         try (final Connection con = ds.getConnection()) {
1081             // close right away.
1082         }
1083     }
1084 
1085     @Test
1086     public void testValidationQueryTimeoutSucceed() throws Exception {
1087         ds.setTestOnBorrow(true);
1088         ds.setTestOnReturn(true);
1089         ds.setValidationQueryTimeout(Duration.ofMillis(100)); // Works for TesterStatement
1090         try (final Connection con = ds.getConnection()) {
1091             // close right away.
1092         }
1093     }
1094 
1095     @Test
1096     public void testValidationQueryTimeoutZero() throws Exception {
1097         ds.setTestOnBorrow(true);
1098         ds.setTestOnReturn(true);
1099         ds.setValidationQueryTimeout(Duration.ZERO);
1100         try (final Connection con = ds.getConnection()) {
1101             // close right away.
1102         }
1103     }
1104 
1105     @Test
1106     public void testValidationQueryTimoutFail() {
1107         ds.setTestOnBorrow(true);
1108         ds.setValidationQueryTimeout(Duration.ofSeconds(3)); // Too fast for TesterStatement
1109         final SQLException e = assertThrows(SQLException.class, ds::getConnection);
1110         assertTrue(e.toString().contains("timeout"));
1111     }
1112 }
1113 
1114 /**
1115  * TesterDriver that adds latency to connection requests. Latency (in ms) is the
1116  * last component of the URL.
1117  */
1118 final class TesterConnectionDelayDriver extends TesterDriver {
1119     private static final String CONNECT_STRING = "jdbc:apache:commons:testerConnectionDelayDriver";
1120 
1121     public TesterConnectionDelayDriver() {
1122         // DBCP expects an explicit no-arg constructor
1123     }
1124 
1125     @Override
1126     public boolean acceptsURL(final String url) throws SQLException {
1127         return url.startsWith(CONNECT_STRING);
1128     }
1129 
1130     @Override
1131     public Connection connect(final String url, final Properties info) throws SQLException {
1132         final String[] parsedUrl = url.split(":");
1133         final int delay = Integer.parseInt(parsedUrl[parsedUrl.length - 1]);
1134         try {
1135             Thread.sleep(delay);
1136         } catch (final InterruptedException ex) {
1137             Thread.currentThread().interrupt();
1138         }
1139         return super.connect(url, info);
1140     }
1141 
1142 }
1143 
1144 /**
1145  * TesterDriver that keeps a static count of connection requests.
1146  */
1147 final class TesterConnRequestCountDriver extends TesterDriver {
1148     private static final String CONNECT_STRING = "jdbc:apache:commons:testerConnRequestCountDriver";
1149     private static final AtomicInteger connectionRequestCount = new AtomicInteger();
1150 
1151     public static int getConnectionRequestCount() {
1152         return connectionRequestCount.get();
1153     }
1154 
1155     public static void initConnRequestCount() {
1156         connectionRequestCount.set(0);
1157     }
1158 
1159     public TesterConnRequestCountDriver() {
1160         // DBCP expects an explicit no-arg constructor
1161     }
1162 
1163     @Override
1164     public boolean acceptsURL(final String url) throws SQLException {
1165         return CONNECT_STRING.startsWith(url);
1166     }
1167 
1168     @Override
1169     public Connection connect(final String url, final Properties info) throws SQLException {
1170         connectionRequestCount.incrementAndGet();
1171         return super.connect(url, info);
1172     }
1173 }