Skip to content

Commit

Permalink
Honor Cache and NoCache annotations on implementations of a JaxRS int…
Browse files Browse the repository at this point in the history
…erface
  • Loading branch information
danielbobbert committed Feb 29, 2024
1 parent 11d6830 commit 7bbc170
Show file tree
Hide file tree
Showing 10 changed files with 840 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.jboss.resteasy.reactive.server.processor.scanning;

import java.util.Arrays;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -10,6 +10,7 @@
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
import org.jboss.resteasy.reactive.Cache;
import org.jboss.resteasy.reactive.NoCache;
import org.jboss.resteasy.reactive.common.processor.EndpointIndexer;
Expand All @@ -27,32 +28,55 @@ public class CacheControlScanner implements MethodScanner {
@Override
public List<HandlerChainCustomizer> scan(MethodInfo method, ClassInfo actualEndpointClass,
Map<String, Object> methodContext) {

ClassInfo currentClassInfo = method.declaringClass();

// Check whether the actualEndpointClass is implementing an interface containing the method
// In this case, allow annotations on the implementation itself to take precedence over annotations on the
// interface.
if (!actualEndpointClass.equals(currentClassInfo) && Modifier.isInterface(currentClassInfo.flags())) {
MethodInfo actualMethod = actualEndpointClass.method(method.name(), method.parameterTypes().toArray(new Type[0]));

// first check bean implementation
List<HandlerChainCustomizer> customizers = doScan(actualMethod, actualEndpointClass, methodContext);
if (customizers.isEmpty()) {
// if no annotations where found on implementation, check the interface
customizers = doScan(method, currentClassInfo, methodContext);
}
return customizers;
} else {
return doScan(method, actualEndpointClass, methodContext);
}
}

private List<HandlerChainCustomizer> doScan(MethodInfo methodInfo, ClassInfo classInfo,
Map<String, Object> methodContext) {
AnnotationStore annotationStore = (AnnotationStore) methodContext.get(EndpointIndexer.METHOD_CONTEXT_ANNOTATION_STORE);
ExtendedCacheControl cacheControl = noCacheToCacheControl(annotationStore.getAnnotation(method, NO_CACHE));
ExtendedCacheControl cacheControl = noCacheToCacheControl(annotationStore.getAnnotation(methodInfo, NO_CACHE));
if (cacheControl != null) {
if (method.annotation(CACHE) != null) {
if (methodInfo.annotation(CACHE) != null) {
throw new IllegalStateException(
"A resource method cannot be simultaneously annotated with '@Cache' and '@NoCache'. Offending method is '"
+ method.name() + "' of class '" + method.declaringClass().name() + "'");
+ methodInfo.name() + "' of class '" + methodInfo.declaringClass().name() + "'");
}
return cacheControlToCustomizerList(cacheControl);
} else {
cacheControl = noCacheToCacheControl(annotationStore.getAnnotation(actualEndpointClass, NO_CACHE));
cacheControl = noCacheToCacheControl(annotationStore.getAnnotation(classInfo, NO_CACHE));
if (cacheControl != null) {
if (actualEndpointClass.declaredAnnotation(CACHE) != null) {
if (classInfo.declaredAnnotation(CACHE) != null) {
throw new IllegalStateException(
"A resource class cannot be simultaneously annotated with '@Cache' and '@NoCache'. Offending class is '"
+ actualEndpointClass.name() + "'");
+ classInfo.name() + "'");
}
return cacheControlToCustomizerList(cacheControl);
}
}

cacheControl = cacheToCacheControl(method.annotation(CACHE));
cacheControl = cacheToCacheControl(methodInfo.annotation(CACHE));
if (cacheControl != null) {
return cacheControlToCustomizerList(cacheControl);
} else {
cacheControl = cacheToCacheControl(actualEndpointClass.declaredAnnotation(CACHE));
cacheControl = cacheToCacheControl(classInfo.declaredAnnotation(CACHE));
if (cacheControl != null) {
return cacheControlToCustomizerList(cacheControl);
}
Expand All @@ -72,7 +96,7 @@ private ExtendedCacheControl noCacheToCacheControl(AnnotationInstance noCacheIns
if (fieldsValue != null) {
String[] fields = fieldsValue.asStringArray();
if ((fields != null) && (fields.length > 0)) {
cacheControl.getNoCacheFields().addAll(Arrays.asList(fields));
cacheControl.getNoCacheFields().addAll(List.of(fields));

}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.jboss.resteasy.reactive.server.vertx.test.cache;

import static org.hamcrest.CoreMatchers.equalTo;

import java.util.function.Consumer;
import java.util.function.Supplier;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.jboss.resteasy.reactive.Cache;
import org.jboss.resteasy.reactive.server.processor.ResteasyReactiveDeploymentManager;
import org.jboss.resteasy.reactive.server.processor.scanning.CacheControlScanner;
import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest;
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.restassured.RestAssured;

public class CacheOnClassAndMethodsImplementationTest {

@RegisterExtension
static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest()
.addScanCustomizer(new Consumer<ResteasyReactiveDeploymentManager.ScanStep>() {
@Override
public void accept(ResteasyReactiveDeploymentManager.ScanStep scanStep) {
scanStep.addMethodScanner(new CacheControlScanner());
}
})
.setArchiveProducer(new Supplier<>() {
@Override
public JavaArchive get() {
return ShrinkWrap.create(JavaArchive.class)
.addClasses(ResourceWithCache.class);
}
});

@Test
public void testWith() {
RestAssured.get("/test/with")
.then()
.statusCode(200)
.body(equalTo("with"))
.header("Cache-Control", "no-store");
}

@Test
public void testWithout() {
RestAssured.get("/test/without")
.then()
.statusCode(200)
.body(equalTo("without"))
.header("Cache-Control", "no-cache, no-transform, proxy-revalidate, s-maxage=100");
}

@Path("test")
public interface IResourceWithCache {

@Path("with")
@GET
String with();

@Path("without")
@GET
String without();
}

@Cache(sMaxAge = 100, noTransform = true, proxyRevalidate = true, noCache = true)
public static class ResourceWithCache implements IResourceWithCache {

@Override
@Cache(noStore = true)
public String with() {
return "with";
}

@Override
public String without() {
return "without";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.jboss.resteasy.reactive.server.vertx.test.cache;

import static org.hamcrest.CoreMatchers.equalTo;

import java.util.function.Consumer;
import java.util.function.Supplier;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.jboss.resteasy.reactive.Cache;
import org.jboss.resteasy.reactive.server.processor.ResteasyReactiveDeploymentManager;
import org.jboss.resteasy.reactive.server.processor.scanning.CacheControlScanner;
import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest;
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.restassured.RestAssured;

public class CacheOnClassAndMethodsInterfaceTest {

@RegisterExtension
static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest()
.addScanCustomizer(new Consumer<ResteasyReactiveDeploymentManager.ScanStep>() {
@Override
public void accept(ResteasyReactiveDeploymentManager.ScanStep scanStep) {
scanStep.addMethodScanner(new CacheControlScanner());
}
})
.setArchiveProducer(new Supplier<>() {
@Override
public JavaArchive get() {
return ShrinkWrap.create(JavaArchive.class)
.addClasses(ResourceWithCache.class);
}
});

@Test
public void testWith() {
RestAssured.get("/test/with")
.then()
.statusCode(200)
.body(equalTo("with"))
.header("Cache-Control", "no-store");
}

@Test
public void testWithout() {
RestAssured.get("/test/without")
.then()
.statusCode(200)
.body(equalTo("without"))
.header("Cache-Control", "no-cache, no-transform, proxy-revalidate, s-maxage=100");
}

@Path("test")
@Cache(sMaxAge = 100, noTransform = true, proxyRevalidate = true, noCache = true)
public interface IResourceWithCache {

@Path("with")
@Cache(noStore = true)
@GET
String with();

@Path("without")
@GET
String without();
}

public static class ResourceWithCache implements IResourceWithCache {

@Override
public String with() {
return "with";
}

@Override
public String without() {
return "without";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.jboss.resteasy.reactive.server.vertx.test.cache;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.nullValue;

import java.util.function.Consumer;
import java.util.function.Supplier;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.jboss.resteasy.reactive.Cache;
import org.jboss.resteasy.reactive.server.processor.ResteasyReactiveDeploymentManager;
import org.jboss.resteasy.reactive.server.processor.scanning.CacheControlScanner;
import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest;
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.restassured.RestAssured;

public class CacheOnMethodsImplementationTest {

@RegisterExtension
static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest()
.addScanCustomizer(new Consumer<ResteasyReactiveDeploymentManager.ScanStep>() {
@Override
public void accept(ResteasyReactiveDeploymentManager.ScanStep scanStep) {
scanStep.addMethodScanner(new CacheControlScanner());
}
})
.setArchiveProducer(new Supplier<>() {
@Override
public JavaArchive get() {
return ShrinkWrap.create(JavaArchive.class)
.addClasses(IResourceWithCache.class, ResourceWithCache.class);
}
});

@Test
public void testWith() {
RestAssured.get("/test/with")
.then()
.statusCode(200)
.body(equalTo("with"))
.header("Cache-Control", "must-revalidate, no-store, max-age=100, private");
}

@Test
public void testWithout() {
RestAssured.get("/test/without")
.then()
.statusCode(200)
.body(equalTo("without"))
.header("Cache-Control", nullValue());
}

@Path("test")
public interface IResourceWithCache {

@Path("with")
@GET
String with();

@Path("without")
@GET
String without();
}

public static class ResourceWithCache implements IResourceWithCache {

@Override
@Cache(maxAge = 100, noStore = true, mustRevalidate = true, isPrivate = true)
public String with() {
return "with";
}

@Override
public String without() {
return "without";
}
}
}
Loading

0 comments on commit 7bbc170

Please sign in to comment.