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.fail;
21  
22  import java.sql.Connection;
23  import java.sql.PreparedStatement;
24  import java.sql.ResultSet;
25  import java.sql.SQLException;
26  import java.sql.Timestamp;
27  import java.time.Duration;
28  
29  import javax.transaction.RollbackException;
30  import javax.transaction.Status;
31  
32  import org.apache.commons.dbcp2.Utils;
33  import org.junit.jupiter.api.AfterEach;
34  import org.junit.jupiter.api.Assertions;
35  import org.junit.jupiter.api.BeforeEach;
36  import org.junit.jupiter.api.Test;
37  
38  import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple;
39  import com.arjuna.ats.jta.common.jtaPropertyManager;
40  
41  /**
42   * Requires Java 8 or above.
43   */
44  public class TestConnectionWithNarayana {
45      private static final String CREATE_STMT = "CREATE TABLE TEST_DATA (KEY1 VARCHAR(100), ID BIGINT, VALUE1 DOUBLE PRECISION, INFO TEXT, TS TIMESTAMP)";
46      private static final String INSERT_STMT = "INSERT INTO TEST_DATA   (KEY1, ID, VALUE1, INFO, TS) VALUES (?,?,?,?,?)";
47      private static final String SELECT_STMT = "SELECT KEY1, ID, VALUE1, INFO, TS FROM TEST_DATA LIMIT 1";
48      private static final String PAYLOAD;
49      private static final String DROP_STMT = "DROP TABLE TEST_DATA";
50  
51      static {
52          final StringBuilder sb = new StringBuilder();
53          sb.append("Start");
54          sb.append("payload");
55          for (int i = 0; i < 10000; i++) {
56              sb.append("...");
57              sb.append(i);
58          }
59          sb.append("End");
60          sb.append("payload");
61  
62          PAYLOAD = sb.toString();
63      }
64  
65      private BasicManagedDataSource mds;
66  
67      @BeforeEach
68      public void setUp() throws Exception {
69          jtaPropertyManager.getJTAEnvironmentBean().setLastResourceOptimisationInterfaceClassName(
70                  "org.apache.commons.dbcp2.managed.LocalXAConnectionFactory$LocalXAResource");
71          mds = new BasicManagedDataSource();
72          mds.setTransactionManager(new TransactionManagerImple());
73          mds.setDriverClassName("org.h2.Driver");
74          mds.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
75  
76          mds.setMaxTotal(80);
77          mds.setMinIdle(0);
78          mds.setMaxIdle(80);
79          mds.setMinEvictableIdle(Duration.ofSeconds(10));
80          mds.setDurationBetweenEvictionRuns(Duration.ofSeconds(10));
81          mds.setLogAbandoned(true);
82          mds.setMaxWait(Duration.ofSeconds(2));
83          mds.setRemoveAbandonedOnMaintenance(true);
84          mds.setRemoveAbandonedOnBorrow(true);
85  
86          mds.setRemoveAbandonedTimeout(Duration.ofSeconds(10));
87          mds.setLogExpiredConnections(true);
88          mds.setLifo(false);
89  
90          try (final Connection conn = mds.getConnection()) {
91              try (final PreparedStatement ps = conn.prepareStatement(CREATE_STMT)) {
92                  ps.execute();
93              }
94          }
95      }
96  
97      @AfterEach
98      public void tearDown() throws Exception {
99          try (final Connection conn = mds.getConnection()) {
100             try (final PreparedStatement ps = conn.prepareStatement(DROP_STMT)) {
101                 ps.execute();
102             }
103         }
104         Utils.closeQuietly(mds);
105     }
106 
107     @Test
108     public void testConnectionCommitAfterTimeout() throws Exception {
109         mds.getTransactionManager().setTransactionTimeout(1);
110         mds.getTransactionManager().begin();
111         try (Connection conn = mds.getConnection()) {
112             do {
113                 Thread.sleep(1000);
114             } while (mds.getTransactionManager().getTransaction().getStatus() != Status.STATUS_ROLLEDBACK);
115             // Let the reaper do it's thing
116             Thread.sleep(1000);
117             try {
118                 conn.commit();
119                 fail("Should not work after timeout");
120             } catch (final SQLException e) {
121                 // Expected
122                 Assertions.assertEquals("Commit can not be set while enrolled in a transaction", e.getMessage());
123             }
124             mds.getTransactionManager().rollback();
125         }
126 
127         Assertions.assertEquals(0, mds.getNumActive());
128     }
129 
130     @Test
131     public void testConnectionInTimeout() throws Exception {
132         Connection conn = null;
133         PreparedStatement ps = null;
134         for (int i = 0; i < 5; i++) {
135             try {
136                 mds.getTransactionManager().setTransactionTimeout(1);
137                 mds.getTransactionManager().begin();
138 
139                 conn = mds.getConnection();
140                 ps = conn.prepareStatement(INSERT_STMT);
141                 ps.setString(1, Thread.currentThread().getName());
142                 ps.setLong(2, i);
143                 ps.setDouble(3, new java.util.Random().nextDouble());
144                 ps.setString(4, PAYLOAD);
145                 ps.setTimestamp(5, new Timestamp(System.currentTimeMillis()));
146                 ps.execute();
147 
148                 int n = 0;
149                 do {
150                     if (mds.getTransactionManager().getTransaction().getStatus() != Status.STATUS_ACTIVE) {
151                         n++;
152                     }
153                     try (Connection c = mds.getConnection(); PreparedStatement ps2 = c.prepareStatement(SELECT_STMT); ResultSet rs = ps2.executeQuery()) {
154                         // nothing here, all auto-close.
155                     }
156                 } while (n < 2);
157 
158                 ps.close();
159                 ps = null;
160                 conn.close();
161                 conn = null;
162 
163                 try {
164                     mds.getTransactionManager().commit();
165                     fail("Should not have been able to commit");
166                 } catch (final RollbackException e) {
167                     // this is expected
168                     if (mds.getTransactionManager().getTransaction() != null) {
169                         // Need to pop it off the thread if a background thread rolled the transaction back
170                         mds.getTransactionManager().rollback();
171                     }
172                 }
173             } catch (final Exception e) {
174                 if (mds.getTransactionManager().getTransaction() != null) {
175                     // Need to pop it off the thread if a background thread rolled the transaction back
176                     mds.getTransactionManager().rollback();
177                 }
178             } finally {
179                 if (ps != null) {
180                     ps.close();
181                 }
182                 if (conn != null) {
183                     conn.close();
184                 }
185             }
186             Assertions.assertEquals(0, mds.getNumActive());
187         }
188     }
189 
190     @Test
191     public void testRepeatedGetConnectionInTimeout() throws Exception {
192         mds.getTransactionManager().setTransactionTimeout(1);
193         mds.getTransactionManager().begin();
194 
195         try {
196             do {
197                 Thread.sleep(1000);
198             } while (mds.getTransactionManager().getTransaction().getStatus() != Status.STATUS_ROLLEDBACK);
199             // Let the reaper do it's thing
200             Thread.sleep(1000);
201             try (Connection conn = mds.getConnection()) {
202                 fail("Should not get the connection 1");
203             } catch (final SQLException e) {
204                 if (!e.getCause().getClass().equals(IllegalStateException.class)) {
205                     throw e;
206                 }
207                 try (Connection conn = mds.getConnection()) {
208                     fail("Should not get connection 2");
209                 } catch (final SQLException e2) {
210                     if (!e2.getCause().getClass().equals(IllegalStateException.class)) {
211                         throw e2;
212                     }
213                 }
214             }
215         } finally {
216             mds.getTransactionManager().rollback();
217         }
218         Assertions.assertEquals(0, mds.getNumActive());
219     }
220 }