View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.dbcp2.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.impl.GenericObjectPool;
34  import org.junit.jupiter.api.BeforeEach;
35  import org.junit.jupiter.api.Test;
36  
37  /**
38   */
39  public class TestCPDSConnectionFactory {
40  
41      protected ConnectionPoolDataSourceProxy cpds;
42  
43      private void checkPoolLimits(final GenericObjectPool<PooledConnectionAndInfo> pool) {
44          assertTrue(pool.getNumActive() + pool.getNumIdle() <= pool.getMaxTotal(),
45                  "Active + Idle should be <= MaxTotal");
46          assertTrue(pool.getNumIdle() <= pool.getMaxIdle(), "Idle should be <= MaxIdle");
47      }
48  
49      @BeforeEach
50      public void setUp() throws Exception {
51          cpds = new ConnectionPoolDataSourceProxy(new DriverAdapterCPDS());
52          final DriverAdapterCPDS delegate = (DriverAdapterCPDS) cpds.getDelegate();
53          delegate.setDriver("org.apache.commons.dbcp2.TesterDriver");
54          delegate.setUrl("jdbc:apache:commons:testdriver");
55          delegate.setUser("userName");
56          delegate.setPassword("password");
57      }
58  
59      /**
60       * JIRA DBCP-216
61       *
62       * Verify that pool counters are maintained properly and listeners are
63       * cleaned up when a PooledConnection throws a connectionError event.
64       */
65      @Test
66      void testConnectionErrorCleanup() throws Exception {
67          // Setup factory
68          final CPDSConnectionFactory factory = new CPDSConnectionFactory(cpds, null, Duration.ofMillis(-1), false, "userName", "password".toCharArray());
69          try (final GenericObjectPool<PooledConnectionAndInfo> pool = new GenericObjectPool<>(factory)) {
70              factory.setPool(pool);
71  
72              // Checkout a pair of connections
73              final PooledConnection pcon1 = pool.borrowObject().getPooledConnection();
74              try (final Connection con1 = pcon1.getConnection()) {
75                  final PooledConnection pcon2 = pool.borrowObject().getPooledConnection();
76                  assertEquals(2, pool.getNumActive());
77                  assertEquals(0, pool.getNumIdle());
78  
79                  // Verify listening
80                  final PooledConnectionProxy pc = (PooledConnectionProxy) pcon1;
81                  assertTrue(pc.getListeners().contains(factory));
82  
83                  // Throw connectionError event
84                  pc.throwConnectionError();
85  
86                  // Active count should be reduced by 1
87                  assertEquals(1, pool.getNumActive());
88                  checkPoolLimits(pool);
89  
90                  // Throw another one - should be ignored
91                  pc.throwConnectionError();
92                  assertEquals(1, pool.getNumActive());
93                  checkPoolLimits(pool);
94  
95                  // Ask for another connection
96                  final PooledConnection pcon3 = pool.borrowObject().getPooledConnection();
97                  assertNotEquals(pcon3, pcon1); // better not get baddie back
98                  assertFalse(pc.getListeners().contains(factory)); // verify cleanup
99                  assertEquals(2, pool.getNumActive());
100                 assertEquals(0, pool.getNumIdle());
101 
102                 // Return good connections back to pool
103                 pcon2.getConnection().close();
104                 pcon3.getConnection().close();
105                 assertEquals(2, pool.getNumIdle());
106                 assertEquals(0, pool.getNumActive());
107 
108                 // Verify pc is closed
109                 assertThrows(SQLException.class, pc::getConnection, "Expecting SQLException using closed PooledConnection");
110 
111                 // Back from the dead - ignore the ghost!
112                 con1.close();
113                 assertEquals(2, pool.getNumIdle());
114                 assertEquals(0, pool.getNumActive());
115 
116                 // Clear pool
117                 pool.clear();
118                 assertEquals(0, pool.getNumIdle());
119             }
120         }
121     }
122 
123     /**
124      * JIRA: DBCP-442
125      */
126     @Test
127     void testNullValidationQuery() throws Exception {
128         final CPDSConnectionFactory factory = new CPDSConnectionFactory(cpds, null, Duration.ofMillis(-1), false, "userName", "password".toCharArray());
129         try (final GenericObjectPool<PooledConnectionAndInfo> pool = new GenericObjectPool<>(factory)) {
130             factory.setPool(pool);
131             pool.setTestOnBorrow(true);
132             final PooledConnection pcon = pool.borrowObject().getPooledConnection();
133             try (final Connection con = pcon.getConnection()) {
134             }
135         }
136     }
137 
138     @Test
139     void testSetPasswordCharArray() {
140         final CPDSConnectionFactory factory = new CPDSConnectionFactory(cpds, null, Duration.ofMillis(-1), false, "userName", "password".toCharArray());
141         final char[] pwd = { 'a' };
142         factory.setPassword(pwd);
143         assertEquals("a", String.valueOf(factory.getPasswordCharArray()));
144         pwd[0] = 'b';
145         assertEquals("a", String.valueOf(factory.getPasswordCharArray()));
146     }
147 
148     @Test
149     void testSetPasswordString() {
150         final CPDSConnectionFactory factory = new CPDSConnectionFactory(cpds, null, Duration.ofMillis(-1), false, "userName", "password".toCharArray());
151         final String pwd = "a";
152         factory.setPassword(pwd);
153         assertEquals("a", String.valueOf(factory.getPasswordCharArray()));
154     }
155 
156     /**
157      * JIRA DBCP-216
158      *
159      * Check PoolableConnection close triggered by destroy is handled
160      * properly. PooledConnectionProxy (dubiously) fires connectionClosed
161      * when PooledConnection itself is closed.
162      */
163     @Test
164     void testSharedPoolDSDestroyOnReturn() throws Exception {
165         try (final PerUserPoolDataSource ds = new PerUserPoolDataSource()) {
166             ds.setConnectionPoolDataSource(cpds);
167             ds.setPerUserMaxTotal("userName", 10);
168             ds.setPerUserMaxWait("userName", Duration.ofMillis(50));
169             ds.setPerUserMaxIdle("userName", 2);
170             final Connection conn1 = ds.getConnection("userName", "password");
171             final Connection conn2 = ds.getConnection("userName", "password");
172             final Connection conn3 = ds.getConnection("userName", "password");
173             assertEquals(3, ds.getNumActive("userName"));
174             conn1.close();
175             assertEquals(1, ds.getNumIdle("userName"));
176             conn2.close();
177             assertEquals(2, ds.getNumIdle("userName"));
178             conn3.close(); // Return to pool will trigger destroy -> close sequence
179             assertEquals(2, ds.getNumIdle("userName"));
180         }
181     }
182 
183 }