From 8c6cd152e9c5d5de93dc32fa07014320db2f428f Mon Sep 17 00:00:00 2001 From: Markus Himmel Date: Sat, 11 Mar 2023 16:29:31 +0100 Subject: [PATCH] RESTEasy Reactive: Handle separator for bean params Fixes #31050 --- .../test/simple/SeparatorQueryParamTest.java | 11 +++++- .../common/processor/EndpointIndexer.java | 4 ++- .../common/processor/IndexedParameter.java | 10 ++++++ .../scanning/ClassInjectorTransformer.java | 32 +++++++++++------ .../core/ResteasyReactiveRequestContext.java | 36 ++++++++++++++++--- .../core/parameters/QueryParamExtractor.java | 26 +------------- .../ResteasyReactiveInjectionContext.java | 2 +- 7 files changed, 79 insertions(+), 42 deletions(-) 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 c6ad9820b5ff88..178f1c356b7374 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 20a679649c4b24..867387efd5e287 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()); 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 7145fdb223c629..4e57e4fc9c280b 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/scanning/ClassInjectorTransformer.java b/independent-projects/resteasy-reactive/server/processor/src/main/java/org/jboss/resteasy/reactive/server/processor/scanning/ClassInjectorTransformer.java index 67fb1fa1d522fc..b8d242287efeb7 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 7e777846d6f50b..92464917cf1f99 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,15 +810,26 @@ 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) { val = Encode.encodeQueryParam(val); } - return val; + + if (separator != null) { + String[] parts = val.split(separator); + return new ArrayList<>(Arrays.asList(parts)); + } else { + return val; + } } + // empty collections must not be turned to null List strings = serverRequest().getAllQueryParams(name); if (encoded) { @@ -821,9 +837,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 +942,7 @@ public String getPathParameter(String name, boolean encoded) { return value; } + @Override public T unwrap(Class type) { return serverRequest().unwrap(type); } @@ -953,6 +980,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 c8b7ddfa8fa1d4..1ad5b468256c78 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 9f630802745503..777202fa17a8f2 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);