From c7483fb86e85da1d24c6023435420252151cfb91 Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 25 Nov 2021 08:51:47 +0100 Subject: [PATCH] Resteasy Reactive - Avoid NPE when using runtime converters The NPE happens here: https://github.com/quarkusio/quarkus/blob/e6ffbe43243f9c852c61aafc307515be0915c6c8/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ClassInjectorTransformer.java#L222. However, it's expected that the delegated converter is null when there are runtime converts. See related lines: https://github.com/quarkusio/quarkus/blob/bd87966540990fa68ec3769e22e67df1854b08a5/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java#L169 and https://github.com/quarkusio/quarkus/blob/bd87966540990fa68ec3769e22e67df1854b08a5/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/QuarkusServerEndpointIndexer.java#L173. Therefore, as It's possible that the delegate is null when there are runtime converters, we check whether the delegate is null and if it is, we don't use it to avoid the NPE. The rest works as expected as proven in the test. Fixes https://github.com/quarkusio/quarkus/issues/21664 --- .../deployment/ClassInjectorTransformer.java | 5 +- .../CustomConverterInBeanParamTest.java | 98 +++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/CustomConverterInBeanParamTest.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ClassInjectorTransformer.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ClassInjectorTransformer.java index 8fa2ef6742dc5..2ede5c9c7ee64 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ClassInjectorTransformer.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ClassInjectorTransformer.java @@ -296,7 +296,10 @@ private void generateConverterInitMethod(FieldInfo fieldInfo, ParameterConverter private ParameterConverterSupplier removeRuntimeResolvedConverterDelegate(ParameterConverterSupplier converter) { if (converter instanceof RuntimeResolvedConverter.Supplier) { - return ((RuntimeResolvedConverter.Supplier) converter).getDelegate(); + ParameterConverterSupplier delegate = ((RuntimeResolvedConverter.Supplier) converter).getDelegate(); + if (delegate != null) { + return delegate; + } } return converter; } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/CustomConverterInBeanParamTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/CustomConverterInBeanParamTest.java new file mode 100644 index 0000000000000..9573dcd9049c3 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/beanparam/CustomConverterInBeanParamTest.java @@ -0,0 +1,98 @@ +package io.quarkus.resteasy.reactive.server.test.beanparam; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import javax.ws.rs.BeanParam; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.ext.ParamConverter; +import javax.ws.rs.ext.ParamConverterProvider; +import javax.ws.rs.ext.Provider; + +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 CustomConverterInBeanParamTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SearchResource.class, FilterData.class, + JavaTimeParamConverterProvider.class, LocalDateTimeParamConverter.class)); + + @Test + void shouldCustomConvertBeUsedForLocalDateTimeInFilterData() { + LocalDateTime since = LocalDateTime.now(); + String request = since.format(DateTimeFormatter.ISO_DATE_TIME); + String expected = since.plusYears(1).format(DateTimeFormatter.ISO_DATE_TIME); + + RestAssured.get("/search?since=" + request) + .then() + .statusCode(200) + .body(equalTo("Got: " + expected)); + } + + @Path("/search") + public static class SearchResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String search(@BeanParam FilterData filter) { + return "Got: " + filter.getSince().plusYears(1).format(DateTimeFormatter.ISO_DATE_TIME); + } + } + + public static class FilterData { + + @QueryParam("since") + private LocalDateTime since; + + public LocalDateTime getSince() { + return since; + } + + public void setSince(LocalDateTime since) { + this.since = since; + } + } + + public static class LocalDateTimeParamConverter implements ParamConverter { + + @Override + public LocalDateTime fromString(String value) { + return LocalDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME); + } + + @Override + public String toString(LocalDateTime value) { + return value.format(DateTimeFormatter.ISO_DATE_TIME); + } + } + + @Provider + public static class JavaTimeParamConverterProvider implements ParamConverterProvider { + + @Override + public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { + if (rawType == LocalDateTime.class) { + return (ParamConverter) new LocalDateTimeParamConverter(); + } + + return null; + } + + } +}