From 04e20f67f3c124f45a59dd6210928a53fde465c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20=C3=89pardaud?= Date: Tue, 13 Dec 2022 11:01:06 +0100 Subject: [PATCH] Deprecate `@ServerRequestFilter(withBody)` in favour of `@WithFormRead` You can also place it on endpoint methods. Fixes #22444 --- .../ResteasyReactiveCommonProcessor.java | 2 +- .../spi/ContainerRequestFilterBuildItem.java | 16 +-- .../ResteasyReactiveScanningProcessor.java | 2 +- .../ImpliedReadBodyRequestFilterTest.java | 119 ++++++++++++++++++ .../ReadBodyRequestFilterTest.java | 41 +++++- .../customproviders/WithFormBodyTest.java | 64 ++++++++++ .../common/processor/EndpointIndexer.java | 3 +- .../processor/ResteasyReactiveDotNames.java | 3 + .../common/model/ResourceInterceptor.java | 10 +- .../generation/filters/FilterGeneration.java | 29 +++-- .../util/ResteasyReactiveServerDotNames.java | 4 +- .../reactive/server/ServerRequestFilter.java | 3 + .../reactive/server/WithFormRead.java | 18 +++ .../startup/RuntimeDeploymentManager.java | 2 +- .../startup/RuntimeInterceptorDeployment.java | 4 +- .../startup/RuntimeResourceDeployment.java | 22 ++-- .../server/handlers/FormBodyHandler.java | 19 +-- .../ResourceRequestFilterHandler.java | 10 +- 18 files changed, 310 insertions(+), 61 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ImpliedReadBodyRequestFilterTest.java create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/WithFormBodyTest.java create mode 100644 independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/WithFormRead.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java index 0c3c5b8ad89e5..fa92296e337b1 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/deployment/src/main/java/io/quarkus/resteasy/reactive/common/deployment/ResteasyReactiveCommonProcessor.java @@ -187,7 +187,7 @@ protected void registerInterceptors( if (filterItem instanceof ContainerRequestFilterBuildItem) { ContainerRequestFilterBuildItem crfbi = (ContainerRequestFilterBuildItem) filterItem; interceptor.setNonBlockingRequired(crfbi.isNonBlockingRequired()); - interceptor.setReadBody(crfbi.isReadBody()); + interceptor.setWithFormRead(crfbi.isWithFormRead()); MethodInfo filterSourceMethod = crfbi.getFilterSourceMethod(); if (filterSourceMethod != null) { interceptor.metadata = Map.of(FILTER_SOURCE_METHOD_METADATA_KEY, filterSourceMethod); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/spi/ContainerRequestFilterBuildItem.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/spi/ContainerRequestFilterBuildItem.java index 6ae524f8145e2..7f7a1faed4c92 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/spi/ContainerRequestFilterBuildItem.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-common/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/spi/ContainerRequestFilterBuildItem.java @@ -6,7 +6,7 @@ public final class ContainerRequestFilterBuildItem extends AbstractInterceptorBu private final boolean preMatching; private final boolean nonBlockingRequired; - private final boolean readBody; + private final boolean withFormRead; private final MethodInfo filterSourceMethod; @@ -14,7 +14,7 @@ protected ContainerRequestFilterBuildItem(Builder builder) { super(builder); this.preMatching = builder.preMatching; this.nonBlockingRequired = builder.nonBlockingRequired; - this.readBody = builder.readBody; + this.withFormRead = builder.withFormRead; this.filterSourceMethod = builder.filterSourceMethod; } @@ -22,7 +22,7 @@ public ContainerRequestFilterBuildItem(String className) { super(className); this.preMatching = false; this.nonBlockingRequired = false; - this.readBody = false; + this.withFormRead = false; this.filterSourceMethod = null; } @@ -34,8 +34,8 @@ public boolean isNonBlockingRequired() { return nonBlockingRequired; } - public boolean isReadBody() { - return readBody; + public boolean isWithFormRead() { + return withFormRead; } public MethodInfo getFilterSourceMethod() { @@ -45,7 +45,7 @@ public MethodInfo getFilterSourceMethod() { public static final class Builder extends AbstractInterceptorBuildItem.Builder { boolean preMatching = false; boolean nonBlockingRequired = false; - boolean readBody = false; + boolean withFormRead = false; MethodInfo filterSourceMethod = null; @@ -63,8 +63,8 @@ public Builder setNonBlockingRequired(boolean nonBlockingRequired) { return this; } - public Builder setReadBody(boolean readBody) { - this.readBody = readBody; + public Builder setWithFormRead(boolean withFormRead) { + this.withFormRead = withFormRead; return this; } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java index c535061abd937..42d5b78e2d8b9 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveScanningProcessor.java @@ -370,7 +370,7 @@ public void handleCustomAnnotatedMethods( .setPriority(generated.getPriority()) .setPreMatching(generated.isPreMatching()) .setNonBlockingRequired(generated.isNonBlocking()) - .setReadBody(generated.isReadBody()) + .setWithFormRead(generated.isWithFormRead()) .setFilterSourceMethod(generated.getFilterSourceMethod()); if (!generated.getNameBindingNames().isEmpty()) { builder.setNameBindingNames(generated.getNameBindingNames()); diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ImpliedReadBodyRequestFilterTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ImpliedReadBodyRequestFilterTest.java new file mode 100644 index 0000000000000..605cb4677e18b --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ImpliedReadBodyRequestFilterTest.java @@ -0,0 +1,119 @@ +package io.quarkus.resteasy.reactive.server.test.customproviders; + +import java.util.function.Supplier; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.HttpHeaders; + +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.RestForm; +import org.jboss.resteasy.reactive.RestQuery; +import org.jboss.resteasy.reactive.server.ServerRequestFilter; +import org.jboss.resteasy.reactive.server.WithFormRead; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveContainerRequestContext; +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.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ImpliedReadBodyRequestFilterTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(HelloResource.class); + } + }); + + @Test + public void testMethodWithBody() { + RestAssured.with() + .formParam("name", "Quarkus") + .post("/hello") + .then().body(Matchers.equalTo("hello Quarkus!!!!!!!")); + } + + @Test + public void testMethodWithUndeclaredBody() { + RestAssured.with() + .formParam("name", "Quarkus") + .post("/hello/empty") + .then().body(Matchers.equalTo("hello !!!!!!!")); + } + + @Test + public void testMethodWithStringBody() { + // make sure that a form-reading filter doesn't prevent non-form request bodies from being deserialised + RestAssured.with() + .formParam("name", "Quarkus") + .post("/hello/string") + .then().body(Matchers.equalTo("hello name=Quarkus!!!!!!!")); + RestAssured.with() + .body("Quarkus") + .post("/hello/string") + .then().body(Matchers.equalTo("hello Quarkus?")); + } + + @Test + public void testMethodWithoutBody() { + RestAssured.with() + .queryParam("name", "Quarkus") + .get("/hello") + .then().body(Matchers.equalTo("hello Quarkus!")); + } + + @Path("hello") + public static class HelloResource { + + @POST + public String helloPost(@RestForm String name, HttpHeaders headers) { + return "hello " + name + headers.getHeaderString("suffix"); + } + + @Path("empty") + @POST + public String helloEmptyPost(HttpHeaders headers) { + return "hello " + headers.getHeaderString("suffix"); + } + + @Path("string") + @POST + public String helloStringPost(String body, HttpHeaders headers) { + return "hello " + body + headers.getHeaderString("suffix"); + } + + @GET + public String helloGet(@RestQuery String name, HttpHeaders headers) { + return "hello " + name + headers.getHeaderString("suffix"); + } + } + + public static class Filters { + + @WithFormRead + @ServerRequestFilter + public void addSuffix(ResteasyReactiveContainerRequestContext containerRequestContext) { + ResteasyReactiveRequestContext rrContext = (ResteasyReactiveRequestContext) containerRequestContext + .getServerRequestContext(); + if (containerRequestContext.getMethod().equals("POST")) { + String nameFormParam = (String) rrContext.getFormParameter("name", true, false); + if (nameFormParam != null) { + containerRequestContext.getHeaders().putSingle("suffix", "!".repeat(nameFormParam.length())); + } else { + containerRequestContext.getHeaders().putSingle("suffix", "?"); + } + } else { + containerRequestContext.getHeaders().putSingle("suffix", "!"); + } + } + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ReadBodyRequestFilterTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ReadBodyRequestFilterTest.java index 86aca31051c54..952260f155988 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ReadBodyRequestFilterTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/ReadBodyRequestFilterTest.java @@ -11,6 +11,7 @@ import org.jboss.resteasy.reactive.RestForm; import org.jboss.resteasy.reactive.RestQuery; import org.jboss.resteasy.reactive.server.ServerRequestFilter; +import org.jboss.resteasy.reactive.server.WithFormRead; import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveContainerRequestContext; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -41,6 +42,27 @@ public void testMethodWithBody() { .then().body(Matchers.equalTo("hello Quarkus!!!!!!!")); } + @Test + public void testMethodWithUndeclaredBody() { + RestAssured.with() + .formParam("name", "Quarkus") + .post("/hello/empty") + .then().body(Matchers.equalTo("hello !!!!!!!")); + } + + @Test + public void testMethodWithStringBody() { + // make sure that a form-reading filter doesn't prevent non-form request bodies from being deserialised + RestAssured.with() + .formParam("name", "Quarkus") + .post("/hello/string") + .then().body(Matchers.equalTo("hello name=Quarkus!!!!!!!")); + RestAssured.with() + .body("Quarkus") + .post("/hello/string") + .then().body(Matchers.equalTo("hello Quarkus?")); + } + @Test public void testMethodWithoutBody() { RestAssured.with() @@ -57,6 +79,18 @@ public String helloPost(@RestForm String name, HttpHeaders headers) { return "hello " + name + headers.getHeaderString("suffix"); } + @Path("empty") + @POST + public String helloEmptyPost(HttpHeaders headers) { + return "hello " + headers.getHeaderString("suffix"); + } + + @Path("string") + @POST + public String helloStringPost(String body, HttpHeaders headers) { + return "hello " + body + headers.getHeaderString("suffix"); + } + @GET public String helloGet(@RestQuery String name, HttpHeaders headers) { return "hello " + name + headers.getHeaderString("suffix"); @@ -65,13 +99,18 @@ public String helloGet(@RestQuery String name, HttpHeaders headers) { public static class Filters { + @WithFormRead @ServerRequestFilter(readBody = true) public void addSuffix(ResteasyReactiveContainerRequestContext containerRequestContext) { ResteasyReactiveRequestContext rrContext = (ResteasyReactiveRequestContext) containerRequestContext .getServerRequestContext(); if (containerRequestContext.getMethod().equals("POST")) { String nameFormParam = (String) rrContext.getFormParameter("name", true, false); - containerRequestContext.getHeaders().putSingle("suffix", "!".repeat(nameFormParam.length())); + if (nameFormParam != null) { + containerRequestContext.getHeaders().putSingle("suffix", "!".repeat(nameFormParam.length())); + } else { + containerRequestContext.getHeaders().putSingle("suffix", "?"); + } } else { containerRequestContext.getHeaders().putSingle("suffix", "!"); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/WithFormBodyTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/WithFormBodyTest.java new file mode 100644 index 0000000000000..bc89943ad593a --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/WithFormBodyTest.java @@ -0,0 +1,64 @@ +package io.quarkus.resteasy.reactive.server.test.customproviders; + +import java.util.function.Supplier; + +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +import org.hamcrest.Matchers; +import org.jboss.resteasy.reactive.RestForm; +import org.jboss.resteasy.reactive.server.WithFormRead; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.spi.ServerRequestContext; +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.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class WithFormBodyTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(HelloResource.class); + } + }); + + @Test + public void testMethodWithBody() { + RestAssured.with() + .formParam("name", "Quarkus") + .post("/hello") + .then().body(Matchers.equalTo("hello Quarkus")); + } + + @Test + public void testMethodWithUndeclaredBody() { + RestAssured.with() + .formParam("name", "Quarkus") + .post("/hello/empty") + .then().body(Matchers.equalTo("hello Quarkus")); + } + + @Path("hello") + public static class HelloResource { + + @POST + public String helloPost(@RestForm String name) { + return "hello " + name; + } + + @WithFormRead + @Path("empty") + @POST + public String helloEmptyPost(ServerRequestContext requestContext) { + return "hello " + ((ResteasyReactiveRequestContext) requestContext).getFormParameter("name", true, false); + } + } +} diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index 8669075c42caa..4a96c60416859 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -570,7 +570,8 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf basicResourceClassInfo.getConsumes()); boolean suspended = false; boolean sse = false; - boolean formParamRequired = false; + boolean formParamRequired = getAnnotationStore().getAnnotation(currentMethodInfo, + ResteasyReactiveDotNames.WITH_FORM_READ) != null; Set fileFormNames = new HashSet<>(); Type bodyParamType = null; TypeArgMapper typeArgMapper = new TypeArgMapper(currentMethodInfo.declaringClass(), index); diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java index 8a7efd8526c8b..ab185a22ab89f 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/ResteasyReactiveDotNames.java @@ -261,6 +261,9 @@ public final class ResteasyReactiveDotNames { public static final DotName RESTEASY_REACTIVE_CONTAINER_REQUEST_CONTEXT = DotName .createSimple("org.jboss.resteasy.reactive.server.spi.ResteasyReactiveContainerRequestContext"); + public static final DotName WITH_FORM_READ = DotName + .createSimple("org.jboss.resteasy.reactive.server.WithFormRead"); + public static final DotName OBJECT = DotName.createSimple(Object.class.getName()); public static final DotName CONTINUATION = DotName.createSimple("kotlin.coroutines.Continuation"); diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceInterceptor.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceInterceptor.java index 1092d32cc6dca..146b956b24fee 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceInterceptor.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceInterceptor.java @@ -16,7 +16,7 @@ public class ResourceInterceptor private BeanFactory factory; private int priority = Priorities.USER; // default priority as defined by spec private boolean nonBlockingRequired; // whether or not @NonBlocking was specified on the class - private boolean readBody; // whether or not 'readBody' was set true for this filter + private boolean withFormRead; // whether or not '@WithFormRead' was set on this filter /** * The class names of the {@code @NameBinding} annotations that the method is annotated with. @@ -76,12 +76,12 @@ public void setNonBlockingRequired(boolean nonBlockingRequired) { this.nonBlockingRequired = nonBlockingRequired; } - public boolean isReadBody() { - return readBody; + public boolean isWithFormRead() { + return withFormRead; } - public void setReadBody(boolean readBody) { - this.readBody = readBody; + public void setWithFormRead(boolean withFormRead) { + this.withFormRead = withFormRead; } // spec says that writer interceptors are sorted in ascending order diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/filters/FilterGeneration.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/filters/FilterGeneration.java index 9d07114902225..7b6508ecc7713 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/filters/FilterGeneration.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/generation/filters/FilterGeneration.java @@ -19,6 +19,7 @@ import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.server.processor.util.GeneratedClass; import org.jboss.resteasy.reactive.server.processor.util.GeneratedClassOutput; +import org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames; public class FilterGeneration { public static List generate(IndexView index, Set unwrappableTypes, @@ -37,6 +38,7 @@ public static List generate(IndexView index, Set unwra boolean preMatching = false; boolean nonBlockingRequired = false; boolean readBody = false; + boolean withFormRead = methodInfo.hasAnnotation(ResteasyReactiveServerDotNames.WITH_FORM_READ); Set nameBindingNames = new HashSet<>(); AnnotationValue priorityValue = instance.value("priority"); @@ -56,10 +58,17 @@ public static List generate(IndexView index, Set unwra readBody = readBodyValue.asBoolean(); } - if (readBody && preMatching) { - throw new IllegalStateException( - "Setting both 'readBody' and 'preMatching' to 'true' on '@ServerRequestFilter' is invalid. Offending method is '" - + methodInfo.name() + "' of class '" + methodInfo.declaringClass().name() + "'"); + if (preMatching) { + if (readBody) { + throw new IllegalStateException( + "Setting both 'readBody' and 'preMatching' to 'true' on '@ServerRequestFilter' is invalid. Offending method is '" + + methodInfo.name() + "' of class '" + methodInfo.declaringClass().name() + "'"); + } + if (withFormRead) { + throw new IllegalStateException( + "Setting both '@WithFormRead' and 'preMatching' to 'true' on '@ServerRequestFilter' is invalid. Offending method is '" + + methodInfo.name() + "' of class '" + methodInfo.declaringClass().name() + "'"); + } } List annotations = methodInfo.annotations(); @@ -78,7 +87,7 @@ public static List generate(IndexView index, Set unwra } ret.add(new GeneratedFilter(output.getOutput(), generatedClassName, methodInfo.declaringClass().name().toString(), - true, priority, preMatching, nonBlockingRequired, nameBindingNames, readBody, methodInfo)); + true, priority, preMatching, nonBlockingRequired, nameBindingNames, withFormRead || readBody, methodInfo)); } for (AnnotationInstance instance : index .getAnnotations(SERVER_RESPONSE_FILTER)) { @@ -128,14 +137,14 @@ public static class GeneratedFilter { final boolean preMatching; final boolean nonBlocking; final Set nameBindingNames; - final boolean readBody; + final boolean withFormRead; final MethodInfo filterSourceMethod; public GeneratedFilter(List generatedClasses, String generatedClassName, String declaringClassName, boolean requestFilter, Integer priority, boolean preMatching, boolean nonBlocking, - Set nameBindingNames, boolean readBody, MethodInfo filterSourceMethod) { + Set nameBindingNames, boolean withFormRead, MethodInfo filterSourceMethod) { this.generatedClasses = generatedClasses; this.generatedClassName = generatedClassName; this.declaringClassName = declaringClassName; @@ -144,7 +153,7 @@ public GeneratedFilter(List generatedClasses, String generatedCl this.preMatching = preMatching; this.nonBlocking = nonBlocking; this.nameBindingNames = nameBindingNames; - this.readBody = readBody; + this.withFormRead = withFormRead; this.filterSourceMethod = filterSourceMethod; } @@ -180,8 +189,8 @@ public Set getNameBindingNames() { return nameBindingNames; } - public boolean isReadBody() { - return readBody; + public boolean isWithFormRead() { + return withFormRead; } public MethodInfo getFilterSourceMethod() { diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/util/ResteasyReactiveServerDotNames.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/util/ResteasyReactiveServerDotNames.java index 4fbe147aae4f3..bf6706327c8fb 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/util/ResteasyReactiveServerDotNames.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/util/ResteasyReactiveServerDotNames.java @@ -6,6 +6,7 @@ import org.jboss.resteasy.reactive.server.ServerRequestFilter; import org.jboss.resteasy.reactive.server.ServerResponseFilter; import org.jboss.resteasy.reactive.server.SimpleResourceInfo; +import org.jboss.resteasy.reactive.server.WithFormRead; import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveContainerRequestContext; import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyReader; import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter; @@ -25,5 +26,6 @@ public class ResteasyReactiveServerDotNames { public static final DotName QUARKUS_REST_CONTAINER_REQUEST_CONTEXT = DotName .createSimple(ResteasyReactiveContainerRequestContext.class.getName()); public static final DotName SIMPLIFIED_RESOURCE_INFO = DotName.createSimple(SimpleResourceInfo.class.getName()); - + public static final DotName WITH_FORM_READ = DotName + .createSimple(WithFormRead.class.getName()); } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/ServerRequestFilter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/ServerRequestFilter.java index bb8a6c2379d24..8e60ebb805a6d 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/ServerRequestFilter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/ServerRequestFilter.java @@ -113,6 +113,9 @@ * Resource Methods that the filter applies to, it will be executed in normal fashion. * * Also note that this setting and {@link ServerRequestFilter#preMatching()} cannot be both set to true. + * + * @deprecated use {@link WithFormRead} on your filter to force reading the form values before your filter is invoked. */ + @Deprecated boolean readBody() default false; } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/WithFormRead.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/WithFormRead.java new file mode 100644 index 0000000000000..3eef6995eee2c --- /dev/null +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/WithFormRead.java @@ -0,0 +1,18 @@ +package org.jboss.resteasy.reactive.server; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Forces the form body to be read and parsed before filters and the endpoint are invoked. This is only + * useful if your endpoint does not contain any declared form parameter, which would otherwise force + * the form body being read anyway. + * You can place this annotation on request filters as well as endpoints. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface WithFormRead { + +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java index e2966a4152ae9..41731c72a8cf9 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeDeploymentManager.java @@ -187,7 +187,7 @@ public BeanFactory.BeanInstance apply(Class aClass) { .entrySet()) { preMatchHandlers .add(new ResourceRequestFilterHandler(entry.getValue(), true, entry.getKey().isNonBlockingRequired(), - entry.getKey().isReadBody())); + entry.getKey().isWithFormRead())); } } for (int i = 0; i < info.getGlobalHandlerCustomizers().size(); i++) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeInterceptorDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeInterceptorDeployment.java index 719a87bbc61e5..89775a73b4620 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeInterceptorDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeInterceptorDeployment.java @@ -98,7 +98,7 @@ public RuntimeInterceptorDeployment(DeploymentInfo info, ConfigurationImpl confi .entrySet()) { globalRequestInterceptorHandlers .add(new ResourceRequestFilterHandler(entry.getValue(), false, entry.getKey().isNonBlockingRequired(), - entry.getKey().isReadBody())); + entry.getKey().isWithFormRead())); } InterceptorHandler globalInterceptorHandler = null; @@ -330,7 +330,7 @@ public List setupRequestFilterHandler() { .entrySet()) { handlers.add( new ResourceRequestFilterHandler(entry.getValue(), false, entry.getKey().isNonBlockingRequired(), - entry.getKey().isNonBlockingRequired())); + entry.getKey().isWithFormRead())); } } return handlers; diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index ecd7adcbc900a..08494b3d68ce1 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -242,7 +242,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, //spec doesn't seem to test this, but RESTEasy does not run request filters for both root and sub resources (which makes sense) //so only run request filters for methods that are leaf resources - i.e. have a HTTP method annotation so we ensure only one will run - boolean hasReadBodyRequestFilters = false; + boolean hasWithFormReadRequestFilters = false; if (method.getHttpMethod() != null) { List containerRequestFilterHandlers = interceptorDeployment .setupRequestFilterHandler(); @@ -257,13 +257,15 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, } else { handlers.add(handler); } - if (handler.isReadBody()) { - hasReadBodyRequestFilters = true; - } } } else { handlers.addAll(containerRequestFilterHandlers); } + for (ResourceRequestFilterHandler handler : containerRequestFilterHandlers) { + if (handler.isWithFormRead()) { + hasWithFormReadRequestFilters = true; + } + } } // some parameters need the body to be read @@ -280,28 +282,28 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, } } // form params can be everywhere (field, beanparam, param) - boolean checkReadBodyRequestFilters = false; - if (method.isFormParamRequired()) { + boolean checkWithFormReadRequestFilters = false; + if (method.isFormParamRequired() || hasWithFormReadRequestFilters) { // read the body as multipart in one go handlers.add(new FormBodyHandler(bodyParameter != null, executorSupplier, method.getFileFormNames())); - checkReadBodyRequestFilters = true; + checkWithFormReadRequestFilters = true; } else if (bodyParameter != null) { if (!defaultBlocking) { if (!method.isBlocking()) { // allow the body to be read by chunks handlers.add(new InputHandler(resteasyReactiveConfig.getInputBufferSize(), executorSupplier)); - checkReadBodyRequestFilters = true; + checkWithFormReadRequestFilters = true; } } } - if (checkReadBodyRequestFilters && hasReadBodyRequestFilters) { + if (checkWithFormReadRequestFilters && hasWithFormReadRequestFilters) { // we need to remove the corresponding filters from the handlers list and add them to its end in the same order List readBodyRequestFilters = new ArrayList<>(1); for (int i = handlers.size() - 2; i >= 0; i--) { var serverRestHandler = handlers.get(i); if (serverRestHandler instanceof ResourceRequestFilterHandler) { ResourceRequestFilterHandler resourceRequestFilterHandler = (ResourceRequestFilterHandler) serverRestHandler; - if (resourceRequestFilterHandler.isReadBody()) { + if (resourceRequestFilterHandler.isWithFormRead()) { readBodyRequestFilters.add(handlers.remove(i)); } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java index 579eae04a83a6..42c7ada9b433e 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FormBodyHandler.java @@ -21,7 +21,6 @@ import org.jboss.resteasy.reactive.server.core.multipart.MultiPartParserDefinition; import org.jboss.resteasy.reactive.server.spi.GenericRuntimeConfigurableServerRestHandler; import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration; -import org.jboss.resteasy.reactive.server.spi.ServerHttpRequest; public class FormBodyHandler implements GenericRuntimeConfigurableServerRestHandler { @@ -78,14 +77,12 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti requestContext.setFormData(existingParsedForm); return; } - ServerHttpRequest serverHttpRequest = requestContext.serverRequest(); + FormDataParser factory = formParserFactory.createParser(requestContext, fileFormNames); + if (factory == null) { + return; + } if (BlockingOperationSupport.isBlockingAllowed()) { //blocking IO approach - - FormDataParser factory = formParserFactory.createParser(requestContext, fileFormNames); - if (factory == null) { - return; - } CapturingInputStream cis = null; if (alsoSetInputStream) { // the TCK allows the body to be read as a form param and also as a body param @@ -99,10 +96,6 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti requestContext.setInputStream(new ByteArrayInputStream(cis.baos.toByteArray())); } } else if (alsoSetInputStream) { - FormDataParser factory = formParserFactory.createParser(requestContext, fileFormNames); - if (factory == null) { - return; - } requestContext.suspend(); executorSupplier.get().execute(new Runnable() { @Override @@ -119,10 +112,6 @@ public void run() { } }); } else { - FormDataParser factory = formParserFactory.createParser(requestContext, fileFormNames); - if (factory == null) { - return; - } //parse will auto resume factory.parse(); } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResourceRequestFilterHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResourceRequestFilterHandler.java index 7fbdca4e10962..6e19c70d8ae4a 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResourceRequestFilterHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResourceRequestFilterHandler.java @@ -11,14 +11,14 @@ public class ResourceRequestFilterHandler implements ServerRestHandler { private final ContainerRequestFilter filter; private final boolean preMatch; private final boolean nonBlockingRequired; - private final boolean readBody; + private final boolean withFormRead; public ResourceRequestFilterHandler(ContainerRequestFilter filter, boolean preMatch, boolean nonBlockingRequired, - boolean readBody) { + boolean withFormRead) { this.filter = filter; this.preMatch = preMatch; this.nonBlockingRequired = nonBlockingRequired; - this.readBody = readBody; + this.withFormRead = withFormRead; } public ContainerRequestFilter getFilter() { @@ -33,8 +33,8 @@ public boolean isNonBlockingRequired() { return nonBlockingRequired; } - public boolean isReadBody() { - return readBody; + public boolean isWithFormRead() { + return withFormRead; } @Override