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