package org.infinispan.persistence.jdbc.stores;
import static javax.transaction.Status.STATUS_ROLLEDBACK;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import static org.testng.AssertJUnit.assertEquals;
import javax.transaction.RollbackException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.context.Flag;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.persistence.jdbc.configuration.JdbcStringBasedStoreConfigurationBuilder;
import org.infinispan.persistence.jdbc.connectionfactory.ConnectionFactory;
import org.infinispan.persistence.jdbc.stringbased.JdbcStringBasedStore;
import org.infinispan.persistence.jdbc.table.management.TableName;
import org.infinispan.persistence.manager.PersistenceManager;
import org.infinispan.persistence.manager.PersistenceManagerImpl;
import org.infinispan.persistence.spi.PersistenceException;
import org.infinispan.test.AbstractInfinispanTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.test.fwk.UnitTestDatabaseManager;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
* Test to check that a transactional store commits/rollsback stored values as expected. Also ensures that a failed
* prepare on the store results in the entire cache Tx rollingback.
*
* @author Ryan Emerson
*/
@Test(groups = "functional", testName = "persistence.jdbc.stores.TxStoreTest")
public class TxStoreTest extends AbstractInfinispanTest {
private static final String KEY1 = "Key 1";
private static final String KEY2 = "Key 2";
private static final String VAL1 = "Val 1";
private static final String VAL2 = "Val 2";
private EmbeddedCacheManager cacheManager;
private Cache<String, String> cache;
private JdbcStringBasedStore store;
@BeforeMethod
public void beforeClass() {
ConfigurationBuilder cc = TestCacheManagerFactory.getDefaultCacheConfiguration(true);
JdbcStringBasedStoreConfigurationBuilder storeBuilder = cc
.persistence()
.addStore(JdbcStringBasedStoreConfigurationBuilder.class)
.shared(true)
.transactional(true);
UnitTestDatabaseManager.configureUniqueConnectionFactory(storeBuilder);
UnitTestDatabaseManager.setDialect(storeBuilder);
UnitTestDatabaseManager.buildTableManipulation(storeBuilder.table());
cacheManager = TestCacheManagerFactory.createCacheManager(new GlobalConfigurationBuilder().defaultCacheName("Test"), cc);
cache = cacheManager.getCache("Test");
store = TestingUtil.getFirstTxWriter(cache);
}
@AfterMethod
public void tearDown() throws PersistenceException {
store.clear();
assertRowCount(0);
TestingUtil.killCacheManagers(cacheManager);
}
@Test
public void testTxCommit() throws Exception {
cache.put(KEY1, VAL1);
TransactionManager tm = TestingUtil.getTransactionManager(cache);
tm.begin();
cache.put(KEY2, VAL1);
String oldValue = cache.put(KEY1, VAL2);
assertEquals(oldValue, VAL1);
tm.commit();
String cacheVal = cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD).get(KEY1);
assertEquals(cacheVal, VAL2);
// Ensure the values committed in the Tx were actually written to the store as well as to the cache
assertRowCount(2);
assertEquals(store.load(KEY1).getValue(), VAL2);
assertEquals(store.load(KEY2).getValue(), VAL1);
}
@Test
public void testTxRollback() throws Exception {
TransactionManager tm = TestingUtil.getTransactionManager(cache);
tm.begin();
Transaction tx = tm.getTransaction();
cache.put(KEY1, VAL1);
cache.put(KEY2, VAL2);
tm.rollback();
assert tx.getStatus() == STATUS_ROLLEDBACK;
assertRowCount(0);
}
@Test
public void testTxRollbackOnStoreException() throws Exception {
PersistenceManagerImpl pm = (PersistenceManagerImpl) TestingUtil.extractComponent(cache, PersistenceManager.class);
pm = spy(pm);
doThrow(new PersistenceException()).when(pm).prepareAllTxStores(any(), any(), any());
TestingUtil.replaceComponent(cache, PersistenceManager.class, pm, true);
TransactionManager tm = TestingUtil.getTransactionManager(cache);
Transaction tx = null;
try {
tm.begin();
tx = tm.getTransaction();
cache.put(KEY1, VAL1);
cache.put(KEY2, VAL2); // Throws PersistenceException, forcing the Tx to rollback
tm.commit();
} catch (RollbackException e) {
// Ensure PersistenceException was the cause of the rollback
boolean persistenceEx = false;
Throwable[] suppressed = e.getSuppressed();
for (Throwable ex : suppressed) {
persistenceEx = ex.getCause() instanceof PersistenceException;
if (persistenceEx) break;
}
assert persistenceEx;
}
assert tx != null && tx.getStatus() == STATUS_ROLLEDBACK;
assertRowCount(0);
}
private void assertRowCount(int rowCount) {
ConnectionFactory connectionFactory = store.getConnectionFactory();
TableName tableName = store.getTableManager().getTableName();
int value = UnitTestDatabaseManager.rowCount(connectionFactory, tableName);
assert value == rowCount : "Expected " + rowCount + " rows, actual value is " + value;
}
}