Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Honor Cache and NoCache annotations on implementations of a JAX-RS interface #39052

Merged
merged 1 commit into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,59 @@ 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]));
if (actualMethod == null) {
// method from interface not overridden in actual class. Use original method from interface.
actualMethod = method;
}

// 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 +100,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
Loading