package org.ovirt.engine.core.utils;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import junit.framework.Assert;
import org.jboss.embedded.Bootstrap;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import org.ovirt.engine.core.common.businessentities.bookmarks;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.TransactionScopeOption;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
import org.ovirt.engine.core.utils.ejb.ContainerManagedResourceType;
import org.ovirt.engine.core.utils.ejb.EjbUtils;
import org.ovirt.engine.core.utils.ejb.JBossEmbeddedEJBUtilsStrategy;
import org.ovirt.engine.core.utils.transaction.TransactionMethod;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
@Ignore("Running these tests requires manual configuration of timeouts. They should be revisited when TransactionSupport is changed")
public class TransactionSupportTest {
private static TransactionManager tm;
private static Bootstrap bootstrap;
private Transaction outerTransaction;
private Transaction innerTransaction;
/**
* Initializing the embadded Jboss and looking-up the transaction manager.
*
* @throws Exception
*/
@BeforeClass
public static void initState() throws Exception {
try {
bootstrap = Bootstrap.getInstance();
if (!bootstrap.isStarted()) {
bootstrap.bootstrap();
EjbUtils.setStrategy(new JBossEmbeddedEJBUtilsStrategy());
}
} catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
tm = EjbUtils.findResource(ContainerManagedResourceType.TRANSACTION_MANAGER);
}
@Before
public void cleanState() {
outerTransaction = null;
innerTransaction = null;
}
/**
* Testing that opening a transaction within another transaction indeed opens a new transaction. Testing that the
* transactions status are as expected during the whole test.
*/
@Test
public void runInNewTransaction() {
TransactionSupport.executeInScope(TransactionScopeOption.RequiresNew, new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
try {
outerTransaction = tm.getTransaction();
} catch (SystemException e) {
e.printStackTrace();
}
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
// open a new transaction
TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
try {
innerTransaction = tm.getTransaction();
} catch (SystemException e) {
e.printStackTrace();
}
// validate that the inner and outer
// transactions are not equals
Assert.assertFalse(outerTransaction.equals(innerTransaction));
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
validateStatus(Status.STATUS_ACTIVE, innerTransaction);
return null;
}
});
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
validateStatus(Status.STATUS_COMMITTED, innerTransaction);
return null;
}
});
validateStatus(Status.STATUS_COMMITTED, outerTransaction);
}
/**
* This test validates that changing inner transaction status to STATUS_MARKED_ROLLBACK The inner transaction is
* rolled back even if no exception was thrown.
*/
@Test
public void runInNewTransactionAndRollBack() {
TransactionSupport.executeInScope(TransactionScopeOption.RequiresNew, new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
try {
outerTransaction = tm.getTransaction();
} catch (SystemException e) {
e.printStackTrace();
}
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
// open a new transaction
TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
try {
innerTransaction = tm.getTransaction();
innerTransaction.setRollbackOnly();
} catch (SystemException e) {
e.printStackTrace();
}
// validate that the inner and outer
// transactions are not equals
Assert.assertFalse(outerTransaction.equals(innerTransaction));
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
validateStatus(Status.STATUS_MARKED_ROLLBACK, innerTransaction);
return null;
}
});
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
validateStatus(Status.STATUS_ROLLEDBACK, innerTransaction);
return null;
}
});
validateStatus(Status.STATUS_COMMITTED, outerTransaction);
}
/**
* Testing that if inner transaction fails - the outer transaction still commits.
*/
@Test
public void runInNewTransactionWithInnerErrors() {
TransactionSupport.executeInScope(TransactionScopeOption.RequiresNew, new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
try {
outerTransaction = tm.getTransaction();
} catch (SystemException e) {
e.printStackTrace();
}
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
try {
// open a new transaction
TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
try {
innerTransaction = tm.getTransaction();
} catch (SystemException e) {
e.printStackTrace();
}
// validate that the inner and outer
// transactions are not equals
Assert.assertFalse(outerTransaction.equals(innerTransaction));
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
validateStatus(Status.STATUS_ACTIVE, innerTransaction);
throw new RuntimeException("failing inner transaction");
}
});
} catch (Exception e) {
System.out.println("Exception for inner transaction with msg: " + e.getMessage());
} finally {
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
validateStatus(Status.STATUS_ROLLEDBACK, innerTransaction);
}
return null;
}
});
validateStatus(Status.STATUS_COMMITTED, outerTransaction);
}
/**
* Testing that if inner transaction throws exception and fails - the outer transaction fails as well.
*/
@Test
public void runInNewTransactionWithErrors() {
try {
TransactionSupport.executeInScope(TransactionScopeOption.RequiresNew, new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
try {
outerTransaction = tm.getTransaction();
} catch (SystemException e) {
e.printStackTrace();
}
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
try {
// open a new transaction
TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
try {
innerTransaction = tm.getTransaction();
} catch (SystemException e) {
e.printStackTrace();
}
// validate that the inner and
// outer transactions are not
// equals
Assert.assertFalse(outerTransaction.equals(innerTransaction));
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
validateStatus(Status.STATUS_ACTIVE, innerTransaction);
throw new RuntimeException("failing inner transaction");
}
});
} finally {
validateStatus(Status.STATUS_ACTIVE, outerTransaction);
validateStatus(Status.STATUS_ROLLEDBACK, innerTransaction);
}
return null;
}
});
} catch (Exception e) {
validateStatus(Status.STATUS_ROLLEDBACK, outerTransaction);
}
}
private DbFacade dbFacade;
private DbFacade dbFacade2;
private void createMsFacade() throws ClassNotFoundException {
this.getClass().getClassLoader().loadClass("com.microsoft.sqlserver.jdbc.SQLServerDriver");
SingleConnectionDataSource dataSource = new SingleConnectionDataSource(
"jdbc:sqlserver://10.35.113.39\\SQLEXPRESS:1433;databaseName=engine", "sa", "ENGINEadmin2009!", true);
dbFacade = new DbFacade();
dbFacade.setTemplate(new JdbcTemplate(dataSource));
SingleConnectionDataSource dataSource2 = new SingleConnectionDataSource(
"jdbc:sqlserver://10.35.113.39\\SQLEXPRESS:1433;databaseName=engine", "sa", "ENGINEadmin2009!", true);
dbFacade2 = new DbFacade();
dbFacade2.setTemplate(new JdbcTemplate(dataSource2));
// org.springframework.jdbc.datasource.DataSourceTransactionManager
}
/**
* Testing that opening a transaction within another transaction indeed opens a new transaction. Testing that the
* transactions status are as expected during the whole test.
*/
// @Test
public void runInNewDBTransaction() throws Exception {
createMsFacade();
TransactionSupport.executeInScope(TransactionScopeOption.RequiresNew, new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
try {
outerTransaction = tm.getTransaction();
} catch (SystemException e) {
e.printStackTrace();
}
// validateStatus(Status.STATUS_ACTIVE, outerTransaction);
// TAKE DB lOCK
bookmarks b = new bookmarks();
b.setbookmark_id(Guid.NewGuid());
b.setbookmark_name("MyBookmark");
b.setbookmark_value("test");
dbFacade.getBookmarkDAO().save(b);
// open a new transaction
TransactionSupport.executeInNewTransaction(new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
try {
innerTransaction = tm.getTransaction();
} catch (SystemException e) {
e.printStackTrace();
}
// TAKE DB lOCK
// open a new transaction
bookmarks c = dbFacade2.getBookmarkDAO().getByName("MyBookmark");
c.setbookmark_value("test2");
dbFacade2.getBookmarkDAO().update(c);
throw new RuntimeException();
// validate that the inner and outer
// transactions are not equals
// Assert.assertFalse(outerTransaction.equals(innerTransaction));
// validateStatus(Status.STATUS_ACTIVE,
// outerTransaction);
// validateStatus(Status.STATUS_ACTIVE,
// innerTransaction);
// return null;
}
});
// validateStatus(Status.STATUS_ACTIVE, outerTransaction);
// validateStatus(Status.STATUS_COMMITTED, innerTransaction);
return null;
}
});
// validateStatus(Status.STATUS_COMMITTED, outerTransaction);
}
private static void validateStatus(int expected, Transaction t) {
try {
Assert.assertEquals(expected, t.getStatus());
} catch (SystemException e) {
e.printStackTrace();
}
}
}