diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/UniReturnTypeTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/UniReturnTypeTest.java new file mode 100644 index 00000000000000..2032442f72b791 --- /dev/null +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/UniReturnTypeTest.java @@ -0,0 +1,188 @@ +package io.quarkus.cache.test.runtime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.cache.CacheInvalidate; +import io.quarkus.cache.CacheInvalidateAll; +import io.quarkus.cache.CacheResult; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.mutiny.Uni; + +/** + * Tests the caching annotations on methods returning {@link Uni}. + */ +public class UniReturnTypeTest { + + private static final String CACHE_NAME_1 = "test-cache-1"; + private static final String CACHE_NAME_2 = "test-cache-2"; + private static final String KEY_1 = "key-1"; + private static final String KEY_2 = "key-2"; + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().withApplicationRoot((jar) -> jar.addClass(CachedService.class)); + + @Inject + CachedService cachedService; + + @Test + void testCacheResult() { + // STEP 1 + // Action: a method annotated with @CacheResult and returning a Uni is called. + // Expected effect: the method is not invoked, as Uni is lazy. + // Verified by: invocations counter. + Uni uni1 = cachedService.cacheResult1(KEY_1); + assertEquals(0, cachedService.getCacheResultInvocations()); + + // STEP 2 + // Action: same call as STEP 1. + // Expected effect: same as STEP 1 with a different Uni instance returned. + // Verified by: invocations counter and different objects references between STEPS 1 AND 2 results. + Uni uni2 = cachedService.cacheResult1(KEY_1); + assertEquals(0, cachedService.getCacheResultInvocations()); + assertNotSame(uni1, uni2); + + // STEP 3 + // Action: the Uni returned in STEP 1 is subscribed to and we wait for an item event to be fired. + // Expected effect: the method from STEP 1 is invoked and its result is cached. + // Verified by: invocations counter and STEP 4. + String emittedItem1 = uni1.await().indefinitely(); + assertEquals(1, cachedService.getCacheResultInvocations()); + + // STEP 4 + // Action: the Uni returned in STEP 2 is subscribed to and we wait for an item event to be fired. + // Expected effect: the method from STEP 2 is not invoked and the value cached in STEP 3 is returned. + // Verified by: invocations counter and same object reference between STEPS 3 and 4 emitted items. + String emittedItem2 = uni2.await().indefinitely(); + assertEquals(1, cachedService.getCacheResultInvocations()); + assertSame(emittedItem1, emittedItem2); + + // STEP 5 + // Action: same call as STEP 2 with a different key and an immediate subscription. + // Expected effect: the method is invoked and a new item is emitted (also cached). + // Verified by: invocations counter and different objects references between STEPS 2 and 3 emitted items. + String emittedItem3 = cachedService.cacheResult1("another-key").await().indefinitely(); + assertEquals(2, cachedService.getCacheResultInvocations()); + assertNotSame(emittedItem2, emittedItem3); + } + + @Test + void testCacheInvalidate() { + // First, let's put some data into the caches. + String value1 = cachedService.cacheResult1(KEY_1).await().indefinitely(); + Object value2 = cachedService.cacheResult2(KEY_1).await().indefinitely(); + Object value3 = cachedService.cacheResult2(KEY_2).await().indefinitely(); + + // We will invalidate some data (only KEY_1) in all caches later. + Uni invalidateUni = cachedService.cacheInvalidate(KEY_1); + // For now, the method that will invalidate the data should not be invoked, as Uni is lazy. + assertEquals(0, cachedService.getCacheInvalidateInvocations()); + + // The data should still be cached at this point. + String value4 = cachedService.cacheResult1(KEY_1).await().indefinitely(); + Object value5 = cachedService.cacheResult2(KEY_1).await().indefinitely(); + Object value6 = cachedService.cacheResult2(KEY_2).await().indefinitely(); + assertSame(value1, value4); + assertSame(value2, value5); + assertSame(value3, value6); + + // It's time to perform the data invalidation. + invalidateUni.await().indefinitely(); + // The method annotated with @CacheInvalidate should have been invoked now. + assertEquals(1, cachedService.getCacheInvalidateInvocations()); + + // Let's call the methods annotated with @CacheResult again. + String value7 = cachedService.cacheResult1(KEY_1).await().indefinitely(); + Object value8 = cachedService.cacheResult2(KEY_1).await().indefinitely(); + Object value9 = cachedService.cacheResult2(KEY_2).await().indefinitely(); + + // The objects references should be different for the invalidated key. + assertNotSame(value4, value7); + assertNotSame(value5, value8); + // The object reference should remain unchanged for the key that was not invalidated. + assertSame(value6, value9); + } + + @Test + void testCacheInvalidateAll() { + // First, let's put some data into the caches. + String value1 = cachedService.cacheResult1(KEY_1).await().indefinitely(); + Object value2 = cachedService.cacheResult2(KEY_2).await().indefinitely(); + + // We will invalidate all the data in all caches later. + Uni invalidateAllUni = cachedService.cacheInvalidateAll(); + // For now, the method that will invalidate the data should not be invoked, as Uni is lazy. + assertEquals(0, cachedService.getCacheInvalidateAllInvocations()); + + // The data should still be cached at this point. + String value3 = cachedService.cacheResult1(KEY_1).await().indefinitely(); + Object value4 = cachedService.cacheResult2(KEY_2).await().indefinitely(); + assertSame(value1, value3); + assertSame(value2, value4); + + // It's time to perform the data invalidation. + invalidateAllUni.await().indefinitely(); + // The method annotated with @CacheInvalidateAll should have been invoked now. + assertEquals(1, cachedService.getCacheInvalidateAllInvocations()); + + // Let's call the methods annotated with @CacheResult again. + String value5 = cachedService.cacheResult1(KEY_1).await().indefinitely(); + Object value6 = cachedService.cacheResult2(KEY_2).await().indefinitely(); + + // All objects references should be different. + assertNotSame(value1, value5); + assertNotSame(value2, value6); + } + + @ApplicationScoped + static class CachedService { + + private volatile int cacheResultInvocations; + private volatile int cacheInvalidateInvocations; + private volatile int cacheInvalidateAllInvocations; + + @CacheResult(cacheName = CACHE_NAME_1) + public Uni cacheResult1(String key) { + cacheResultInvocations++; + return Uni.createFrom().item(() -> new String()); + } + + @CacheResult(cacheName = CACHE_NAME_2) + public Uni cacheResult2(String key) { + return Uni.createFrom().item(() -> new Object()); + } + + @CacheInvalidate(cacheName = CACHE_NAME_1) + @CacheInvalidate(cacheName = CACHE_NAME_2) + public Uni cacheInvalidate(String key) { + cacheInvalidateInvocations++; + return Uni.createFrom().nullItem(); + } + + @CacheInvalidateAll(cacheName = CACHE_NAME_1) + @CacheInvalidateAll(cacheName = CACHE_NAME_2) + public Uni cacheInvalidateAll() { + cacheInvalidateAllInvocations++; + return Uni.createFrom().nullItem(); + } + + public int getCacheResultInvocations() { + return cacheResultInvocations; + } + + public int getCacheInvalidateInvocations() { + return cacheInvalidateInvocations; + } + + public int getCacheInvalidateAllInvocations() { + return cacheInvalidateAllInvocations; + } + } +} diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/UniValueTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/UniValueTest.java deleted file mode 100644 index a75ec898955787..00000000000000 --- a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/UniValueTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package io.quarkus.cache.test.runtime; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; - -import org.jboss.shrinkwrap.api.asset.StringAsset; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.cache.CacheResult; -import io.quarkus.test.QuarkusUnitTest; -import io.smallrye.mutiny.Uni; - -/** - * Tests the {@link CacheResult} annotation on methods returning {@link Uni}. - */ -public class UniValueTest { - - private static final String KEY = "key"; - - @RegisterExtension - static final QuarkusUnitTest TEST = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar.addClass(CachedService.class).addAsResource( - new StringAsset("quarkus.log.category.\"io.quarkus.cache\".level=DEBUG"), "application.properties")); - - @Inject - CachedService cachedService; - - @Test - public void test() { - // STEP 1 - // Action: a method annotated with @CacheResult and returning a Uni is called. - // Expected effect: the method is not invoked, as Uni is lazy - // Verified by: invocations counter and CacheResultInterceptor log. - Uni uni1 = cachedService.cachedMethod(KEY); - assertEquals(0, cachedService.getInvocations()); - - // STEP 2 - // Action: same call as STEP 1. - // Expected effect: the method is not invoked, as Uni is lazy - Uni uni2 = cachedService.cachedMethod(KEY); - assertEquals(0, cachedService.getInvocations()); - - // STEP 3 - // Action: the Uni returned in STEP 1 is subscribed to and we wait for an item event to be fired. - // Expected effect: the UnresolvedUniValue cached during STEP 1 is replaced with the emitted item from this step in the cache. - // Verified by: subscriptions counter and CaffeineCache log. - String emittedItem1 = uni1.await().indefinitely(); - assertEquals("1", emittedItem1); // This checks the subscriptions counter value. - //the method would be called to resolve the value - assertEquals(1, cachedService.getInvocations()); - - // STEP 4 - // Action: the Uni returned in STEP 2 is subscribed to and we wait for an item event to be fired. - // Expected effect: the emitted item from STEP 3 is replaced with the emitted item from this step in the cache. - // Verified by: subscriptions counter, CaffeineCache log and different objects references between STEPS 3 and 4 emitted items. - String emittedItem2 = uni2.await().indefinitely(); - assertTrue(emittedItem1 == emittedItem2); - assertEquals("1", emittedItem2); // This checks the subscriptions counter value. - assertEquals(1, cachedService.getInvocations()); - - // STEP 5 - // Action: same call as STEP 2 but we immediately subscribe to the returned Uni and wait for an item event to be fired. - // Expected effect: the method is not invoked and the emitted item cached during STEP 4 is returned. - // Verified by: invocations and subscriptions counters, same object reference between STEPS 4 and 5 emitted items. - String emittedItem3 = cachedService.cachedMethod(KEY).await().indefinitely(); - assertEquals(1, cachedService.getInvocations()); - assertEquals("1", emittedItem3); // This checks the subscriptions counter value. - assertTrue(emittedItem2 == emittedItem3); - - // STEP 6 - // Action: same call as STEP 5 with a different key. - // Expected effect: the method is invoked and an UnresolvedUniValue is cached. - // Verified by: invocations and subscriptions counters, CacheResultInterceptor log and different objects references between STEPS 5 and 6 emitted items. - String emittedItem4 = cachedService.cachedMethod("another-key").await().indefinitely(); - assertEquals(2, cachedService.getInvocations()); - assertEquals("2", emittedItem4); // This checks the subscriptions counter value. - assertTrue(emittedItem3 != emittedItem4); - } - - @ApplicationScoped - static class CachedService { - - private volatile int invocations; - private volatile int subscriptions; - - @CacheResult(cacheName = "test-cache") - public Uni cachedMethod(String key) { - invocations++; - return Uni.createFrom().item(() -> { - subscriptions++; - return "" + subscriptions; - }); - } - - public int getInvocations() { - return invocations; - } - } -} diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInterceptor.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInterceptor.java index 9a2ca5fdefcbb3..846980a17de038 100644 --- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInterceptor.java +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInterceptor.java @@ -19,6 +19,7 @@ import io.quarkus.cache.CacheKey; import io.quarkus.cache.CacheManager; import io.quarkus.cache.CompositeCacheKey; +import io.smallrye.mutiny.Uni; public abstract class CacheInterceptor { @@ -131,4 +132,8 @@ protected Object getCacheKey(Cache cache, List cacheKeyParameterPositions return new CompositeCacheKey(methodParameterValues); } } + + protected static boolean isUniReturnType(InvocationContext invocationContext) { + return Uni.class.isAssignableFrom(invocationContext.getMethod().getReturnType()); + } } diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInvalidateAllInterceptor.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInvalidateAllInterceptor.java index 69b9e30c23e774..89866fa1cff34e 100644 --- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInvalidateAllInterceptor.java +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInvalidateAllInterceptor.java @@ -1,5 +1,7 @@ package io.quarkus.cache.runtime; +import java.util.function.Function; + import javax.annotation.Priority; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; @@ -8,7 +10,10 @@ import org.jboss.logging.Logger; import io.quarkus.cache.Cache; +import io.quarkus.cache.CacheException; import io.quarkus.cache.CacheInvalidateAll; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; @CacheInvalidateAll(cacheName = "") // The `cacheName` attribute is @Nonbinding. @Interceptor @@ -25,15 +30,49 @@ public Object intercept(InvocationContext invocationContext) throws Exception { if (interceptionContext.getInterceptorBindings().isEmpty()) { // This should never happen. LOGGER.warn(INTERCEPTOR_BINDINGS_ERROR_MSG); + return invocationContext.proceed(); + } else if (isUniReturnType(invocationContext)) { + return invalidateAllNonBlocking(invocationContext, interceptionContext); } else { - for (CacheInvalidateAll binding : interceptionContext.getInterceptorBindings()) { - Cache cache = cacheManager.getCache(binding.cacheName()).get(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debugf("Invalidating all entries from cache [%s]", binding.cacheName()); - } - cache.invalidateAll().await().indefinitely(); - } + return invalidateAllBlocking(invocationContext, interceptionContext); + } + } + + private Object invalidateAllNonBlocking(InvocationContext invocationContext, + CacheInterceptionContext interceptionContext) { + return Multi.createFrom().iterable(interceptionContext.getInterceptorBindings()) + .onItem().transformToUniAndMerge(new Function>() { + @Override + public Uni apply(CacheInvalidateAll binding) { + return invalidateAll(binding); + } + }) + .onItem().ignoreAsUni() + .onItem().transformToUni(new Function>() { + @Override + public Uni apply(Object ignored) { + try { + return (Uni) invocationContext.proceed(); + } catch (Exception e) { + throw new CacheException(e); + } + } + }); + } + + private Object invalidateAllBlocking(InvocationContext invocationContext, + CacheInterceptionContext interceptionContext) throws Exception { + for (CacheInvalidateAll binding : interceptionContext.getInterceptorBindings()) { + invalidateAll(binding).await().indefinitely(); } return invocationContext.proceed(); } + + private Uni invalidateAll(CacheInvalidateAll binding) { + Cache cache = cacheManager.getCache(binding.cacheName()).get(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debugf("Invalidating all entries from cache [%s]", binding.cacheName()); + } + return cache.invalidateAll(); + } } diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInvalidateInterceptor.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInvalidateInterceptor.java index 90932335ac29ab..0f0407c41f61a1 100644 --- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInvalidateInterceptor.java +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheInvalidateInterceptor.java @@ -1,5 +1,8 @@ package io.quarkus.cache.runtime; +import java.util.List; +import java.util.function.Function; + import javax.annotation.Priority; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; @@ -8,7 +11,10 @@ import org.jboss.logging.Logger; import io.quarkus.cache.Cache; +import io.quarkus.cache.CacheException; import io.quarkus.cache.CacheInvalidate; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; @CacheInvalidate(cacheName = "") // The `cacheName` attribute is @Nonbinding. @Interceptor @@ -25,20 +31,52 @@ public Object intercept(InvocationContext invocationContext) throws Exception { if (interceptionContext.getInterceptorBindings().isEmpty()) { // This should never happen. LOGGER.warn(INTERCEPTOR_BINDINGS_ERROR_MSG); + return invocationContext.proceed(); + } else if (isUniReturnType(invocationContext)) { + return invalidateNonBlocking(invocationContext, interceptionContext); } else { - Object key = null; - for (CacheInvalidate binding : interceptionContext.getInterceptorBindings()) { - Cache cache = cacheManager.getCache(binding.cacheName()).get(); - if (key == null) { - key = getCacheKey(cache, interceptionContext.getCacheKeyParameterPositions(), - invocationContext.getParameters()); - } - if (LOGGER.isDebugEnabled()) { - LOGGER.debugf("Invalidating entry with key [%s] from cache [%s]", key, binding.cacheName()); - } - cache.invalidate(key).await().indefinitely(); - } + return invalidateBlocking(invocationContext, interceptionContext); + } + } + + private Object invalidateNonBlocking(InvocationContext invocationContext, + CacheInterceptionContext interceptionContext) { + return Multi.createFrom().iterable(interceptionContext.getInterceptorBindings()) + .onItem().transformToUniAndMerge(new Function>() { + @Override + public Uni apply(CacheInvalidate binding) { + return invalidate(binding, interceptionContext.getCacheKeyParameterPositions(), + invocationContext.getParameters()); + } + }) + .onItem().ignoreAsUni() + .onItem().transformToUni(new Function>() { + @Override + public Uni apply(Object ignored) { + try { + return (Uni) invocationContext.proceed(); + } catch (Exception e) { + throw new CacheException(e); + } + } + }); + } + + private Object invalidateBlocking(InvocationContext invocationContext, + CacheInterceptionContext interceptionContext) throws Exception { + for (CacheInvalidate binding : interceptionContext.getInterceptorBindings()) { + invalidate(binding, interceptionContext.getCacheKeyParameterPositions(), invocationContext.getParameters()) + .await().indefinitely(); } return invocationContext.proceed(); } + + private Uni invalidate(CacheInvalidate binding, List cacheKeyParameterPositions, Object[] parameters) { + Cache cache = cacheManager.getCache(binding.cacheName()).get(); + Object key = getCacheKey(cache, cacheKeyParameterPositions, parameters); + if (LOGGER.isDebugEnabled()) { + LOGGER.debugf("Invalidating entry with key [%s] from cache [%s]", key, binding.cacheName()); + } + return cache.invalidate(key); + } } diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheResultInterceptor.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheResultInterceptor.java index e780bbda78691b..c4d36e00084154 100644 --- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheResultInterceptor.java +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheResultInterceptor.java @@ -2,6 +2,7 @@ import java.time.Duration; import java.util.function.Function; +import java.util.function.Supplier; import javax.annotation.Priority; import javax.interceptor.AroundInvoke; @@ -42,41 +43,52 @@ public Object intercept(InvocationContext invocationContext) throws Throwable { } try { - final boolean isUni = Uni.class.isAssignableFrom(invocationContext.getMethod().getReturnType()); - if (isUni) { - Uni ret = cache.get(key, new Function() { + if (isUniReturnType(invocationContext)) { + Uni cacheValue = cache.get(key, new Function() { @Override public Object apply(Object k) { LOGGER.debugf("Adding %s entry with key [%s] into cache [%s]", UnresolvedUniValue.class.getSimpleName(), key, binding.cacheName()); return UnresolvedUniValue.INSTANCE; } - }).onItem().transformToUni(o -> { - if (o == UnresolvedUniValue.INSTANCE) { - try { - return ((Uni) invocationContext.proceed()) - .onItem().call(emittedValue -> cache.replaceUniValue(key, emittedValue)); - } catch (CacheException e) { - throw e; - } catch (Exception e) { - throw new CacheException(e); + }).onItem().transformToUni(new Function>() { + @Override + public Uni apply(Object value) { + if (value == UnresolvedUniValue.INSTANCE) { + try { + return ((Uni) invocationContext.proceed()) + .call(new Function>() { + @Override + public Uni apply(Object emittedValue) { + return cache.replaceUniValue(key, emittedValue); + } + }); + } catch (CacheException e) { + throw e; + } catch (Exception e) { + throw new CacheException(e); + } + } else { + return Uni.createFrom().item(value); } - } else { - return Uni.createFrom().item(o); } }); if (binding.lockTimeout() <= 0) { - return ret; + return cacheValue; } - return ret.ifNoItem().after(Duration.ofMillis(binding.lockTimeout())).recoverWithUni(() -> { - try { - return (Uni) invocationContext.proceed(); - } catch (CacheException e) { - throw e; - } catch (Exception e) { - throw new CacheException(e); - } - }); + return cacheValue.ifNoItem().after(Duration.ofMillis(binding.lockTimeout())) + .recoverWithUni(new Supplier>() { + @Override + public Uni get() { + try { + return (Uni) invocationContext.proceed(); + } catch (CacheException e) { + throw e; + } catch (Exception e) { + throw new CacheException(e); + } + } + }); } else { Uni cacheValue = cache.get(key, new Function() { diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCacheImpl.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCacheImpl.java index 8da184c01d899d..653cee52b4521e 100644 --- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCacheImpl.java +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCacheImpl.java @@ -6,6 +6,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -159,17 +160,24 @@ public Void get() { @Override public Uni replaceUniValue(Object key, Object emittedValue) { - return Uni.createFrom().item(() -> { - // If the cache no longer contains the key because it was removed, we don't want to put it back. - cache.asMap().computeIfPresent(key, (k, currentValue) -> { - LOGGER.debugf("Replacing Uni value entry with key [%s] into cache [%s]", key, cacheInfo.name); - /* - * The following computed value will always replace the current cache value (whether it is an - * UnresolvedUniValue or not) if this method is called multiple times with the same key. - */ - return CompletableFuture.completedFuture(NullValueConverter.toCacheValue(emittedValue)); - }); - return null; + return Uni.createFrom().item(new Supplier() { + @Override + public Void get() { + // If the cache no longer contains the key because it was removed, we don't want to put it back. + cache.asMap().computeIfPresent(key, + new BiFunction, CompletableFuture>() { + @Override + public CompletableFuture apply(Object k, CompletableFuture currentValue) { + LOGGER.debugf("Replacing Uni value entry with key [%s] into cache [%s]", key, cacheInfo.name); + /* + * The following computed value will always replace the current cache value (whether it is an + * UnresolvedUniValue or not) if this method is called multiple times with the same key. + */ + return CompletableFuture.completedFuture(NullValueConverter.toCacheValue(emittedValue)); + } + }); + return null; + } }); }