package org.infinispan.functional;
import static org.infinispan.test.Exceptions.expectExceptionNonStrict;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;
import java.io.Serializable;
import java.util.Collections;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.CompletionException;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.IntStream;
import javax.transaction.Status;
import javax.transaction.TransactionManager;
import org.infinispan.commons.CacheException;
import org.infinispan.commons.api.functional.EntryView;
import org.infinispan.commons.api.functional.FunctionalMap;
import org.infinispan.commons.marshall.MarshallableFunctions;
import org.infinispan.functional.impl.ReadOnlyMapImpl;
import org.infinispan.test.TestingUtil;
import org.infinispan.transaction.LockingMode;
import org.infinispan.util.concurrent.IsolationLevel;
import org.testng.Assert;
import org.testng.annotations.Test;
@Test(groups = "functional")
public class FunctionalTxInMemoryTest extends FunctionalInMemoryTest {
private static final int NUM_KEYS = 2;
private static final Integer[] INT_KEYS = IntStream.range(0, NUM_KEYS).mapToObj(Integer::valueOf).toArray(Integer[]::new);
TransactionManager tm;
@Override
public Object[] factory() {
return new Object[]{
new FunctionalTxInMemoryTest().transactional(true).lockingMode(LockingMode.OPTIMISTIC).isolationLevel(IsolationLevel.READ_COMMITTED),
new FunctionalTxInMemoryTest().transactional(true).lockingMode(LockingMode.PESSIMISTIC).isolationLevel(IsolationLevel.READ_COMMITTED),
new FunctionalTxInMemoryTest().transactional(true).lockingMode(LockingMode.PESSIMISTIC).isolationLevel(IsolationLevel.REPEATABLE_READ),
};
}
@Override
protected void createCacheManagers() throws Throwable {
super.createCacheManagers();
tm = TestingUtil.extractComponentRegistry(cache(0)).getComponent(TransactionManager.class);
}
@Test(dataProvider = "owningModeAndReadMethod")
public void testReadLoads(boolean isOwner, ReadMethod method) throws Exception {
Object[] keys = getKeys(isOwner, NUM_KEYS);
for (Object key : keys) {
cache(0, DIST).put(key, key);
}
tm.begin();
for (Object key : keys) {
assertEquals(method.action.eval(key, ro, (Serializable & Function<EntryView.ReadEntryView<Object, String>, String>) (e -> {
assertTrue(e.find().isPresent());
assertEquals(e.get(), e.key());
return "OK";
})), "OK");
}
tm.commit();
}
@Test(dataProvider = "readMethods")
public void testReadLoadsLocal(ReadMethod method) throws Exception {
Integer[] keys = INT_KEYS;
for (Integer key : keys) {
cache(0).put(key, key);
}
tm.begin();
for (Integer key : keys) {
assertEquals(method.action.eval(key, lro, (Serializable & Function<EntryView.ReadEntryView<Integer, String>, String>) (e -> {
assertTrue(e.find().isPresent());
assertEquals(e.get(), e.key());
return "OK";
})), "OK");
}
tm.commit();
}
@Test(dataProvider = "owningModeAndReadMethod")
public void testReadsAfterMods(boolean isOwner, ReadMethod method) throws Exception {
Object KEY = getKey(isOwner);
cache(0, DIST).put(KEY, "a");
tm.begin();
assertEquals("a", rw.eval(KEY, append("b")).join());
assertEquals("ab", rw.evalMany(Collections.singleton(KEY), append("c")).findAny().get());
assertEquals(null, rw.eval("otherKey", append("d")).join());
assertEquals("abc", method.action.eval(KEY, ro, MarshallableFunctions.returnReadOnlyFindOrNull()));
tm.commit();
}
@Test(dataProvider = "owningModeAndReadWrites")
public void testReadWriteAfterMods(boolean isOwner, WriteMethod method) throws Exception {
Object KEY = getKey(isOwner);
cache(0, DIST).put(KEY, "a");
tm.begin();
assertEquals("a", rw.eval(KEY, append("b")).join());
assertEquals("ab", rw.evalMany(Collections.singleton(KEY), append("c")).findAny().get());
assertEquals(null, rw.eval("otherKey", append("d")).join());
assertEquals("abc", method.action.eval(KEY, wo, rw,
MarshallableFunctions.returnReadOnlyFindOrNull(),
(BiConsumer<EntryView.WriteEntryView<String>, String> & Serializable) (e, prev) -> {}, getClass()));
tm.commit();
}
public void testNonFunctionalReadsAfterMods() throws Exception {
Object KEY = getKey(false);
cache(0, DIST).put(KEY, "a");
tm.begin();
assertEquals("a", rw.eval(KEY, append("b")).join());
assertEquals("ab", cache.get(KEY));
// try second time to make sure the modification is not applied twice
assertEquals("ab", cache.get(KEY));
tm.commit();
tm.begin();
assertEquals("ab", rw.evalMany(Collections.singleton(KEY), append("c")).findAny().get());
assertEquals("abc", cache.put(KEY, "abcd"));
tm.commit();
tm.begin();
assertEquals("abcd", cache.get(KEY));
tm.commit();
tm.begin();
wo.eval(KEY, "x", MarshallableFunctions.setValueConsumer()).join();
assertEquals("x", cache.putIfAbsent(KEY, "otherValue"));
tm.commit();
tm.begin();
wo.eval(KEY, MarshallableFunctions.removeConsumer()).join();
assertNull(cache.putIfAbsent(KEY, "y"));
assertEquals("y", ro.eval(KEY, MarshallableFunctions.returnReadOnlyFindOrNull()).join());
tm.commit();
tm.begin();
assertEquals("y", rw.eval(KEY, "z", MarshallableFunctions.setValueReturnPrevOrNull()).join());
assertTrue(cache.replace(KEY, "z", "a"));
tm.commit();
tm.begin();
assertEquals("a", rw.eval(KEY, append("b")).join());
assertEquals("ab", cache.getAll(Collections.singleton(KEY)).get(KEY));
tm.commit();
}
@Test(dataProvider = "owningModeAndReadWrites")
public void testWriteModsInTxContext(boolean isOwner, WriteMethod method) throws Exception {
Object KEY = getKey(isOwner);
cache(0, DIST).put(KEY, "a");
tm.begin();
assertEquals("a", cache(0, DIST).put(KEY, "b"));
// read-write operation should execute locally instead
assertEquals("b", method.action.eval(KEY, null, rw,
(Function<EntryView.ReadEntryView<Object, String>, String> & Serializable) EntryView.ReadEntryView::get,
(BiConsumer<EntryView.WriteEntryView<String>, String> & Serializable) (e, prev) -> e.set(prev + "c"), getClass()));
// make sure that the operation was executed in context
assertEquals("bc", ro.eval(KEY, MarshallableFunctions.returnReadOnlyFindOrNull()).join());
tm.commit();
}
private static Function<EntryView.ReadWriteEntryView<Object, String>, String> append(String str) {
return (Serializable & Function<EntryView.ReadWriteEntryView<Object, String>, String>) ev -> {
Optional<String> prev = ev.find();
if (prev.isPresent()) {
ev.set(prev.get() + str);
return prev.get();
} else {
ev.set(str);
return null;
}
};
}
@Test(dataProvider = "owningModeAndReadMethod")
public void testReadOnMissingValues(boolean isOwner, ReadMethod method) throws Exception {
testReadOnMissingValue(getKeys(isOwner, NUM_KEYS), ro, method);
}
@Test(dataProvider = "readMethods")
public void testReadOnMissingValuesLocal(ReadMethod method) throws Exception {
testReadOnMissingValue(INT_KEYS, ReadOnlyMapImpl.create(fmapL1), method);
}
private <K> void testReadOnMissingValue(K[] keys, FunctionalMap.ReadOnlyMap<K, String> ro, ReadMethod method) throws Exception {
tm.begin();
for (K key : keys) {
Assert.assertEquals(ro.eval(key,
(Function<EntryView.ReadEntryView<K, String>, Boolean> & Serializable) (view -> view.find().isPresent())).join(), Boolean.FALSE);
}
tm.commit();
tm.begin();
for (K key : keys) {
expectExceptionNonStrict(CompletionException.class, CacheException.class, NoSuchElementException.class, () ->
method.action.eval(key, ro, (Function<EntryView.ReadEntryView<K, String>, Object> & Serializable) EntryView.ReadEntryView::get));
// The first exception should cause the whole transaction to fail
assertEquals(Status.STATUS_MARKED_ROLLBACK, tm.getStatus());
tm.rollback();
break;
}
if (tm.getStatus() == Status.STATUS_ACTIVE) {
tm.commit();
}
}
private Object[] getKeys(boolean isOwner, int num) {
return IntStream.iterate(0, i -> i + 1)
.mapToObj(i -> "key" + i)
.filter(k -> cache(0, DIST).getAdvancedCache().getDistributionManager().getLocality(k).isLocal() == isOwner)
.limit(num).toArray(Object[]::new);
}
}