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.cpdsadapter;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  import static org.junit.jupiter.api.Assertions.fail;
27  
28  import java.io.PrintWriter;
29  import java.sql.Connection;
30  import java.sql.PreparedStatement;
31  import java.sql.ResultSet;
32  import java.sql.SQLException;
33  import java.sql.SQLFeatureNotSupportedException;
34  import java.sql.Statement;
35  import java.time.Duration;
36  import java.util.Properties;
37  
38  import javax.naming.NamingException;
39  import javax.naming.Reference;
40  import javax.naming.StringRefAddr;
41  import javax.sql.DataSource;
42  
43  import org.apache.commons.dbcp2.Constants;
44  import org.apache.commons.dbcp2.DelegatingPreparedStatement;
45  import org.apache.commons.dbcp2.DelegatingStatement;
46  import org.apache.commons.dbcp2.PStmtKey;
47  import org.apache.commons.dbcp2.PoolablePreparedStatement;
48  import org.apache.commons.dbcp2.TestUtils;
49  import org.apache.commons.dbcp2.datasources.SharedPoolDataSource;
50  import org.apache.commons.pool2.impl.DefaultPooledObject;
51  import org.junit.jupiter.api.Assertions;
52  import org.junit.jupiter.api.BeforeEach;
53  import org.junit.jupiter.api.Test;
54  
55  /**
56   * Tests for DriverAdapterCPDS
57   */
58  public class TestDriverAdapterCPDS {
59  
60      private static final class ThreadDbcp367 extends Thread {
61  
62          private final DataSource dataSource;
63  
64          private volatile boolean failed;
65  
66          public ThreadDbcp367(final DataSource dataSource) {
67              this.dataSource = dataSource;
68          }
69  
70          public boolean isFailed() {
71              return failed;
72          }
73  
74          @Override
75          public void run() {
76              Connection conn = null;
77              try {
78                  for (int j = 0; j < 5000; j++) {
79                      conn = dataSource.getConnection();
80                      conn.close();
81                  }
82              } catch (final SQLException sqle) {
83                  failed = true;
84                  sqle.printStackTrace();
85              }
86          }
87      }
88  
89      @SuppressWarnings("resource")
90      private static void checkAfterClose(final Connection element, final PStmtKey pStmtKey) throws SQLException {
91          final ConnectionImpl connectionImpl = (ConnectionImpl) element;
92          assertNull(connectionImpl.getInnermostDelegate());
93          assertNotNull(connectionImpl.getInnermostDelegateInternal());
94          final PooledConnectionImpl pooledConnectionImpl = connectionImpl.getPooledConnectionImpl();
95          assertNotNull(pooledConnectionImpl);
96          // Simulate released resources, should not throw NPEs
97          pooledConnectionImpl.destroyObject(pStmtKey, null);
98          pooledConnectionImpl.destroyObject(pStmtKey, new DefaultPooledObject<>(null));
99          pooledConnectionImpl.destroyObject(pStmtKey, new DefaultPooledObject<>(new DelegatingPreparedStatement(null, null)));
100     }
101 
102     private DriverAdapterCPDS pcds;
103 
104     @BeforeEach
105     public void setUp() throws Exception {
106         pcds = new DriverAdapterCPDS();
107         pcds.setDriver("org.apache.commons.dbcp2.TesterDriver");
108         pcds.setUrl("jdbc:apache:commons:testdriver");
109         pcds.setUser("foo");
110         pcds.setPassword("bar");
111         pcds.setPoolPreparedStatements(true);
112     }
113 
114     @Test
115     public void testClose()
116             throws Exception {
117         final Connection[] c = new Connection[10];
118         for (int i = 0; i < c.length; i++) {
119             c[i] = pcds.getPooledConnection().getConnection();
120         }
121 
122         // close one of the connections
123         c[0].close();
124         assertTrue(c[0].isClosed());
125         // get a new connection
126         c[0] = pcds.getPooledConnection().getConnection();
127 
128         for (final Connection element : c) {
129             element.close();
130             checkAfterClose(element, null);
131         }
132 
133         // open all the connections
134         for (int i = 0; i < c.length; i++) {
135             c[i] = pcds.getPooledConnection().getConnection();
136         }
137         for (final Connection element : c) {
138             element.close();
139             checkAfterClose(element, null);
140         }
141     }
142 
143     @Test
144     public void testCloseWithUserName()
145             throws Exception {
146         final Connection[] c = new Connection[10];
147         for (int i = 0; i < c.length; i++) {
148             c[i] = pcds.getPooledConnection("u1", "p1").getConnection();
149         }
150 
151         // close one of the connections
152         c[0].close();
153         assertTrue(c[0].isClosed());
154         // get a new connection
155         c[0] = pcds.getPooledConnection("u1", "p1").getConnection();
156 
157         for (final Connection element : c) {
158             element.close();
159             checkAfterClose(element, null);
160         }
161 
162         // open all the connections
163         for (int i = 0; i < c.length; i++) {
164             c[i] = pcds.getPooledConnection("u1", "p1").getConnection();
165         }
166         for (final Connection element : c) {
167             element.close();
168             checkAfterClose(element, null);
169         }
170     }
171 
172     // https://issues.apache.org/jira/browse/DBCP-376
173     @Test
174     public void testDbcp367() throws Exception {
175         final ThreadDbcp367[] threads = new ThreadDbcp367[200];
176 
177         pcds.setPoolPreparedStatements(true);
178         pcds.setMaxPreparedStatements(-1);
179         pcds.setAccessToUnderlyingConnectionAllowed(true);
180 
181         try (final SharedPoolDataSource spds = new SharedPoolDataSource()) {
182             spds.setConnectionPoolDataSource(pcds);
183             spds.setMaxTotal(threads.length + 10);
184             spds.setDefaultMaxWait(Duration.ofMillis(-1));
185             spds.setDefaultMaxIdle(10);
186             spds.setDefaultAutoCommit(Boolean.FALSE);
187 
188             spds.setValidationQuery("SELECT 1");
189             spds.setDefaultDurationBetweenEvictionRuns(Duration.ofSeconds(10));
190             spds.setDefaultNumTestsPerEvictionRun(-1);
191             spds.setDefaultTestWhileIdle(true);
192             spds.setDefaultTestOnBorrow(true);
193             spds.setDefaultTestOnReturn(false);
194 
195             for (int i = 0; i < threads.length; i++) {
196                 threads[i] = new ThreadDbcp367(spds);
197                 threads[i].start();
198             }
199 
200             for (int i = 0; i < threads.length; i++) {
201                 threads[i].join();
202                 Assertions.assertFalse(threads[i].isFailed(), "Thread " + i + " has failed");
203             }
204         }
205     }
206 
207     @SuppressWarnings("deprecation")
208     @Test
209     public void testDeprecatedAccessors() {
210         int i = 0;
211         //
212         i++;
213         pcds.setMinEvictableIdleTimeMillis(i);
214         assertEquals(i, pcds.getMinEvictableIdleTimeMillis());
215         assertEquals(Duration.ofMillis(i), pcds.getMinEvictableIdleDuration());
216         //
217         i++;
218         pcds.setTimeBetweenEvictionRunsMillis(i);
219         assertEquals(i, pcds.getTimeBetweenEvictionRunsMillis());
220         assertEquals(Duration.ofMillis(i), pcds.getDurationBetweenEvictionRuns());
221     }
222 
223     @Test
224     public void testGetObjectInstance() throws Exception {
225         final Reference ref = pcds.getReference();
226         final Object o = pcds.getObjectInstance(ref, null, null, null);
227         assertEquals(pcds.getDriver(), ((DriverAdapterCPDS) o).getDriver());
228     }
229 
230     @Test
231     public void testGetObjectInstanceChangeDescription() throws Exception {
232         final Reference ref = pcds.getReference();
233         for (int i = 0; i < ref.size(); i++) {
234             if (ref.get(i).getType().equals("description")) {
235                 ref.remove(i);
236                 break;
237             }
238         }
239         ref.add(new StringRefAddr("description", "anything"));
240         final Object o = pcds.getObjectInstance(ref, null, null, null);
241         assertEquals(pcds.getDescription(), ((DriverAdapterCPDS) o).getDescription());
242     }
243 
244     @Test
245     public void testGetObjectInstanceNull() throws Exception {
246         final Object o = pcds.getObjectInstance(null, null, null, null);
247         assertNull(o);
248     }
249 
250     @Test
251     public void testGetParentLogger() {
252         assertThrows(SQLFeatureNotSupportedException.class, pcds::getParentLogger);
253     }
254 
255     @Test
256     public void testGetReference() throws NamingException {
257         final Reference ref = pcds.getReference();
258         assertEquals(pcds.getDriver(), ref.get("driver").getContent());
259         assertEquals(pcds.getDescription(), ref.get("description").getContent());
260     }
261 
262     @Test
263     public void testGettersAndSetters() {
264         pcds.setUser("foo");
265         assertEquals("foo", pcds.getUser());
266         pcds.setPassword("bar");
267         assertEquals("bar", pcds.getPassword());
268         pcds.setPassword(new char[] {'a', 'b'});
269         assertArrayEquals(new char[] {'a', 'b'}, pcds.getPasswordCharArray());
270         final PrintWriter pw = new PrintWriter(System.err);
271         pcds.setLogWriter(pw);
272         @SuppressWarnings("resource")
273         final PrintWriter logWriter = pcds.getLogWriter();
274         assertEquals(pw, logWriter);
275         pcds.setLoginTimeout(10);
276         assertEquals(10, pcds.getLoginTimeout());
277         pcds.setMaxIdle(100);
278         assertEquals(100, pcds.getMaxIdle());
279         pcds.setDurationBetweenEvictionRuns(Duration.ofMillis(100));
280         assertEquals(100, pcds.getDurationBetweenEvictionRuns().toMillis());
281         pcds.setNumTestsPerEvictionRun(1);
282         assertEquals(1, pcds.getNumTestsPerEvictionRun());
283         pcds.setMinEvictableIdleDuration(Duration.ofMillis(11));
284         assertEquals(Duration.ofMillis(11), pcds.getMinEvictableIdleDuration());
285         pcds.setDescription("jo");
286         assertEquals("jo", pcds.getDescription());
287     }
288 
289     /**
290      * JIRA: DBCP-245
291      */
292     @Test
293     public void testIncorrectPassword() throws Exception
294     {
295         pcds.getPooledConnection("u2", "p2").close();
296         try {
297             // Use bad password
298             pcds.getPooledConnection("u1", "zlsafjk");
299             fail("Able to retrieve connection with incorrect password");
300         } catch (final SQLException e1) {
301             // should fail
302 
303         }
304 
305         // Use good password
306         pcds.getPooledConnection("u1", "p1").close();
307         try {
308             pcds.getPooledConnection("u1", "x");
309             fail("Able to retrieve connection with incorrect password");
310         }
311         catch (final SQLException e) {
312             if (!e.getMessage().startsWith("x is not the correct password")) {
313                 throw e;
314             }
315             // else the exception was expected
316         }
317 
318         // Make sure we can still use our good password.
319         pcds.getPooledConnection("u1", "p1").close();
320     }
321 
322     /**
323      * JIRA: DBCP-442
324      */
325     @Test
326     public void testNullValidationQuery() throws Exception {
327         try (final SharedPoolDataSource spds = new SharedPoolDataSource()) {
328             spds.setConnectionPoolDataSource(pcds);
329             spds.setDefaultTestOnBorrow(true);
330             try (final Connection c = spds.getConnection()) {
331                 // close right away
332             }
333         }
334     }
335 
336     @Test
337     public void testSetConnectionProperties() throws Exception {
338         // Set user property to bad value
339         pcds.setUser("bad");
340         // Supply correct value in connection properties
341         // This will overwrite field value
342         final Properties properties = new Properties();
343         properties.put(Constants.KEY_USER, "foo");
344         properties.put(Constants.KEY_PASSWORD, pcds.getPassword());
345         pcds.setConnectionProperties(properties);
346         pcds.getPooledConnection().close();
347         assertEquals("foo", pcds.getUser());
348         // Put bad password into properties
349         properties.put("password", "bad");
350         // This does not change local field
351         assertEquals("bar", pcds.getPassword());
352         // Supply correct password in getPooledConnection
353         // Call will succeed and overwrite property
354         pcds.getPooledConnection("foo", "bar").close();
355         assertEquals("bar", pcds.getConnectionProperties().getProperty("password"));
356     }
357 
358     @Test
359     public void testSetConnectionPropertiesConnectionCalled() throws Exception {
360         final Properties properties = new Properties();
361         // call to the connection
362         pcds.getPooledConnection().close();
363         assertThrows(IllegalStateException.class, () -> pcds.setConnectionProperties(properties));
364     }
365 
366     @Test
367     public void testSetConnectionPropertiesNull() throws Exception {
368         pcds.setConnectionProperties(null);
369     }
370 
371     @Test
372     public void testSetPasswordNull() throws Exception {
373         pcds.setPassword("Secret");
374         assertEquals("Secret", pcds.getPassword());
375         pcds.setPassword((char[]) null);
376         assertNull(pcds.getPassword());
377     }
378 
379     @Test
380     public void testSetPasswordNullWithConnectionProperties() throws Exception {
381         pcds.setConnectionProperties(new Properties());
382         pcds.setPassword("Secret");
383         assertEquals("Secret", pcds.getPassword());
384         pcds.setPassword((char[]) null);
385         assertNull(pcds.getPassword());
386     }
387 
388     @Test
389     public void testSetPasswordThenModCharArray() {
390         final char[] pwd = {'a'};
391         pcds.setPassword(pwd);
392         assertEquals("a", pcds.getPassword());
393         pwd[0] = 'b';
394         assertEquals("a", pcds.getPassword());
395     }
396 
397     @Test
398     public void testSetUserNull() throws Exception {
399         pcds.setUser("Alice");
400         assertEquals("Alice", pcds.getUser());
401         pcds.setUser(null);
402         assertNull(pcds.getUser());
403     }
404 
405     @Test
406     public void testSetUserNullWithConnectionProperties() throws Exception {
407         pcds.setConnectionProperties(new Properties());
408         pcds.setUser("Alice");
409         assertEquals("Alice", pcds.getUser());
410         pcds.setUser(null);
411         assertNull(pcds.getUser());
412     }
413 
414     @Test
415     public void testSimple() throws Exception {
416         try (final Connection conn = pcds.getPooledConnection().getConnection()) {
417             assertNotNull(conn);
418             try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) {
419                 assertNotNull(stmt);
420                 try (final ResultSet resultSet = stmt.executeQuery()) {
421                     assertNotNull(resultSet);
422                     assertTrue(resultSet.next());
423                 }
424             }
425         }
426     }
427 
428     @SuppressWarnings("resource")
429     @Test
430     public void testSimpleWithUsername() throws Exception {
431         final Connection connCheck;
432         PStmtKey pStmtKey;
433         try (final Connection conn = pcds.getPooledConnection("u1", "p1").getConnection()) {
434             assertNotNull(conn);
435             connCheck = conn;
436             try (final PreparedStatement stmt = conn.prepareStatement("select * from dual")) {
437                 assertNotNull(stmt);
438                 final DelegatingStatement delegatingStatement = (DelegatingStatement) stmt;
439                 final Statement delegateStatement = delegatingStatement.getDelegate();
440                 pStmtKey = TestUtils.getPStmtKey((PoolablePreparedStatement) delegateStatement);
441                 assertNotNull(pStmtKey);
442                 try (final ResultSet resultSet = stmt.executeQuery()) {
443                     assertNotNull(resultSet);
444                     assertTrue(resultSet.next());
445                 }
446             }
447         }
448         checkAfterClose(connCheck, pStmtKey);
449     }
450 
451     @Test
452     public void testToStringWithoutConnectionProperties() throws ClassNotFoundException {
453         final DriverAdapterCPDS cleanCpds = new DriverAdapterCPDS();
454         cleanCpds.setDriver("org.apache.commons.dbcp2.TesterDriver");
455         cleanCpds.setUrl("jdbc:apache:commons:testdriver");
456         cleanCpds.setUser("foo");
457         cleanCpds.setPassword("bar");
458         cleanCpds.toString();
459     }
460 }