/* | |
* 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 { | |
} | |
} | |
} |