From ddd838252c0670dc2bfca48b0f91eb9d636d66e6 Mon Sep 17 00:00:00 2001 From: Gwenneg Lepage Date: Sat, 14 Nov 2020 19:56:04 +0100 Subject: [PATCH] Introduce cache injection and NoOpCache --- .../quarkus/cache/deployment/CacheConfig.java | 3 +- .../deployment/CacheDeploymentConstants.java | 7 +- .../cache/deployment/CacheProcessor.java | 84 +++++++++++++------ .../test/deployment/CacheConfigTest.java | 6 +- .../test/deployment/MethodCacheNameTest.java | 36 ++++++++ .../test/deployment/UnknownCacheTypeTest.java | 14 +++- ...nownConstructorParameterCacheNameTest.java | 46 ++++++++++ .../deployment/UnknownFieldCacheNameTest.java | 48 +++++++++++ .../UnknownMethodParameterCacheNameTest.java | 46 ++++++++++ .../test/runtime/CacheInterceptorTest.java | 7 +- .../runtime/CaffeineCacheInjectionTest.java | 72 ++++++++++++++++ .../test/runtime/NoOpCacheInjectionTest.java | 52 ++++++++++++ .../cache/test/runtime/NoOpCacheTest.java | 68 +++++++++++++++ .../test/runtime/NullKeyOrValueCacheTest.java | 2 +- .../src/main/java/io/quarkus/cache/Cache.java | 13 +++ .../java/io/quarkus/cache/CacheManager.java | 46 ++++++++++ .../main/java/io/quarkus/cache/CacheName.java | 79 +++++++++++++++++ .../quarkus/cache/runtime/AbstractCache.java | 35 ++++++++ .../cache/runtime/CacheInterceptor.java | 6 +- .../CacheInvalidateAllInterceptor.java | 6 +- .../runtime/CacheInvalidateInterceptor.java | 6 +- .../cache/runtime/CacheManagerImpl.java | 40 +++++++++ .../quarkus/cache/runtime/CacheProducer.java | 31 +++++++ .../cache/runtime/CacheRepository.java | 26 ------ .../cache/runtime/CacheResultInterceptor.java | 6 +- .../cache/runtime/DefaultCacheKey.java | 12 ++- .../cache/runtime/caffeine/CaffeineCache.java | 38 ++++----- .../caffeine/CaffeineCacheBuildRecorder.java | 7 +- .../quarkus/cache/runtime/noop/NoOpCache.java | 40 +++++++++ .../runtime/noop/NoOpCacheBuildRecorder.java | 26 ++++++ 30 files changed, 803 insertions(+), 105 deletions(-) create mode 100644 extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/MethodCacheNameTest.java create mode 100644 extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownConstructorParameterCacheNameTest.java create mode 100644 extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownFieldCacheNameTest.java create mode 100644 extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownMethodParameterCacheNameTest.java create mode 100644 extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/CaffeineCacheInjectionTest.java create mode 100644 extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NoOpCacheInjectionTest.java create mode 100644 extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NoOpCacheTest.java create mode 100644 extensions/cache/runtime/src/main/java/io/quarkus/cache/Cache.java create mode 100644 extensions/cache/runtime/src/main/java/io/quarkus/cache/CacheManager.java create mode 100644 extensions/cache/runtime/src/main/java/io/quarkus/cache/CacheName.java create mode 100644 extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/AbstractCache.java create mode 100644 extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheManagerImpl.java create mode 100644 extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheProducer.java delete mode 100644 extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheRepository.java create mode 100644 extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/noop/NoOpCache.java create mode 100644 extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/noop/NoOpCacheBuildRecorder.java diff --git a/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheConfig.java b/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheConfig.java index b07e242a00e1a..fa87ec3073a9d 100644 --- a/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheConfig.java +++ b/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheConfig.java @@ -15,8 +15,7 @@ public class CacheConfig { /** - * Whether or not the cache extension is enabled. If the extension is disabled, the caching annotations will have no effect - * at run time. + * Whether or not the cache extension is enabled. */ @ConfigItem(defaultValue = "true") public boolean enabled; diff --git a/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheDeploymentConstants.java b/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheDeploymentConstants.java index 82c9f24635e8e..f9106a2fd82b2 100644 --- a/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheDeploymentConstants.java +++ b/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheDeploymentConstants.java @@ -8,12 +8,15 @@ import io.quarkus.cache.CacheInvalidate; import io.quarkus.cache.CacheInvalidateAll; import io.quarkus.cache.CacheKey; +import io.quarkus.cache.CacheName; import io.quarkus.cache.CacheResult; import io.quarkus.cache.runtime.CacheKeyParameterPositions; +import io.quarkus.cache.runtime.CacheProducer; public class CacheDeploymentConstants { // API annotations names. + public static final DotName CACHE_NAME = dotName(CacheName.class); public static final DotName CACHE_INVALIDATE_ALL = dotName(CacheInvalidateAll.class); public static final DotName CACHE_INVALIDATE_ALL_LIST = dotName(CacheInvalidateAll.List.class); public static final DotName CACHE_INVALIDATE = dotName(CacheInvalidate.class); @@ -24,8 +27,8 @@ public class CacheDeploymentConstants { CACHE_RESULT, CACHE_INVALIDATE, CACHE_INVALIDATE_ALL); public static final List API_METHODS_ANNOTATIONS_LISTS = Arrays.asList( CACHE_INVALIDATE_LIST, CACHE_INVALIDATE_ALL_LIST); - public static final DotName CACHE_KEY_PARAMETER_POSITIONS = DotName - .createSimple(CacheKeyParameterPositions.class.getName()); + public static final DotName CACHE_KEY_PARAMETER_POSITIONS = dotName(CacheKeyParameterPositions.class); + public static final DotName CACHE_PRODUCER = dotName(CacheProducer.class); // Annotations parameters. public static final String CACHE_NAME_PARAM = "cacheName"; diff --git a/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheProcessor.java b/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheProcessor.java index ff6e12e34157f..686c437493f77 100644 --- a/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheProcessor.java +++ b/extensions/cache/deployment/src/main/java/io/quarkus/cache/deployment/CacheProcessor.java @@ -2,7 +2,9 @@ import static io.quarkus.cache.deployment.CacheDeploymentConstants.API_METHODS_ANNOTATIONS; import static io.quarkus.cache.deployment.CacheDeploymentConstants.API_METHODS_ANNOTATIONS_LISTS; +import static io.quarkus.cache.deployment.CacheDeploymentConstants.CACHE_NAME; import static io.quarkus.cache.deployment.CacheDeploymentConstants.CACHE_NAME_PARAM; +import static io.quarkus.cache.deployment.CacheDeploymentConstants.CACHE_PRODUCER; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; import static org.jboss.jandex.AnnotationTarget.Kind.METHOD; @@ -11,17 +13,20 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.BooleanSupplier; import javax.enterprise.inject.spi.DeploymentException; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; +import io.quarkus.arc.deployment.AutoInjectAnnotationBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.ValidationPhaseBuildItem; import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem; @@ -33,6 +38,7 @@ import io.quarkus.cache.runtime.CacheResultInterceptor; import io.quarkus.cache.runtime.caffeine.CaffeineCacheBuildRecorder; import io.quarkus.cache.runtime.caffeine.CaffeineCacheInfo; +import io.quarkus.cache.runtime.noop.NoOpCacheBuildRecorder; import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; @@ -46,12 +52,17 @@ FeatureBuildItem feature() { return new FeatureBuildItem(Feature.CACHE); } - @BuildStep(onlyIf = CacheEnabled.class) + @BuildStep + AutoInjectAnnotationBuildItem autoInjectCacheName() { + return new AutoInjectAnnotationBuildItem(CACHE_NAME); + } + + @BuildStep AnnotationsTransformerBuildItem annotationsTransformer() { return new AnnotationsTransformerBuildItem(new CacheAnnotationsTransformer()); } - @BuildStep(onlyIf = CacheEnabled.class) + @BuildStep List additionalBeans() { return Arrays.asList( new AdditionalBeanBuildItem(CacheInvalidateAllInterceptor.class), @@ -59,7 +70,7 @@ List additionalBeans() { new AdditionalBeanBuildItem(CacheResultInterceptor.class)); } - @BuildStep(onlyIf = CacheEnabled.class) + @BuildStep ValidationErrorBuildItem validateBeanDeployment(ValidationPhaseBuildItem validationPhase) { AnnotationStore annotationStore = validationPhase.getContext().get(Key.ANNOTATION_STORE); List throwables = new ArrayList<>(); @@ -75,26 +86,30 @@ ValidationErrorBuildItem validateBeanDeployment(ValidationPhaseBuildItem validat return new ValidationErrorBuildItem(throwables.toArray(new Throwable[0])); } - @BuildStep(onlyIf = CacheEnabled.class) + @BuildStep @Record(STATIC_INIT) void recordCachesBuild(CombinedIndexBuildItem combinedIndex, BeanContainerBuildItem beanContainer, CacheConfig config, - CaffeineCacheBuildRecorder caffeineRecorder, + CaffeineCacheBuildRecorder caffeineRecorder, NoOpCacheBuildRecorder noOpRecorder, List additionalCacheNames) { - Set cacheNames = getCacheNames(combinedIndex.getIndex()); - for (AdditionalCacheNameBuildItem additionalCacheName : additionalCacheNames) { - cacheNames.add(additionalCacheName.getName()); - } - switch (config.type) { - case CacheDeploymentConstants.CAFFEINE_CACHE_TYPE: - Set cacheInfos = CaffeineCacheInfoBuilder.build(cacheNames, config); - caffeineRecorder.buildCaches(beanContainer.getValue(), cacheInfos); - break; - default: - throw new DeploymentException("Unknown cache type: " + config.type); + Set cacheNames = getCacheNames(combinedIndex.getIndex(), additionalCacheNames); + validateCacheNameAnnotations(combinedIndex.getIndex(), cacheNames); + if (cacheNames.size() > 0) { + if (config.enabled) { + switch (config.type) { + case CacheDeploymentConstants.CAFFEINE_CACHE_TYPE: + Set cacheInfos = CaffeineCacheInfoBuilder.build(cacheNames, config); + caffeineRecorder.buildCaches(beanContainer.getValue(), cacheInfos); + break; + default: + throw new DeploymentException("Unknown cache type: " + config.type); + } + } else { + noOpRecorder.buildCaches(beanContainer.getValue(), cacheNames); + } } } - private Set getCacheNames(IndexView index) { + private Set getCacheNames(IndexView index, List additionalCacheNames) { Set cacheNames = new HashSet<>(); for (DotName cacheAnnotation : API_METHODS_ANNOTATIONS) { for (AnnotationInstance annotation : index.getAnnotations(cacheAnnotation)) { @@ -112,15 +127,36 @@ private Set getCacheNames(IndexView index) { } } } + for (AdditionalCacheNameBuildItem additionalCacheName : additionalCacheNames) { + cacheNames.add(additionalCacheName.getName()); + } return cacheNames; } - private static class CacheEnabled implements BooleanSupplier { - - CacheConfig config; - - public boolean getAsBoolean() { - return config.enabled; + private void validateCacheNameAnnotations(IndexView index, Set cacheNames) { + for (AnnotationInstance cacheNameAnnotation : index.getAnnotations(CACHE_NAME)) { + AnnotationTarget target = cacheNameAnnotation.target(); + if (target.kind() == Kind.FIELD || target.kind() == Kind.METHOD_PARAMETER) { + String cacheName = cacheNameAnnotation.value().asString(); + if (!cacheNames.contains(cacheName)) { + ClassInfo declaringClass; + if (target.kind() == Kind.FIELD) { + declaringClass = target.asField().declaringClass(); + } else { + declaringClass = target.asMethodParameter().method().declaringClass(); + } + throw new DeploymentException( + "A field or method parameter is annotated with @CacheName(\"" + cacheName + "\") in the " + + declaringClass + " class but there is no cache with this name in the application"); + } + } else if (target.kind() == Kind.METHOD) { + ClassInfo declaringClass = target.asMethod().declaringClass(); + if (!CACHE_PRODUCER.equals(declaringClass.name())) { + throw new DeploymentException( + "The @CacheName annotation is not allowed on a method: [class= " + + declaringClass + ", method= " + target.asMethod().name() + "]"); + } + } } } } diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/CacheConfigTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/CacheConfigTest.java index d2cfac8cd281f..013daa0dd8b94 100644 --- a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/CacheConfigTest.java +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/CacheConfigTest.java @@ -13,8 +13,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.cache.CacheManager; import io.quarkus.cache.CacheResult; -import io.quarkus.cache.runtime.CacheRepository; import io.quarkus.cache.runtime.caffeine.CaffeineCache; import io.quarkus.test.QuarkusUnitTest; @@ -29,11 +29,11 @@ public class CacheConfigTest { private static final String CACHE_NAME = "test-cache"; @Inject - CacheRepository cacheRepository; + CacheManager cacheManager; @Test public void testConfig() { - CaffeineCache cache = (CaffeineCache) cacheRepository.getCache(CACHE_NAME); + CaffeineCache cache = (CaffeineCache) cacheManager.getCache(CACHE_NAME).get(); assertEquals(10, cache.getInitialCapacity()); assertEquals(100L, cache.getMaximumSize()); assertEquals(Duration.ofSeconds(30L), cache.getExpireAfterWrite()); diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/MethodCacheNameTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/MethodCacheNameTest.java new file mode 100644 index 0000000000000..c84f0bd9c210e --- /dev/null +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/MethodCacheNameTest.java @@ -0,0 +1,36 @@ +package io.quarkus.cache.test.deployment; + +import static org.junit.jupiter.api.Assertions.fail; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.spi.DeploymentException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.cache.CacheName; +import io.quarkus.test.QuarkusUnitTest; + +public class MethodCacheNameTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(CachedService.class)) + .setExpectedException(DeploymentException.class); + + @Test + public void shouldNotBeInvoked() { + fail("This method should not be invoked"); + } + + @ApplicationScoped + static class CachedService { + + @CacheName("illegal-annotation-target") + public Object getObject(Object key) { + return new Object(); + } + } +} diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownCacheTypeTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownCacheTypeTest.java index e45781af4745f..3beff50967f1e 100644 --- a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownCacheTypeTest.java +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownCacheTypeTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.fail; +import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.spi.DeploymentException; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -10,6 +11,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.cache.CacheResult; import io.quarkus.test.QuarkusUnitTest; public class UnknownCacheTypeTest { @@ -17,11 +19,21 @@ public class UnknownCacheTypeTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addAsResource(new StringAsset("quarkus.cache.type=i_am_an_unknown_cache_type"), "application.properties")) + .addAsResource(new StringAsset("quarkus.cache.type=i_am_an_unknown_cache_type"), "application.properties") + .addClass(CachedService.class)) .setExpectedException(DeploymentException.class); @Test public void shouldNotBeInvoked() { fail("This method should not be invoked"); } + + @ApplicationScoped + static class CachedService { + + @CacheResult(cacheName = "test-cache") + public Object cachedMethod(String key) { + return new Object(); + } + } } diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownConstructorParameterCacheNameTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownConstructorParameterCacheNameTest.java new file mode 100644 index 0000000000000..df11ec2feac0f --- /dev/null +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownConstructorParameterCacheNameTest.java @@ -0,0 +1,46 @@ +package io.quarkus.cache.test.deployment; + +import static org.junit.jupiter.api.Assertions.fail; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.spi.DeploymentException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.cache.Cache; +import io.quarkus.cache.CacheName; +import io.quarkus.cache.CacheResult; +import io.quarkus.test.QuarkusUnitTest; + +public class UnknownConstructorParameterCacheNameTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(CachedService.class, TestBean.class)) + .setExpectedException(DeploymentException.class); + + @Test + public void shouldNotBeInvoked() { + fail("This method should not be invoked"); + } + + @ApplicationScoped + static class CachedService { + + @CacheResult(cacheName = "test-cache") + public Object getObject(Object key) { + return new Object(); + } + } + + @Dependent + static class TestBean { + + public TestBean(@CacheName("unknown-cache") Cache cache) { + } + } +} diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownFieldCacheNameTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownFieldCacheNameTest.java new file mode 100644 index 0000000000000..ad2df110a74be --- /dev/null +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownFieldCacheNameTest.java @@ -0,0 +1,48 @@ +package io.quarkus.cache.test.deployment; + +import static org.junit.jupiter.api.Assertions.fail; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.spi.DeploymentException; +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.cache.Cache; +import io.quarkus.cache.CacheName; +import io.quarkus.cache.CacheResult; +import io.quarkus.test.QuarkusUnitTest; + +public class UnknownFieldCacheNameTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(CachedService.class, TestBean.class)) + .setExpectedException(DeploymentException.class); + + @Test + public void shouldNotBeInvoked() { + fail("This method should not be invoked"); + } + + @ApplicationScoped + static class CachedService { + + @CacheResult(cacheName = "test-cache") + public Object getObject(Object key) { + return new Object(); + } + } + + @Dependent + static class TestBean { + + @Inject + @CacheName("unknown-cache") + Cache cache; + } +} diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownMethodParameterCacheNameTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownMethodParameterCacheNameTest.java new file mode 100644 index 0000000000000..c803c37ca6902 --- /dev/null +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/deployment/UnknownMethodParameterCacheNameTest.java @@ -0,0 +1,46 @@ +package io.quarkus.cache.test.deployment; + +import static org.junit.jupiter.api.Assertions.fail; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.spi.DeploymentException; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.cache.Cache; +import io.quarkus.cache.CacheName; +import io.quarkus.cache.CacheResult; +import io.quarkus.test.QuarkusUnitTest; + +public class UnknownMethodParameterCacheNameTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(CachedService.class, TestBean.class)) + .setExpectedException(DeploymentException.class); + + @Test + public void shouldNotBeInvoked() { + fail("This method should not be invoked"); + } + + @ApplicationScoped + static class CachedService { + + @CacheResult(cacheName = "test-cache") + public Object getObject(Object key) { + return new Object(); + } + } + + @Dependent + static class TestBean { + + public void unknownCacheName(@CacheName("unknown-cache") Cache cache) { + } + } +} diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/CacheInterceptorTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/CacheInterceptorTest.java index 3c09b3bd669a8..feceed7dd293a 100644 --- a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/CacheInterceptorTest.java +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/CacheInterceptorTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; +import io.quarkus.cache.runtime.AbstractCache; import io.quarkus.cache.runtime.CacheInterceptor; import io.quarkus.cache.runtime.CompositeCacheKey; import io.quarkus.cache.runtime.DefaultCacheKey; @@ -21,7 +22,7 @@ public void testDefaultKey() { cacheInfo.name = "test-cache"; CaffeineCache cache = new CaffeineCache(cacheInfo); - DefaultCacheKey expectedKey = new DefaultCacheKey(cache.getName()); + DefaultCacheKey expectedKey = new DefaultCacheKey(cacheInfo.name); Object actualKey = getCacheKey(cache, new short[] {}, new Object[] {}); assertEquals(expectedKey, actualKey); } @@ -60,7 +61,7 @@ public void testImplicitCompositeKey() { assertEquals(expectedKey, actualKey); } - private Object getCacheKey(CaffeineCache cache, short[] cacheKeyParameterPositions, Object[] methodParameterValues) { + private Object getCacheKey(AbstractCache cache, short[] cacheKeyParameterPositions, Object[] methodParameterValues) { return TEST_CACHE_INTERCEPTOR.getCacheKey(cache, cacheKeyParameterPositions, methodParameterValues); } @@ -71,7 +72,7 @@ private Object getCacheKey(short[] cacheKeyParameterPositions, Object[] methodPa // This inner class changes the CacheInterceptor#getCacheKey method visibility to public. private static class TestCacheInterceptor extends CacheInterceptor { @Override - public Object getCacheKey(CaffeineCache cache, short[] cacheKeyParameterPositions, Object[] methodParameterValues) { + public Object getCacheKey(AbstractCache cache, short[] cacheKeyParameterPositions, Object[] methodParameterValues) { return super.getCacheKey(cache, cacheKeyParameterPositions, methodParameterValues); } } diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/CaffeineCacheInjectionTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/CaffeineCacheInjectionTest.java new file mode 100644 index 0000000000000..dd90c8f09d77d --- /dev/null +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/CaffeineCacheInjectionTest.java @@ -0,0 +1,72 @@ +package io.quarkus.cache.test.runtime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.cache.Cache; +import io.quarkus.cache.CacheInvalidateAll; +import io.quarkus.cache.CacheManager; +import io.quarkus.cache.CacheName; +import io.quarkus.cache.runtime.caffeine.CaffeineCache; +import io.quarkus.test.QuarkusUnitTest; + +public class CaffeineCacheInjectionTest { + + private static final String CACHE_NAME = "test-cache"; + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClass(CachedService.class)); + + @Inject + CachedService cachedService; + + @CacheName(CACHE_NAME) + Cache cache; + + @Inject + CacheManager cacheManager; + + @Test + public void testInjection() { + assertEquals(CaffeineCache.class, cache.getClass()); + assertEquals(cache, cacheManager.getCache(CACHE_NAME).get()); + assertEquals(cache, cachedService.getConstructorInjectedCache()); + assertEquals(cache, cachedService.getMethodInjectedCache()); + } + + @ApplicationScoped + static class CachedService { + + Cache constructorInjectedCache; + Cache methodInjectedCache; + + public CachedService(@CacheName(CACHE_NAME) Cache cache) { + constructorInjectedCache = cache; + } + + public Cache getConstructorInjectedCache() { + return constructorInjectedCache; + } + + public Cache getMethodInjectedCache() { + return methodInjectedCache; + } + + @Inject + public void setMethodInjectedCache(@CacheName(CACHE_NAME) Cache cache) { + methodInjectedCache = cache; + } + + @CacheInvalidateAll(cacheName = CACHE_NAME) + public void invalidateAll() { + } + } +} diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NoOpCacheInjectionTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NoOpCacheInjectionTest.java new file mode 100644 index 0000000000000..3d1278adf7835 --- /dev/null +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NoOpCacheInjectionTest.java @@ -0,0 +1,52 @@ +package io.quarkus.cache.test.runtime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.cache.Cache; +import io.quarkus.cache.CacheInvalidateAll; +import io.quarkus.cache.CacheManager; +import io.quarkus.cache.CacheName; +import io.quarkus.cache.runtime.noop.NoOpCache; +import io.quarkus.test.QuarkusUnitTest; + +public class NoOpCacheInjectionTest { + + private static final String CACHE_NAME = "test-cache"; + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new StringAsset("quarkus.cache.enabled=false"), "application.properties") + .addClass(CachedService.class)); + + @Inject + CachedService cachedService; + + @CacheName(CACHE_NAME) + Cache cache; + + @Inject + CacheManager cacheManager; + + @Test + public void testInjection() { + assertEquals(NoOpCache.class, cache.getClass()); + assertEquals(cache, cacheManager.getCache(CACHE_NAME).get()); + } + + @Singleton + static class CachedService { + + @CacheInvalidateAll(cacheName = CACHE_NAME) + public void invalidateAll() { + } + } +} diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NoOpCacheTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NoOpCacheTest.java new file mode 100644 index 0000000000000..482a6654ffa25 --- /dev/null +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NoOpCacheTest.java @@ -0,0 +1,68 @@ +package io.quarkus.cache.test.runtime; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +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; + +public class NoOpCacheTest { + + private static final String CACHE_NAME = "test-cache"; + private static final Object KEY = new Object(); + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(new StringAsset("quarkus.cache.enabled=false"), "application.properties") + .addClass(CachedService.class)); + + @Inject + CachedService cachedService; + + @Test + public void testAllCacheAnnotations() { + // STEP 1 + // Action: @CacheResult-annotated method call. + // Expected effect: method invoked and result not cached. + // Verified by: STEP 2. + String value1 = cachedService.cachedMethod(KEY); + + // STEP 2 + // Action: same call as STEP 1. + // Expected effect: method invoked and result not cached. + // Verified by: different objects references between STEPS 1 and 2 results. + String value2 = cachedService.cachedMethod(KEY); + assertTrue(value1 != value2); + + // The following methods have no effect at all, but let's check if they're running fine anyway. + cachedService.invalidate(KEY); + cachedService.invalidateAll(); + } + + @Singleton + static class CachedService { + + @CacheResult(cacheName = CACHE_NAME) + public String cachedMethod(Object key) { + return new String(); + } + + @CacheInvalidate(cacheName = CACHE_NAME) + public void invalidate(Object key) { + } + + @CacheInvalidateAll(cacheName = CACHE_NAME) + public void invalidateAll() { + } + } +} diff --git a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NullKeyOrValueCacheTest.java b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NullKeyOrValueCacheTest.java index 6c2de8a8eed23..05047bc4e705b 100644 --- a/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NullKeyOrValueCacheTest.java +++ b/extensions/cache/deployment/src/test/java/io/quarkus/cache/test/runtime/NullKeyOrValueCacheTest.java @@ -1,6 +1,6 @@ package io.quarkus.cache.test.runtime; -import static io.quarkus.cache.runtime.caffeine.CaffeineCache.NULL_KEYS_NOT_SUPPORTED_MSG; +import static io.quarkus.cache.runtime.AbstractCache.NULL_KEYS_NOT_SUPPORTED_MSG; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/Cache.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/Cache.java new file mode 100644 index 0000000000000..8faeb4ee1449a --- /dev/null +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/Cache.java @@ -0,0 +1,13 @@ +package io.quarkus.cache; + +/** + * Use this interface to interact with a cache programmatically. The cache can be injected using the {@link CacheName} + * annotation or retrieved using {@link CacheManager}. + */ +public interface Cache { + + /* + * This interface is intentionally empty for now. + * TODO: Add the methods that will be exposed by the programmatic API. + */ +} diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/CacheManager.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/CacheManager.java new file mode 100644 index 0000000000000..114d65b9345bc --- /dev/null +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/CacheManager.java @@ -0,0 +1,46 @@ +package io.quarkus.cache; + +import java.util.Collection; +import java.util.Optional; + +/** + *

+ * Use this interface to retrieve all existing {@link Cache} names and interact with any cache programmatically. It shares the + * same cache collection the Quarkus caching annotations use. The {@link CacheName} annotation can also be used to inject and + * access a specific cache from its name. + *

+ *

+ * Code example: + * + *

+ * {@literal @}ApplicationScoped
+ * public class CachedService {
+ * 
+ *     {@literal @}Inject
+ *     CacheManager cacheManager;
+ *     
+ *     void doSomething() {
+ *         Cache cache = cacheManager.getCache("my-cache");
+ *         // Interact with the cache.
+ *     }
+ * }
+ * 
+ *

+ */ +public interface CacheManager { + + /** + * Gets a collection of all cache names. + * + * @return names of all caches + */ + Collection getCacheNames(); + + /** + * Gets the cache identified by the given name. + * + * @param name cache name + * @return an {@link Optional} containing the identified cache if it exists, or an empty {@link Optional} otherwise + */ + Optional getCache(String name); +} diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/CacheName.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/CacheName.java new file mode 100644 index 0000000000000..7a375c3b3aeaf --- /dev/null +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/CacheName.java @@ -0,0 +1,79 @@ +package io.quarkus.cache; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.enterprise.util.Nonbinding; +import javax.inject.Qualifier; + +/** + *

+ * Use this annotation on a field, a constructor parameter or a method parameter to inject a {@link Cache} and interact with it + * programmatically. + *

+ *

+ * Field injection example: + * + *

+ * {@literal @}ApplicationScoped
+ * public class CachedService {
+ * 
+ *     {@literal @}CacheName("my-cache")
+ *     Cache cache;
+ *     
+ *     // Interact with the cache.
+ * }
+ * 
+ * + * Constructor parameter injection example: + * + *
+ * {@literal @}ApplicationScoped
+ * public class CachedService {
+ * 
+ *     private Cache cache;
+ * 
+ *     public CachedService(@CacheName("my-cache") Cache cache) {
+ *         this.cache = cache;
+ *     }
+ *     
+ *     // Interact with the cache.
+ * }
+ * 
+ * + * Method parameter injection example: + * + *
+ * {@literal @}ApplicationScoped
+ * public class CachedService {
+ * 
+ *     private Cache cache;
+ * 
+ *     {@literal @}Inject
+ *     public void setCache(@CacheName("my-cache") Cache cache) {
+ *         this.cache = cache;
+ *     }
+ *     
+ *     // Interact with the cache.
+ * }
+ * 
+ *

+ * + * @see CacheManager + */ +@Qualifier +@Target({ FIELD, METHOD, PARAMETER }) +@Retention(RUNTIME) +public @interface CacheName { + + /** + * The name of the cache. + */ + @Nonbinding + String value(); +} diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/AbstractCache.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/AbstractCache.java new file mode 100644 index 0000000000000..12cd4e18aceb9 --- /dev/null +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/AbstractCache.java @@ -0,0 +1,35 @@ +package io.quarkus.cache.runtime; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import io.quarkus.cache.Cache; + +public abstract class AbstractCache implements Cache { + + public static final String NULL_KEYS_NOT_SUPPORTED_MSG = "Null keys are not supported by the Quarkus application data cache"; + + private Object defaultKey; + + protected abstract String getName(); + + /** + * Returns the unique and immutable default key for the current cache. This key is used by the annotations caching API when + * a no-args method annotated with {@link io.quarkus.cache.CacheResult CacheResult} or + * {@link io.quarkus.cache.CacheInvalidate CacheInvalidate} is invoked. + * + * @return default cache key + */ + public Object getDefaultKey() { + if (defaultKey == null) { + defaultKey = new DefaultCacheKey(getName()); + } + return defaultKey; + } + + public abstract CompletableFuture get(Object key, Function valueLoader); + + public abstract void invalidate(Object key); + + public abstract void invalidateAll(); +} 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 27bc160e75dc4..49f353cc05ddc 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 @@ -9,14 +9,14 @@ import javax.interceptor.InvocationContext; import io.quarkus.arc.runtime.InterceptorBindings; -import io.quarkus.cache.runtime.caffeine.CaffeineCache; +import io.quarkus.cache.CacheManager; public abstract class CacheInterceptor { public static final int BASE_PRIORITY = Priority.PLATFORM_BEFORE; @Inject - protected CacheRepository cacheRepository; + CacheManager cacheManager; @SuppressWarnings("unchecked") protected List getInterceptorBindings(InvocationContext context, Class bindingClass) { @@ -42,7 +42,7 @@ protected short[] getCacheKeyParameterPositions(InvocationContext context) { } } - protected Object getCacheKey(CaffeineCache cache, short[] cacheKeyParameterPositions, Object[] methodParameterValues) { + protected Object getCacheKey(AbstractCache cache, short[] cacheKeyParameterPositions, Object[] methodParameterValues) { if (methodParameterValues == null || methodParameterValues.length == 0) { // If the intercepted method doesn't have any parameter, then the default cache key will be used. return cache.getDefaultKey(); 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 21add43b4e6f2..4ebf105a17c98 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 @@ -7,8 +7,6 @@ import org.jboss.logging.Logger; -import io.quarkus.cache.runtime.caffeine.CaffeineCache; - @CacheInvalidateAllInterceptorBinding @Interceptor @Priority(CacheInterceptor.BASE_PRIORITY) @@ -20,9 +18,9 @@ public class CacheInvalidateAllInterceptor extends CacheInterceptor { public Object intercept(InvocationContext context) throws Exception { for (CacheInvalidateAllInterceptorBinding binding : getInterceptorBindings(context, CacheInvalidateAllInterceptorBinding.class)) { - CaffeineCache cache = cacheRepository.getCache(binding.cacheName()); + AbstractCache cache = (AbstractCache) cacheManager.getCache(binding.cacheName()).get(); if (LOGGER.isDebugEnabled()) { - LOGGER.debugf("Invalidating all entries from cache [%s]", cache.getName()); + LOGGER.debugf("Invalidating all entries from cache [%s]", binding.cacheName()); } 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 064c9f9bd7c43..b30c6af2501e7 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 @@ -7,8 +7,6 @@ import org.jboss.logging.Logger; -import io.quarkus.cache.runtime.caffeine.CaffeineCache; - @CacheInvalidateInterceptorBinding @Interceptor @Priority(CacheInterceptor.BASE_PRIORITY + 1) @@ -22,12 +20,12 @@ public Object intercept(InvocationContext context) throws Exception { short[] cacheKeyParameterPositions = getCacheKeyParameterPositions(context); for (CacheInvalidateInterceptorBinding binding : getInterceptorBindings(context, CacheInvalidateInterceptorBinding.class)) { - CaffeineCache cache = cacheRepository.getCache(binding.cacheName()); + AbstractCache cache = (AbstractCache) cacheManager.getCache(binding.cacheName()).get(); if (key == null) { key = getCacheKey(cache, cacheKeyParameterPositions, context.getParameters()); } if (LOGGER.isDebugEnabled()) { - LOGGER.debugf("Invalidating entry with key [%s] from cache [%s]", key, cache.getName()); + LOGGER.debugf("Invalidating entry with key [%s] from cache [%s]", key, binding.cacheName()); } cache.invalidate(key); } diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheManagerImpl.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheManagerImpl.java new file mode 100644 index 0000000000000..ee2f970d09dc5 --- /dev/null +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheManagerImpl.java @@ -0,0 +1,40 @@ +package io.quarkus.cache.runtime; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.cache.Cache; +import io.quarkus.cache.CacheManager; + +@ApplicationScoped +public class CacheManagerImpl implements CacheManager { + + // There's no need for concurrency here since the caches are created at build time and never modified after that. + private Map caches; + private Set cacheNames; + + public void setCaches(Map caches) { + if (this.caches != null) { + throw new IllegalStateException("The caches map must only be set once at build time"); + } + this.caches = Collections.unmodifiableMap(caches); + cacheNames = Collections.unmodifiableSet(caches.keySet()); + } + + @Override + public Set getCacheNames() { + return cacheNames; + } + + @Override + public Optional getCache(String name) { + if (name == null) { + return Optional.empty(); + } + return Optional.ofNullable(caches.get(name)); + } +} diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheProducer.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheProducer.java new file mode 100644 index 0000000000000..e5b68b5961b17 --- /dev/null +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheProducer.java @@ -0,0 +1,31 @@ +package io.quarkus.cache.runtime; + +import java.lang.annotation.Annotation; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Inject; + +import io.quarkus.cache.Cache; +import io.quarkus.cache.CacheManager; +import io.quarkus.cache.CacheName; + +@ApplicationScoped +public class CacheProducer { + + @Inject + CacheManager cacheManager; + + @Produces + @CacheName("") // The `value` attribute here is not important because it is @Nonbinding. + Cache produce(InjectionPoint injectionPoint) { + for (Annotation qualifier : injectionPoint.getQualifiers()) { + if (qualifier instanceof CacheName) { + return cacheManager.getCache(((CacheName) qualifier).value()).get(); + } + } + // This will never be returned. + return null; + } +} diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheRepository.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheRepository.java deleted file mode 100644 index 87b1d5df4d44c..0000000000000 --- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.quarkus.cache.runtime; - -import java.util.Collections; -import java.util.Map; - -import javax.enterprise.context.ApplicationScoped; - -import io.quarkus.cache.runtime.caffeine.CaffeineCache; - -@ApplicationScoped -public class CacheRepository { - - // There's no need for concurrency here since the map is created at build time and never modified after that. - private Map caches; - - public void setCaches(Map caches) { - if (this.caches != null) { - throw new IllegalStateException("The caches map must only be set at build time"); - } - this.caches = Collections.unmodifiableMap(caches); - } - - public CaffeineCache getCache(String cacheName) { - return caches.get(cacheName); - } -} 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 c534a51713186..9812aa55dff2a 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 @@ -13,8 +13,6 @@ import org.jboss.logging.Logger; -import io.quarkus.cache.runtime.caffeine.CaffeineCache; - @CacheResultInterceptorBinding @Interceptor @Priority(CacheInterceptor.BASE_PRIORITY + 2) @@ -26,11 +24,11 @@ public class CacheResultInterceptor extends CacheInterceptor { public Object intercept(InvocationContext context) throws Exception { CacheResultInterceptorBinding binding = getInterceptorBinding(context, CacheResultInterceptorBinding.class); - CaffeineCache cache = cacheRepository.getCache(binding.cacheName()); + AbstractCache cache = (AbstractCache) cacheManager.getCache(binding.cacheName()).get(); short[] cacheKeyParameterPositions = getCacheKeyParameterPositions(context); Object key = getCacheKey(cache, cacheKeyParameterPositions, context.getParameters()); if (LOGGER.isDebugEnabled()) { - LOGGER.debugf("Loading entry with key [%s] from cache [%s]", key, cache.getName()); + LOGGER.debugf("Loading entry with key [%s] from cache [%s]", key, binding.cacheName()); } try { diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/DefaultCacheKey.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/DefaultCacheKey.java index 694cf25d087df..54181cffb8aa7 100644 --- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/DefaultCacheKey.java +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/DefaultCacheKey.java @@ -2,12 +2,22 @@ import java.util.Objects; +/** + * A default cache key is used by the annotations caching API when a no-args method annotated with + * {@link io.quarkus.cache.CacheResult CacheResult} or {@link io.quarkus.cache.CacheInvalidate CacheInvalidate} is invoked. + */ public class DefaultCacheKey { private final String cacheName; + /** + * Constructor. + * + * @param cacheName cache name + * @throws NullPointerException if the cache name is {@code null} + */ public DefaultCacheKey(String cacheName) { - this.cacheName = cacheName; + this.cacheName = Objects.requireNonNull(cacheName); } @Override diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCache.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCache.java index dcb27cf47ac34..bc3c61568f7c4 100644 --- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCache.java +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCache.java @@ -7,13 +7,15 @@ import com.github.benmanes.caffeine.cache.AsyncCache; import com.github.benmanes.caffeine.cache.Caffeine; +import io.quarkus.cache.runtime.AbstractCache; import io.quarkus.cache.runtime.CacheException; -import io.quarkus.cache.runtime.DefaultCacheKey; import io.quarkus.cache.runtime.NullValueConverter; -public class CaffeineCache { - - public static final String NULL_KEYS_NOT_SUPPORTED_MSG = "Null keys are not supported by the Quarkus application data cache"; +/** + * This class is an internal Quarkus cache implementation. Do not use it explicitly from your Quarkus application. The public + * methods signatures may change without prior notice. + */ +public class CaffeineCache extends AbstractCache { private AsyncCache cache; @@ -27,8 +29,6 @@ public class CaffeineCache { private Duration expireAfterAccess; - private Object defaultKey; - public CaffeineCache(CaffeineCacheInfo cacheInfo) { this.name = cacheInfo.name; Caffeine builder = Caffeine.newBuilder(); @@ -51,6 +51,11 @@ public CaffeineCache(CaffeineCacheInfo cacheInfo) { cache = builder.buildAsync(); } + @Override + protected String getName() { + return name; + } + /** * Returns a {@link CompletableFuture} holding the cache value identified by {@code key}, obtaining that value from * {@code valueLoader} if necessary. The value computation is done synchronously on the calling thread and the @@ -61,6 +66,7 @@ public CaffeineCache(CaffeineCacheInfo cacheInfo) { * @return a {@link CompletableFuture} holding the cache value * @throws CacheException if an exception is thrown during the cache value computation */ + @Override public CompletableFuture get(Object key, Function valueLoader) { if (key == null) { throw new NullPointerException(NULL_KEYS_NOT_SUPPORTED_MSG); @@ -100,6 +106,7 @@ public Object apply(Object value) { }); } + @Override public void invalidate(Object key) { if (key == null) { throw new NullPointerException(NULL_KEYS_NOT_SUPPORTED_MSG); @@ -107,14 +114,11 @@ public void invalidate(Object key) { cache.synchronous().invalidate(key); } + @Override public void invalidateAll() { cache.synchronous().invalidateAll(); } - public String getName() { - return name; - } - // For testing purposes only. public Integer getInitialCapacity() { return initialCapacity; @@ -134,18 +138,4 @@ public Duration getExpireAfterWrite() { public Duration getExpireAfterAccess() { return expireAfterAccess; } - - /** - * Returns the unique and immutable default key for the current cache. This key is used by the annotations caching API when - * a no-args method annotated with {@link io.quarkus.cache.CacheResult CacheResult} or - * {@link io.quarkus.cache.CacheInvalidate CacheInvalidate} is invoked. - * - * @return default cache key - */ - public Object getDefaultKey() { - if (defaultKey == null) { - defaultKey = new DefaultCacheKey(getName()); - } - return defaultKey; - } } diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCacheBuildRecorder.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCacheBuildRecorder.java index a3a86c5db4f9b..bd24bcbf7ad19 100644 --- a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCacheBuildRecorder.java +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/caffeine/CaffeineCacheBuildRecorder.java @@ -7,7 +7,8 @@ import org.jboss.logging.Logger; import io.quarkus.arc.runtime.BeanContainer; -import io.quarkus.cache.runtime.CacheRepository; +import io.quarkus.cache.Cache; +import io.quarkus.cache.runtime.CacheManagerImpl; import io.quarkus.runtime.annotations.Recorder; @Recorder @@ -17,7 +18,7 @@ public class CaffeineCacheBuildRecorder { public void buildCaches(BeanContainer beanContainer, Set cacheInfos) { // The number of caches is known at build time so we can use fixed initialCapacity and loadFactor for the caches map. - Map caches = new HashMap<>(cacheInfos.size() + 1, 1.0F); + Map caches = new HashMap<>(cacheInfos.size() + 1, 1.0F); for (CaffeineCacheInfo cacheInfo : cacheInfos) { if (LOGGER.isDebugEnabled()) { @@ -30,6 +31,6 @@ public void buildCaches(BeanContainer beanContainer, Set cach caches.put(cacheInfo.name, cache); } - beanContainer.instance(CacheRepository.class).setCaches(caches); + beanContainer.instance(CacheManagerImpl.class).setCaches(caches); } } diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/noop/NoOpCache.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/noop/NoOpCache.java new file mode 100644 index 0000000000000..d326ffa7d1091 --- /dev/null +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/noop/NoOpCache.java @@ -0,0 +1,40 @@ +package io.quarkus.cache.runtime.noop; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import io.quarkus.cache.runtime.AbstractCache; + +/** + * This class is an internal Quarkus cache implementation. Do not use it explicitly from your Quarkus application. The public + * methods signatures may change without prior notice. + */ +public class NoOpCache extends AbstractCache { + + private static final String NAME = NoOpCache.class.getName(); + + @Override + protected String getName() { + return NAME; + } + + @Override + public CompletableFuture get(Object key, Function valueLoader) { + CompletableFuture cacheValue = new CompletableFuture(); + try { + Object value = valueLoader.apply(key); + cacheValue.complete(value); + } catch (Throwable t) { + cacheValue.completeExceptionally(t); + } + return cacheValue; + } + + @Override + public void invalidate(Object key) { + } + + @Override + public void invalidateAll() { + } +} diff --git a/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/noop/NoOpCacheBuildRecorder.java b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/noop/NoOpCacheBuildRecorder.java new file mode 100644 index 0000000000000..522893e96214d --- /dev/null +++ b/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/noop/NoOpCacheBuildRecorder.java @@ -0,0 +1,26 @@ +package io.quarkus.cache.runtime.noop; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.cache.Cache; +import io.quarkus.cache.runtime.CacheManagerImpl; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class NoOpCacheBuildRecorder { + + public void buildCaches(BeanContainer beanContainer, Set cacheNames) { + // The number of caches is known at build time so we can use fixed initialCapacity and loadFactor for the caches map. + Map caches = new HashMap<>(cacheNames.size() + 1, 1.0F); + + NoOpCache cache = new NoOpCache(); + for (String cacheName : cacheNames) { + caches.put(cacheName, cache); + } + + beanContainer.instance(CacheManagerImpl.class).setCaches(caches); + } +}