View Javadoc
1   /*
2   
3     Licensed to the Apache Software Foundation (ASF) under one or more
4     contributor license agreements.  See the NOTICE file distributed with
5     this work for additional information regarding copyright ownership.
6     The ASF licenses this file to You under the Apache License, Version 2.0
7     (the "License"); you may not use this file except in compliance with
8     the License.  You may obtain a copy of the License at
9   
10        http://www.apache.org/licenses/LICENSE-2.0
11  
12     Unless required by applicable law or agreed to in writing, software
13     distributed under the License is distributed on an "AS IS" BASIS,
14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15     See the License for the specific language governing permissions and
16     limitations under the License.
17   */
18  package org.apache.commons.dbcp2.managed;
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.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertSame;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  import static org.junit.jupiter.api.Assertions.fail;
27  
28  import java.sql.CallableStatement;
29  import java.sql.Connection;
30  import java.sql.PreparedStatement;
31  import java.sql.ResultSet;
32  import java.sql.SQLException;
33  import java.sql.Statement;
34  
35  import javax.transaction.Synchronization;
36  import javax.transaction.Transaction;
37  
38  import org.apache.commons.dbcp2.DelegatingConnection;
39  import org.junit.jupiter.api.AfterEach;
40  import org.junit.jupiter.api.Assertions;
41  import org.junit.jupiter.api.BeforeEach;
42  import org.junit.jupiter.api.Test;
43  
44  /**
45   * Tests ManagedDataSource with an active transaction in progress.
46   */
47  public class TestManagedDataSourceInTx extends TestManagedDataSource {
48  
49      // can't actually test close in a transaction
50      @Override
51      protected void assertBackPointers(final Connection conn, final Statement statement) throws SQLException {
52          assertFalse(conn.isClosed());
53          assertFalse(isClosed(statement));
54  
55          assertSame(conn, statement.getConnection(),
56                  "statement.getConnection() should return the exact same connection instance that was used to create the statement");
57  
58          try (ResultSet resultSet = statement.getResultSet()) {
59              assertFalse(isClosed(resultSet));
60              assertSame(statement, resultSet.getStatement(),
61                      "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
62  
63              try (ResultSet executeResultSet = statement.executeQuery("select * from dual")) {
64                  assertFalse(isClosed(executeResultSet));
65                  assertSame(statement, executeResultSet.getStatement(),
66                          "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
67              }
68  
69              try (ResultSet keysResultSet = statement.getGeneratedKeys()) {
70                  assertFalse(isClosed(keysResultSet));
71                  assertSame(statement, keysResultSet.getStatement(),
72                          "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
73              }
74              if (statement instanceof PreparedStatement) {
75                  final PreparedStatement preparedStatement = (PreparedStatement) statement;
76                  try (ResultSet preparedResultSet = preparedStatement.executeQuery()) {
77                      assertFalse(isClosed(preparedResultSet));
78                      assertSame(statement, preparedResultSet.getStatement(),
79                              "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
80                  }
81              }
82  
83              resultSet.getStatement().getConnection().close();
84          }
85      }
86  
87      @Override
88      @BeforeEach
89      public void setUp() throws Exception {
90          super.setUp();
91          transactionManager.begin();
92      }
93  
94      @Override
95      @AfterEach
96      public void tearDown() throws Exception {
97          if (transactionManager.getTransaction() != null) {
98              transactionManager.commit();
99          }
100         super.tearDown();
101     }
102 
103     @Override
104     @Test
105     public void testAutoCommitBehavior() throws Exception {
106         final Connection connection = newConnection();
107 
108         // auto commit should be off
109         assertFalse(connection.getAutoCommit(), "Auto-commit should be disabled");
110 
111         // attempt to set auto commit
112         try {
113             connection.setAutoCommit(true);
114             fail("setAutoCommit method should be disabled while enlisted in a transaction");
115         } catch (final SQLException e) {
116             // expected
117         }
118 
119         // make sure it is still disabled
120         assertFalse(connection.getAutoCommit(), "Auto-commit should be disabled");
121 
122         // close connection
123         connection.close();
124     }
125 
126     @Override
127     @Test
128     public void testClearWarnings() throws Exception {
129         // open a connection
130         Connection connection = newConnection();
131         assertNotNull(connection);
132 
133         // generate SQLWarning on connection
134         final CallableStatement statement = connection.prepareCall("warning");
135         assertNotNull(connection.getWarnings());
136 
137         // create a new shared connection
138         final Connection sharedConnection = newConnection();
139 
140         // shared connection should see warning
141         assertNotNull(sharedConnection.getWarnings());
142 
143         // close and allocate a new (original) connection
144         connection.close();
145         connection = newConnection();
146 
147         // warnings should not have been cleared by closing the connection
148         assertNotNull(connection.getWarnings());
149         assertNotNull(sharedConnection.getWarnings());
150 
151         statement.close();
152         sharedConnection.close();
153         connection.close();
154     }
155 
156     @Test
157     public void testCloseInTransaction() throws Exception {
158         try (DelegatingConnection<?> connectionA = (DelegatingConnection<?>) newConnection();
159                 DelegatingConnection<?> connectionB = (DelegatingConnection<?>) newConnection()) {
160             assertNotEquals(connectionA, connectionB);
161             assertNotEquals(connectionB, connectionA);
162             assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
163             assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
164         }
165 
166         final Connection connection = newConnection();
167 
168         assertFalse(connection.isClosed(), "Connection should be open");
169 
170         connection.close();
171 
172         assertTrue(connection.isClosed(), "Connection should be closed");
173     }
174 
175     @Test
176     public void testCommit() throws Exception {
177         try (Connection connection = newConnection()) {
178 
179             // connection should be open
180             assertFalse(connection.isClosed(), "Connection should be open");
181 
182             // attempt commit directly
183             try {
184                 connection.commit();
185                 fail("commit method should be disabled while enlisted in a transaction");
186             } catch (final SQLException e) {
187                 // expected
188             }
189 
190             // make sure it is still open
191             assertFalse(connection.isClosed(), "Connection should be open");
192 
193         }
194     }
195 
196     @Override
197     @Test
198     public void testConnectionReturnOnCommit() throws Exception {
199        // override with no-op test
200     }
201 
202     @Override
203     @Test
204     public void testConnectionsAreDistinct() throws Exception {
205         final Connection[] conn = new Connection[getMaxTotal()];
206         for (int i = 0; i < conn.length; i++) {
207             conn[i] = newConnection();
208             for (int j = 0; j < i; j++) {
209                 // two connections should be distinct instances
210                 Assertions.assertNotSame(conn[j], conn[i]);
211                 // neither should they should be equivalent even though they are
212                 // sharing the same underlying connection
213                 Assertions.assertNotEquals(conn[j], conn[i]);
214                 // Check underlying connection is the same
215                 Assertions.assertEquals(((DelegatingConnection<?>) conn[j]).getInnermostDelegateInternal(),
216                         ((DelegatingConnection<?>) conn[i]).getInnermostDelegateInternal());
217             }
218         }
219         for (final Connection element : conn) {
220             element.close();
221         }
222     }
223 
224     @Test
225     public void testDoubleReturn() throws Exception {
226         transactionManager.getTransaction().registerSynchronization(new Synchronization() {
227             private ManagedConnection<?> conn;
228 
229             @Override
230             public void afterCompletion(final int i) {
231                 final int numActive = pool.getNumActive();
232                 try {
233                     conn.checkOpen();
234                 } catch (final Exception e) {
235                     // Ignore
236                 }
237                 assertEquals(numActive, pool.getNumActive());
238                 try {
239                     conn.close();
240                 } catch (final Exception e) {
241                     fail("Should have been able to close the connection");
242                 }
243                 // TODO Requires DBCP-515 assertTrue(numActive -1 == pool.getNumActive());
244             }
245 
246             @Override
247             public void beforeCompletion() {
248                 try {
249                     conn = (ManagedConnection<?>) ds.getConnection();
250                     assertNotNull(conn);
251                 } catch (final SQLException e) {
252                     fail("Could not get connection");
253                 }
254             }
255         });
256         transactionManager.commit();
257     }
258 
259     @Test
260     public void testGetConnectionInAfterCompletion() throws Exception {
261         try (DelegatingConnection<?> connection = (DelegatingConnection<?>) newConnection()) {
262             // Don't close so we can check it for warnings in afterCompletion
263             transactionManager.getTransaction().registerSynchronization(new SynchronizationAdapter() {
264                 @Override
265                 public void afterCompletion(final int i) {
266                     try {
267                         final Connection connection1 = ds.getConnection();
268                         try {
269                             connection1.getWarnings();
270                             fail("Could operate on closed connection");
271                         } catch (final SQLException e) {
272                             // This is expected
273                         }
274                     } catch (final SQLException e) {
275                         fail("Should have been able to get connection");
276                     }
277                 }
278             });
279         }
280         transactionManager.commit();
281     }
282 
283     @Override
284     @Test
285     public void testHashCode() throws Exception {
286         try (Connection conn1 = newConnection()) {
287             assertNotNull(conn1);
288             try (Connection conn2 = newConnection()) {
289                 assertNotNull(conn2);
290 
291                 // shared connections should not have the same hash code
292                 Assertions.assertNotEquals(conn1.hashCode(), conn2.hashCode());
293             }
294         }
295     }
296 
297     /**
298      * @see #testSharedConnection()
299      */
300     @Override
301     @Test
302     public void testManagedConnectionEqualsFail() throws Exception {
303         // this test is invalid for managed connections since because
304         // two connections to the same datasource are supposed to share
305         // a single connection
306     }
307 
308     @Override
309     @Test
310     public void testMaxTotal() throws Exception {
311         final Transaction[] transactions = new Transaction[getMaxTotal()];
312         final Connection[] c = new Connection[getMaxTotal()];
313         for (int i = 0; i < c.length; i++) {
314             // create a new connection in the current transaction
315             c[i] = newConnection();
316             assertNotNull(c[i]);
317 
318             // suspend the current transaction and start a new one
319             transactions[i] = transactionManager.suspend();
320             assertNotNull(transactions[i]);
321             transactionManager.begin();
322         }
323 
324         try {
325             newConnection();
326             fail("Allowed to open more than DefaultMaxTotal connections.");
327         } catch (final java.sql.SQLException e) {
328             // should only be able to open 10 connections, so this test should
329             // throw an exception
330         } finally {
331             transactionManager.commit();
332             for (int i = 0; i < c.length; i++) {
333                 transactionManager.resume(transactions[i]);
334                 c[i].close();
335                 transactionManager.commit();
336             }
337         }
338     }
339 
340     @Override
341     @Test
342     public void testNestedConnections() {
343         // Not supported
344     }
345 
346     @Test
347     public void testReadOnly() throws Exception {
348         try (Connection connection = newConnection()) {
349 
350             // NOTE: This test class uses connections that are read-only by default
351 
352             // connection should be read only
353             assertTrue(connection.isReadOnly(), "Connection be read-only");
354 
355             // attempt to setReadOnly
356             try {
357                 connection.setReadOnly(true);
358                 fail("setReadOnly method should be disabled while enlisted in a transaction");
359             } catch (final SQLException e) {
360                 // expected
361             }
362 
363             // make sure it is still read-only
364             assertTrue(connection.isReadOnly(), "Connection be read-only");
365 
366             // attempt to setReadonly
367             try {
368                 connection.setReadOnly(false);
369                 fail("setReadOnly method should be disabled while enlisted in a transaction");
370             } catch (final SQLException e) {
371                 // expected
372             }
373 
374             // make sure it is still read-only
375             assertTrue(connection.isReadOnly(), "Connection be read-only");
376 
377             // TwR closes the connection
378         }
379     }
380 
381     @Override
382     @Test
383     public void testSharedConnection() throws Exception {
384         try (DelegatingConnection<?> connectionA = (DelegatingConnection<?>) newConnection();
385                 DelegatingConnection<?> connectionB = (DelegatingConnection<?>) newConnection()) {
386             assertNotEquals(connectionA, connectionB);
387             assertNotEquals(connectionB, connectionA);
388             assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
389             assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
390         }
391     }
392 
393     @Test
394     public void testSharedTransactionConversion() throws Exception {
395         try (DelegatingConnection<?> connectionA = (DelegatingConnection<?>) newConnection();
396                 DelegatingConnection<?> connectionB = (DelegatingConnection<?>) newConnection()) {
397             // in a transaction the inner connections should be equal
398             assertNotEquals(connectionA, connectionB);
399             assertNotEquals(connectionB, connectionA);
400             assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
401             assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
402 
403             transactionManager.commit();
404 
405             // use the connection so it adjusts to the completed transaction
406             connectionA.getAutoCommit();
407             connectionB.getAutoCommit();
408 
409             // no there is no transaction so inner connections should not be equal
410             assertNotEquals(connectionA, connectionB);
411             assertNotEquals(connectionB, connectionA);
412             assertFalse(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
413             assertFalse(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
414 
415             transactionManager.begin();
416 
417             // use the connection so it adjusts to the new transaction
418             connectionA.getAutoCommit();
419             connectionB.getAutoCommit();
420 
421             // back in a transaction so inner connections should be equal again
422             assertNotEquals(connectionA, connectionB);
423             assertNotEquals(connectionB, connectionA);
424             assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
425             assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
426         }
427     }
428 }