/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIESOR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.aries.tx.control.itests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.ops4j.pax.exam.CoreOptions.junitBundles; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; import static org.ops4j.pax.exam.CoreOptions.options; import static org.ops4j.pax.exam.CoreOptions.systemProperty; import static org.ops4j.pax.exam.CoreOptions.when; import java.io.File; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; import javax.inject.Inject; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import org.h2.tools.Server; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.Configuration; import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.junit.PaxExam; import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; import org.ops4j.pax.exam.spi.reactors.PerClass; import org.ops4j.pax.exam.util.Filter; import org.osgi.service.jdbc.DataSourceFactory; import org.osgi.service.transaction.control.TransactionControl; import org.osgi.service.transaction.control.TransactionRolledBackException; import org.osgi.service.transaction.control.jdbc.JDBCConnectionProviderFactory; @RunWith(PaxExam.class) @ExamReactorStrategy(PerClass.class) public class XATransactionTest { @Inject @Filter("(osgi.xa.enabled=true)") private TransactionControl txControl; @Inject @Filter("(osgi.xa.enabled=true)") private JDBCConnectionProviderFactory factory; @Inject @Filter("(osgi.jdbc.driver.class=org.h2.Driver)") private DataSourceFactory dsf; protected Connection connection1; protected Connection connection2; private Server server1; private Server server2; @Before public void setUp() throws Exception { Properties jdbc = new Properties(); server1 = Server.createTcpServer("-tcpPort", "0"); server1.start(); server2 = Server.createTcpServer("-tcpPort", "0"); server2.start(); String jdbcUrl1 = "jdbc:h2:tcp://127.0.0.1:" + server1.getPort() + "/" + getRemoteDBPath("db1"); String jdbcUrl2 = "jdbc:h2:tcp://127.0.0.1:" + server2.getPort() + "/" + getRemoteDBPath("db2"); jdbc.setProperty(DataSourceFactory.JDBC_URL, jdbcUrl1); connection1 = factory.getProviderFor(dsf, jdbc, null).getResource(txControl); jdbc.setProperty(DataSourceFactory.JDBC_URL, jdbcUrl2); connection2 = factory.getProviderFor(dsf, jdbc, null).getResource(txControl); txControl.required(() -> { Statement s = connection1.createStatement(); try { s.execute("DROP TABLE TEST_TABLE"); } catch (SQLException sqle) {} s.execute("CREATE TABLE TEST_TABLE ( message varchar(255) )"); return null; }); txControl.required(() -> { Statement s = connection2.createStatement(); try { s.execute("DROP TABLE TEST_TABLE"); } catch (SQLException sqle) {} s.execute("CREATE TABLE TEST_TABLE ( idValue varchar(16) PRIMARY KEY )"); return null; }); } @After public void tearDown() { try { txControl.required(() -> connection1.createStatement() .execute("DROP TABLE TEST_TABLE")); } catch (Exception e) {} try { txControl.required(() -> connection2.createStatement() .execute("DROP TABLE TEST_TABLE")); } catch (Exception e) {} if(server1 != null) { server1.stop(); } if(server2 != null) { server2.stop(); } connection1 = null; connection2 = null; } @Test public void testTwoPhaseCommit() { txControl.required(() -> { connection1.createStatement() .execute("Insert into TEST_TABLE values ( 'Hello World!' )"); connection2.createStatement() .execute("Insert into TEST_TABLE values ( 'Hello 1' )"); return null; }); assertEquals("Hello World!", txControl.notSupported(() -> { ResultSet rs = connection1.createStatement() .executeQuery("Select * from TEST_TABLE"); rs.next(); return rs.getString(1); })); assertEquals("Hello 1", txControl.notSupported(() -> { ResultSet rs = connection2.createStatement() .executeQuery("Select * from TEST_TABLE"); rs.next(); return rs.getString(1); })); } @Test public void testTwoPhaseRollback() { try { txControl.required(() -> { connection1.createStatement() .execute("Insert into TEST_TABLE values ( 'Hello World!' )"); connection2.createStatement() .execute("Insert into TEST_TABLE values ( 'Hello 1' )"); txControl.requiresNew(() -> { connection2.createStatement() .execute("Insert into TEST_TABLE values ( 'Hello 2' )"); return null; }); txControl.getCurrentContext().registerXAResource(new PoisonResource(), null); return null; }); fail("Should roll back"); } catch (TransactionRolledBackException trbe) { } assertEquals(0, (int) txControl.notSupported(() -> { ResultSet rs = connection1.createStatement() .executeQuery("Select count(*) from TEST_TABLE"); rs.next(); return rs.getInt(1); })); assertEquals("1: Hello 2", txControl.notSupported(() -> { Statement s = connection2.createStatement(); ResultSet rs = s.executeQuery("Select count(*) from TEST_TABLE"); rs.next(); int count = rs.getInt(1); rs = s.executeQuery("Select idValue from TEST_TABLE ORDER BY idValue"); rs.next(); return "" + count + ": " + rs.getString(1); })); } @Configuration public Option[] xaServerH2XATxConfiguration() { String localRepo = System.getProperty("maven.repo.local"); if (localRepo == null) { localRepo = System.getProperty("org.ops4j.pax.url.mvn.localRepository"); } return options(junitBundles(), systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("INFO"), when(localRepo != null) .useOptions(CoreOptions.vmOption("-Dorg.ops4j.pax.url.mvn.localRepository=" + localRepo)), mavenBundle("org.apache.aries.tx-control", "tx-control-service-xa").versionAsInProject(), mavenBundle("com.h2database", "h2").versionAsInProject(), mavenBundle("org.apache.aries.tx-control", "tx-control-provider-jdbc-xa").versionAsInProject(), mavenBundle("org.ops4j.pax.logging", "pax-logging-api").versionAsInProject(), mavenBundle("org.ops4j.pax.logging", "pax-logging-service").versionAsInProject() // ,CoreOptions.vmOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005") ); } private String getRemoteDBPath(String name) { String fullResourceName = getClass().getName().replace('.', '/') + ".class"; String resourcePath = getClass().getResource(getClass().getSimpleName() + ".class").getPath(); File testClassesDir = new File(resourcePath.substring(0, resourcePath.length() - fullResourceName.length())); String dbPath = new File(testClassesDir.getParentFile(), "testdb/" + name).getAbsolutePath(); return dbPath; } private static class PoisonResource implements XAResource { @Override public void commit(Xid arg0, boolean arg1) throws XAException { throw new XAException(XAException.XA_RBOTHER); } @Override public void end(Xid arg0, int arg1) throws XAException { } @Override public void forget(Xid arg0) throws XAException { } @Override public int getTransactionTimeout() throws XAException { return 30; } @Override public boolean isSameRM(XAResource arg0) throws XAException { return false; } @Override public int prepare(Xid arg0) throws XAException { throw new XAException(XAException.XA_RBOTHER); } @Override public Xid[] recover(int arg0) throws XAException { return new Xid[0]; } @Override public void rollback(Xid arg0) throws XAException { } @Override public boolean setTransactionTimeout(int arg0) throws XAException { return false; } @Override public void start(Xid arg0, int arg1) throws XAException { } } }