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.
+ *
+ */
+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.
+ * }
+ *