1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.dbcp2.managed;
19
20 import static org.junit.jupiter.api.Assertions.assertFalse;
21 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
22 import static org.junit.jupiter.api.Assertions.assertTrue;
23
24 import java.lang.reflect.InvocationHandler;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Proxy;
28 import java.sql.Connection;
29 import java.sql.SQLException;
30 import java.time.Duration;
31 import java.util.Arrays;
32 import java.util.concurrent.atomic.AtomicInteger;
33
34 import javax.sql.XAConnection;
35 import javax.sql.XADataSource;
36 import javax.transaction.NotSupportedException;
37 import javax.transaction.RollbackException;
38 import javax.transaction.Synchronization;
39 import javax.transaction.SystemException;
40 import javax.transaction.Transaction;
41 import javax.transaction.TransactionManager;
42 import javax.transaction.TransactionSynchronizationRegistry;
43 import javax.transaction.xa.XAResource;
44
45 import org.apache.commons.dbcp2.BasicDataSource;
46 import org.apache.commons.dbcp2.DelegatingConnection;
47 import org.apache.commons.dbcp2.PoolableConnection;
48 import org.apache.commons.dbcp2.PoolableConnectionFactory;
49 import org.apache.commons.dbcp2.TesterClassLoader;
50 import org.apache.commons.dbcp2.transaction.TransactionAdapter;
51 import org.apache.commons.dbcp2.transaction.TransactionManagerAdapter;
52 import org.apache.commons.dbcp2.transaction.TransactionSynchronizationRegistryAdapter;
53 import org.apache.commons.pool2.impl.GenericObjectPool;
54 import org.junit.jupiter.api.AfterEach;
55 import org.junit.jupiter.api.BeforeEach;
56 import org.junit.jupiter.api.Test;
57
58 public class TestSynchronizationOrder {
59
60 private boolean transactionManagerRegistered;
61 private boolean transactionSynchronizationRegistryRegistered;
62 private TransactionManager transactionManager;
63 private TransactionSynchronizationRegistry transactionSynchronizationRegistry;
64 private XADataSource xads;
65 private BasicManagedDataSource bmds;
66 private BasicDataSource bds;
67
68 @BeforeEach
69 public void setup() {
70 transactionManager = new TransactionManagerAdapter() {
71
72 private Transaction transaction;
73
74 @Override
75 public void begin() throws NotSupportedException, SystemException {
76 transaction = new TransactionAdapter() {
77
78 @Override
79 public boolean enlistResource(final XAResource xaResource) throws IllegalStateException, RollbackException, SystemException {
80
81 return true;
82 }
83
84 @Override
85 public void registerSynchronization(final Synchronization synchronization) throws IllegalStateException, RollbackException, SystemException {
86 transactionManagerRegistered = true;
87 }
88 };
89 }
90
91 @Override
92 public Transaction getTransaction() throws SystemException {
93 return transaction;
94 }
95
96 };
97
98 transactionSynchronizationRegistry = new TransactionSynchronizationRegistryAdapter() {
99
100 @Override
101 public void registerInterposedSynchronization(final Synchronization synchronization) {
102 transactionSynchronizationRegistryRegistered = true;
103 }
104
105 };
106
107 bmds = new BasicManagedDataSource();
108 bmds.setTransactionManager(transactionManager);
109 bmds.setTransactionSynchronizationRegistry(transactionSynchronizationRegistry);
110 bmds.setXADataSource("notnull");
111 bds = new BasicDataSource();
112 bds.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
113 bds.setUrl("jdbc:apache:commons:testdriver");
114 bds.setMaxTotal(10);
115 bds.setMaxWait(Duration.ofMillis(100));
116 bds.setDefaultAutoCommit(Boolean.TRUE);
117 bds.setDefaultReadOnly(Boolean.FALSE);
118 bds.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
119 bds.setDefaultCatalog("test catalog");
120 bds.setUsername("userName");
121 bds.setPassword("password");
122 bds.setValidationQuery("SELECT DUMMY FROM DUAL");
123 bds.setConnectionInitSqls(Arrays.asList("SELECT 1", "SELECT 2"));
124 bds.setDriverClassLoader(new TesterClassLoader());
125 bds.setJmxName("org.apache.commons.dbcp2:name=test");
126 final AtomicInteger closeCounter = new AtomicInteger();
127 final InvocationHandler handle = new InvocationHandler() {
128 @SuppressWarnings("resource")
129 protected XAConnection getXAConnection() throws SQLException {
130 return new TesterBasicXAConnection(bds.getConnection(), closeCounter);
131 }
132
133 @Override
134 public Object invoke(final Object proxy, final Method method, final Object[] args)
135 throws Throwable {
136 final String methodName = method.getName();
137 switch (methodName) {
138 case "hashCode":
139 return System.identityHashCode(proxy);
140 case "equals":
141 return proxy == args[0];
142 case "getXAConnection":
143
144 return getXAConnection();
145 default:
146 break;
147 }
148 try {
149 return method.invoke(bds, args);
150 } catch (final InvocationTargetException e) {
151 throw e.getTargetException();
152 }
153 }
154 };
155 xads = (XADataSource) Proxy.newProxyInstance(
156 TestSynchronizationOrder.class.getClassLoader(),
157 new Class[]{XADataSource.class}, handle);
158 bmds.setXaDataSourceInstance(xads);
159
160 }
161
162 @AfterEach
163 public void tearDown() throws SQLException {
164 bds.close();
165 bmds.close();
166 }
167
168 @Test
169 void testInterposedSynchronization() throws Exception {
170 final DataSourceXAConnectionFactory xaConnectionFactory = new DataSourceXAConnectionFactory(transactionManager,
171 xads, transactionSynchronizationRegistry);
172
173 final PoolableConnectionFactory factory = new PoolableConnectionFactory(xaConnectionFactory, null);
174 factory.setValidationQuery("SELECT DUMMY FROM DUAL");
175 factory.setDefaultReadOnly(Boolean.TRUE);
176 factory.setDefaultAutoCommit(Boolean.TRUE);
177
178
179 try (final GenericObjectPool<PoolableConnection> pool = new GenericObjectPool<>(factory)) {
180 factory.setPool(pool);
181 pool.setMaxTotal(10);
182 pool.setMaxWait(Duration.ofSeconds(1));
183
184
185 try (final ManagedDataSource<PoolableConnection> ds = new ManagedDataSource<>(pool,
186 xaConnectionFactory.getTransactionRegistry())) {
187 ds.setAccessToUnderlyingConnectionAllowed(true);
188
189 transactionManager.begin();
190 try (final Connection connectionA = ds.getConnection()) {
191
192 assertInstanceOf(DelegatingConnection.class, connectionA);
193 }
194 transactionManager.commit();
195 assertFalse(transactionManagerRegistered);
196 assertTrue(transactionSynchronizationRegistryRegistered);
197 }
198 }
199 }
200
201 @Test
202 void testSessionSynchronization() throws Exception {
203 final DataSourceXAConnectionFactory xaConnectionFactory = new DataSourceXAConnectionFactory(transactionManager,
204 xads);
205
206 final PoolableConnectionFactory factory = new PoolableConnectionFactory(xaConnectionFactory, null);
207 factory.setValidationQuery("SELECT DUMMY FROM DUAL");
208 factory.setDefaultReadOnly(Boolean.TRUE);
209 factory.setDefaultAutoCommit(Boolean.TRUE);
210
211
212 try (final GenericObjectPool<PoolableConnection> pool = new GenericObjectPool<>(factory)) {
213 factory.setPool(pool);
214 pool.setMaxTotal(10);
215 pool.setMaxWait(Duration.ofSeconds(1));
216
217
218 try (final ManagedDataSource<PoolableConnection> ds = new ManagedDataSource<>(pool,
219 xaConnectionFactory.getTransactionRegistry())) {
220 ds.setAccessToUnderlyingConnectionAllowed(true);
221
222 transactionManager.begin();
223 try (final Connection connectionA = ds.getConnection()) {
224
225 assertInstanceOf(DelegatingConnection.class, connectionA);
226 }
227 transactionManager.commit();
228 assertTrue(transactionManagerRegistered);
229 assertFalse(transactionSynchronizationRegistryRegistered);
230 }
231 }
232 }
233 }