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        http://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.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                          // Called and used
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") // caller closes
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                     // both zero and 2-arg signatures
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         // create the pool
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             // finally create the datasource
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                     // Check and close right away.
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         // create the pool
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             // finally create the datasource
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                     // Check and close right away.
223                     assertTrue(connectionA instanceof DelegatingConnection);
224                 }
225                 transactionManager.commit();
226                 assertTrue(transactionManagerRegistered);
227                 assertFalse(transactionSynchronizationRegistryRegistered);
228             }
229         }
230     }
231 }