From 7819ba8fb3c9c8dcf1078ecfc09898dcebda34e1 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 26 Aug 2022 14:05:26 +0300 Subject: [PATCH] Properly use ParamConverter to converting the values of collection types Fixes: #27515 (cherry picked from commit 2b85546cf4d65265645c210d3c05a5113a0ac579) --- .../test/providers/ParamConverterTest.java | 110 ++++++++++++++++++ .../parameters/converters/ListConverter.java | 5 + .../converters/ParameterConverter.java | 5 + .../parameters/converters/SetConverter.java | 5 + .../converters/SortedSetConverter.java | 5 + .../startup/RuntimeResourceDeployment.java | 34 +++++- 6 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ParamConverterTest.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ParamConverterTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ParamConverterTest.java new file mode 100644 index 0000000000000..e1f5c8fa6d979 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ParamConverterTest.java @@ -0,0 +1,110 @@ +package io.quarkus.resteasy.reactive.server.test.providers; + +import static io.restassured.RestAssured.get; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.ext.ParamConverter; +import javax.ws.rs.ext.ParamConverterProvider; +import javax.ws.rs.ext.Provider; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class ParamConverterTest { + + private static final String STATIC_UUID = "42f425f1-5923-41ca-a43c-713888762c68"; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(UUIDResource.class, UUIDParamConverterProvider.class)); + + @Test + public void single() { + get("/uuid/single?id=whatever") + .then() + .statusCode(200) + .body(Matchers.equalTo(STATIC_UUID)); + } + + @Test + public void set() { + get("/uuid/set?id=whatever&id=whatever2") + .then() + .statusCode(200) + .body(Matchers.equalTo(STATIC_UUID)); + } + + @Test + public void list() { + get("/uuid/list?id=whatever&id=whatever2") + .then() + .statusCode(200) + .body(Matchers.equalTo(STATIC_UUID + "," + STATIC_UUID)); + } + + @Path("uuid") + public static class UUIDResource { + + @Path("single") + @GET + public String single(@QueryParam("id") UUID uuid) { + return uuid.toString(); + } + + @Path("set") + @GET + public String set(@QueryParam("id") Set uuids) { + return join(uuids.stream()); + } + + @Path("list") + @GET + public String list(@QueryParam("id") List uuids) { + return join(uuids.stream()); + } + + private static String join(Stream uuids) { + return uuids.map(UUID::toString).collect(Collectors.joining(",")); + } + } + + @Provider + public static class UUIDParamConverterProvider implements ParamConverterProvider { + @SuppressWarnings("unchecked") + @Override + public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { + if (rawType.equals(UUID.class)) { + return (ParamConverter) new UUIDParamConverter(); + } + + return null; + } + + public static class UUIDParamConverter implements ParamConverter { + @Override + public UUID fromString(String value) { + return UUID.fromString(STATIC_UUID); + } + + @Override + public String toString(UUID value) { + return value.toString(); + } + } + + } +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/ListConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/ListConverter.java index ea2cbf683b170..c6b8ed73f260b 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/ListConverter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/ListConverter.java @@ -42,6 +42,11 @@ public void init(ParamConverterProviders deployment, Class rawType, Type gene delegate.init(deployment, rawType, genericType, annotations); } + @Override + public boolean isForSingleObjectContainer() { + return true; + } + public ParameterConverter getDelegate() { return delegate; } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/ParameterConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/ParameterConverter.java index 4edb434f91077..b6fa2947c3623 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/ParameterConverter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/ParameterConverter.java @@ -11,4 +11,9 @@ public interface ParameterConverter { default void init(ParamConverterProviders deployment, Class rawType, Type genericType, Annotation[] annotations) { } + + // TODO: this API method may be too limiting + default boolean isForSingleObjectContainer() { + return false; + } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/SetConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/SetConverter.java index 2edd9c8d278cd..9aed9ccbd2c65 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/SetConverter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/SetConverter.java @@ -43,6 +43,11 @@ public void init(ParamConverterProviders deployment, Class rawType, Type gene delegate.init(deployment, rawType, genericType, annotations); } + @Override + public boolean isForSingleObjectContainer() { + return true; + } + public static class SetSupplier implements DelegatingParameterConverterSupplier { private ParameterConverterSupplier delegate; diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/SortedSetConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/SortedSetConverter.java index b62e81b91342b..b77d8214613d4 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/SortedSetConverter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/SortedSetConverter.java @@ -47,6 +47,11 @@ public void init(ParamConverterProviders deployment, Class rawType, Type gene delegate.init(deployment, rawType, genericType, annotations); } + @Override + public boolean isForSingleObjectContainer() { + return true; + } + public static class SortedSetSupplier implements DelegatingParameterConverterSupplier { private ParameterConverterSupplier delegate; 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 9c26b9b902b7d..1306ad35f371a 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 @@ -6,6 +6,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -345,8 +346,9 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, Class[] parameterTypes = javaMethod.getParameterTypes(); Type[] genericParameterTypes = javaMethod.getGenericParameterTypes(); Annotation[][] parameterAnnotations = javaMethod.getParameterAnnotations(); - converter.init(paramConverterProviders, parameterTypes[i], genericParameterTypes[i], - parameterAnnotations[i]); + smartInitParameterConverter(i, converter, paramConverterProviders, parameterTypes, genericParameterTypes, + parameterAnnotations); + // make sure we give the user provided resolvers the chance to convert converter = new RuntimeResolvedConverter(converter); converter.init(paramConverterProviders, parameterTypes[i], genericParameterTypes[i], @@ -487,6 +489,34 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, clazz.resourceExceptionMapper()); } + /** + * This method takes into account the case where a parameter is for example List + * and we want to allow users to be able to use their implementation of + * ParamConverter. + */ + private static void smartInitParameterConverter(int i, ParameterConverter quarkusConverter, + ParamConverterProviders paramConverterProviders, + Class[] parameterTypes, Type[] genericParameterTypes, + Annotation[][] parameterAnnotations) { + if (quarkusConverter.isForSingleObjectContainer()) { + + if (genericParameterTypes[i] instanceof ParameterizedType) { + Type[] genericArguments = ((ParameterizedType) genericParameterTypes[i]).getActualTypeArguments(); + if (genericArguments.length == 1) { + quarkusConverter.init(paramConverterProviders, loadClass(genericArguments[0].getTypeName()), + genericArguments[0], + parameterAnnotations[i]); + return; + } + } + } + + // TODO: this is almost certainly wrong when genericParameterTypes[i] is a ParameterizedType not handle above, + // but there is no obvious way to handle it... + quarkusConverter.init(paramConverterProviders, parameterTypes[i], genericParameterTypes[i], + parameterAnnotations[i]); + } + private static boolean isNotVoid(Class rawEffectiveReturnType) { return rawEffectiveReturnType != Void.class && rawEffectiveReturnType != void.class;