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