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  package org.apache.commons.dbcp2;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertNotNull;
21  import static org.junit.jupiter.api.Assertions.fail;
22  
23  import java.sql.Connection;
24  import java.sql.SQLException;
25  import java.util.ArrayList;
26  
27  import javax.management.OperationsException;
28  
29  import org.apache.commons.pool2.impl.GenericObjectPool;
30  import org.junit.jupiter.api.AfterEach;
31  import org.junit.jupiter.api.Assertions;
32  import org.junit.jupiter.api.BeforeEach;
33  import org.junit.jupiter.api.Test;
34  
35  /**
36   */
37  public class TestPoolableConnection {
38  
39      private GenericObjectPool<PoolableConnection> pool;
40  
41      @BeforeEach
42      public void setUp() throws Exception {
43          final PoolableConnectionFactory factory = new PoolableConnectionFactory(
44                  new DriverConnectionFactory(
45                          new TesterDriver(),"jdbc:apache:commons:testdriver", null),
46                  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      public 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      public 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      public void testFastFailValidation() throws Exception {
94          pool.setTestOnReturn(true);
95          final PoolableConnectionFactory factory = (PoolableConnectionFactory) pool.getFactory();
96          factory.setFastFailValidation(true);
97          final PoolableConnection conn = pool.borrowObject();
98          final TesterConnection nativeConnection = (TesterConnection) conn.getInnermostDelegate();
99  
100         // Set up non-fatal exception
101         nativeConnection.setFailure(new SQLException("Not fatal error.", "Invalid syntax."));
102         try {
103             conn.createStatement();
104             fail("Should throw SQL exception.");
105         } catch (final SQLException ignored) {
106             // cleanup failure
107             nativeConnection.setFailure(null);
108         }
109 
110         // validate should not fail - error was not fatal and condition was cleaned up
111         conn.validate("SELECT 1", 1000);
112 
113         // now set up fatal failure
114         nativeConnection.setFailure(new SQLException("Fatal connection error.", "01002"));
115 
116         try {
117             conn.createStatement();
118             fail("Should throw SQL exception.");
119         } catch (final SQLException ignored) {
120             // cleanup failure
121             nativeConnection.setFailure(null);
122         }
123 
124         // validate should now fail because of previous fatal error, despite cleanup
125         try {
126             conn.validate("SELECT 1", 1000);
127             fail("Should throw SQL exception on validation.");
128         } catch (final SQLException notValid){
129             // expected - fatal error && fastFailValidation
130         }
131 
132         // verify that bad connection does not get returned to the pool
133         conn.close();  // testOnReturn triggers validate, which should fail
134         assertEquals(0, pool.getNumActive(), "The pool should have no active connections");
135         assertEquals(0, pool.getNumIdle(), "The pool should have no idle connections");
136     }
137 
138     @Test
139     public void testFastFailValidationCustomCodes() throws Exception {
140         pool.setTestOnReturn(true);
141         final PoolableConnectionFactory factory = (PoolableConnectionFactory) pool.getFactory();
142         factory.setFastFailValidation(true);
143         final ArrayList<String> disconnectionSqlCodes = new ArrayList<>();
144         disconnectionSqlCodes.add("XXX");
145         factory.setDisconnectionSqlCodes(disconnectionSqlCodes);
146         final PoolableConnection conn = pool.borrowObject();
147         final TesterConnection nativeConnection = (TesterConnection) conn.getInnermostDelegate();
148 
149         // Set up fatal exception
150         nativeConnection.setFailure(new SQLException("Fatal connection error.", "XXX"));
151 
152         try {
153             conn.createStatement();
154             fail("Should throw SQL exception.");
155         } catch (final SQLException ignored) {
156             // cleanup failure
157             nativeConnection.setFailure(null);
158         }
159 
160         // verify that bad connection does not get returned to the pool
161         conn.close();  // testOnReturn triggers validate, which should fail
162         assertEquals(0, pool.getNumActive(), "The pool should have no active connections");
163         assertEquals(0, pool.getNumIdle(), "The pool should have no idle connections");
164     }
165 
166     @Test
167     public void testIsDisconnectionSqlExceptionStackOverflow() throws Exception {
168         final int maxDeep = 100_000;
169         final SQLException rootException = new SQLException("Data truncated", "22001");
170         SQLException parentException = rootException;
171         for (int i = 0; i <= maxDeep; i++) {
172             final SQLException childException = new SQLException("Data truncated: " + i, "22001");
173             parentException.setNextException(childException);
174             parentException = childException;
175         }
176         final Connection conn = pool.borrowObject();
177         assertEquals(false, ((PoolableConnection) conn).isDisconnectionSqlException(rootException));
178         assertEquals(false, ((PoolableConnection) conn).isFatalException(rootException));
179     }
180 
181     /**
182      * Tests if the {@link PoolableConnectionMXBean} interface is a valid MXBean
183      * interface.
184      */
185     @Test
186     public void testMXBeanCompliance() throws OperationsException {
187        TestBasicDataSourceMXBean.testMXBeanCompliance(PoolableConnectionMXBean.class);
188     }
189 
190     // Bugzilla Bug 33591: PoolableConnection leaks connections if the
191     // delegated connection closes itself.
192     @Test
193     public void testPoolableConnectionLeak() throws Exception {
194         // 'Borrow' a connection from the pool
195         final Connection conn = pool.borrowObject();
196 
197         // Now close our innermost delegate, simulating the case where the
198         // underlying connection closes itself
199         ((PoolableConnection)conn).getInnermostDelegate().close();
200 
201         // At this point, we can close the pooled connection. The
202         // PoolableConnection *should* realize that its underlying
203         // connection is gone and invalidate itself. The pool should have no
204         // active connections.
205 
206         try {
207             conn.close();
208         } catch (final SQLException e) {
209             // Here we expect 'connection already closed', but the connection
210             // should *NOT* be returned to the pool
211         }
212 
213         assertEquals(0, pool.getNumActive(), "The pool should have no active connections");
214     }
215 }