diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/SingleQueryParamWithSeparatorTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/SingleQueryParamWithSeparatorTest.java new file mode 100644 index 0000000000000..d49b90ad5265d --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/SingleQueryParamWithSeparatorTest.java @@ -0,0 +1,39 @@ +package io.quarkus.resteasy.reactive.server.test; + +import static org.junit.jupiter.api.Assertions.fail; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.jboss.resteasy.reactive.RestQuery; +import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.Separator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class SingleQueryParamWithSeparatorTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(TestResource.class)) // + .setExpectedException(RuntimeException.class); + + @Test + public void test() { + fail("Should never have been called"); + } + + @Path("test") + public static class TestResource { + + @GET + @Path("endpoint") + public RestResponse endpoint(@RestQuery @Separator(",") String parameter) { + return RestResponse.ResponseBuilder.ok(parameter).build(); + } + + } +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SeparatorQueryParamTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SeparatorQueryParamTest.java index c6ad9820b5ff8..178f1c356b737 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SeparatorQueryParamTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/simple/SeparatorQueryParamTest.java @@ -1,6 +1,6 @@ package io.quarkus.resteasy.reactive.server.test.simple; -import static io.restassured.RestAssured.*; +import static io.restassured.RestAssured.get; import java.util.List; @@ -69,6 +69,15 @@ public void multipleQueryParams() { .header("x-size", "6"); } + @Test + public void multipleQueryParamsBean() { + get("/hello/bean?name=foo,bar&name=one,two,three&name=yolo") + .then() + .statusCode(200) + .body(Matchers.equalTo("hello foo bar one two three yolo")) + .header("x-size", "6"); + } + @Path("hello") public static class HelloResource { 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 005dfc9cbcb01..c9459d2678ea2 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 @@ -274,7 +274,7 @@ public Optional createEndpoints(ClassInfo classInfo, boolean cons clazz.setPath(sanitizePath(path)); } if (factoryCreator != null) { - clazz.setFactory((BeanFactory) factoryCreator.apply(classInfo.name().toString())); + clazz.setFactory(factoryCreator.apply(classInfo.name().toString())); } Map classLevelExceptionMappers = this.classLevelExceptionMappers.get(classInfo.name()); if (classLevelExceptionMappers != null) { @@ -1211,10 +1211,12 @@ public PARAM extractParameterInfo(ClassInfo currentClassInfo, ClassInfo actualEn } else if (queryParam != null) { builder.setName(queryParam.value().asString()); builder.setType(ParameterType.QUERY); + builder.setSeparator(getSeparator(anns)); convertible = true; } else if (restQueryParam != null) { builder.setName(valueOrDefault(restQueryParam.value(), sourceName)); builder.setType(ParameterType.QUERY); + builder.setSeparator(getSeparator(anns)); convertible = true; } else if (cookieParam != null) { builder.setName(cookieParam.value().asString()); @@ -1410,6 +1412,9 @@ && isParameterContainerType(paramType.asClassType())) { if (suspendedAnnotation != null && !elementType.equals(AsyncResponse.class.getName())) { throw new RuntimeException("Can only inject AsyncResponse on methods marked @Suspended"); } + if (builder.isSingle() && builder.getSeparator() != null) { + throw new RuntimeException("Single parameters should not be marked with @Separator"); + } builder.setElementType(elementType); return builder; } diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/IndexedParameter.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/IndexedParameter.java index 7145fdb223c62..4e57e4fc9c280 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/IndexedParameter.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/IndexedParameter.java @@ -29,6 +29,7 @@ public class IndexedParameter> { protected String elementType; protected boolean single; protected boolean optional; + protected String separator; public boolean isObtainedAsCollection() { return !single @@ -208,4 +209,13 @@ public T setOptional(boolean optional) { this.optional = optional; return (T) this; } + + public String getSeparator() { + return separator; + } + + public T setSeparator(String separator) { + this.separator = separator; + return (T) this; + } } 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 155c8f6f04f76..f562f684d943f 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 @@ -115,6 +115,7 @@ protected ServerEndpointIndexer(AbstractBuilder builder) { this.converterSupplierIndexerExtension = builder.converterSupplierIndexerExtension; } + @Override protected void addWriterForType(AdditionalWriters additionalWriters, Type paramType) { DotName dotName = paramType.name(); if (dotName.equals(JSONP_JSON_VALUE) @@ -130,6 +131,7 @@ protected void addWriterForType(AdditionalWriters additionalWriters, Type paramT } } + @Override protected void addReaderForType(AdditionalReaders additionalReaders, Type paramType) { DotName dotName = paramType.name(); if (dotName.equals(JSONP_JSON_NUMBER) @@ -198,6 +200,7 @@ protected boolean handleBeanParam(ClassInfo actualEndpointInfo, Type paramType, return injectableBean.isFormParamRequired(); } + @Override protected boolean doesMethodHaveBlockingSignature(MethodInfo info) { for (var i : methodScanners) { if (i.isMethodSignatureAsync(info)) { @@ -344,7 +347,6 @@ protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, Clas ParameterConverterSupplier converter = parameterResult.getConverter(); DeclaredTypes declaredTypes = getDeclaredTypes(paramType, currentClassInfo, actualEndpointInfo); String mimeType = getPartMime(parameterResult.getAnns()); - String separator = getSeparator(parameterResult.getAnns()); String declaredType = declaredTypes.getDeclaredType(); if (SUPPORTED_MULTIPART_FILE_TYPES.contains(DotName.createSimple(declaredType))) { @@ -354,9 +356,10 @@ protected MethodParameter createMethodParameter(ClassInfo currentClassInfo, Clas elementType, declaredType, declaredTypes.getDeclaredUnresolvedType(), type, single, signature, converter, defaultValue, parameterResult.isObtainedAsCollection(), parameterResult.isOptional(), encoded, - parameterResult.getCustomParameterExtractor(), mimeType, separator); + parameterResult.getCustomParameterExtractor(), mimeType, parameterResult.getSeparator()); } + @Override protected void handleOtherParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType) { try { @@ -368,6 +371,7 @@ protected void handleOtherParam(Map existingConverters, String e } } + @Override protected void handleSortedSetParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType) { ParameterConverterSupplier converter = extractConverter(elementType, index, @@ -375,6 +379,7 @@ protected void handleSortedSetParam(Map existingConverters, Stri builder.setConverter(new SortedSetConverter.SortedSetSupplier(converter)); } + @Override protected void handleOptionalParam(Map existingConverters, Map parameterAnnotations, String errorLocation, @@ -409,6 +414,7 @@ protected void handleOptionalParam(Map existingConverters, builder.setConverter(new OptionalConverter.OptionalSupplier(converter)); } + @Override protected void handleSetParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType) { ParameterConverterSupplier converter = extractConverter(elementType, index, @@ -416,6 +422,7 @@ protected void handleSetParam(Map existingConverters, String err builder.setConverter(new SetConverter.SetSupplier(converter)); } + @Override protected void handleListParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType) { ParameterConverterSupplier converter = extractConverter(elementType, index, @@ -423,6 +430,7 @@ protected void handleListParam(Map existingConverters, String er builder.setConverter(new ListConverter.ListSupplier(converter)); } + @Override protected void handleArrayParam(Map existingConverters, String errorLocation, boolean hasRuntimeConverters, ServerIndexedParameter builder, String elementType) { ParameterConverterSupplier converter = extractConverter(elementType, index, @@ -430,6 +438,7 @@ protected void handleArrayParam(Map existingConverters, String e builder.setConverter(new ArrayConverter.ArraySupplier(converter, elementType)); } + @Override protected void handlePathSegmentParam(ServerIndexedParameter builder) { builder.setConverter(new PathSegmentParamConverter.Supplier()); } @@ -439,6 +448,7 @@ protected String handleTrailingSlash(String path) { return path.substring(0, path.length() - 1); } + @Override protected void handleTemporalParam(ServerIndexedParameter builder, DotName paramType, Map parameterAnnotations, MethodInfo currentMethodInfo) { diff --git a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ClassInjectorTransformer.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ClassInjectorTransformer.java index 67fb1fa1d522f..b8d242287efeb 100644 --- a/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ClassInjectorTransformer.java +++ b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ClassInjectorTransformer.java @@ -275,26 +275,27 @@ public void visitEnd() { break; case FORM: injectParameterWithConverter(injectMethod, "getFormParameter", fieldInfo, extractor, true, true, - fieldInfo.hasAnnotation(ResteasyReactiveDotNames.ENCODED)); + fieldInfo.hasAnnotation(ResteasyReactiveDotNames.ENCODED), false); break; case HEADER: - injectParameterWithConverter(injectMethod, "getHeader", fieldInfo, extractor, true, false, false); + injectParameterWithConverter(injectMethod, "getHeader", fieldInfo, extractor, true, false, false, + false); break; case MATRIX: injectParameterWithConverter(injectMethod, "getMatrixParameter", fieldInfo, extractor, true, true, - fieldInfo.hasAnnotation(ResteasyReactiveDotNames.ENCODED)); + fieldInfo.hasAnnotation(ResteasyReactiveDotNames.ENCODED), false); break; case COOKIE: injectParameterWithConverter(injectMethod, "getCookieParameter", fieldInfo, extractor, false, false, - false); + false, false); break; case PATH: injectParameterWithConverter(injectMethod, "getPathParameter", fieldInfo, extractor, false, true, - fieldInfo.hasAnnotation(ResteasyReactiveDotNames.ENCODED)); + fieldInfo.hasAnnotation(ResteasyReactiveDotNames.ENCODED), false); break; case QUERY: injectParameterWithConverter(injectMethod, "getQueryParameter", fieldInfo, extractor, true, true, - fieldInfo.hasAnnotation(ResteasyReactiveDotNames.ENCODED)); + fieldInfo.hasAnnotation(ResteasyReactiveDotNames.ENCODED), true); break; default: break; @@ -518,7 +519,8 @@ private ParameterConverterSupplier removeRuntimeResolvedConverterDelegate(Parame } private void injectParameterWithConverter(MethodVisitor injectMethod, String methodName, FieldInfo fieldInfo, - ServerIndexedParameter extractor, boolean extraSingleParameter, boolean extraEncodedParam, boolean encoded) { + ServerIndexedParameter extractor, boolean extraSingleParameter, boolean extraEncodedParam, boolean encoded, + boolean extraSeparatorParam) { // spec says: /* @@ -552,7 +554,8 @@ private void injectParameterWithConverter(MethodVisitor injectMethod, String met // push the parameter value MultipartFormParamExtractor.Type multipartType = getMultipartFormType(extractor); if (multipartType == null) { - loadParameter(injectMethod, methodName, extractor, extraSingleParameter, extraEncodedParam, encoded); + loadParameter(injectMethod, methodName, extractor, extraSingleParameter, extraEncodedParam, encoded, + extraSeparatorParam); } else { loadMultipartParameter(injectMethod, fieldInfo, extractor, multipartType); } @@ -886,13 +889,22 @@ private void convertParameter(MethodVisitor injectMethod, ServerIndexedParameter } private void loadParameter(MethodVisitor injectMethod, String methodName, IndexedParameter extractor, - boolean extraSingleParameter, boolean extraEncodedParam, boolean encoded) { + boolean extraSingleParameter, boolean extraEncodedParam, boolean encoded, boolean extraSeparatorParam) { // ctx param injectMethod.visitIntInsn(Opcodes.ALOAD, 1); // name param injectMethod.visitLdcInsn(extractor.getName()); String methodSignature; - if (extraEncodedParam && extraSingleParameter) { + if (extraEncodedParam && extraSingleParameter && extraSeparatorParam) { + injectMethod.visitLdcInsn(extractor.isSingle()); + injectMethod.visitLdcInsn(encoded); + if (extractor.getSeparator() != null) { + injectMethod.visitLdcInsn(extractor.getSeparator()); + } else { + injectMethod.visitInsn(Opcodes.ACONST_NULL); + } + methodSignature = "(Ljava/lang/String;ZZLjava/lang/String;)Ljava/lang/Object;"; + } else if (extraEncodedParam && extraSingleParameter) { injectMethod.visitLdcInsn(extractor.isSingle()); injectMethod.visitLdcInsn(encoded); methodSignature = "(Ljava/lang/String;ZZ)Ljava/lang/Object;"; diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java index 7e777846d6f50..59d15a4c3fe69 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ResteasyReactiveRequestContext.java @@ -145,6 +145,7 @@ public ResteasyReactiveRequestContext(Deployment deployment, public abstract ServerHttpRequest serverRequest(); + @Override public abstract ServerHttpResponse serverResponse(); public Deployment getDeployment() { @@ -286,6 +287,7 @@ public Object getResult() { return result; } + @Override public Throwable getThrowable() { return throwable; } @@ -618,6 +620,7 @@ public ResteasyReactiveRequestContext setWriterInterceptors(WriterInterceptor[] return this; } + @Override protected void handleUnrecoverableError(Throwable throwable) { log.error("Request failed", throwable); if (serverResponse().headWritten()) { @@ -628,6 +631,7 @@ protected void handleUnrecoverableError(Throwable throwable) { close(); } + @Override protected void handleRequestScopeActivation() { CurrentRequestManager.set(this); } @@ -703,6 +707,7 @@ public boolean hasInputStream() { return inputStream != null; } + @Override public InputStream getInputStream() { if (inputStream == null) { inputStream = serverRequest().createInputStream(); @@ -805,8 +810,12 @@ public Object getHeader(String name, boolean single) { } } - @Override public Object getQueryParameter(String name, boolean single, boolean encoded) { + return getQueryParameter(name, single, encoded, null); + } + + @Override + public Object getQueryParameter(String name, boolean single, boolean encoded, String separator) { if (single) { String val = serverRequest().getQueryParam(name); if (encoded && val != null) { @@ -814,6 +823,7 @@ public Object getQueryParameter(String name, boolean single, boolean encoded) { } return val; } + // empty collections must not be turned to null List strings = serverRequest().getAllQueryParams(name); if (encoded) { @@ -821,9 +831,19 @@ public Object getQueryParameter(String name, boolean single, boolean encoded) { for (String i : strings) { newStrings.add(Encode.encodeQueryParam(i)); } - return newStrings; + strings = newStrings; + } + + if (separator != null) { + List result = new ArrayList<>(strings.size()); + for (int i = 0; i < strings.size(); i++) { + String[] parts = strings.get(i).split(separator); + result.addAll(Arrays.asList(parts)); + } + return result; + } else { + return strings; } - return strings; } @Override @@ -916,6 +936,7 @@ public String getPathParameter(String name, boolean encoded) { return value; } + @Override public T unwrap(Class type) { return serverRequest().unwrap(type); } @@ -953,6 +974,7 @@ public OutputStream getOutputStream() { return outputStream; } + @Override public OutputStream getOrCreateOutputStream() { if (outputStream == null) { return outputStream = underlyingOutputStream = serverResponse().createResponseOutputStream(); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/QueryParamExtractor.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/QueryParamExtractor.java index c8b7ddfa8fa1d..1ad5b468256c7 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/QueryParamExtractor.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/QueryParamExtractor.java @@ -1,9 +1,5 @@ package org.jboss.resteasy.reactive.server.core.parameters; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; @SuppressWarnings("ForLoopReplaceableByForEach") @@ -22,27 +18,7 @@ public QueryParamExtractor(String name, boolean single, boolean encoded, String } @Override - @SuppressWarnings("unchecked") public Object extractParameter(ResteasyReactiveRequestContext context) { - Object queryParameter = context.getQueryParameter(name, single, encoded); - if (separator != null) { - if (queryParameter instanceof List) { // it's List - List list = (List) queryParameter; - List result = new ArrayList<>(list.size()); - for (int i = 0; i < list.size(); i++) { - String[] parts = list.get(i).split(separator); - result.addAll(Arrays.asList(parts)); - } - queryParameter = result; - } else if (queryParameter instanceof String) { - List result = new ArrayList<>(1); - String[] parts = ((String) queryParameter).split(separator); - result.addAll(Arrays.asList(parts)); - queryParameter = result; - } else { - // can't really happen - } - } - return queryParameter; + return context.getQueryParameter(name, single, encoded, separator); } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ResteasyReactiveInjectionContext.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ResteasyReactiveInjectionContext.java index 9f63080274550..777202fa17a8f 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ResteasyReactiveInjectionContext.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ResteasyReactiveInjectionContext.java @@ -3,7 +3,7 @@ public interface ResteasyReactiveInjectionContext { Object getHeader(String name, boolean single); - Object getQueryParameter(String name, boolean single, boolean encoded); + Object getQueryParameter(String name, boolean single, boolean encoded, String separator); String getPathParameter(String name, boolean encoded);