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.datasources;
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.assertNotEquals;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.sql.Connection;
27  import java.sql.SQLException;
28  import java.time.Duration;
29  
30  import javax.sql.PooledConnection;
31  
32  import org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS;
33  import org.apache.commons.pool2.KeyedObjectPool;
34  import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
35  import org.junit.jupiter.api.BeforeEach;
36  import org.junit.jupiter.api.Test;
37  
38  /**
39   * Tests {@link KeyedCPDSConnectionFactory}.
40   */
41  public class TestKeyedCPDSConnectionFactory {
42  
43      protected ConnectionPoolDataSourceProxy cpds;
44  
45      @BeforeEach
46      public void setUp() throws Exception {
47          cpds = new ConnectionPoolDataSourceProxy(new DriverAdapterCPDS());
48          final DriverAdapterCPDS delegate = (DriverAdapterCPDS) cpds.getDelegate();
49          delegate.setDriver("org.apache.commons.dbcp2.TesterDriver");
50          delegate.setUrl("jdbc:apache:commons:testdriver");
51          delegate.setUser("userName");
52          delegate.setPassword("password");
53      }
54  
55      /**
56       * JIRA DBCP-216
57       *
58       * Verify that pool counters are maintained properly and listeners are
59       * cleaned up when a PooledConnection throws a connectionError event.
60       */
61      @Test
62      public void testConnectionErrorCleanup() throws Exception {
63          // Setup factory
64          final UserPassKey key = new UserPassKey("userName", "password");
65          final KeyedCPDSConnectionFactory factory = new KeyedCPDSConnectionFactory(cpds, null, -1, false);
66          try (final KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool = new GenericKeyedObjectPool<>(factory)) {
67              factory.setPool(pool);
68  
69              // Checkout a pair of connections
70              final PooledConnection pcon1 = pool.borrowObject(key).getPooledConnection();
71              try (final Connection con1 = pcon1.getConnection()) {
72                  final PooledConnection pcon2 = pool.borrowObject(key).getPooledConnection();
73                  assertEquals(2, pool.getNumActive(key));
74                  assertEquals(0, pool.getNumIdle(key));
75  
76                  // Verify listening
77                  final PooledConnectionProxy pc = (PooledConnectionProxy) pcon1;
78                  assertTrue(pc.getListeners().contains(factory));
79  
80                  // Throw connectionError event
81                  pc.throwConnectionError();
82  
83                  // Active count should be reduced by 1 and no idle increase
84                  assertEquals(1, pool.getNumActive(key));
85                  assertEquals(0, pool.getNumIdle(key));
86  
87                  // Throw another one - we should be on cleanup list, so ignored
88                  pc.throwConnectionError();
89                  assertEquals(1, pool.getNumActive(key));
90                  assertEquals(0, pool.getNumIdle(key));
91  
92                  // Ask for another connection - should trigger makeObject, which causes
93                  // cleanup, removing listeners.
94                  final PooledConnection pcon3 = pool.borrowObject(key).getPooledConnection();
95                  assertNotEquals(pcon3, pcon1); // better not get baddie back
96                  assertFalse(pc.getListeners().contains(factory)); // verify cleanup
97                  assertEquals(2, pool.getNumActive(key));
98                  assertEquals(0, pool.getNumIdle(key));
99  
100                 // Return good connections back to pool
101                 pcon2.getConnection().close();
102                 pcon3.getConnection().close();
103                 assertEquals(2, pool.getNumIdle(key));
104                 assertEquals(0, pool.getNumActive(key));
105 
106                 // Verify pc is closed
107                 assertThrows(SQLException.class, pc::getConnection, "Expecting SQLException using closed PooledConnection");
108 
109                 // Back from the dead - ignore the ghost!
110             }
111             assertEquals(2, pool.getNumIdle(key));
112             assertEquals(0, pool.getNumActive(key));
113 
114             // Clear pool
115             pool.clear();
116             assertEquals(0, pool.getNumIdle(key));
117         }
118     }
119 
120     /**
121      * JIRA: DBCP-442
122      */
123     @Test
124     public void testNullValidationQuery() throws Exception {
125         final UserPassKey key = new UserPassKey("userName", "password");
126         final KeyedCPDSConnectionFactory factory = new KeyedCPDSConnectionFactory(cpds, null, -1, false);
127         try (final GenericKeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool = new GenericKeyedObjectPool<>(factory)) {
128             factory.setPool(pool);
129             pool.setTestOnBorrow(true);
130             final PooledConnection pcon = pool.borrowObject(key).getPooledConnection();
131             try (final Connection con = pcon.getConnection()) {
132             }
133         }
134     }
135 
136     /**
137      * JIRA DBCP-216
138      *
139      * Check PoolableConnection close triggered by destroy is handled
140      * properly. PooledConnectionProxy (dubiously) fires connectionClosed
141      * when PooledConnection itself is closed.
142      */
143     @Test
144     public void testSharedPoolDSDestroyOnReturn() throws Exception {
145         try (final SharedPoolDataSource ds = new SharedPoolDataSource()) {
146             ds.setConnectionPoolDataSource(cpds);
147             ds.setMaxTotal(10);
148             ds.setDefaultMaxWait(Duration.ofMillis(50));
149             ds.setDefaultMaxIdle(2);
150             final Connection conn1 = ds.getConnection("userName", "password");
151             final Connection conn2 = ds.getConnection("userName", "password");
152             final Connection conn3 = ds.getConnection("userName", "password");
153             assertEquals(3, ds.getNumActive());
154             conn1.close();
155             assertEquals(1, ds.getNumIdle());
156             conn2.close();
157             assertEquals(2, ds.getNumIdle());
158             conn3.close(); // Return to pool will trigger destroy -> close sequence
159             assertEquals(2, ds.getNumIdle());
160         }
161     }
162 }