From c14cf3fb5f76d4d72d8475f6d9179a4da529fddc Mon Sep 17 00:00:00 2001 From: Lorenzo Vannucchi Date: Sat, 20 Apr 2024 15:31:30 +0200 Subject: [PATCH] feat: Implement skipping of not Rest parameters indexing processing using new build item [The implementation brings a new build item that extension can produce in order to allow Rest resource methods with parameters that are not related to Rest] --- .../deployment/ResteasyReactiveProcessor.java | 7 ++- .../basic/AllowNotResteasyParametersTest.java | 60 +++++++++++++++++++ .../resource/MixedParameterResource.java | 44 ++++++++++++++ .../spi/AllowNotRestParametersBuildItem.java | 11 ++++ .../common/processor/EndpointIndexer.java | 25 +++++++- .../startup/RuntimeResourceDeployment.java | 2 + 6 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/AllowNotResteasyParametersTest.java create mode 100644 extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/MixedParameterResource.java create mode 100644 extensions/resteasy-reactive/rest/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/server/spi/AllowNotRestParametersBuildItem.java diff --git a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 8fbaa8e3f20db1..f260b04ff23b2e 100644 --- a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -190,6 +190,7 @@ import io.quarkus.resteasy.reactive.server.spi.NonBlockingReturnTypeBuildItem; import io.quarkus.resteasy.reactive.server.spi.PreExceptionMapperHandlerBuildItem; import io.quarkus.resteasy.reactive.server.spi.ResumeOn404BuildItem; +import io.quarkus.resteasy.reactive.server.spi.AllowNotRestParametersBuildItem; import io.quarkus.resteasy.reactive.spi.CustomExceptionMapperBuildItem; import io.quarkus.resteasy.reactive.spi.DynamicFeatureBuildItem; import io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem; @@ -413,8 +414,8 @@ public void setupEndpoints(ApplicationIndexBuildItem applicationIndexBuildItem, List contextTypeBuildItems, CompiledJavaVersionBuildItem compiledJavaVersionBuildItem, ResourceInterceptorsBuildItem resourceInterceptorsBuildItem, - Capabilities capabilities) - throws NoSuchMethodException { + Capabilities capabilities, + Optional allowNotRestParametersBuildItem) { if (!resourceScanningResultBuildItem.isPresent()) { // no detected @Path, bail out @@ -634,6 +635,8 @@ public Supplier apply(ClassInfo classInfo) { } }); + serverEndpointIndexerBuilder.skipNotRestParameters(allowNotRestParametersBuildItem.isPresent()); + if (!serverDefaultProducesHandlers.isEmpty()) { List handlers = new ArrayList<>(serverDefaultProducesHandlers.size()); for (ServerDefaultProducesHandlerBuildItem bi : serverDefaultProducesHandlers) { diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/AllowNotResteasyParametersTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/AllowNotResteasyParametersTest.java new file mode 100644 index 00000000000000..8e079448fc1d64 --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/AllowNotResteasyParametersTest.java @@ -0,0 +1,60 @@ +package io.quarkus.resteasy.reactive.server.test.resource.basic; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +import java.util.function.Consumer; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; +import io.quarkus.resteasy.reactive.server.spi.AllowNotRestParametersBuildItem; +import io.quarkus.resteasy.reactive.server.test.resource.basic.resource.MixedParameterResource; +import io.quarkus.test.QuarkusUnitTest; + +/** + * @tpSubChapter Resources + * @tpChapter Integration tests + * @tpTestCaseDetails Test resources with not all method parameters related to RESTEasy. + */ +@DisplayName("Allow Not RESTEasy Method Parameters") +public class AllowNotResteasyParametersTest { + + @RegisterExtension + static QuarkusUnitTest quarkusUnitTest = new QuarkusUnitTest() + .addBuildChainCustomizer(new Consumer() { + @Override + public void accept(BuildChainBuilder buildChainBuilder) { + buildChainBuilder.addBuildStep(new BuildStep() { + @Override + public void execute(BuildContext context) { + context.produce(new AllowNotRestParametersBuildItem()); + } + }).produces(AllowNotRestParametersBuildItem.class).build(); + } + }) + .withApplicationRoot((jar) -> jar + .addClass(MixedParameterResource.class)); + + @Test + @DisplayName("Test Resource Method with one param not related to RESTEasy") + public void shouldOkEvenNotResteasyParameterPresence() { + given() + .body("value") + .post("/" + MixedParameterResource.class.getSimpleName() + "/mixed?foo=bar") + .then().statusCode(200).body(is("bar.value")); + } + + @Test + @DisplayName("Test Resource Method with only one param not related to RESTEasy") + public void shouldOkEvenNotResteasySingleParameterPresence() { + given() + .body("value") + .get("/" + MixedParameterResource.class.getSimpleName() + "/single") + .then().statusCode(200); + } +} diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/MixedParameterResource.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/MixedParameterResource.java new file mode 100644 index 00000000000000..560d91196074c0 --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/resource/basic/resource/MixedParameterResource.java @@ -0,0 +1,44 @@ +package io.quarkus.resteasy.reactive.server.test.resource.basic.resource; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; + +@Path("/MixedParameterResource") +public class MixedParameterResource { + + @POST + @Path("/mixed") + public String mixedParam(@OtherAnnotation MyOtherObject otherObject, @QueryParam("foo") String query, String body) { + return query.concat(".").concat(body); + } + + @GET + @Path("/single") + public String singleParam(@OtherAnnotation MyOtherObject otherObject) { + return "ok"; + } + + public static class MyOtherObject { + String param; + + public String getParam() { + return param; + } + + public void setParam(String param) { + this.param = param; + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ ElementType.FIELD, ElementType.PARAMETER }) + public @interface OtherAnnotation { + } +} diff --git a/extensions/resteasy-reactive/rest/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/server/spi/AllowNotRestParametersBuildItem.java b/extensions/resteasy-reactive/rest/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/server/spi/AllowNotRestParametersBuildItem.java new file mode 100644 index 00000000000000..e519d85bf41407 --- /dev/null +++ b/extensions/resteasy-reactive/rest/spi-deployment/src/main/java/io/quarkus/resteasy/reactive/server/spi/AllowNotRestParametersBuildItem.java @@ -0,0 +1,11 @@ +package io.quarkus.resteasy.reactive.server.spi; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * A build item which extensions can generate when they want to allow RESTEasy Reactive methods + * with parameters that are annotated with not REST annotations. This allows RESTEasy Reactive to let mixed parameters coexist + * within resource methods signature + */ +public final class AllowNotRestParametersBuildItem extends SimpleBuildItem { +} 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 c2eb42017bb18f..09023b8a051c37 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 @@ -25,6 +25,7 @@ import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.HTTP_HEADERS; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.INSTANT; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.INTEGER; +import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.JAX_RS_ANNOTATIONS_FOR_FIELDS; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.LIST; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.LOCAL_DATE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.LOCAL_DATE_TIME; @@ -237,6 +238,8 @@ public abstract class EndpointIndexer> isDisabledCreator; private final Predicate> skipMethodParameter; + private final boolean skipNotRestParameters; + private SerializerScanningResult serializerScanningResult; protected EndpointIndexer(Builder builder) { @@ -262,6 +265,7 @@ protected EndpointIndexer(Builder builder) { this.targetJavaVersion = builder.targetJavaVersion; this.isDisabledCreator = builder.isDisabledCreator; this.skipMethodParameter = builder.skipMethodParameter; + this.skipNotRestParameters = builder.skipNotRestParameters; } public Optional createEndpoints(ClassInfo classInfo, boolean considerApplication) { @@ -576,7 +580,7 @@ private ResourceMethod createResourceMethod(ClassInfo currentClassInfo, ClassInf Type paramType = currentMethodInfo.parameterType(i); String errorLocation = "method " + currentMethodInfo + " on class " + currentMethodInfo.declaringClass(); - if (skipParameter(anns)) { + if (skipParameter(anns) || skipNotRestParameters(skipNotRestParameters).apply(anns)) { parameterResult = createIndexedParam() .setCurrentClassInfo(currentClassInfo) .setActualEndpointInfo(actualEndpointInfo) @@ -782,6 +786,18 @@ protected void warnAboutMissUsedBodyParameter(DotName httpMethod, MethodInfo met + methodInfo.declaringClass().name() + "#" + methodInfo + "'"); } + private Function, Boolean> skipNotRestParameters(boolean skipAllNotMethodParameter) { + return new Function, Boolean>() { + @Override + public Boolean apply(Map anns) { + if (skipAllNotMethodParameter) { + return anns.size() > 0 && JAX_RS_ANNOTATIONS_FOR_FIELDS.stream().noneMatch(dotName -> anns.containsKey(dotName)); + } + return false; + } + }; + } + protected boolean skipParameter(Map anns) { return skipMethodParameter != null && skipMethodParameter.test(anns); } @@ -1678,6 +1694,8 @@ public boolean handleMultipartForReturnType(AdditionalWriters additionalWriters, private Function> isDisabledCreator = null; private Predicate> skipMethodParameter = null; + + private boolean skipNotRestParameters = false; public B setMultipartReturnTypeIndexerExtension(MultipartReturnTypeIndexerExtension multipartReturnTypeHandler) { this.multipartReturnTypeIndexerExtension = multipartReturnTypeHandler; @@ -1801,6 +1819,11 @@ public B setSkipMethodParameter( return (B) this; } + public B skipNotRestParameters(boolean skipNotRestParameters) { + this.skipNotRestParameters = skipNotRestParameters; + return (B) this; + } + public abstract T build(); } 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 e1bb21511e916b..041796659ddf34 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 @@ -353,6 +353,8 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, addHandlers(handlers, clazz, method, info, HandlerChainCustomizer.Phase.RESOLVE_METHOD_PARAMETERS); for (int i = 0; i < parameters.length; i++) { ServerMethodParameter param = (ServerMethodParameter) parameters[i]; + if (param.parameterType.equals(ParameterType.SKIPPED)) + continue; ParameterExtractor extractor = parameterExtractor(pathParameterIndexes, locatableResource, param); ParameterConverter converter = null; ParamConverterProviders paramConverterProviders = info.getParamConverterProviders();