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