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  package org.apache.commons.dbcp2;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  
24  import java.sql.Connection;
25  import java.sql.SQLException;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  
29  import javax.management.OperationsException;
30  
31  import org.apache.commons.pool2.impl.GenericObjectPool;
32  import org.junit.jupiter.api.AfterEach;
33  import org.junit.jupiter.api.Assertions;
34  import org.junit.jupiter.api.BeforeEach;
35  import org.junit.jupiter.api.Test;
36  
37  /**
38   */
39  public class TestPoolableConnection {
40  
41      private GenericObjectPool<PoolableConnection> pool;
42  
43      @BeforeEach
44      public void setUp() throws Exception {
45          final PoolableConnectionFactory factory = new PoolableConnectionFactory(
46                  new DriverConnectionFactory(new TesterDriver(), "jdbc:apache:commons:testdriver", null), null);
47          factory.setDefaultAutoCommit(Boolean.TRUE);
48          factory.setDefaultReadOnly(Boolean.TRUE);
49  
50          pool = new GenericObjectPool<>(factory);
51          factory.setPool(pool);
52      }
53  
54      @AfterEach
55      public void tearDown() {
56          pool.close();
57      }
58  
59      @Test
60      void testClosingWrappedInDelegate() throws Exception {
61          Assertions.assertEquals(0, pool.getNumActive());
62  
63          final Connection conn = pool.borrowObject();
64          final DelegatingConnection<Connection> outer = new DelegatingConnection<>(conn);
65  
66          Assertions.assertFalse(outer.isClosed());
67          Assertions.assertFalse(conn.isClosed());
68          Assertions.assertEquals(1, pool.getNumActive());
69  
70          outer.close();
71  
72          Assertions.assertTrue(outer.isClosed());
73          Assertions.assertTrue(conn.isClosed());
74          Assertions.assertEquals(0, pool.getNumActive());
75          Assertions.assertEquals(1, pool.getNumIdle());
76      }
77  
78      @Test
79      void testConnectionPool() throws Exception {
80          // Grab a new connection from the pool
81          final Connection c = pool.borrowObject();
82  
83          assertNotNull(c, "Connection should be created and should not be null");
84          assertEquals(1, pool.getNumActive(), "There should be exactly one active object in the pool");
85  
86          // Now return the connection by closing it
87          c.close(); // Can't be null
88  
89          assertEquals(0, pool.getNumActive(), "There should now be zero active objects in the pool");
90      }
91  
92      @Test
93      void testDisconnectionIgnoreSqlCodes() throws Exception {
94          pool.setTestOnReturn(true);
95          final PoolableConnectionFactory factory = (PoolableConnectionFactory) pool.getFactory();
96          factory.setFastFailValidation(true);
97          factory.setDisconnectionIgnoreSqlCodes(Arrays.asList("08S02", "08007"));
98  
99          final PoolableConnection conn = pool.borrowObject();
100         final TesterConnection nativeConnection = (TesterConnection) conn.getInnermostDelegate();
101 
102         // set up non-fatal exception
103         nativeConnection.setFailure(new SQLException("Non-fatal connection error.", "08S02"));
104         assertThrows(SQLException.class, conn::createStatement);
105         nativeConnection.setFailure(null);
106 
107         // verify that non-fatal connection is returned to the pool
108         conn.close();
109         assertEquals(0, pool.getNumActive(), "The pool should have no active connections");
110         assertEquals(1, pool.getNumIdle(), "The pool should have one idle connection");
111     }
112 
113     @Test
114     void testFastFailValidation() throws Exception {
115         pool.setTestOnReturn(true);
116         final PoolableConnectionFactory factory = (PoolableConnectionFactory) pool.getFactory();
117         factory.setFastFailValidation(true);
118         final PoolableConnection conn = pool.borrowObject();
119         final TesterConnection nativeConnection = (TesterConnection) conn.getInnermostDelegate();
120 
121         // Set up non-fatal exception
122         nativeConnection.setFailure(new SQLException("Not fatal error.", "Invalid syntax."));
123         assertThrows(SQLException.class, conn::createStatement);
124         // cleanup failure
125         nativeConnection.setFailure(null);
126 
127         // validate should not fail - error was not fatal and condition was cleaned up
128         conn.validate("SELECT 1", 1000);
129 
130         // now set up fatal failure
131         nativeConnection.setFailure(new SQLException("Fatal connection error.", "01002"));
132         assertThrows(SQLException.class, conn::createStatement);
133         // cleanup failure
134         nativeConnection.setFailure(null);
135 
136         // validate should now fail because of previous fatal error, despite cleanup
137         assertThrows(SQLException.class, () -> conn.validate("SELECT 1", 1000), "Should throw SQL exception on validation.");
138 
139         // verify that bad connection does not get returned to the pool
140         conn.close(); // testOnReturn triggers validate, which should fail
141         assertEquals(0, pool.getNumActive(), "The pool should have no active connections");
142         assertEquals(0, pool.getNumIdle(), "The pool should have no idle connections");
143     }
144 
145     @Test
146     void testFastFailValidationCustomCodes() throws Exception {
147         pool.setTestOnReturn(true);
148         final PoolableConnectionFactory factory = (PoolableConnectionFactory) pool.getFactory();
149         factory.setFastFailValidation(true);
150         final ArrayList<String> disconnectionSqlCodes = new ArrayList<>();
151         disconnectionSqlCodes.add("XXX");
152         factory.setDisconnectionSqlCodes(disconnectionSqlCodes);
153         final PoolableConnection conn = pool.borrowObject();
154         final TesterConnection nativeConnection = (TesterConnection) conn.getInnermostDelegate();
155 
156         // Set up fatal exception
157         nativeConnection.setFailure(new SQLException("Fatal connection error.", "XXX"));
158         assertThrows(SQLException.class, conn::createStatement);
159         // cleanup failure
160         nativeConnection.setFailure(null);
161 
162         // verify that bad connection does not get returned to the pool
163         conn.close(); // testOnReturn triggers validate, which should fail
164         assertEquals(0, pool.getNumActive(), "The pool should have no active connections");
165         assertEquals(0, pool.getNumIdle(), "The pool should have no idle connections");
166     }
167 
168     @Test
169     void testIsDisconnectionSqlExceptionStackOverflow() throws Exception {
170         final int maxDeep = 100_000;
171         final SQLException rootException = new SQLException("Data truncated", "22001");
172         SQLException parentException = rootException;
173         for (int i = 0; i <= maxDeep; i++) {
174             final SQLException childException = new SQLException("Data truncated: " + i, "22001");
175             parentException.setNextException(childException);
176             parentException = childException;
177         }
178         final Connection conn = pool.borrowObject();
179         assertFalse(((PoolableConnection) conn).isDisconnectionSqlException(rootException));
180         assertFalse(((PoolableConnection) conn).isFatalException(rootException));
181     }
182 
183     /**
184      * Tests if the {@link PoolableConnectionMXBean} interface is a valid MXBean interface.
185      */
186     @Test
187     void testMXBeanCompliance() throws OperationsException {
188         TestBasicDataSourceMXBean.testMXBeanCompliance(PoolableConnectionMXBean.class);
189     }
190 
191     // Bugzilla Bug 33591: PoolableConnection leaks connections if the
192     // delegated connection closes itself.
193     @Test
194     void testPoolableConnectionLeak() throws Exception {
195         // 'Borrow' a connection from the pool
196         final Connection conn = pool.borrowObject();
197 
198         // Now close our innermost delegate, simulating the case where the
199         // underlying connection closes itself
200         ((PoolableConnection) conn).getInnermostDelegate().close();
201 
202         // At this point, we can close the pooled connection. The
203         // PoolableConnection *should* realize that its underlying
204         // connection is gone and invalidate itself. The pool should have no
205         // active connections.
206 
207         try {
208             conn.close();
209         } catch (final SQLException e) {
210             // Here we expect 'connection already closed', but the connection
211             // should *NOT* be returned to the pool
212         }
213 
214         assertEquals(0, pool.getNumActive(), "The pool should have no active connections");
215     }
216 }