/*
* Copyright Terracotta, Inc.
*
* Licensed 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 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ehcache.integration.transactions.xa;
import bitronix.tm.BitronixTransactionManager;
import bitronix.tm.TransactionManagerServices;
import bitronix.tm.internal.TransactionStatusChangeListener;
import bitronix.tm.recovery.Recoverer;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.core.spi.time.TimeSource;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expirations;
import org.ehcache.impl.config.copy.DefaultCopierConfiguration;
import org.ehcache.impl.config.persistence.CacheManagerPersistenceConfiguration;
import org.ehcache.impl.internal.DefaultTimeSourceService;
import org.ehcache.impl.internal.TimeSourceConfiguration;
import org.ehcache.spi.copy.Copier;
import org.ehcache.transactions.xa.XACacheException;
import org.ehcache.transactions.xa.configuration.XAStoreConfiguration;
import org.ehcache.transactions.xa.txmgr.btm.BitronixTransactionManagerLookup;
import org.ehcache.transactions.xa.txmgr.provider.LookupTransactionManagerProviderConfiguration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Transaction;
import java.io.File;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* @author Ludovic Orban
*/
public class XACacheTest {
@Before
public void setUpBtmConfig() throws Exception {
TransactionManagerServices.getConfiguration()
.setLogPart1Filename(getStoragePath() + "/btm1.tlog")
.setLogPart2Filename(getStoragePath() + "/btm2.tlog")
.setServerId("XACacheTest")
.setGracefulShutdownInterval(0);
}
@After
public void tearDownBtm() throws Exception {
if (TransactionManagerServices.isTransactionManagerRunning()) {
TransactionManagerServices.getTransactionManager().shutdown();
}
}
@Test
public void testEndToEnd() throws Exception {
BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager();
CacheConfigurationBuilder<Long, String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB));
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class))
.withCache("txCache1", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache1")).build())
.withCache("txCache2", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache2")).build())
.withCache("nonTxCache", cacheConfigurationBuilder.build())
.build(true);
final Cache<Long, String> txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
final Cache<Long, String> txCache2 = cacheManager.getCache("txCache2", Long.class, String.class);
Cache<Long, String> nonTxCache = cacheManager.getCache("nonTxCache", Long.class, String.class);
nonTxCache.put(1L, "eins");
assertThat(nonTxCache.get(1L), equalTo("eins"));
try {
txCache1.put(1L, "one");
fail("expected XACacheException");
} catch (XACacheException e) {
// expected
}
transactionManager.begin();
{
txCache1.put(1L, "one");
}
transactionManager.commit();
transactionManager.begin();
{
txCache1.get(1L);
txCache2.get(1L);
}
transactionManager.commit();
transactionManager.begin();
{
String s = txCache1.get(1L);
assertThat(s, equalTo("one"));
txCache1.remove(1L);
Transaction suspended = transactionManager.suspend();
transactionManager.begin();
{
txCache2.put(1L, "uno");
String s2 = txCache1.get(1L);
assertThat(s2, equalTo("one"));
}
transactionManager.commit();
transactionManager.resume(suspended);
String s1 = txCache2.get(1L);
assertThat(s1, equalTo("uno"));
}
transactionManager.commit();
cacheManager.close();
transactionManager.shutdown();
}
@Test
public void testRecoveryWithInflightTx() throws Exception {
BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager();
CacheConfigurationBuilder<Long, String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB));
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("txCache1", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache1")).build())
.withCache("txCache2", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache2")).build())
.using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class))
.build(true);
final Cache<Long, String> txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
final Cache<Long, String> txCache2 = cacheManager.getCache("txCache2", Long.class, String.class);
transactionManager.begin();
{
txCache1.put(1L, "one");
txCache2.put(1L, "un");
}
transactionManager.commit();
transactionManager.begin();
{
txCache1.remove(1L);
txCache2.remove(1L);
}
transactionManager.getCurrentTransaction().addTransactionStatusChangeListener(new TransactionStatusChangeListener() {
@Override
public void statusChanged(int oldStatus, int newStatus) {
if (newStatus == Status.STATUS_PREPARED) {
Recoverer recoverer = TransactionManagerServices.getRecoverer();
recoverer.run();
assertThat(recoverer.getCommittedCount(), is(0));
assertThat(recoverer.getRolledbackCount(), is(0));
}
}
});
transactionManager.commit();
cacheManager.close();
transactionManager.shutdown();
}
@Test
public void testRecoveryAfterCrash() throws Exception {
BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager();
CacheConfigurationBuilder<Long, String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB)
.disk(20, MemoryUnit.MB, true));
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(new CacheManagerPersistenceConfiguration(new File(getStoragePath())))
.withCache("txCache1", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache1")).build())
.withCache("txCache2", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache2")).build())
.using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class))
.build(true);
Cache<Long, String> txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
Cache<Long, String> txCache2 = cacheManager.getCache("txCache2", Long.class, String.class);
transactionManager.begin();
{
txCache1.put(1L, "one");
txCache2.put(1L, "un");
}
transactionManager.getCurrentTransaction().addTransactionStatusChangeListener(new TransactionStatusChangeListener() {
@Override
public void statusChanged(int oldStatus, int newStatus) {
if (newStatus == Status.STATUS_COMMITTING) {
throw new AbortError();
}
}
});
try {
transactionManager.commit();
fail("expected AbortError");
} catch (AbortError e) {
// expected
}
cacheManager.close();
txCache1 = null;
txCache2 = null;
transactionManager.shutdown();
setUpBtmConfig();
transactionManager = TransactionManagerServices.getTransactionManager();
cacheManager.init();
txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
txCache2 = cacheManager.getCache("txCache2", Long.class, String.class);
transactionManager.begin();
{
assertThat(txCache1.get(1L), equalTo("one"));
assertThat(txCache2.get(1L), equalTo("un"));
}
transactionManager.commit();
cacheManager.close();
transactionManager.shutdown();
}
static class AbortError extends Error {
}
@Test
public void testExpiry() throws Exception {
TestTimeSource testTimeSource = new TestTimeSource();
BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager();
CacheConfigurationBuilder<Long, String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB))
.withExpiry(Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.SECONDS)));
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("txCache1", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache1")).build())
.withCache("txCache2", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache2")).build())
.using(new DefaultTimeSourceService(new TimeSourceConfiguration(testTimeSource)))
.using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class))
.build(true);
final Cache<Long, String> txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
final Cache<Long, String> txCache2 = cacheManager.getCache("txCache2", Long.class, String.class);
transactionManager.begin();
{
txCache1.put(1L, "one");
txCache2.put(1L, "un");
}
transactionManager.commit();
transactionManager.begin();
{
txCache1.put(1L, "eins");
txCache2.put(1L, "uno");
}
transactionManager.commit();
testTimeSource.advanceTime(2000);
transactionManager.begin();
{
assertThat(txCache1.get(1L), is(nullValue()));
assertThat(txCache2.get(1L), is(nullValue()));
}
transactionManager.commit();
cacheManager.close();
transactionManager.shutdown();
}
@Test
public void testCopiers() throws Exception {
TestTimeSource testTimeSource = new TestTimeSource();
BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager();
CacheConfigurationBuilder<Long, String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB)
.disk(20, MemoryUnit.MB, true));
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(new CacheManagerPersistenceConfiguration(new File(getStoragePath())))
.withCache("txCache1", cacheConfigurationBuilder
.add(new XAStoreConfiguration("txCache1"))
.add(new DefaultCopierConfiguration<Long>(LongCopier.class, DefaultCopierConfiguration.Type.KEY))
.add(new DefaultCopierConfiguration<String>(StringCopier.class, DefaultCopierConfiguration.Type.VALUE))
.build()
)
.withCache("txCache2", cacheConfigurationBuilder
.add(new XAStoreConfiguration("txCache2"))
.add(new DefaultCopierConfiguration<Long>(LongCopier.class, DefaultCopierConfiguration.Type.KEY))
.add(new DefaultCopierConfiguration<String>(StringCopier.class, DefaultCopierConfiguration.Type.VALUE))
.build())
.using(new DefaultTimeSourceService(new TimeSourceConfiguration(testTimeSource)))
.using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class))
.build(true);
final Cache<Long, String> txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
final Cache<Long, String> txCache2 = cacheManager.getCache("txCache2", Long.class, String.class);
transactionManager.begin();
{
txCache1.put(1L, "one");
txCache2.put(1L, "un");
}
transactionManager.commit();
transactionManager.begin();
{
txCache1.put(1L, "eins");
txCache2.put(1L, "uno");
}
transactionManager.commit();
transactionManager.begin();
{
assertThat(txCache1.get(1L), equalTo("eins"));
assertThat(txCache2.get(1L), equalTo("uno"));
}
transactionManager.commit();
cacheManager.close();
transactionManager.shutdown();
}
@Test
public void testTimeout() throws Exception {
TestTimeSource testTimeSource = new TestTimeSource();
BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager();
CacheConfigurationBuilder<Long, String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB));
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.with(new CacheManagerPersistenceConfiguration(new File(getStoragePath())))
.withCache("txCache1", cacheConfigurationBuilder
.add(new XAStoreConfiguration("txCache1"))
.add(new DefaultCopierConfiguration<Long>(LongCopier.class, DefaultCopierConfiguration.Type.KEY))
.add(new DefaultCopierConfiguration<String>(StringCopier.class, DefaultCopierConfiguration.Type.VALUE))
.build()
)
.withCache("txCache2", cacheConfigurationBuilder
.add(new XAStoreConfiguration("txCache2"))
.add(new DefaultCopierConfiguration<Long>(LongCopier.class, DefaultCopierConfiguration.Type.KEY))
.add(new DefaultCopierConfiguration<String>(StringCopier.class, DefaultCopierConfiguration.Type.VALUE))
.build())
.using(new DefaultTimeSourceService(new TimeSourceConfiguration(testTimeSource)))
.using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class))
.build(true);
final Cache<Long, String> txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
final Cache<Long, String> txCache2 = cacheManager.getCache("txCache2", Long.class, String.class);
transactionManager.setTransactionTimeout(1);
transactionManager.begin();
{
txCache1.put(1L, "one");
txCache2.put(1L, "un");
testTimeSource.advanceTime(2000);
}
try {
transactionManager.commit();
fail("Expected RollbackException");
} catch (RollbackException e) {
// expected
}
transactionManager.setTransactionTimeout(1);
transactionManager.begin();
{
txCache1.put(1L, "one");
txCache2.put(1L, "un");
testTimeSource.advanceTime(2000);
try {
txCache2.put(1L, "uno");
fail("expected XACacheException");
} catch (XACacheException e) {
// expected
}
}
try {
transactionManager.commit();
fail("Expected RollbackException");
} catch (RollbackException e) {
// expected
}
cacheManager.close();
transactionManager.shutdown();
}
@Test
public void testConcurrentTx() throws Exception {
TestTimeSource testTimeSource = new TestTimeSource();
BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager();
CacheConfigurationBuilder<Long, String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB))
.withExpiry(Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.SECONDS)));
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("txCache1", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache1")).build())
.withCache("txCache2", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache2")).build())
.using(new DefaultTimeSourceService(new TimeSourceConfiguration(testTimeSource)))
.using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class))
.build(true);
final Cache<Long, String> txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
final Cache<Long, String> txCache2 = cacheManager.getCache("txCache2", Long.class, String.class);
CyclicBarrier barrier = new CyclicBarrier(2);
TxThread tx1 = new TxThread(transactionManager, barrier) {
@Override
public void doTxWork() throws Exception {
Thread.currentThread().setName("tx1");
txCache1.put(0L, "zero");
txCache1.put(1L, "one");
txCache2.put(-1L, "-one");
txCache2.put(-2L, "-two");
}
};
TxThread tx2 = new TxThread(transactionManager, barrier) {
@Override
public void doTxWork() throws Exception {
Thread.currentThread().setName("tx2");
txCache1.put(1L, "un");
txCache1.put(2L, "deux");
txCache2.put(-1L, "-un");
txCache2.put(-3L, "-trois");
}
};
tx1.start();
tx2.start();
tx1.join();
tx2.join();
if (tx1.ex != null) {
System.err.println("tx1 error");
tx1.ex.printStackTrace();
}
if (tx2.ex != null) {
System.err.println("tx2 error");
tx2.ex.printStackTrace();
}
transactionManager.begin();
assertThat(txCache1.get(0L), equalTo("zero"));
assertThat(txCache1.get(1L), is(nullValue()));
assertThat(txCache1.get(2L), equalTo("deux"));
assertThat(txCache2.get(-1L), is(nullValue()));
assertThat(txCache2.get(-2L), equalTo("-two"));
assertThat(txCache2.get(-3L), equalTo("-trois"));
transactionManager.commit();
cacheManager.close();
transactionManager.shutdown();
}
@Test
public void testAtomicsWithoutLoaderWriter() throws Exception {
TestTimeSource testTimeSource = new TestTimeSource();
BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager();
CacheConfigurationBuilder<Long, String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB)
)
.withExpiry(Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.SECONDS)));
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("txCache1", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache1")).build())
.using(new DefaultTimeSourceService(new TimeSourceConfiguration(testTimeSource)))
.using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class))
.build(true);
final Cache<Long, String> txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
putIfAbsentAssertions(transactionManager, txCache1);
txCache1.clear();
remove2ArgsAssertions(transactionManager, txCache1);
txCache1.clear();
replace2ArgsAssertions(transactionManager, txCache1);
txCache1.clear();
replace3ArgsAssertions(transactionManager, txCache1);
txCache1.clear();
cacheManager.close();
transactionManager.shutdown();
}
@Test
public void testAtomicsWithLoaderWriter() throws Exception {
TestTimeSource testTimeSource = new TestTimeSource();
BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager();
SampleLoaderWriter<Long, String> loaderWriter = new SampleLoaderWriter<Long, String>();
CacheConfigurationBuilder<Long, String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB))
.withExpiry(Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.SECONDS)))
.withLoaderWriter(loaderWriter);
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("txCache1", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache1")).build())
.using(new DefaultTimeSourceService(new TimeSourceConfiguration(testTimeSource)))
.using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class))
.build(true);
final Cache<Long, String> txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
putIfAbsentAssertions(transactionManager, txCache1);
txCache1.clear();
loaderWriter.clear();
remove2ArgsAssertions(transactionManager, txCache1);
txCache1.clear();
loaderWriter.clear();
replace2ArgsAssertions(transactionManager, txCache1);
txCache1.clear();
loaderWriter.clear();
replace3ArgsAssertions(transactionManager, txCache1);
txCache1.clear();
loaderWriter.clear();
cacheManager.close();
transactionManager.shutdown();
}
private void putIfAbsentAssertions(BitronixTransactionManager transactionManager, Cache<Long, String> txCache1) throws Exception {
transactionManager.begin();
{
assertThat(txCache1.putIfAbsent(1L, "one"), is(nullValue()));
assertThat(txCache1.putIfAbsent(1L, "un"), equalTo("one"));
}
transactionManager.commit();
assertMapping(transactionManager, txCache1, 1L, "one");
transactionManager.begin();
{
assertThat(txCache1.putIfAbsent(1L, "eins"), equalTo("one"));
txCache1.remove(1L);
assertThat(txCache1.putIfAbsent(1L, "een"), is(nullValue()));
}
transactionManager.commit();
assertMapping(transactionManager, txCache1, 1L, "een");
}
private void remove2ArgsAssertions(BitronixTransactionManager transactionManager, Cache<Long, String> txCache1) throws Exception {
transactionManager.begin();
{
assertThat(txCache1.remove(1L, "one"), is(false));
assertThat(txCache1.putIfAbsent(1L, "un"), is(nullValue()));
assertThat(txCache1.remove(1L, "one"), is(false));
assertThat(txCache1.remove(1L, "un"), is(true));
assertThat(txCache1.remove(1L, "un"), is(false));
}
transactionManager.commit();
assertMapping(transactionManager, txCache1, 1L, null);
transactionManager.begin();
{
txCache1.put(1L, "one");
}
transactionManager.commit();
assertMapping(transactionManager, txCache1, 1L, "one");
transactionManager.begin();
{
assertThat(txCache1.remove(1L, "un"), is(false));
assertThat(txCache1.remove(1L, "one"), is(true));
assertThat(txCache1.remove(1L, "one"), is(false));
assertThat(txCache1.putIfAbsent(1L, "un"), is(nullValue()));
assertThat(txCache1.remove(1L, "one"), is(false));
assertThat(txCache1.remove(1L, "un"), is(true));
}
transactionManager.commit();
assertMapping(transactionManager, txCache1, 1L, null);
}
private void replace2ArgsAssertions(BitronixTransactionManager transactionManager, Cache<Long, String> txCache1) throws Exception {
transactionManager.begin();
{
assertThat(txCache1.replace(1L, "one"), is(nullValue()));
txCache1.put(1L, "un");
assertThat(txCache1.replace(1L, "eins"), equalTo("un"));
}
transactionManager.commit();
assertMapping(transactionManager, txCache1, 1L, "eins");
transactionManager.begin();
{
assertThat(txCache1.replace(1L, "een"), equalTo("eins"));
txCache1.put(1L, "un");
assertThat(txCache1.replace(1L, "een"), equalTo("un"));
txCache1.remove(1L);
assertThat(txCache1.replace(1L, "one"), is(nullValue()));
assertThat(txCache1.get(1L), is(nullValue()));
}
transactionManager.commit();
assertMapping(transactionManager, txCache1, 1L, null);
}
private void replace3ArgsAssertions(BitronixTransactionManager transactionManager, Cache<Long, String> txCache1) throws Exception {
transactionManager.begin();
{
assertThat(txCache1.replace(1L, "one", "un"), is(false));
txCache1.put(1L, "un");
assertThat(txCache1.replace(1L, "uno", "eins"), is(false));
assertThat(txCache1.replace(1L, "un", "eins"), is(true));
assertThat(txCache1.get(1L), equalTo("eins"));
}
transactionManager.commit();
assertMapping(transactionManager, txCache1, 1L, "eins");
transactionManager.begin();
{
assertThat(txCache1.replace(1L, "one", "un"), is(false));
assertThat(txCache1.replace(1L, "eins", "un"), is(true));
assertThat(txCache1.replace(1L, "uno", "een"), is(false));
assertThat(txCache1.get(1L), equalTo("un"));
}
transactionManager.commit();
assertMapping(transactionManager, txCache1, 1L, "un");
}
private void assertMapping(BitronixTransactionManager transactionManager, Cache<Long, String> cache, long key, String expected) throws Exception {
transactionManager.begin();
String value = cache.get(key);
if (expected == null) {
assertThat(value, is(nullValue()));
} else {
assertThat(value, equalTo(expected));
}
transactionManager.commit();
}
@Test
public void testIterate() throws Throwable {
TestTimeSource testTimeSource = new TestTimeSource();
final BitronixTransactionManager transactionManager = TransactionManagerServices.getTransactionManager();
CacheConfigurationBuilder<Long, String> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
newResourcePoolsBuilder()
.heap(10, EntryUnit.ENTRIES)
.offheap(10, MemoryUnit.MB))
.withExpiry(Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.SECONDS)));
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.withCache("txCache1", cacheConfigurationBuilder.add(new XAStoreConfiguration("txCache1")).build())
.using(new DefaultTimeSourceService(new TimeSourceConfiguration(testTimeSource)))
.using(new LookupTransactionManagerProviderConfiguration(BitronixTransactionManagerLookup.class))
.build(true);
final Cache<Long, String> txCache1 = cacheManager.getCache("txCache1", Long.class, String.class);
transactionManager.begin();
{
txCache1.put(1L, "one");
txCache1.put(2L, "two");
Map<Long, String> result = new HashMap<Long, String>();
Iterator<Cache.Entry<Long, String>> iterator = txCache1.iterator();
while (iterator.hasNext()) {
Cache.Entry<Long, String> next = iterator.next();
result.put(next.getKey(), next.getValue());
iterator.remove();
}
assertThat(result.size(), equalTo(2));
assertThat(result.keySet(), containsInAnyOrder(1L, 2L));
}
transactionManager.commit();
transactionManager.begin();
{
Map<Long, String> result = new HashMap<Long, String>();
for (Cache.Entry<Long, String> next : txCache1) {
result.put(next.getKey(), next.getValue());
}
assertThat(result.size(), equalTo(0));
}
transactionManager.commit();
transactionManager.begin();
{
final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
txCache1.put(1L, "one");
txCache1.put(2L, "two");
Thread t = new Thread() {
@Override
public void run() {
try {
transactionManager.begin();
Map<Long, String> result = new HashMap<Long, String>();
for (Cache.Entry<Long, String> next : txCache1) {
result.put(next.getKey(), next.getValue());
}
assertThat(result.size(), equalTo(0));
transactionManager.commit();
} catch (Throwable t) {
throwableRef.set(t);
}
}
};
t.start();
t.join();
if (throwableRef.get() != null) {
throw throwableRef.get();
}
}
transactionManager.commit();
transactionManager.begin();
{
Map<Long, String> result = new HashMap<Long, String>();
Iterator<Cache.Entry<Long, String>> iterator = txCache1.iterator();
while (iterator.hasNext()) {
Cache.Entry<Long, String> next = iterator.next();
iterator.remove();
result.put(next.getKey(), next.getValue());
}
assertThat(result.size(), equalTo(2));
assertThat(result.keySet(), containsInAnyOrder(1L, 2L));
}
transactionManager.commit();
transactionManager.begin();
{
Map<Long, String> result = new HashMap<Long, String>();
for (Cache.Entry<Long, String> next : txCache1) {
result.put(next.getKey(), next.getValue());
}
assertThat(result.size(), equalTo(0));
}
transactionManager.commit();
cacheManager.close();
transactionManager.shutdown();
}
static abstract class TxThread extends Thread {
protected final BitronixTransactionManager transactionManager;
protected final CyclicBarrier barrier;
protected volatile Throwable ex;
public TxThread(BitronixTransactionManager transactionManager, CyclicBarrier barrier) {
this.transactionManager = transactionManager;
this.barrier = barrier;
}
@Override
public final void run() {
try {
transactionManager.begin();
transactionManager.getCurrentTransaction().addTransactionStatusChangeListener(new TransactionStatusChangeListener() {
@Override
public void statusChanged(int oldStatus, int newStatus) {
if (oldStatus == Status.STATUS_PREPARED) {
try {
barrier.await(5L, TimeUnit.SECONDS);
} catch (Exception e) {
throw new AssertionError();
}
}
}
});
doTxWork();
transactionManager.commit();
} catch (Throwable t) {
this.ex = t;
}
}
public abstract void doTxWork() throws Exception;
}
public static class LongCopier implements Copier<Long> {
@Override
public Long copyForRead(Long obj) {
return obj;
}
@Override
public Long copyForWrite(Long obj) {
return obj;
}
}
public static class StringCopier implements Copier<String> {
@Override
public String copyForRead(String obj) {
return obj;
}
@Override
public String copyForWrite(String obj) {
return obj;
}
}
static class TestTimeSource implements TimeSource {
private long time = 0;
public TestTimeSource() {
}
public TestTimeSource(final long time) {
this.time = time;
}
@Override
public long getTimeMillis() {
return time;
}
public void advanceTime(long delta) {
this.time += delta;
}
}
private String getStoragePath() throws URISyntaxException {
return getClass().getClassLoader().getResource(".").toURI().getPath();
}
}