Skip to content

Commit 446b47e

Browse files
committed
DBCP-592: Support request boundaries
1 parent 0a2adaa commit 446b47e

3 files changed

Lines changed: 245 additions & 0 deletions

File tree

src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package org.apache.commons.dbcp2;
1818

19+
import java.lang.reflect.InvocationTargetException;
20+
import java.lang.reflect.Method;
1921
import java.sql.Array;
2022
import java.sql.Blob;
2123
import java.sql.CallableStatement;
@@ -42,6 +44,8 @@
4244
import java.util.concurrent.Executor;
4345

4446
import org.apache.commons.dbcp2.managed.ManagedConnection;
47+
import org.apache.commons.logging.Log;
48+
import org.apache.commons.logging.LogFactory;
4549

4650
/**
4751
* A base delegating implementation of {@link Connection}.
@@ -76,6 +80,18 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
7680
private String cachedSchema;
7781
private Duration defaultQueryTimeoutDuration;
7882

83+
private static final Log log = LogFactory.getLog(DelegatingConnection.class);
84+
/**
85+
* Request boundaries
86+
*/
87+
private static final Method beginRequest;
88+
private static final Method endRequest;
89+
90+
static {
91+
beginRequest = getConnectionMethod("beginRequest");
92+
endRequest = getConnectionMethod("endRequest");
93+
}
94+
7995
/**
8096
* Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool.
8197
*
@@ -85,6 +101,21 @@ public DelegatingConnection(final C connection) {
85101
this.connection = connection;
86102
}
87103

104+
/**
105+
* Uses reflection to get the method form the Connection interface
106+
* @param methodName name of the method to get
107+
* @return the method if it exists, otherwise null
108+
*/
109+
private static Method getConnectionMethod(String methodName) {
110+
Method method = null;
111+
try {
112+
method = Connection.class.getMethod(methodName);
113+
} catch (NoSuchMethodException ex) {
114+
// Ignore exception and set both methods to null
115+
}
116+
return method;
117+
}
118+
88119
@Override
89120
public void abort(final Executor executor) throws SQLException {
90121
try {
@@ -100,6 +131,13 @@ public void abort(final Executor executor) throws SQLException {
100131
protected void activate() {
101132
closed = false;
102133
setLastUsed();
134+
if (beginRequest != null && connection != null) {
135+
try {
136+
beginRequest.invoke(connection);
137+
} catch (InvocationTargetException | IllegalAccessException ex) {
138+
log.warn("Error calling beginRequest on connection", ex);
139+
}
140+
}
103141
if (connection instanceof DelegatingConnection) {
104142
((DelegatingConnection<?>) connection).activate();
105143
}
@@ -687,6 +725,13 @@ protected void passivate() throws SQLException {
687725
throw new SQLExceptionList(thrownList);
688726
}
689727
}
728+
if (endRequest != null && connection != null) {
729+
try {
730+
endRequest.invoke(connection);
731+
} catch (InvocationTargetException | IllegalAccessException ex) {
732+
log.warn("Error calling endRequest on connection", ex);
733+
}
734+
}
690735
setLastUsed(Instant.EPOCH);
691736
}
692737

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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+
18+
package org.apache.commons.dbcp2;
19+
20+
import org.junit.jupiter.api.Test;
21+
import org.mockito.stubbing.OngoingStubbing;
22+
23+
import java.sql.Connection;
24+
import java.sql.Driver;
25+
import java.sql.SQLException;
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
import java.util.Properties;
29+
30+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
31+
import static org.junit.jupiter.api.Assertions.assertEquals;
32+
import static org.mockito.ArgumentMatchers.any;
33+
import static org.mockito.ArgumentMatchers.isNull;
34+
import static org.mockito.Mockito.mock;
35+
import static org.mockito.Mockito.when;
36+
import static org.mockito.Mockito.reset;
37+
38+
public class TestRequestBoundaries {
39+
Driver driver = mock(TesterDriver.class);
40+
41+
@Test
42+
public void testBeginRequestOneConnection() throws SQLException {
43+
// Verify JDK version
44+
assumeTrue(Double.valueOf(System.getProperty("java.class.version")) >= 53);
45+
46+
// Setup
47+
BasicDataSource dataSource = getDataSource(driver);
48+
TesterConnection connection = setupPhysicalConnections(1).get(0);
49+
50+
// Get connection
51+
dataSource.getConnection();
52+
53+
// Verify number of calls
54+
assertCallCount(connection, 1, 0);
55+
}
56+
57+
@Test
58+
public void testEndRequestOneConnection() throws SQLException {
59+
// Verify JDK version
60+
assumeTrue(Double.valueOf(System.getProperty("java.class.version")) >= 53);
61+
62+
// Setup
63+
BasicDataSource dataSource = getDataSource(driver);
64+
TesterConnection connection = setupPhysicalConnections(1).get(0);
65+
66+
// Get then close connection
67+
dataSource.getConnection().close();
68+
69+
// Verify number of calls
70+
assertCallCount(connection, 1, 1);
71+
}
72+
73+
@Test
74+
public void testBeginRequestTwoVirtualConnections() throws SQLException {
75+
// Verify JDK version
76+
assumeTrue(Double.valueOf(System.getProperty("java.class.version")) >= 53);
77+
78+
// Setup
79+
BasicDataSource dataSource = getDataSource(driver);
80+
TesterConnection connection = setupPhysicalConnections(1).get(0);
81+
82+
// Get connection close it then get another connection
83+
dataSource.getConnection().close();
84+
dataSource.getConnection();
85+
86+
// Verify number calls
87+
assertCallCount(connection, 2, 1);
88+
}
89+
90+
@Test
91+
public void testEndRequestTwoVirtualConnections() throws SQLException {
92+
// Verify JDK version
93+
assumeTrue(Double.valueOf(System.getProperty("java.class.version")) >= 53);
94+
95+
// Setup
96+
BasicDataSource dataSource = getDataSource(driver);
97+
TesterConnection connection = setupPhysicalConnections(1).get(0);
98+
99+
// Get a connection and close then get another connection and close it
100+
dataSource.getConnection().close();
101+
dataSource.getConnection().close();
102+
103+
// Verify number of calls
104+
assertCallCount(connection, 2, 2);
105+
}
106+
107+
@Test
108+
public void testRequestBoundariesTwoPhysicalConnections() throws SQLException {
109+
// Verify JDK version
110+
assumeTrue(Double.valueOf(System.getProperty("java.class.version")) >= 53);
111+
112+
// Setup
113+
BasicDataSource dataSource = getDataSource(driver);
114+
List<TesterConnection> connections = setupPhysicalConnections(2);
115+
116+
// Get a connection, then get another connection, then close the first connection
117+
Connection fetchedConnection = dataSource.getConnection();
118+
dataSource.getConnection();
119+
fetchedConnection.close();
120+
121+
// Verify number of calls
122+
assertCallCount(connections.get(0), 1, 1);
123+
assertCallCount(connections.get(1), 1, 0);
124+
}
125+
126+
@Test
127+
public void testConnectionWithoutRequestBoundaries() throws SQLException {
128+
// Verify JDK version
129+
assumeTrue(Double.valueOf(System.getProperty("java.class.version")) < 53);
130+
131+
// Setup
132+
BasicDataSource dataSource = getDataSource(driver);
133+
TesterConnection connection = setupPhysicalConnections(1).get(0);
134+
135+
// Get connection
136+
dataSource.getConnection().close();
137+
138+
assertCallCount(connection, 0, 0);
139+
}
140+
141+
public BasicDataSource getDataSource(Driver driver) throws SQLException {
142+
reset(driver);
143+
144+
BasicDataSource dataSource = BasicDataSourceFactory.createDataSource(new Properties());
145+
dataSource.setDriver(driver);
146+
147+
// Before testing the call count of beginRequest and endRequest method we'll make sure that the
148+
// connectionFactory has been validated which involves creating a physical connection and destroying it. If we
149+
// don't, it's going to be done automatically when the first connection is requested. This is going to mess the
150+
// call count.
151+
validateConnectionFactory(dataSource, driver);
152+
153+
return dataSource;
154+
}
155+
156+
public void validateConnectionFactory(BasicDataSource dataSource, Driver driver) throws SQLException {
157+
when(driver.connect(isNull(), any(Properties.class))).thenReturn(getTesterConnection());
158+
dataSource.getLogWriter();
159+
}
160+
161+
public TesterConnection getTesterConnection() throws SQLException {
162+
TesterConnection connection = new TesterConnection(null, null);
163+
164+
return connection;
165+
}
166+
167+
public List<TesterConnection> setupPhysicalConnections(int numOfConnections) throws SQLException {
168+
List<TesterConnection> listOfConnections = new ArrayList<>();
169+
170+
for (int i = 0; i < numOfConnections; i++) {
171+
listOfConnections.add(getTesterConnection());
172+
}
173+
174+
OngoingStubbing<Connection> ongoingStubbing = when(driver.connect(isNull(), any(Properties.class)));
175+
176+
for (Connection connection : listOfConnections) {
177+
ongoingStubbing = ongoingStubbing.thenReturn(connection);
178+
}
179+
return listOfConnections;
180+
}
181+
182+
public void assertCallCount(TesterConnection connection, int expectedBeginRequestCalls, int expectedEndRequestCalls)
183+
throws SQLException {
184+
assertEquals(expectedBeginRequestCalls, connection.beginRequestCount.get());
185+
assertEquals(expectedEndRequestCalls, connection.endRequestCount.get());
186+
}
187+
}

src/test/java/org/apache/commons/dbcp2/TesterConnection.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.Map;
3535
import java.util.Properties;
3636
import java.util.concurrent.Executor;
37+
import java.util.concurrent.atomic.AtomicInteger;
3738

3839
/**
3940
* A dummy {@link Connection}, for testing purposes.
@@ -53,6 +54,8 @@ public class TesterConnection extends AbandonedTrace implements Connection {
5354
protected final String userName;
5455
protected Exception failure;
5556
protected boolean sqlExceptionOnClose;
57+
public AtomicInteger beginRequestCount = new AtomicInteger(0);
58+
public AtomicInteger endRequestCount = new AtomicInteger(0);
5659

5760
TesterConnection(final String userName, @SuppressWarnings("unused") final String password) {
5861
this.userName = userName;
@@ -421,4 +424,14 @@ public void setWarnings(final SQLWarning warning) {
421424
public <T> T unwrap(final Class<T> iface) throws SQLException {
422425
throw new SQLException("Not implemented.");
423426
}
427+
428+
@Override
429+
public void beginRequest() {
430+
beginRequestCount.incrementAndGet();
431+
}
432+
433+
@Override
434+
public void endRequest() {
435+
endRequestCount.incrementAndGet();
436+
}
424437
}

0 commit comments

Comments
 (0)