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 f4af2c7732735..56a9601c012ad 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 @@ -1355,21 +1355,24 @@ && isParameterContainerType(paramType.asClassType())) { builder.setSingle(false); elementType = toClassName(pt.arguments().get(0), currentClassInfo, actualEndpointInfo, index); if (convertible) { - handleListParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); + handleListParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType, + currentMethodInfo); } } else if (pt.name().equals(SET)) { typeHandled = true; builder.setSingle(false); elementType = toClassName(pt.arguments().get(0), currentClassInfo, actualEndpointInfo, index); if (convertible) { - handleSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); + handleSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType, + currentMethodInfo); } } else if (pt.name().equals(SORTED_SET)) { typeHandled = true; builder.setSingle(false); elementType = toClassName(pt.arguments().get(0), currentClassInfo, actualEndpointInfo, index); if (convertible) { - handleSortedSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); + handleSortedSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType, + currentMethodInfo); } } else if (pt.name().equals(OPTIONAL)) { typeHandled = true; @@ -1388,7 +1391,8 @@ && isParameterContainerType(paramType.asClassType())) { } else if (convertible) { typeHandled = true; elementType = toClassName(pt, currentClassInfo, actualEndpointInfo, index); - handleOtherParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); + handleOtherParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType, + currentMethodInfo); } else if (builder.getType() != ParameterType.BEAN) { // the "element" type is not of importance as in this case the signature is used at runtime to determine the proper types elementType = DUMMY_ELEMENT_TYPE.toString(); @@ -1411,21 +1415,24 @@ && isParameterContainerType(paramType.asClassType())) { typeHandled = true; builder.setSingle(false); if (convertible) { - handleListParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); + handleListParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType, + currentMethodInfo); } } else if (paramType.name().equals(SET) && type == ParameterType.HEADER) { // RESTEasy Classic handles the non-generic Set type elementType = String.class.getName(); typeHandled = true; builder.setSingle(false); if (convertible) { - handleSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); + handleSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType, + currentMethodInfo); } } else if (paramType.name().equals(SORTED_SET) && type == ParameterType.HEADER) { // RESTEasy Classic handles the non-generic SortedSet type elementType = String.class.getName(); typeHandled = true; builder.setSingle(false); if (convertible) { - handleSortedSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); + handleSortedSetParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType, + currentMethodInfo); } } else if (paramType.kind() == Kind.ARRAY) { ArrayType at = paramType.asArrayType(); @@ -1437,7 +1444,8 @@ && isParameterContainerType(paramType.asClassType())) { } elementType = toClassName(at.constituent(), currentClassInfo, actualEndpointInfo, index); if (convertible) { - handleArrayParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); + handleArrayParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType, + currentMethodInfo); } } @@ -1447,7 +1455,8 @@ && isParameterContainerType(paramType.asClassType())) { if (type != ParameterType.CONTEXT && type != ParameterType.BEAN && type != ParameterType.BODY && type != ParameterType.ASYNC_RESPONSE && type != ParameterType.MULTI_PART_FORM) { - handleOtherParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType); + handleOtherParam(existingConverters, errorLocation, hasRuntimeConverters, builder, elementType, + currentMethodInfo); } if (type == ParameterType.CONTEXT && elementType.equals(SseEventSink.class.getName())) { builder.setSse(true); @@ -1507,11 +1516,11 @@ protected DeclaredTypes getDeclaredTypes(Type paramType, ClassInfo currentClassI } protected void handleOtherParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, - PARAM builder, String elementType) { + PARAM builder, String elementType, MethodInfo currentMethodInfo) { } protected void handleSortedSetParam(Map existingConverters, String errorLocation, - boolean hasRuntimeConverters, PARAM builder, String elementType) { + boolean hasRuntimeConverters, PARAM builder, String elementType, MethodInfo currentMethodInfo) { } protected void handleOptionalParam(Map existingConverters, @@ -1522,15 +1531,15 @@ protected void handleOptionalParam(Map existingConverters, } protected void handleSetParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, - PARAM builder, String elementType) { + PARAM builder, String elementType, MethodInfo currentMethodInfo) { } protected void handleListParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, - PARAM builder, String elementType) { + PARAM builder, String elementType, MethodInfo currentMethodInfo) { } protected void handleArrayParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, - PARAM builder, String elementType) { + PARAM builder, String elementType, MethodInfo currentMethodInfo) { } final boolean isContextType(ClassType klass) { diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java index f15d4beac65d4..8ec88c0e5b89e 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/ServerEndpointIndexer.java @@ -469,10 +469,10 @@ protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, Clas @Override protected void handleOtherParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, - ServerIndexedParameter builder, String elementType) { + ServerIndexedParameter builder, String elementType, MethodInfo currentMethodInfo) { try { builder.setConverter(extractConverter(elementType, index, - existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns())); + existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo)); } catch (Throwable throwable) { throw new RuntimeException("Could not create converter for " + elementType + " for " + builder.getErrorLocation() + " of type " + builder.getType(), throwable); @@ -481,9 +481,9 @@ protected void handleOtherParam(Map existingConverters, String e @Override protected void handleSortedSetParam(Map existingConverters, String errorLocation, - boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType) { + boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType, MethodInfo currentMethodInfo) { ParameterConverterSupplier converter = extractConverter(elementType, index, - existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns()); + existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo); builder.setConverter(new SortedSetConverter.SortedSetSupplier(converter)); } @@ -497,7 +497,7 @@ protected void handleOptionalParam(Map existingConverters, if (genericElementType != null) { ParameterConverterSupplier genericTypeConverter = extractConverter(genericElementType, index, existingConverters, - errorLocation, hasRuntimeConverters, builder.getAnns()); + errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo); if (LIST.toString().equals(elementType)) { converter = new ListConverter.ListSupplier(genericTypeConverter); builder.setSingle(false); @@ -516,7 +516,7 @@ protected void handleOptionalParam(Map existingConverters, if (converter == null) { // If no generic type provided or element type is not supported, then we try to use a custom runtime converter: converter = extractConverter(elementType, index, existingConverters, errorLocation, hasRuntimeConverters, - builder.getAnns()); + builder.getAnns(), currentMethodInfo); } builder.setConverter(new OptionalConverter.OptionalSupplier(converter)); @@ -524,25 +524,25 @@ protected void handleOptionalParam(Map existingConverters, @Override protected void handleSetParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, - ServerIndexedParameter builder, String elementType) { + ServerIndexedParameter builder, String elementType, MethodInfo currentMethodInfo) { ParameterConverterSupplier converter = extractConverter(elementType, index, - existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns()); + existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo); builder.setConverter(new SetConverter.SetSupplier(converter)); } @Override protected void handleListParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, - ServerIndexedParameter builder, String elementType) { + ServerIndexedParameter builder, String elementType, MethodInfo currentMethodInfo) { ParameterConverterSupplier converter = extractConverter(elementType, index, - existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns()); + existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo); builder.setConverter(new ListConverter.ListSupplier(converter)); } @Override protected void handleArrayParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, - ServerIndexedParameter builder, String elementType) { + ServerIndexedParameter builder, String elementType, MethodInfo currentMethodInfo) { ParameterConverterSupplier converter = extractConverter(elementType, index, - existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns()); + existingConverters, errorLocation, hasRuntimeConverters, builder.getAnns(), currentMethodInfo); builder.setConverter(new ArrayConverter.ArraySupplier(converter, elementType)); } @@ -652,7 +652,7 @@ private String contextualizeErrorMessage(String errorMessage, MethodInfo current private ParameterConverterSupplier extractConverter(String elementType, IndexView indexView, Map existingConverters, String errorLocation, boolean hasRuntimeConverters, - Map annotations) { + Map annotations, MethodInfo currentMethodInfo) { // no converter if we have a RestForm mime type: this goes via message body readers in MultipartFormParamExtractor if (getPartMime(annotations) != null) return null; @@ -685,7 +685,14 @@ private ParameterConverterSupplier extractConverter(String elementType, IndexVie || elementType.equals(InputStream.class.getName())) { // this is handled by MultipartFormParamExtractor return null; + } else { + DotName typeName = DotName.createSimple(elementType); + if (SUPPORT_TEMPORAL_PARAMS.contains(typeName)) { + //It might be a LocalDate[Time] object + return determineTemporalConverter(typeName, annotations, currentMethodInfo); + } } + return converterSupplierIndexerExtension.extractConverterImpl(elementType, indexView, existingConverters, errorLocation, hasRuntimeConverters); } diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateParamTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateParamTest.java index c98a886a8e865..402f5d856e0ab 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateParamTest.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateParamTest.java @@ -2,6 +2,8 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.Set; +import java.util.stream.Collectors; import jakarta.ws.rs.CookieParam; import jakarta.ws.rs.FormParam; @@ -14,6 +16,7 @@ import org.hamcrest.Matchers; import org.jboss.resteasy.reactive.DateFormat; +import org.jboss.resteasy.reactive.Separator; import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -33,6 +36,12 @@ public void localDateAsQueryParam() { .then().statusCode(200).body(Matchers.equalTo("hello#1984-08-08")); } + @Test + public void localDateCollectionAsQueryParam() { + RestAssured.get("/hello/set?dates=08-08-1984,25-04-1992") + .then().statusCode(200).body(Matchers.equalTo("hello#08-08-1984,25-04-1992")); + } + @Test public void localDateAsPathParam() { RestAssured.get("/hello/1995-09-21") @@ -42,7 +51,13 @@ public void localDateAsPathParam() { @Test public void localDateAsFormParam() { RestAssured.given().formParam("date", "1995/09/22").post("/hello") - .then().statusCode(200).body(Matchers.equalTo("hello:1995-09-22")); + .then().statusCode(200).body(Matchers.equalTo("hello:1995/09/22")); + } + + @Test + public void localDateCollectionAsFormParam() { + RestAssured.given().formParam("date", "1995/09/22", "1992/04/25").post("/hello") + .then().statusCode(200).body(Matchers.equalTo("hello:1992/04/25,1995/09/22")); } @Test @@ -67,6 +82,16 @@ public String helloQuery(@QueryParam("date") @DateFormat(pattern = "dd-MM-yyyy") return "hello#" + date; } + @GET + @Path("/set") + public String helloQuerySet( + @Separator(",") @QueryParam("dates") @DateFormat(pattern = "dd-MM-yyyy") Set dates) { + String formattedDates = dates.stream() + .map(date -> date.format(DateTimeFormatter.ofPattern("dd-MM-yyyy"))) + .collect(Collectors.joining(",")); + return "hello#" + formattedDates; + } + @GET @Path("{date}") public String helloPath(@PathParam("date") LocalDate date) { @@ -79,6 +104,15 @@ public String helloForm( return "hello:" + date; } + @POST + public String helloFormSet( + @FormParam("date") @DateFormat(dateTimeFormatterProvider = CustomDateTimeFormatterProvider.class) Set dates) { + String formattedDates = dates.stream() + .map(date -> date.format(CustomDateTimeFormatterProvider.FORMATTER)) + .collect(Collectors.joining(",")); + return "hello:" + formattedDates; + } + @GET @Path("cookie") public String helloCookie( @@ -95,9 +129,12 @@ public String helloHeader( } public static class CustomDateTimeFormatterProvider implements DateFormat.DateTimeFormatterProvider { + + static DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd"); + @Override public DateTimeFormatter get() { - return DateTimeFormatter.ofPattern("yyyy/MM/dd"); + return FORMATTER; } } diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateTimeParamTest.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateTimeParamTest.java index 7c4e9cb8e8416..9f4eed3465505 100644 --- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateTimeParamTest.java +++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/simple/LocalDateTimeParamTest.java @@ -3,6 +3,8 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import jakarta.ws.rs.FormParam; import jakarta.ws.rs.GET; @@ -15,6 +17,7 @@ import org.jboss.resteasy.reactive.RestHeader; import org.jboss.resteasy.reactive.RestPath; import org.jboss.resteasy.reactive.RestQuery; +import org.jboss.resteasy.reactive.Separator; import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; @@ -36,6 +39,12 @@ public void localDateTimeAsQueryParam() { .then().statusCode(200).body(Matchers.equalTo("hello#1984")); } + @Test + public void localDateTimeCollectionAsQueryParam() { + RestAssured.get("/hello?date=1984-08-08T01:02:03,1992-04-25T01:02:03") + .then().statusCode(200).body(Matchers.equalTo("hello#1984,1992")); + } + @Test public void localDateTimeAsOptionalQueryParam() { RestAssured.get("/hello/optional?date=1984-08-08T01:02:03") @@ -57,6 +66,12 @@ public void localDateTimeAsFormParam() { .then().statusCode(200).body(Matchers.equalTo("hello:22")); } + @Test + public void localDateTimeCollectionAsFormParam() { + RestAssured.given().formParam("date", "1995/09/22 01:02", "1992/04/25 01:02").post("/hello") + .then().statusCode(200).body(Matchers.equalTo("hello:22,25")); + } + @Test public void localDateTimeAsHeader() { RestAssured.with().header("date", "1984-08-08 01:02:03") @@ -79,6 +94,16 @@ public String helloQuery(@RestQuery LocalDateTime date) { return "hello#" + date.getYear(); } + @GET + public String helloQuerySet(@RestQuery @Separator(",") Set date) { + String joinedYears = date.stream() + .map(LocalDateTime::getYear) + .sorted() + .map(Object::toString) + .collect(Collectors.joining(",")); + return "hello#" + joinedYears; + } + @Path("optional") @GET public String helloOptionalQuery(@RestQuery Optional date) { @@ -97,6 +122,17 @@ public String helloForm( return "hello:" + date.getDayOfMonth(); } + @POST + public String helloFormSet( + @FormParam("date") @DateFormat(dateTimeFormatterProvider = CustomDateTimeFormatterProvider.class) Set dates) { + String joinedDays = dates.stream() + .map(LocalDateTime::getDayOfMonth) + .sorted() + .map(Object::toString) + .collect(Collectors.joining(",")); + return "hello:" + joinedDays; + } + @Path("cookie") @GET public String helloCookie(@RestCookie @DateFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime date) {