Skip to content

Commit

Permalink
Merge pull request #13296 from gwenneg/issue-8140-cache-injection
Browse files Browse the repository at this point in the history
Introduce cache injection and NoOpCache
  • Loading branch information
gsmet authored Dec 11, 2020
2 parents 9aef799 + ddd8382 commit 3f816ad
Show file tree
Hide file tree
Showing 30 changed files with 803 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -24,8 +27,8 @@ public class CacheDeploymentConstants {
CACHE_RESULT, CACHE_INVALIDATE, CACHE_INVALIDATE_ALL);
public static final List<DotName> 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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -46,20 +52,25 @@ 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<AdditionalBeanBuildItem> additionalBeans() {
return Arrays.asList(
new AdditionalBeanBuildItem(CacheInvalidateAllInterceptor.class),
new AdditionalBeanBuildItem(CacheInvalidateInterceptor.class),
new AdditionalBeanBuildItem(CacheResultInterceptor.class));
}

@BuildStep(onlyIf = CacheEnabled.class)
@BuildStep
ValidationErrorBuildItem validateBeanDeployment(ValidationPhaseBuildItem validationPhase) {
AnnotationStore annotationStore = validationPhase.getContext().get(Key.ANNOTATION_STORE);
List<Throwable> throwables = new ArrayList<>();
Expand All @@ -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<AdditionalCacheNameBuildItem> additionalCacheNames) {
Set<String> cacheNames = getCacheNames(combinedIndex.getIndex());
for (AdditionalCacheNameBuildItem additionalCacheName : additionalCacheNames) {
cacheNames.add(additionalCacheName.getName());
}
switch (config.type) {
case CacheDeploymentConstants.CAFFEINE_CACHE_TYPE:
Set<CaffeineCacheInfo> cacheInfos = CaffeineCacheInfoBuilder.build(cacheNames, config);
caffeineRecorder.buildCaches(beanContainer.getValue(), cacheInfos);
break;
default:
throw new DeploymentException("Unknown cache type: " + config.type);
Set<String> 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<CaffeineCacheInfo> 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<String> getCacheNames(IndexView index) {
private Set<String> getCacheNames(IndexView index, List<AdditionalCacheNameBuildItem> additionalCacheNames) {
Set<String> cacheNames = new HashSet<>();
for (DotName cacheAnnotation : API_METHODS_ANNOTATIONS) {
for (AnnotationInstance annotation : index.getAnnotations(cacheAnnotation)) {
Expand All @@ -112,15 +127,36 @@ private Set<String> 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<String> 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() + "]");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -10,18 +11,29 @@
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 {

@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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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) {
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 3f816ad

Please sign in to comment.