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.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.sql.Array;
27  import java.sql.Blob;
28  import java.sql.CallableStatement;
29  import java.sql.Clob;
30  import java.sql.Connection;
31  import java.sql.DatabaseMetaData;
32  import java.sql.NClob;
33  import java.sql.PreparedStatement;
34  import java.sql.SQLClientInfoException;
35  import java.sql.SQLException;
36  import java.sql.SQLWarning;
37  import java.sql.SQLXML;
38  import java.sql.Savepoint;
39  import java.sql.Statement;
40  import java.sql.Struct;
41  import java.util.Map;
42  import java.util.Properties;
43  import java.util.concurrent.Executor;
44  
45  import javax.transaction.xa.XAException;
46  import javax.transaction.xa.XAResource;
47  import javax.transaction.xa.Xid;
48  
49  import org.junit.jupiter.api.BeforeEach;
50  import org.junit.jupiter.api.Test;
51  
52  /**
53   * Tests for LocalXAConnectionFactory$LocalXAResource
54   */
55  public class TestLocalXaResource {
56  
57      private static final class TestConnection implements Connection {
58  
59          public boolean throwWhenGetAutoCommit;
60          public boolean throwWhenSetAutoCommit;
61          boolean autoCommit;
62          boolean readOnly;
63          public boolean committed;
64          public boolean rolledback;
65          public boolean closed;
66  
67          @Override
68          public void abort(final Executor executor) throws SQLException {
69          }
70  
71          @Override
72          public void clearWarnings() throws SQLException {
73          }
74  
75          @Override
76          public void close() throws SQLException {
77              closed = true;
78          }
79  
80          @Override
81          public void commit() throws SQLException {
82              committed = true;
83          }
84  
85          @Override
86          public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException {
87              return null;
88          }
89  
90          @Override
91          public Blob createBlob() throws SQLException {
92              return null;
93          }
94  
95          @Override
96          public Clob createClob() throws SQLException {
97              return null;
98          }
99  
100         @Override
101         public NClob createNClob() throws SQLException {
102             return null;
103         }
104 
105         @Override
106         public SQLXML createSQLXML() throws SQLException {
107             return null;
108         }
109 
110         @Override
111         public Statement createStatement() throws SQLException {
112             return null;
113         }
114 
115         @Override
116         public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
117             return null;
118         }
119 
120         @Override
121         public Statement createStatement(final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability)
122                 throws SQLException {
123             return null;
124         }
125 
126         @Override
127         public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException {
128             return null;
129         }
130 
131         @Override
132         public boolean getAutoCommit() throws SQLException {
133             if (throwWhenGetAutoCommit) {
134                 throw new SQLException();
135             }
136             return autoCommit;
137         }
138 
139         @Override
140         public String getCatalog() throws SQLException {
141             return null;
142         }
143 
144         @Override
145         public Properties getClientInfo() throws SQLException {
146             return null;
147         }
148 
149         @Override
150         public String getClientInfo(final String name) throws SQLException {
151             return null;
152         }
153 
154         @Override
155         public int getHoldability() throws SQLException {
156             return 0;
157         }
158 
159         @Override
160         public DatabaseMetaData getMetaData() throws SQLException {
161             return null;
162         }
163 
164         @Override
165         public int getNetworkTimeout() throws SQLException {
166             return 0;
167         }
168 
169         @Override
170         public String getSchema() throws SQLException {
171             return null;
172         }
173 
174         @Override
175         public int getTransactionIsolation() throws SQLException {
176             return 0;
177         }
178 
179         @Override
180         public Map<String, Class<?>> getTypeMap() throws SQLException {
181             return null;
182         }
183 
184         @Override
185         public SQLWarning getWarnings() throws SQLException {
186             return null;
187         }
188 
189         @Override
190         public boolean isClosed() throws SQLException {
191             return closed;
192         }
193 
194         @Override
195         public boolean isReadOnly() throws SQLException {
196             return readOnly;
197         }
198 
199         @Override
200         public boolean isValid(final int timeout) throws SQLException {
201             return false;
202         }
203 
204         @Override
205         public boolean isWrapperFor(final Class<?> iface) throws SQLException {
206             return false;
207         }
208 
209         @Override
210         public String nativeSQL(final String sql) throws SQLException {
211             return null;
212         }
213 
214         @Override
215         public CallableStatement prepareCall(final String sql) throws SQLException {
216             return null;
217         }
218 
219         @Override
220         public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
221                 throws SQLException {
222             return null;
223         }
224 
225         @Override
226         public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
227                 final int resultSetHoldability) throws SQLException {
228             return null;
229         }
230 
231         @Override
232         public PreparedStatement prepareStatement(final String sql) throws SQLException {
233             return null;
234         }
235 
236         @Override
237         public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
238             return null;
239         }
240 
241         @Override
242         public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
243                 throws SQLException {
244             return null;
245         }
246 
247         @Override
248         public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
249                 final int resultSetHoldability) throws SQLException {
250             return null;
251         }
252 
253         @Override
254         public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
255             return null;
256         }
257 
258         @Override
259         public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
260             return null;
261         }
262 
263         @Override
264         public void releaseSavepoint(final Savepoint savepoint) throws SQLException {
265         }
266 
267         @Override
268         public void rollback() throws SQLException {
269             rolledback = true;
270         }
271 
272         @Override
273         public void rollback(final Savepoint savepoint) throws SQLException {
274         }
275 
276         @Override
277         public void setAutoCommit(final boolean autoCommit) throws SQLException {
278             if (throwWhenSetAutoCommit) {
279                 throw new SQLException();
280             }
281             this.autoCommit = autoCommit;
282         }
283 
284         @Override
285         public void setCatalog(final String catalog) throws SQLException {
286         }
287 
288         @Override
289         public void setClientInfo(final Properties properties) throws SQLClientInfoException {
290         }
291 
292         @Override
293         public void setClientInfo(final String name, final String value) throws SQLClientInfoException {
294         }
295 
296         @Override
297         public void setHoldability(final int holdability) throws SQLException {
298         }
299 
300         @Override
301         public void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException {
302         }
303 
304         @Override
305         public void setReadOnly(final boolean readOnly) throws SQLException {
306             this.readOnly = readOnly;
307         }
308 
309         @Override
310         public Savepoint setSavepoint() throws SQLException {
311             return null;
312         }
313 
314         @Override
315         public Savepoint setSavepoint(final String name) throws SQLException {
316             return null;
317         }
318 
319         @Override
320         public void setSchema(final String schema) throws SQLException {
321         }
322 
323         @Override
324         public void setTransactionIsolation(final int level) throws SQLException {
325         }
326 
327         @Override
328         public void setTypeMap(final Map<String, Class<?>> map) throws SQLException {
329         }
330 
331         @Override
332         public <T> T unwrap(final Class<T> iface) throws SQLException {
333             return null;
334         }
335     }
336     private static final class TestXid implements Xid {
337 
338         @Override
339         public byte[] getBranchQualifier() {
340             return null;
341         }
342 
343         @Override
344         public int getFormatId() {
345             return 0;
346         }
347 
348         @Override
349         public byte[] getGlobalTransactionId() {
350             return null;
351         }
352     }
353 
354     private Connection conn;
355 
356     private LocalXAConnectionFactory.LocalXAResource resource;
357 
358     @BeforeEach
359     public void setUp() {
360         conn = new TestConnection();
361         resource = new LocalXAConnectionFactory.LocalXAResource(conn);
362     }
363 
364     @Test
365     public void testCommit() throws SQLException, XAException {
366         final Xid xid = new TestXid();
367         ((TestConnection) conn).closed = false;
368         conn.setReadOnly(false);
369         resource.start(xid, XAResource.TMNOFLAGS);
370         resource.commit(xid, false);
371         assertTrue(((TestConnection) conn).committed);
372     }
373 
374     @Test
375     public void testCommitConnectionClosed() throws SQLException, XAException {
376         final Xid xid = new TestXid();
377         ((TestConnection) conn).closed = true;
378         conn.setReadOnly(false);
379         resource.start(xid, XAResource.TMNOFLAGS);
380         assertThrows(XAException.class, () -> resource.commit(xid, false));
381     }
382 
383     @Test
384     public void testCommitConnectionNotReadOnly() throws SQLException, XAException {
385         final Xid xid = new TestXid();
386         ((TestConnection) conn).closed = false;
387         conn.setReadOnly(true);
388         resource.start(xid, XAResource.TMNOFLAGS);
389         resource.commit(xid, false);
390         assertFalse(((TestConnection) conn).committed);
391     }
392 
393     @Test
394     public void testCommitInvalidXid() throws SQLException, XAException {
395         final Xid xid = new TestXid();
396         ((TestConnection) conn).closed = false;
397         conn.setReadOnly(false);
398         resource.start(xid, XAResource.TMNOFLAGS);
399         assertThrows(XAException.class, () -> resource.commit(new TestXid(), false));
400     }
401 
402     @Test
403     public void testCommitMissingXid() {
404         assertThrows(NullPointerException.class, () -> resource.commit(null, false));
405     }
406 
407     @Test
408     public void testCommitNoTransaction() throws SQLException {
409         ((TestConnection) conn).closed = false;
410         conn.setReadOnly(false);
411         assertThrows(XAException.class, () -> resource.commit(new TestXid(), false));
412     }
413 
414     @Test
415     public void testConstructor() {
416         assertEquals(0, resource.getTransactionTimeout());
417         assertNull(resource.getXid());
418         // the current implementation always return false, regardless of the input value
419         assertFalse(resource.setTransactionTimeout(100));
420         // the current implementation always return an empty/zero'd array, regardless of the input value
421         assertEquals(0, resource.recover(100).length);
422     }
423 
424     @Test
425     public void testForget() throws XAException {
426         final Xid xid = new TestXid();
427         resource.start(xid, XAResource.TMNOFLAGS);
428         resource.forget(xid);
429         assertNull(resource.getXid());
430     }
431 
432     @Test
433     public void testForgetDifferentXid() throws XAException {
434         final Xid xid = new TestXid();
435         resource.start(xid, XAResource.TMNOFLAGS);
436         resource.forget(new TestXid());
437         assertEquals(xid, resource.getXid());
438     }
439 
440     @Test
441     public void testForgetMissingXid() throws XAException {
442         final Xid xid = new TestXid();
443         resource.start(xid, XAResource.TMNOFLAGS);
444         resource.forget(null);
445         assertEquals(xid, resource.getXid());
446     }
447 
448     @Test
449     public void testIsSame() {
450         assertTrue(resource.isSameRM(resource));
451         assertFalse(resource.isSameRM(new LocalXAConnectionFactory.LocalXAResource(conn)));
452     }
453 
454     @Test
455     public void testRollback() throws SQLException, XAException {
456         final Xid xid = new TestXid();
457         ((TestConnection) conn).closed = false;
458         conn.setReadOnly(false);
459         resource.start(xid, XAResource.TMNOFLAGS);
460         resource.rollback(xid);
461         assertTrue(((TestConnection) conn).rolledback);
462     }
463 
464     @Test
465     public void testRollbackInvalidXid() throws SQLException, XAException {
466         final Xid xid = new TestXid();
467         ((TestConnection) conn).closed = false;
468         conn.setReadOnly(false);
469         resource.start(xid, XAResource.TMNOFLAGS);
470         assertThrows(XAException.class, () -> resource.rollback(new TestXid()));
471     }
472 
473     @Test
474     public void testRollbackMissingXid() {
475         assertThrows(NullPointerException.class, () -> resource.rollback(null));
476     }
477 
478     /**
479      * When an exception is thrown on the {@link Connection#getAutoCommit()}, then the
480      * value is set to {@code true} by default.
481      * @throws XAException when there are errors with the transaction
482      * @throws SQLException when there are errors with other SQL/DB parts
483      */
484     @Test
485     public void testStartExceptionOnGetAutoCommit() throws XAException, SQLException {
486         final Xid xid = new TestXid();
487         ((TestConnection) conn).throwWhenGetAutoCommit = true;
488         conn.setAutoCommit(false);
489         conn.setReadOnly(true);
490         // the start method with no flag will call getAutoCommit, the exception will be thrown, and it will be set
491         // to true
492         resource.start(xid, XAResource.TMNOFLAGS);
493         // and prepare sets the value computed in start in the connection
494         resource.prepare(xid);
495         ((TestConnection) conn).throwWhenGetAutoCommit = false;
496         assertTrue(conn.getAutoCommit());
497     }
498 
499     @Test
500     public void testStartFailsWhenCannotSetAutoCommit() {
501         final Xid xid = new TestXid();
502         ((TestConnection) conn).throwWhenSetAutoCommit = true;
503         assertThrows(XAException.class, () -> resource.start(xid, XAResource.TMNOFLAGS));
504     }
505 
506     @Test
507     public void testStartInvalidFlag() {
508         // currently, valid values are TMNOFLAGS and TMRESUME
509         assertThrows(XAException.class, () -> resource.start(null, XAResource.TMENDRSCAN));
510     }
511 
512     @Test
513     public void testStartNoFlagButAlreadyEnlisted() throws XAException {
514         resource.start(new TestXid(), XAResource.TMNOFLAGS);
515         assertThrows(XAException.class, () -> resource.start(new TestXid(), XAResource.TMNOFLAGS));
516     }
517 
518     @Test
519     public void testStartNoFlagResume() throws XAException {
520         final Xid xid = new TestXid();
521         resource.start(xid, XAResource.TMNOFLAGS);
522         resource.start(xid, XAResource.TMRESUME);
523         assertEquals(xid, resource.getXid());
524     }
525 
526     @Test
527     public void testStartNoFlagResumeButDifferentXid() throws XAException {
528         resource.start(new TestXid(), XAResource.TMNOFLAGS);
529         assertThrows(XAException.class, () -> resource.start(new TestXid(), XAResource.TMRESUME));
530     }
531 
532     @Test
533     public void testStartNoFlagResumeEnd() throws XAException {
534         final Xid xid = new TestXid();
535         resource.start(xid, XAResource.TMNOFLAGS);
536         resource.start(xid, XAResource.TMRESUME);
537         // flag is never used in the end
538         resource.end(xid, 0);
539         assertEquals(xid, resource.getXid());
540     }
541 
542     @Test
543     public void testStartNoFlagResumeEndDifferentXid() throws XAException {
544         final Xid xid = new TestXid();
545         resource.start(xid, XAResource.TMNOFLAGS);
546         resource.start(xid, XAResource.TMRESUME);
547         // flag is never used in the end
548         assertThrows(XAException.class, () -> resource.end(new TestXid(), 0));
549     }
550 
551     @Test
552     public void testStartNoFlagResumeEndMissingXid() throws XAException {
553         final Xid xid = new TestXid();
554         resource.start(xid, XAResource.TMNOFLAGS);
555         resource.start(xid, XAResource.TMRESUME);
556         // flag is never used in the end
557         assertThrows(NullPointerException.class, () -> resource.end(null, 0));
558     }
559 
560     /**
561      * When an exception is thrown on the {@link Connection#getAutoCommit()}, then the
562      * value is set to {@code true} by default. However, if the connection is not read-only,
563      * then the value set by the user in the original connection will be kept.
564      * @throws XAException when there are errors with the transaction
565      * @throws SQLException when there are errors with other SQL/DB parts
566      */
567     @Test
568     public void testStartReadOnlyConnectionExceptionOnGetAutoCommit() throws XAException, SQLException {
569         final Xid xid = new TestXid();
570         ((TestConnection) conn).throwWhenGetAutoCommit = true;
571         conn.setAutoCommit(false);
572         conn.setReadOnly(false);
573         // the start method with no flag will call getAutoCommit, the exception will be thrown, and it will be set
574         // to true
575         resource.start(xid, XAResource.TMNOFLAGS);
576         // and prepare sets the value computed in start in the connection
577         resource.prepare(xid);
578         ((TestConnection) conn).throwWhenGetAutoCommit = false;
579         assertFalse(conn.getAutoCommit());
580     }
581 
582     @Test
583     public void testStartReadOnlyConnectionPrepare() throws XAException, SQLException {
584         final Xid xid = new TestXid();
585         conn.setAutoCommit(false);
586         conn.setReadOnly(true);
587         resource.start(xid, XAResource.TMNOFLAGS);
588         resource.prepare(xid);
589         assertFalse(conn.getAutoCommit());
590     }
591 }