package org.infinispan.functional; import static org.infinispan.commons.marshall.MarshallableFunctions.removeConsumer; import static org.infinispan.commons.marshall.MarshallableFunctions.removeReturnPrevOrNull; import static org.infinispan.commons.marshall.MarshallableFunctions.setValueConsumer; import static org.infinispan.commons.marshall.MarshallableFunctions.setValueReturnPrevOrNull; import static org.infinispan.functional.FunctionalTestUtils.rw; import static org.infinispan.functional.FunctionalTestUtils.supplyIntKey; import static org.infinispan.functional.FunctionalTestUtils.wo; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.infinispan.commons.api.functional.EntryView.ReadEntryView; import org.infinispan.commons.api.functional.FunctionalMap.ReadWriteMap; import org.infinispan.commons.api.functional.FunctionalMap.WriteOnlyMap; import org.infinispan.commons.api.functional.Listeners.ReadWriteListeners.ReadWriteListener; import org.infinispan.commons.api.functional.Listeners.WriteListeners.WriteListener; import org.infinispan.functional.TestFunctionalInterfaces.SetConstantOnReadWrite; import org.infinispan.functional.TestFunctionalInterfaces.SetConstantOnWriteOnly; import org.testng.annotations.Test; /** * Test suite for verifying functional map listener functionality. */ @Test(groups = "functional", testName = "functional.FunctionalListenersTest") public class FunctionalListenersTest extends AbstractFunctionalTest { public void testLocalLambdaReadWriteListeners() throws Exception { doLambdaReadWriteListeners(supplyIntKey(), wo(fmapL1), rw(fmapL2), true); } public void testReplLambdaReadWriteListeners() throws Exception { doLambdaReadWriteListeners(supplyKeyForCache(0, REPL), wo(fmapR1), rw(fmapR2), true); doLambdaReadWriteListeners(supplyKeyForCache(1, REPL), wo(fmapR1), rw(fmapR2), true); } @Test public void testDistLambdaReadWriteListeners() throws Exception { doLambdaReadWriteListeners(supplyKeyForCache(0, DIST), wo(fmapD1), rw(fmapD2), false); doLambdaReadWriteListeners(supplyKeyForCache(1, DIST), wo(fmapD1), rw(fmapD2), true); } private <K> void doLambdaReadWriteListeners(Supplier<K> keySupplier, WriteOnlyMap<K, String> woMap, ReadWriteMap<K, String> rwMap, boolean isOwner) throws Exception { K key1 = keySupplier.get(); List<CountDownLatch> latches = new ArrayList<>(); latches.addAll(Arrays.asList(new CountDownLatch(1), new CountDownLatch(1), new CountDownLatch(1))); AutoCloseable onCreate = rwMap.listeners().onCreate(created -> { assertEquals("created", created.get()); latches.get(0).countDown(); }); AutoCloseable onModify = rwMap.listeners().onModify((before, after) -> { assertEquals("created", before.get()); assertEquals("modified", after.get()); latches.get(1).countDown(); }); AutoCloseable onRemove = rwMap.listeners().onRemove(removed -> { assertEquals("modified", removed.get()); latches.get(2).countDown(); }); awaitNoEvent(woMap.eval(key1, "created", setValueConsumer()), latches.get(0)); awaitNoEvent(woMap.eval(key1, "modified", setValueConsumer()), latches.get(1)); awaitNoEvent(woMap.eval(key1, removeConsumer()), latches.get(2)); K key2 = keySupplier.get(); awaitEventIfOwner(isOwner, rwMap.eval(key2, "created", setValueReturnPrevOrNull()), latches.get(0)); awaitEventIfOwner(isOwner, rwMap.eval(key2, "modified", setValueReturnPrevOrNull()), latches.get(1)); awaitEventIfOwner(isOwner, rwMap.eval(key2, removeReturnPrevOrNull()), latches.get(2)); launderLatches(latches, 3); K key3 = keySupplier.get(); awaitEventIfOwner(isOwner, rwMap.eval(key3, new SetConstantOnReadWrite<>("created")), latches.get(0)); awaitEventIfOwner(isOwner, rwMap.eval(key3, new SetConstantOnReadWrite<>("modified")), latches.get(1)); awaitEventIfOwner(isOwner, rwMap.eval(key3, removeReturnPrevOrNull()), latches.get(2)); // TODO: Test other evals.... onCreate.close(); onModify.close(); onRemove.close(); launderLatches(latches, 3); K key4 = keySupplier.get(); awaitNoEvent(woMap.eval(key4, "tres", setValueConsumer()), latches.get(0)); awaitNoEvent(woMap.eval(key4, "three", setValueConsumer()), latches.get(1)); awaitNoEvent(woMap.eval(key4, removeConsumer()), latches.get(2)); K key5 = keySupplier.get(); awaitNoEvent(rwMap.eval(key5, "cuatro", setValueReturnPrevOrNull()), latches.get(0)); awaitNoEvent(rwMap.eval(key5, "four", setValueReturnPrevOrNull()), latches.get(1)); awaitNoEvent(rwMap.eval(key5, removeReturnPrevOrNull()), latches.get(2)); } public void testLocalLambdaWriteListeners() throws Exception { doLambdaWriteListeners(supplyIntKey(), wo(fmapL1), true); } @Test public void testReplLambdaWriteListeners() throws Exception { doLambdaWriteListeners(supplyKeyForCache(0, REPL), wo(fmapR1), true); doLambdaWriteListeners(supplyKeyForCache(1, REPL), wo(fmapR2), true); } @Test public void testDistLambdaWriteListeners() throws Exception { doLambdaWriteListeners(supplyKeyForCache(0, DIST), wo(fmapD1), true); doLambdaWriteListeners(supplyKeyForCache(0, DIST), wo(fmapD2), false); } private <K> void doLambdaWriteListeners(Supplier<K> keySupplier, WriteOnlyMap<K, String> wo, boolean isOwner) throws Exception { K key1 = keySupplier.get(), key2 = keySupplier.get(); List<CountDownLatch> latches = launderLatches(new ArrayList<>(), 1); AutoCloseable onWrite = wo.listeners().onWrite(read -> { assertEquals("write", read.get()); latches.get(0).countDown(); }); awaitEventIfOwnerAndLaunderLatch(isOwner, wo.eval(key1, new SetConstantOnWriteOnly("write")), latches); awaitEventIfOwnerAndLaunderLatch(isOwner, wo.eval(key1, new SetConstantOnWriteOnly("write")), latches); onWrite.close(); awaitNoEvent(wo.eval(key2, new SetConstantOnWriteOnly("write")), latches.get(0)); awaitNoEvent(wo.eval(key2, new SetConstantOnWriteOnly("write")), latches.get(0)); // TODO: Test other eval methods :) AutoCloseable onWriteRemove = wo.listeners().onWrite(read -> { assertFalse(read.find().isPresent()); latches.get(0).countDown(); }); awaitEventIfOwnerAndLaunderLatch(isOwner, wo.eval(key1, removeConsumer()), latches); onWriteRemove.close(); awaitNoEvent(wo.eval(key2, removeConsumer()), latches.get(0)); } // @Test // public void testObjectReadWriteListeners() throws Exception { // TrackingReadWriteListener<Integer, String> listener = new TrackingReadWriteListener<>(); // AutoCloseable closeable = readWriteMap.listeners().add(listener); // // awaitNoEvent(writeOnlyMap.eval(1, writeView -> writeView.set("created")), listener.latch); // awaitNoEvent(writeOnlyMap.eval(1, writeView -> writeView.set("modified")), listener.latch); // awaitNoEvent(writeOnlyMap.eval(1, WriteEntryView::remove), listener.latch); // // awaitEvent(readWriteMap.eval(2, rwView -> rwView.set("created")), listener.latch); // awaitEvent(readWriteMap.eval(2, rwView -> rwView.set("modified")), listener.latch); // awaitEvent(readWriteMap.eval(2, ReadWriteEntryView::remove), listener.latch); // // closeable.close(); // awaitNoEvent(writeOnlyMap.eval(3, writeView -> writeView.set("tres")), listener.latch); // awaitNoEvent(writeOnlyMap.eval(3, writeView -> writeView.set("three")), listener.latch); // awaitNoEvent(writeOnlyMap.eval(3, WriteEntryView::remove), listener.latch); // // awaitNoEvent(readWriteMap.eval(4, rwView -> rwView.set("cuatro")), listener.latch); // awaitNoEvent(readWriteMap.eval(4, rwView -> rwView.set("four")), listener.latch); // awaitNoEvent(readWriteMap.eval(4, ReadWriteEntryView::remove), listener.latch); // } // // @Test // public void testObjectWriteListeners() throws Exception { // TrackingWriteListener<Integer, String> writeListener = new TrackingWriteListener<>(); // AutoCloseable writeListenerCloseable = writeOnlyMap.listeners().add(writeListener); // // awaitEvent(writeOnlyMap.eval(1, writeView -> writeView.set("write")), writeListener.latch); // awaitEvent(writeOnlyMap.eval(1, writeView -> writeView.set("write")), writeListener.latch); // writeListenerCloseable.close(); // awaitNoEvent(writeOnlyMap.eval(2, writeView -> writeView.set("write")), writeListener.latch); // awaitNoEvent(writeOnlyMap.eval(2, writeView -> writeView.set("write")), writeListener.latch); // // TrackingRemoveOnWriteListener<Integer, String> writeRemoveListener = new TrackingRemoveOnWriteListener<>(); // AutoCloseable writeRemoveListenerCloseable = writeOnlyMap.listeners().add(writeRemoveListener); // // awaitEvent(writeOnlyMap.eval(1, WriteEntryView::remove), writeRemoveListener.latch); // writeRemoveListenerCloseable.close(); // awaitNoEvent(writeOnlyMap.eval(2, WriteEntryView::remove), writeRemoveListener.latch); // } private static List<CountDownLatch> launderLatches(List<CountDownLatch> latches, int numLatches) { latches.clear(); for (int i = 0; i < numLatches; i++) latches.add(new CountDownLatch(1)); return latches; } public static <T> T awaitEvent(CompletableFuture<T> cf, CountDownLatch eventLatch) { try { T t = cf.get(); assertTrue(eventLatch.await(500, TimeUnit.MILLISECONDS)); return t; } catch (InterruptedException | ExecutionException e) { throw new Error(e); } } public static <T> T awaitNoEvent(CompletableFuture<T> cf, CountDownLatch eventLatch) { try { T t = cf.get(); assertFalse(eventLatch.await(50, TimeUnit.MILLISECONDS)); return t; } catch (InterruptedException | ExecutionException e) { throw new Error(e); } } public static <T> T awaitEventIfOwner(boolean isOwner, CompletableFuture<T> cf, CountDownLatch eventLatch) { return isOwner ? awaitEvent(cf, eventLatch) : awaitNoEvent(cf, eventLatch); } public static <T> T awaitEventAndLaunderLatch(CompletableFuture<T> cf, List<CountDownLatch> latches) { T t = awaitEvent(cf, latches.get(0)); launderLatches(latches, 1); return t; } public static <T> T awaitEventIfOwnerAndLaunderLatch(boolean isOwner, CompletableFuture<T> cf, List<CountDownLatch> latches) { if (isOwner) { T t = awaitEvent(cf, latches.get(0)); launderLatches(latches, 1); return t; } return awaitNoEvent(cf, latches.get(0)); } private static final class TrackingReadWriteListener<K, V> implements ReadWriteListener<K, V> { CountDownLatch latch = new CountDownLatch(1); @Override public void onCreate(ReadEntryView<K, V> created) { assertEquals("created", created.get()); latchCountAndLaunder(); } @Override public void onModify(ReadEntryView<K, V> before, ReadEntryView<K, V> after) { assertEquals("created", before.get()); assertEquals("modified", after.get()); latchCountAndLaunder(); } @Override public void onRemove(ReadEntryView<K, V> removed) { assertEquals("modified", removed.get()); latchCountAndLaunder(); } private void latchCountAndLaunder() { latch.countDown(); latch = new CountDownLatch(1); } } public static final class TrackingWriteListener<K, V> implements WriteListener<K, V> { CountDownLatch latch = new CountDownLatch(1); @Override public void onWrite(ReadEntryView<K, V> write) { assertEquals("write", write.get()); latchCountAndLaunder(); } private void latchCountAndLaunder() { latch.countDown(); latch = new CountDownLatch(1); } } public static final class TrackingRemoveOnWriteListener<K, V> implements WriteListener<K, V> { CountDownLatch latch = new CountDownLatch(1); @Override public void onWrite(ReadEntryView<K, V> write) { assertFalse(write.find().isPresent()); latchCountAndLaunder(); } private void latchCountAndLaunder() { latch.countDown(); latch = new CountDownLatch(1); } } }