View Javadoc
1   /*
2   
3     Licensed to the Apache Software Foundation (ASF) under one or more
4     contributor license agreements.  See the NOTICE file distributed with
5     this work for additional information regarding copyright ownership.
6     The ASF licenses this file to You under the Apache License, Version 2.0
7     (the "License"); you may not use this file except in compliance with
8     the License.  You may obtain a copy of the License at
9   
10        https://www.apache.org/licenses/LICENSE-2.0
11  
12     Unless required by applicable law or agreed to in writing, software
13     distributed under the License is distributed on an "AS IS" BASIS,
14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15     See the License for the specific language governing permissions and
16     limitations under the License.
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                          // Called and used
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") // caller closes
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                     // both zero and 2-arg signatures
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         // create the pool
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             // finally create the datasource
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                     // Check and close right away.
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         // create the pool
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             // finally create the datasource
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                     // Check and close right away.
225                     assertInstanceOf(DelegatingConnection.class, connectionA);
226                 }
227                 transactionManager.commit();
228                 assertTrue(transactionManagerRegistered);
229                 assertFalse(transactionSynchronizationRegistryRegistered);
230             }
231         }
232     }
233 }