From f82f3b505f4d3c01f3cb548b746cca280585aa4e Mon Sep 17 00:00:00 2001 From: Jose Date: Thu, 27 Jan 2022 08:01:31 +0100 Subject: [PATCH] Inject "annotations" and "genericType" in ParamConverterProvider Note that this PR will introduce a performance penalty at load time (first time, when the rest client instance is loaded AND if and only if the rest client methods use param annotations - which is most of the times tho). What I did was to always generate the `javaMethodX` static fields (before, it was also generated always, but it was done in the implementation MicroProfileRestClientEnricher). Plus, apart of the method information, we will also load the annotations and the generic types. This is done at load class time (static init). Moreover, as I had to move some code from MicroProfileRestClientEnricher to JaxrsClientReactiveProcessor, in order to not increase the length of this class JaxrsClientReactiveProcessor, I started moving some code to ClassRestClientContext (which is protected - not available for users) Fix https://github.com/quarkusio/quarkus/issues/22870 --- .../deployment/ClassRestClientContext.java | 127 ++++++ .../JaxrsClientReactiveEnricher.java | 8 +- .../JaxrsClientReactiveProcessor.java | 414 +++++++++++------- .../reactive/runtime/RestClientBase.java | 12 +- .../MicroProfileRestClientEnricher.java | 44 +- .../converter/ParamConverterProviderTest.java | 9 + .../client/main/ClientCallingResource.java | 15 + .../io/quarkus/it/rest/client/main/Param.java | 6 + .../it/rest/client/main/ParamClient.java | 21 + .../it/rest/client/main/ParamConverter.java | 46 ++ .../src/main/resources/application.properties | 1 + .../io/quarkus/it/rest/client/BasicTest.java | 11 +- 12 files changed, 515 insertions(+), 199 deletions(-) create mode 100644 extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java create mode 100644 integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/Param.java create mode 100644 integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ParamClient.java create mode 100644 integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ParamConverter.java diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java new file mode 100644 index 00000000000000..98b964e31351f7 --- /dev/null +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/ClassRestClientContext.java @@ -0,0 +1,127 @@ +package io.quarkus.jaxrs.client.reactive.deployment; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; +import org.objectweb.asm.Opcodes; + +import io.quarkus.deployment.GeneratedClassGizmoAdaptor; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; + +class ClassRestClientContext implements AutoCloseable { + + public final ClassCreator classCreator; + public final MethodCreator constructor; + public final MethodCreator clinit; + + public final Map methodStaticFields = new HashMap<>(); + public final Map methodParamAnnotationsStaticFields = new HashMap<>(); + public final Map methodGenericParametersStaticFields = new HashMap<>(); + + public ClassRestClientContext(String name, BuildProducer generatedClasses, + String... interfaces) { + this(name, MethodDescriptor.ofConstructor(name), generatedClasses, Object.class, interfaces); + } + + public ClassRestClientContext(String name, MethodDescriptor constructorDesc, + BuildProducer generatedClasses, + Class superClass, String... interfaces) { + + this.classCreator = new ClassCreator(new GeneratedClassGizmoAdaptor(generatedClasses, true), + name, null, superClass.getName(), interfaces); + this.constructor = classCreator.getMethodCreator(constructorDesc); + this.clinit = classCreator.getMethodCreator(MethodDescriptor.ofMethod(name, "", void.class)); + this.clinit.setModifiers(Opcodes.ACC_STATIC); + } + + @Override + public void close() { + classCreator.close(); + } + + protected FieldDescriptor createJavaMethodField(ClassInfo interfaceClass, MethodInfo method, int methodIndex) { + ResultHandle interfaceClassHandle = clinit.loadClassFromTCCL(interfaceClass.toString()); + + ResultHandle parameterArray = clinit.newArray(Class.class, method.parameters().size()); + for (int i = 0; i < method.parameters().size(); i++) { + String parameterClass = method.parameters().get(i).name().toString(); + clinit.writeArrayValue(parameterArray, i, clinit.loadClassFromTCCL(parameterClass)); + } + + ResultHandle javaMethodHandle = clinit.invokeVirtualMethod( + MethodDescriptor.ofMethod(Class.class, "getMethod", Method.class, String.class, Class[].class), + interfaceClassHandle, clinit.load(method.name()), parameterArray); + FieldDescriptor javaMethodField = FieldDescriptor.of(classCreator.getClassName(), "javaMethod" + methodIndex, + Method.class); + classCreator.getFieldCreator(javaMethodField).setModifiers(Modifier.PRIVATE | Modifier.FINAL | Modifier.STATIC); + clinit.writeStaticField(javaMethodField, javaMethodHandle); + + methodStaticFields.put(methodIndex, javaMethodField); + + return javaMethodField; + } + + /** + * Generates "method.getParameterAnnotations()" and it will only be created if and only if the supplier is used + * in order to not have a penalty performance. + */ + protected Supplier getLazyJavaMethodParamAnnotationsField(int methodIndex) { + return () -> { + FieldDescriptor methodParamAnnotationsField = methodParamAnnotationsStaticFields.get(methodIndex); + if (methodParamAnnotationsField != null) { + return methodParamAnnotationsField; + } + + ResultHandle javaMethodParamAnnotationsHandle = clinit.invokeVirtualMethod( + MethodDescriptor.ofMethod(Method.class, "getParameterAnnotations", Annotation[][].class), + clinit.readStaticField(methodStaticFields.get(methodIndex))); + FieldDescriptor javaMethodParamAnnotationsField = FieldDescriptor.of(classCreator.getClassName(), + "javaMethodParameterAnnotations" + methodIndex, Annotation[][].class); + classCreator.getFieldCreator(javaMethodParamAnnotationsField) + .setModifiers(Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC); + clinit.writeStaticField(javaMethodParamAnnotationsField, javaMethodParamAnnotationsHandle); + + methodParamAnnotationsStaticFields.put(methodIndex, javaMethodParamAnnotationsField); + + return javaMethodParamAnnotationsField; + }; + } + + /** + * Generates "method.getGenericParameterTypes()" and it will only be created if and only if the supplier is used + * in order to not have a penalty performance. + */ + protected Supplier getLazyJavaMethodGenericParametersField(int methodIndex) { + return () -> { + FieldDescriptor methodGenericTypeField = methodGenericParametersStaticFields.get(methodIndex); + if (methodGenericTypeField != null) { + return methodGenericTypeField; + } + + ResultHandle javaMethodGenericParametersHandle = clinit.invokeVirtualMethod( + MethodDescriptor.ofMethod(Method.class, "getGenericParameterTypes", java.lang.reflect.Type[].class), + clinit.readStaticField(methodStaticFields.get(methodIndex))); + FieldDescriptor javaMethodGenericParametersField = FieldDescriptor.of(classCreator.getClassName(), + "javaMethodGenericParameters" + methodIndex, java.lang.reflect.Type[].class); + classCreator.getFieldCreator(javaMethodGenericParametersField) + .setModifiers(Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC); + clinit.writeStaticField(javaMethodGenericParametersField, javaMethodGenericParametersHandle); + + methodGenericParametersStaticFields.put(methodIndex, javaMethodGenericParametersField); + + return javaMethodGenericParametersField; + }; + } +} diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveEnricher.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveEnricher.java index b519ea39d3124d..8c5d16fc906f64 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveEnricher.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveEnricher.java @@ -8,6 +8,7 @@ import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.gizmo.AssignableResultHandle; import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.FieldDescriptor; import io.quarkus.gizmo.MethodCreator; /** @@ -42,13 +43,15 @@ void forClass(MethodCreator ctor, AssignableResultHandle globalTarget, * @param index jandex index * @param generatedClasses build producer used to generate classes. Used e.g. to generate classes for header filling * @param methodIndex 0-based index of the method in the interface. Used to assure there is no clash in generating classes + * @param javaMethodField method reference in a static class field */ void forMethod(ClassCreator classCreator, MethodCreator constructor, MethodCreator clinit, MethodCreator methodCreator, ClassInfo interfaceClass, MethodInfo method, AssignableResultHandle invocationBuilder, - IndexView index, BuildProducer generatedClasses, int methodIndex); + IndexView index, BuildProducer generatedClasses, int methodIndex, + FieldDescriptor javaMethodField); /** * Method-level alterations for methods of sub-resources @@ -65,11 +68,12 @@ void forMethod(ClassCreator classCreator, MethodCreator constructor, * @param generatedClasses build producer used to generate classes * @param methodIndex 0-based index of method in the root interface * @param subMethodIndex index of the method in the sub-resource interface + * @param javaMethodField method reference in a static class field */ void forSubResourceMethod(ClassCreator subClassCreator, MethodCreator subConstructor, MethodCreator subClinit, MethodCreator subMethodCreator, ClassInfo rootInterfaceClass, ClassInfo subInterfaceClass, MethodInfo subMethod, MethodInfo rootMethod, AssignableResultHandle invocationBuilder, // sub-level IndexView index, BuildProducer generatedClasses, - int methodIndex, int subMethodIndex); + int methodIndex, int subMethodIndex, FieldDescriptor javaMethodField); } diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index b5a374ab54b109..29a4e134987cb7 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -17,6 +17,7 @@ import java.io.Closeable; import java.io.File; +import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.nio.file.Path; import java.util.ArrayList; @@ -34,6 +35,7 @@ import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -107,7 +109,6 @@ import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames; import org.jboss.resteasy.reactive.common.processor.scanning.ResourceScanningResult; import org.jboss.resteasy.reactive.multipart.FileDownload; -import org.objectweb.asm.Opcodes; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; @@ -370,7 +371,6 @@ public void accept(EndpointIndexer.ResourceMethodCallbackData entry) { * @param multipartResponseTypeInfo a class to scan * @param generatedClasses build producer for generating classes * @param context recorder context to instantiate the newly created class - * * @return a runtime value with an instance of the MultipartResponseData corresponding to the given class */ private RuntimeValue createMultipartResponseData(ClassInfo multipartResponseTypeInfo, @@ -730,31 +730,24 @@ A more full example of generated client (with sub-resource) can is at the bottom IndexView index, String defaultMediaType, Map httpAnnotationToMethod, boolean observabilityIntegrationNeeded, Set multipartResponseTypes) { + String creatorName = restClientInterface.getClassName() + "$$QuarkusRestClientInterfaceCreator"; String name = restClientInterface.getClassName() + "$$QuarkusRestClientInterface"; - MethodDescriptor ctorDesc = MethodDescriptor.ofConstructor(name, WebTarget.class.getName(), List.class); - try (ClassCreator c = new ClassCreator(new GeneratedClassGizmoAdaptor(generatedClasses, true), - name, null, RestClientBase.class.getName(), - Closeable.class.getName(), restClientInterface.getClassName())) { + MethodDescriptor constructorDesc = MethodDescriptor.ofConstructor(name, WebTarget.class.getName(), List.class); + try (ClassRestClientContext classContext = new ClassRestClientContext(name, constructorDesc, generatedClasses, + RestClientBase.class, Closeable.class.getName(), restClientInterface.getClassName())) { - // - // initialize basic WebTarget in constructor - // + classContext.constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(RestClientBase.class, List.class), + classContext.constructor.getThis(), classContext.constructor.getMethodParam(1)); - MethodCreator constructor = c.getMethodCreator(ctorDesc); - constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(RestClientBase.class, List.class), - constructor.getThis(), constructor.getMethodParam(1)); - - MethodCreator clinit = c.getMethodCreator(MethodDescriptor.ofMethod(name, "", void.class)); - clinit.setModifiers(Opcodes.ACC_STATIC); - - AssignableResultHandle baseTarget = constructor.createVariable(WebTarget.class); - constructor.assign(baseTarget, - constructor.invokeInterfaceMethod( + AssignableResultHandle baseTarget = classContext.constructor.createVariable(WebTarget.class); + classContext.constructor.assign(baseTarget, + classContext.constructor.invokeInterfaceMethod( MethodDescriptor.ofMethod(WebTarget.class, "path", WebTarget.class, String.class), - constructor.getMethodParam(0), constructor.load(restClientInterface.getPath()))); + classContext.constructor.getMethodParam(0), + classContext.constructor.load(restClientInterface.getPath()))); for (JaxrsClientReactiveEnricherBuildItem enricher : enrichers) { - enricher.getEnricher().forClass(constructor, baseTarget, interfaceClass, index); + enricher.getEnricher().forClass(classContext.constructor, baseTarget, interfaceClass, index); } // @@ -785,35 +778,45 @@ A more full example of generated client (with sub-resource) can is at the bottom if (method.getHttpMethod() == null) { handleSubResourceMethod(enrichers, generatedClasses, interfaceClass, index, defaultMediaType, httpAnnotationToMethod, - name, c, constructor, + name, classContext, classContext.constructor, baseTarget, methodIndex, webTargets, method, javaMethodParameters, jandexMethod, multipartResponseTypes); } else { + FieldDescriptor methodField = classContext.createJavaMethodField(interfaceClass, jandexMethod, + methodIndex); + Supplier methodParamAnnotationsField = classContext + .getLazyJavaMethodParamAnnotationsField(methodIndex); + Supplier methodGenericParametersField = classContext + .getLazyJavaMethodGenericParametersField(methodIndex); // if the response is multipart, let's add it's class to the appropriate collection: addResponseTypeIfMultipart(multipartResponseTypes, jandexMethod, index); // constructor: initializing the immutable part of the method-specific web target FieldDescriptor webTargetForMethod = FieldDescriptor.of(name, "target" + methodIndex, WebTargetImpl.class); - c.getFieldCreator(webTargetForMethod).setModifiers(Modifier.FINAL); + classContext.classCreator.getFieldCreator(webTargetForMethod).setModifiers(Modifier.FINAL); webTargets.add(webTargetForMethod); - AssignableResultHandle constructorTarget = createWebTargetForMethod(constructor, baseTarget, method); - constructor.writeInstanceField(webTargetForMethod, constructor.getThis(), constructorTarget); + AssignableResultHandle constructorTarget = createWebTargetForMethod(classContext.constructor, baseTarget, + method); + classContext.constructor.writeInstanceField(webTargetForMethod, classContext.constructor.getThis(), + constructorTarget); if (observabilityIntegrationNeeded) { String templatePath = MULTIPLE_SLASH_PATTERN.matcher(restClientInterface.getPath() + method.getPath()) .replaceAll("/"); - constructor.invokeVirtualMethod( + classContext.constructor.invokeVirtualMethod( MethodDescriptor.ofMethod(WebTargetImpl.class, "setPreClientSendHandler", void.class, ClientRestHandler.class), - constructor.readInstanceField(webTargetForMethod, constructor.getThis()), - constructor.newInstance( + classContext.constructor.readInstanceField(webTargetForMethod, + classContext.constructor.getThis()), + classContext.constructor.newInstance( MethodDescriptor.ofConstructor(ClientObservabilityHandler.class, String.class), - constructor.load(templatePath))); + classContext.constructor.load(templatePath))); } // generate implementation for a method from jaxrs interface: - MethodCreator methodCreator = c.getMethodCreator(method.getName(), method.getSimpleReturnType(), + MethodCreator methodCreator = classContext.classCreator.getMethodCreator(method.getName(), + method.getSimpleReturnType(), javaMethodParameters); AssignableResultHandle methodTarget = methodCreator.createVariable(WebTarget.class); @@ -835,7 +838,11 @@ A more full example of generated client (with sub-resource) can is at the bottom // query params have to be set on a method-level web target (they vary between invocations) methodCreator.assign(methodTarget, addQueryParam(methodCreator, methodTarget, param.name, methodCreator.getMethodParam(paramIdx), - jandexMethod.parameters().get(paramIdx), index, methodCreator.getThis())); + jandexMethod.parameters().get(paramIdx), index, methodCreator.getThis(), + methodCreator.readArrayValue( + methodCreator.readStaticField(methodGenericParametersField.get()), paramIdx), + methodCreator.readArrayValue( + methodCreator.readStaticField(methodParamAnnotationsField.get()), paramIdx))); } else if (param.parameterType == ParameterType.BEAN) { // bean params require both, web-target and Invocation.Builder, modifications // The web target changes have to be done on the method level. @@ -847,7 +854,8 @@ A more full example of generated client (with sub-resource) can is at the bottom method.getName() + "$$" + methodIndex + "$$handleBeanParam$$" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleBeanParamMethod = c.getMethodCreator(handleBeanParamDescriptor); + MethodCreator handleBeanParamMethod = classContext.classCreator.getMethodCreator( + handleBeanParamDescriptor); AssignableResultHandle invocationBuilderRef = handleBeanParamMethod .createVariable(Invocation.Builder.class); @@ -858,14 +866,18 @@ A more full example of generated client (with sub-resource) can is at the bottom restClientInterface.getClassName(), methodCreator.getThis(), handleBeanParamMethod.getThis(), - formParams); + formParams, methodGenericParametersField, methodParamAnnotationsField, paramIdx); handleBeanParamMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleBeanParamDescriptor, methodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.PATH) { // methodTarget = methodTarget.resolveTemplate(paramname, paramvalue); addPathParam(methodCreator, methodTarget, param.name, methodCreator.getMethodParam(paramIdx), - param.type, methodCreator.getThis()); + param.type, methodCreator.getThis(), + methodCreator.readArrayValue( + methodCreator.readStaticField(methodGenericParametersField.get()), paramIdx), + methodCreator.readArrayValue( + methodCreator.readStaticField(methodParamAnnotationsField.get()), paramIdx)); } else if (param.parameterType == ParameterType.BODY) { // just store the index of parameter used to create the body, we'll use it later bodyParameterIdx = paramIdx; @@ -875,14 +887,16 @@ A more full example of generated client (with sub-resource) can is at the bottom method.getName() + "$$" + methodIndex + "$$handleHeader$$" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleHeaderMethod = c.getMethodCreator(handleHeaderDescriptor); + MethodCreator handleHeaderMethod = classContext.classCreator.getMethodCreator( + handleHeaderDescriptor); AssignableResultHandle invocationBuilderRef = handleHeaderMethod .createVariable(Invocation.Builder.class); handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0)); addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name, handleHeaderMethod.getMethodParam(1), param.type, - handleHeaderMethod.getThis()); + handleHeaderMethod.getThis(), methodGenericParametersField.get(), + methodParamAnnotationsField.get(), paramIdx); handleHeaderMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.COOKIE) { @@ -891,20 +905,26 @@ A more full example of generated client (with sub-resource) can is at the bottom method.getName() + "$$" + methodIndex + "$$handleCookie$$" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleCookieMethod = c.getMethodCreator(handleHeaderDescriptor); + MethodCreator handleCookieMethod = classContext.classCreator.getMethodCreator( + handleHeaderDescriptor); AssignableResultHandle invocationBuilderRef = handleCookieMethod .createVariable(Invocation.Builder.class); handleCookieMethod.assign(invocationBuilderRef, handleCookieMethod.getMethodParam(0)); addCookieParam(handleCookieMethod, invocationBuilderRef, param.name, handleCookieMethod.getMethodParam(1), param.type, - handleCookieMethod.getThis()); + handleCookieMethod.getThis(), + methodGenericParametersField.get(), methodParamAnnotationsField.get(), paramIdx); handleCookieMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.FORM) { formParams = createIfAbsent(methodCreator, formParams); addFormParam(methodCreator, param.name, methodCreator.getMethodParam(paramIdx), param.type, - restClientInterface.getClassName(), methodCreator.getThis(), formParams); + restClientInterface.getClassName(), methodCreator.getThis(), formParams, + methodCreator.readArrayValue( + methodCreator.readStaticField(methodGenericParametersField.get()), paramIdx), + methodCreator.readArrayValue( + methodCreator.readStaticField(methodParamAnnotationsField.get()), paramIdx)); } else if (param.parameterType == ParameterType.MULTI_PART_FORM) { if (multipartForm != null) { throw new IllegalArgumentException("MultipartForm data set twice for method " @@ -939,8 +959,9 @@ A more full example of generated client (with sub-resource) can is at the bottom for (JaxrsClientReactiveEnricherBuildItem enricher : enrichers) { enricher.getEnricher() - .forMethod(c, constructor, clinit, methodCreator, interfaceClass, jandexMethod, builder, - index, generatedClasses, methodIndex); + .forMethod(classContext.classCreator, classContext.constructor, classContext.clinit, + methodCreator, interfaceClass, jandexMethod, builder, index, generatedClasses, + methodIndex, methodField); } handleReturn(interfaceClass, defaultMediaType, method.getHttpMethod(), @@ -948,11 +969,13 @@ A more full example of generated client (with sub-resource) can is at the bottom bodyParameterIdx == null ? null : methodCreator.getMethodParam(bodyParameterIdx), builder); } } - constructor.returnValue(null); - clinit.returnValue(null); + + classContext.constructor.returnValue(null); + classContext.clinit.returnValue(null); // create `void close()` method: - MethodCreator closeCreator = c.getMethodCreator(MethodDescriptor.ofMethod(Closeable.class, "close", void.class)); + MethodCreator closeCreator = classContext.classCreator + .getMethodCreator(MethodDescriptor.ofMethod(Closeable.class, "close", void.class)); for (FieldDescriptor target : webTargets) { ResultHandle webTarget = closeCreator.readInstanceField(target, closeCreator.getThis()); ResultHandle webTargetImpl = closeCreator.checkCast(webTarget, WebTargetImpl.class); @@ -962,15 +985,17 @@ A more full example of generated client (with sub-resource) can is at the bottom } closeCreator.returnValue(null); } - String creatorName = restClientInterface.getClassName() + "$$QuarkusRestClientInterfaceCreator"; + try (ClassCreator c = new ClassCreator(new GeneratedClassGizmoAdaptor(generatedClasses, true), creatorName, null, Object.class.getName(), BiFunction.class.getName())) { MethodCreator apply = c .getMethodCreator( MethodDescriptor.ofMethod(creatorName, "apply", Object.class, Object.class, Object.class)); - apply.returnValue(apply.newInstance(ctorDesc, apply.getMethodParam(0), apply.getMethodParam(1))); + apply.returnValue( + apply.newInstance(constructorDesc, apply.getMethodParam(0), apply.getMethodParam(1))); } + return recorderContext.newInstance(creatorName); } @@ -1003,8 +1028,9 @@ private ClassInfo returnTypeAsClass(MethodInfo jandexMethod, IndexView index) { private void handleSubResourceMethod(List enrichers, BuildProducer generatedClasses, ClassInfo interfaceClass, IndexView index, - String defaultMediaType, Map httpAnnotationToMethod, String name, ClassCreator c, - MethodCreator constructor, AssignableResultHandle baseTarget, int methodIndex, List webTargets, + String defaultMediaType, Map httpAnnotationToMethod, String name, + ClassRestClientContext classContext, MethodCreator constructor, AssignableResultHandle baseTarget, int methodIndex, + List webTargets, ResourceMethod method, String[] javaMethodParameters, MethodInfo jandexMethod, Set multipartResponseTypes) { Type returnType = jandexMethod.returnType(); @@ -1021,33 +1047,34 @@ private void handleSubResourceMethod(List + "If it's a sub resource method, it has to return an interface. " + "If it's not, it has to have one of the HTTP method annotations."); } + + classContext.createJavaMethodField(interfaceClass, jandexMethod, methodIndex); + Supplier methodParamAnnotationsField = classContext.getLazyJavaMethodParamAnnotationsField( + methodIndex); + Supplier methodGenericParametersField = classContext.getLazyJavaMethodGenericParametersField( + methodIndex); + // generate implementation for a method from the jaxrs interface: - MethodCreator methodCreator = c.getMethodCreator(method.getName(), method.getSimpleReturnType(), + MethodCreator methodCreator = classContext.classCreator.getMethodCreator(method.getName(), method.getSimpleReturnType(), javaMethodParameters); String subName = subResourceInterface.name().toString() + HashUtil.sha1(name) + methodIndex; - try (ClassCreator sub = new ClassCreator( - new GeneratedClassGizmoAdaptor(generatedClasses, true), - subName, null, Object.class.getName(), subResourceInterface.name().toString())) { + try (ClassRestClientContext subClassContext = new ClassRestClientContext(subName, generatedClasses, + subResourceInterface.name().toString())) { - ResultHandle subInstance = methodCreator.newInstance(MethodDescriptor.ofConstructor(subName)); + subClassContext.constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), + subClassContext.constructor.getThis()); - MethodCreator subConstructor = null; - MethodCreator subClinit = null; - if (!enrichers.isEmpty()) { - subConstructor = sub.getMethodCreator(MethodDescriptor.ofConstructor(subName)); - subConstructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), - subConstructor.getThis()); - subClinit = sub.getMethodCreator(MethodDescriptor.ofMethod(subName, "", void.class)); - subClinit.setModifiers(Opcodes.ACC_STATIC); - } + ResultHandle subInstance = methodCreator.newInstance(MethodDescriptor.ofConstructor(subName)); Map paramFields = new HashMap<>(); - FieldDescriptor clientField = createRestClientField(name, c, methodCreator, sub, subInstance); + FieldDescriptor clientField = createRestClientField(name, classContext.classCreator, methodCreator, + subClassContext.classCreator, subInstance); for (int i = 0; i < method.getParameters().length; i++) { - FieldDescriptor paramField = sub.getFieldCreator("param" + i, method.getParameters()[i].type) + FieldDescriptor paramField = subClassContext.classCreator.getFieldCreator("param" + i, + method.getParameters()[i].type) .setModifiers(Modifier.PUBLIC) .getFieldDescriptor(); methodCreator.writeInstanceField(paramField, subInstance, methodCreator.getMethodParam(i)); @@ -1066,12 +1093,17 @@ private void handleSubResourceMethod(List + ". It may have unresolved parameter types (generics)")); subMethodIndex++; // WebTarget field in the root stub implementation (not to recreate it on each call): + FieldDescriptor subMethodField = subClassContext.createJavaMethodField(subResourceInterface, jandexSubMethod, + subMethodIndex); + Supplier subMethodParamAnnotationsField = subClassContext + .getLazyJavaMethodParamAnnotationsField(subMethodIndex); + Supplier subMethodGenericParametersField = subClassContext + .getLazyJavaMethodGenericParametersField(subMethodIndex); // initializing the web target in the root stub constructor: - FieldDescriptor webTargetForSubMethod = FieldDescriptor.of(name, - "target" + methodIndex + "_" + subMethodIndex, + FieldDescriptor webTargetForSubMethod = FieldDescriptor.of(name, "target" + methodIndex + "_" + subMethodIndex, WebTarget.class); - c.getFieldCreator(webTargetForSubMethod).setModifiers(Modifier.FINAL); + classContext.classCreator.getFieldCreator(webTargetForSubMethod).setModifiers(Modifier.FINAL); webTargets.add(webTargetForSubMethod); AssignableResultHandle constructorTarget = createWebTargetForMethod(constructor, baseTarget, @@ -1083,13 +1115,14 @@ private void handleSubResourceMethod(List constructor.writeInstanceField(webTargetForSubMethod, constructor.getThis(), constructorTarget); // set the sub stub target field value to the target created above: - FieldDescriptor subWebTarget = sub.getFieldCreator("target" + subMethodIndex, WebTarget.class) + FieldDescriptor subWebTarget = subClassContext.classCreator.getFieldCreator("target" + subMethodIndex, + WebTarget.class) .setModifiers(Modifier.PUBLIC) .getFieldDescriptor(); methodCreator.writeInstanceField(subWebTarget, subInstance, methodCreator.readInstanceField(webTargetForSubMethod, methodCreator.getThis())); - MethodCreator subMethodCreator = sub.getMethodCreator(subMethod.getName(), + MethodCreator subMethodCreator = subClassContext.classCreator.getMethodCreator(subMethod.getName(), jandexSubMethod.returnType().name().toString(), parametersAsStringArray(jandexSubMethod)); @@ -1113,8 +1146,13 @@ private void handleSubResourceMethod(List subMethodCreator.assign(methodTarget, addQueryParam(subMethodCreator, methodTarget, param.name, paramValue, jandexMethod.parameters().get(paramIdx), index, - subMethodCreator.readInstanceField(clientField, - subMethodCreator.getThis()))); + subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), + subMethodCreator.readArrayValue( + subMethodCreator.readStaticField(methodGenericParametersField.get()), + paramIdx), + subMethodCreator.readArrayValue( + subMethodCreator.readStaticField(methodParamAnnotationsField.get()), + paramIdx))); } else if (param.parameterType == ParameterType.BEAN) { // bean params require both, web-target and Invocation.Builder, modifications // The web target changes have to be done on the method level. @@ -1126,7 +1164,8 @@ private void handleSubResourceMethod(List subMethod.getName() + "$$" + methodIndex + "$$handleBeanParam$$" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleBeanParamMethod = sub.getMethodCreator(handleBeanParamDescriptor); + MethodCreator handleBeanParamMethod = subClassContext.classCreator.getMethodCreator( + handleBeanParamDescriptor); AssignableResultHandle invocationBuilderRef = handleBeanParamMethod .createVariable(Invocation.Builder.class); @@ -1136,9 +1175,9 @@ private void handleSubResourceMethod(List paramValue, methodTarget, index, interfaceClass.name().toString(), subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), - handleBeanParamMethod.readInstanceField(clientField, - handleBeanParamMethod.getThis()), - formParams); + handleBeanParamMethod.readInstanceField(clientField, handleBeanParamMethod.getThis()), + formParams, + methodGenericParametersField, methodParamAnnotationsField, paramIdx); handleBeanParamMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleBeanParamDescriptor, paramValue); @@ -1146,7 +1185,11 @@ private void handleSubResourceMethod(List // methodTarget = methodTarget.resolveTemplate(paramname, paramvalue); addPathParam(subMethodCreator, methodTarget, param.name, paramValue, param.type, - subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis())); + subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), + subMethodCreator.readArrayValue( + subMethodCreator.readStaticField(methodGenericParametersField.get()), paramIdx), + subMethodCreator.readArrayValue( + subMethodCreator.readStaticField(methodParamAnnotationsField.get()), paramIdx)); } else if (param.parameterType == ParameterType.BODY) { // just store the index of parameter used to create the body, we'll use it later bodyParameterValue = paramValue; @@ -1156,7 +1199,8 @@ private void handleSubResourceMethod(List subMethod.getName() + "$$" + subMethodIndex + "$$handleHeader$$param" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleHeaderMethod = sub.getMethodCreator(handleHeaderDescriptor); + MethodCreator handleHeaderMethod = subClassContext.classCreator.getMethodCreator( + handleHeaderDescriptor); AssignableResultHandle invocationBuilderRef = handleHeaderMethod .createVariable(Invocation.Builder.class); @@ -1164,7 +1208,8 @@ private void handleSubResourceMethod(List addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name, handleHeaderMethod.getMethodParam(1), param.type, - handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis())); + handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis()), + methodGenericParametersField.get(), methodParamAnnotationsField.get(), paramIdx); handleHeaderMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, paramValue); } else if (param.parameterType == ParameterType.COOKIE) { @@ -1173,7 +1218,8 @@ private void handleSubResourceMethod(List subMethod.getName() + "$$" + subMethodIndex + "$$handleCookie$$param" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleCookieMethod = sub.getMethodCreator(handleCookieDescriptor); + MethodCreator handleCookieMethod = subClassContext.classCreator.getMethodCreator( + handleCookieDescriptor); AssignableResultHandle invocationBuilderRef = handleCookieMethod .createVariable(Invocation.Builder.class); @@ -1181,7 +1227,8 @@ private void handleSubResourceMethod(List addCookieParam(handleCookieMethod, invocationBuilderRef, param.name, handleCookieMethod.getMethodParam(1), param.type, - handleCookieMethod.readInstanceField(clientField, handleCookieMethod.getThis())); + handleCookieMethod.readInstanceField(clientField, handleCookieMethod.getThis()), + methodGenericParametersField.get(), methodParamAnnotationsField.get(), paramIdx); handleCookieMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleCookieDescriptor, paramValue); } else if (param.parameterType == ParameterType.FORM) { @@ -1208,8 +1255,13 @@ private void handleSubResourceMethod(List addQueryParam(subMethodCreator, methodTarget, param.name, subMethodCreator.getMethodParam(paramIdx), jandexSubMethod.parameters().get(paramIdx), index, - subMethodCreator.readInstanceField(clientField, - subMethodCreator.getThis()))); + subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), + subMethodCreator.readArrayValue( + subMethodCreator.readStaticField(subMethodGenericParametersField.get()), + paramIdx), + subMethodCreator.readArrayValue( + subMethodCreator.readStaticField(subMethodParamAnnotationsField.get()), + paramIdx))); } else if (param.parameterType == ParameterType.BEAN) { // bean params require both, web-target and Invocation.Builder, modifications // The web target changes have to be done on the method level. @@ -1221,7 +1273,8 @@ private void handleSubResourceMethod(List subMethod.getName() + "$$" + subMethodIndex + "$$handleBeanParam$$" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleBeanParamMethod = c.getMethodCreator(handleBeanParamDescriptor); + MethodCreator handleBeanParamMethod = classContext.classCreator.getMethodCreator( + handleBeanParamDescriptor); AssignableResultHandle invocationBuilderRef = handleBeanParamMethod .createVariable(Invocation.Builder.class); @@ -1231,9 +1284,9 @@ private void handleSubResourceMethod(List subMethodCreator.getMethodParam(paramIdx), methodTarget, index, interfaceClass.name().toString(), subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), - handleBeanParamMethod.readInstanceField(clientField, - handleBeanParamMethod.getThis()), - formParams); + handleBeanParamMethod.readInstanceField(clientField, handleBeanParamMethod.getThis()), + formParams, + subMethodGenericParametersField, subMethodParamAnnotationsField, paramIdx); handleBeanParamMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleBeanParamDescriptor, @@ -1241,7 +1294,11 @@ private void handleSubResourceMethod(List } else if (param.parameterType == ParameterType.PATH) { addPathParam(subMethodCreator, methodTarget, param.name, subMethodCreator.getMethodParam(paramIdx), param.type, - subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis())); + subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), + subMethodCreator.readArrayValue( + subMethodCreator.readStaticField(subMethodGenericParametersField.get()), paramIdx), + subMethodCreator.readArrayValue( + subMethodCreator.readStaticField(subMethodParamAnnotationsField.get()), paramIdx)); } else if (param.parameterType == ParameterType.BODY) { // just store the index of parameter used to create the body, we'll use it later bodyParameterValue = subMethodCreator.getMethodParam(paramIdx); @@ -1251,24 +1308,26 @@ private void handleSubResourceMethod(List subMethod.getName() + "$$" + subMethodIndex + "$$handleHeader$$" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleHeaderMethod = sub.getMethodCreator(handleHeaderDescriptor); + MethodCreator handleHeaderMethod = subClassContext.classCreator.getMethodCreator( + handleHeaderDescriptor); AssignableResultHandle invocationBuilderRef = handleHeaderMethod .createVariable(Invocation.Builder.class); handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0)); addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name, handleHeaderMethod.getMethodParam(1), param.type, - handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis())); + handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis()), + subMethodGenericParametersField.get(), subMethodParamAnnotationsField.get(), paramIdx); handleHeaderMethod.returnValue(invocationBuilderRef); - invocationBuilderEnrichers.put(handleHeaderDescriptor, - subMethodCreator.getMethodParam(paramIdx)); + invocationBuilderEnrichers.put(handleHeaderDescriptor, subMethodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.COOKIE) { // cookies are added at the invocation builder level MethodDescriptor handleCookieDescriptor = MethodDescriptor.ofMethod(subName, subMethod.getName() + "$$" + subMethodIndex + "$$handleCookie$$" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleCookieMethod = sub.getMethodCreator(handleCookieDescriptor); + MethodCreator handleCookieMethod = subClassContext.classCreator.getMethodCreator( + handleCookieDescriptor); AssignableResultHandle invocationBuilderRef = handleCookieMethod .createVariable(Invocation.Builder.class); @@ -1276,10 +1335,10 @@ private void handleSubResourceMethod(List addCookieParam(handleCookieMethod, invocationBuilderRef, param.name, handleCookieMethod.getMethodParam(1), param.type, - handleCookieMethod.readInstanceField(clientField, handleCookieMethod.getThis())); + handleCookieMethod.readInstanceField(clientField, handleCookieMethod.getThis()), + subMethodGenericParametersField.get(), subMethodParamAnnotationsField.get(), paramIdx); handleCookieMethod.returnValue(invocationBuilderRef); - invocationBuilderEnrichers.put(handleCookieDescriptor, - subMethodCreator.getMethodParam(paramIdx)); + invocationBuilderEnrichers.put(handleCookieDescriptor, subMethodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.FORM) { formParams = createIfAbsent(subMethodCreator, formParams); subMethodCreator.invokeInterfaceMethod(MULTIVALUED_MAP_ADD, formParams, @@ -1308,7 +1367,7 @@ private void handleSubResourceMethod(List handleSubResourceMethod(enrichers, generatedClasses, subResourceInterface, index, defaultMediaType, httpAnnotationToMethod, - subName, sub, subMethodCreator, + subName, subClassContext, subMethodCreator, methodTarget, subMethodIndex, webTargets, subMethod, subJavaMethodParameters, jandexSubMethod, multipartResponseTypes); } else { @@ -1343,9 +1402,10 @@ private void handleSubResourceMethod(List for (JaxrsClientReactiveEnricherBuildItem enricher : enrichers) { enricher.getEnricher() - .forSubResourceMethod(sub, subConstructor, subClinit, subMethodCreator, interfaceClass, + .forSubResourceMethod(subClassContext.classCreator, subClassContext.constructor, + subClassContext.clinit, subMethodCreator, interfaceClass, subResourceInterface, jandexSubMethod, jandexMethod, builder, index, - generatedClasses, methodIndex, subMethodIndex); + generatedClasses, methodIndex, subMethodIndex, subMethodField); } String[] consumes = extractProducesConsumesValues( @@ -1358,15 +1418,33 @@ private void handleSubResourceMethod(List } } - if (subConstructor != null) { - subConstructor.returnValue(null); - subClinit.returnValue(null); - } + subClassContext.constructor.returnValue(null); + subClassContext.clinit.returnValue(null); methodCreator.returnValue(subInstance); } } + private AssignableResultHandle createWebTargetForMethod(MethodCreator constructor, AssignableResultHandle baseTarget, + ResourceMethod method) { + AssignableResultHandle target = constructor.createVariable(WebTarget.class); + constructor.assign(target, baseTarget); + + if (method.getPath() != null) { + appendPath(constructor, method.getPath(), target); + } + return target; + } + + private void appendPath(MethodCreator constructor, String pathPart, AssignableResultHandle target) { + AssignableResultHandle path = constructor.createVariable(String.class); + constructor.assign(path, constructor.load(pathPart)); + constructor.assign(target, + constructor.invokeInterfaceMethod( + MethodDescriptor.ofMethod(WebTarget.class, "path", WebTarget.class, String.class), + target, path)); + } + /** * Create the `client` field into the `c` class that represents a RestClientBase instance. * The RestClientBase instance is coming from either a root client or a sub client (clients generated from root clients). @@ -1701,7 +1779,7 @@ private boolean is(DotName desiredClass, ClassInfo fieldClass, IndexView index) return is(desiredClass, superClass, index); } - private AssignableResultHandle createIfAbsent(MethodCreator methodCreator, AssignableResultHandle formValues) { + private AssignableResultHandle createIfAbsent(BytecodeCreator methodCreator, AssignableResultHandle formValues) { if (formValues == null) { formValues = methodCreator.createVariable(MultivaluedMap.class); methodCreator.assign(formValues, @@ -2055,26 +2133,6 @@ private ResultHandle createGenericTypeFromParameterizedType(MethodCreator method return genericReturnType; } - private AssignableResultHandle createWebTargetForMethod(MethodCreator constructor, AssignableResultHandle baseTarget, - ResourceMethod method) { - AssignableResultHandle target = constructor.createVariable(WebTarget.class); - constructor.assign(target, baseTarget); - - if (method.getPath() != null) { - appendPath(constructor, method.getPath(), target); - } - return target; - } - - private void appendPath(MethodCreator constructor, String pathPart, AssignableResultHandle target) { - AssignableResultHandle path = constructor.createVariable(String.class); - constructor.assign(path, constructor.load(pathPart)); - constructor.assign(target, - constructor.invokeInterfaceMethod( - MethodDescriptor.ofMethod(WebTarget.class, "path", WebTarget.class, String.class), - target, path)); - } - private Optional getJavaMethod(ClassInfo interfaceClass, ResourceMethod method, MethodParameter[] parameters, IndexView index) { @@ -2107,42 +2165,53 @@ private Optional getJavaMethod(ClassInfo interfaceClass, ResourceMet return maybeMethod; } - private AssignableResultHandle addBeanParamData(MethodCreator methodCreator, - BytecodeCreator invocationBuilderEnricher, // Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder) + private AssignableResultHandle addBeanParamData(BytecodeCreator methodCreator, + // Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder) + BytecodeCreator invocationBuilderEnricher, AssignableResultHandle invocationBuilder, List beanParamItems, ResultHandle param, - AssignableResultHandle target, // can only be used in the current method, not in `invocationBuilderEnricher` + // can only be used in the current method, not in `invocationBuilderEnricher` + AssignableResultHandle target, IndexView index, String restClientInterfaceClassName, ResultHandle client, ResultHandle invocationEnricherClient, // this client or containing client if this is a sub-client - AssignableResultHandle formParams) { + AssignableResultHandle formParams, + Supplier methodGenericTypeField, Supplier methodParamAnnotationsField, + int paramIdx) { // Form params collector must be initialized at method root level before any inner blocks that may use it if (areFormParamsDefinedIn(beanParamItems)) { formParams = createIfAbsent(methodCreator, formParams); } addSubBeanParamData(methodCreator, invocationBuilderEnricher, invocationBuilder, beanParamItems, param, target, - index, restClientInterfaceClassName, client, invocationEnricherClient, formParams); + index, restClientInterfaceClassName, client, invocationEnricherClient, formParams, + methodGenericTypeField, methodParamAnnotationsField, paramIdx); return formParams; } private void addSubBeanParamData(BytecodeCreator methodCreator, - BytecodeCreator invocationBuilderEnricher, // Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder) + // Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder) + BytecodeCreator invocationBuilderEnricher, AssignableResultHandle invocationBuilder, List beanParamItems, ResultHandle param, - AssignableResultHandle target, // can only be used in the current method, not in `invocationBuilderEnricher` + // can only be used in the current method, not in `invocationBuilderEnricher` + AssignableResultHandle target, IndexView index, String restClientInterfaceClassName, ResultHandle client, - ResultHandle invocationEnricherClient, // this client or containing client if this is a sub-client - AssignableResultHandle formParams) { + // this client or containing client if this is a sub-client + ResultHandle invocationEnricherClient, + AssignableResultHandle formParams, + Supplier methodGenericTypeField, Supplier methodParamAnnotationsField, + int paramIdx) { BytecodeCreator creator = methodCreator.ifNotNull(param).trueBranch(); BytecodeCreator invoEnricher = invocationBuilderEnricher.ifNotNull(invocationBuilderEnricher.getMethodParam(1)) .trueBranch(); + for (Item item : beanParamItems) { switch (item.type()) { case BEAN_PARAM: @@ -2150,7 +2219,8 @@ private void addSubBeanParamData(BytecodeCreator methodCreator, ResultHandle beanParamElementHandle = beanParamItem.extract(creator, param); addSubBeanParamData(creator, invoEnricher, invocationBuilder, beanParamItem.items(), beanParamElementHandle, target, index, restClientInterfaceClassName, client, - invocationEnricherClient, formParams); + invocationEnricherClient, formParams, + methodGenericTypeField, methodParamAnnotationsField, paramIdx); break; case QUERY_PARAM: QueryParamItem queryParam = (QueryParamItem) item; @@ -2158,32 +2228,42 @@ private void addSubBeanParamData(BytecodeCreator methodCreator, addQueryParam(creator, target, queryParam.name(), queryParam.extract(creator, param), queryParam.getValueType(), - index, client)); + index, client, + creator.readArrayValue(creator.readStaticField(methodGenericTypeField.get()), + paramIdx), + creator.readArrayValue(creator.readStaticField(methodParamAnnotationsField.get()), + paramIdx))); break; case COOKIE: CookieParamItem cookieParam = (CookieParamItem) item; addCookieParam(invoEnricher, invocationBuilder, cookieParam.getCookieName(), cookieParam.extract(invoEnricher, invoEnricher.getMethodParam(1)), - cookieParam.getParamType(), invocationEnricherClient); + cookieParam.getParamType(), invocationEnricherClient, + methodGenericTypeField.get(), methodParamAnnotationsField.get(), paramIdx); break; case HEADER_PARAM: HeaderParamItem headerParam = (HeaderParamItem) item; addHeaderParam(invoEnricher, invocationBuilder, headerParam.getHeaderName(), headerParam.extract(invoEnricher, invoEnricher.getMethodParam(1)), - headerParam.getParamType(), invocationEnricherClient); + headerParam.getParamType(), invocationEnricherClient, methodGenericTypeField.get(), + methodParamAnnotationsField.get(), paramIdx); break; case PATH_PARAM: PathParamItem pathParam = (PathParamItem) item; addPathParam(creator, target, pathParam.getPathParamName(), - pathParam.extract(creator, param), pathParam.getParamType(), client); + pathParam.extract(creator, param), pathParam.getParamType(), client, + creator.readArrayValue(creator.readStaticField(methodGenericTypeField.get()), paramIdx), + creator.readArrayValue(creator.readStaticField(methodParamAnnotationsField.get()), paramIdx)); break; case FORM_PARAM: FormParamItem formParam = (FormParamItem) item; addFormParam(creator, formParam.getFormParamName(), formParam.extract(creator, param), - formParam.getParamType(), restClientInterfaceClassName, client, formParams); + formParam.getParamType(), restClientInterfaceClassName, client, formParams, + creator.readArrayValue(creator.readStaticField(methodGenericTypeField.get()), paramIdx), + creator.readArrayValue(creator.readStaticField(methodParamAnnotationsField.get()), paramIdx)); break; default: throw new IllegalStateException("Unimplemented"); @@ -2214,7 +2294,10 @@ private ResultHandle addQueryParam(BytecodeCreator methodCreator, ResultHandle queryParamHandle, Type type, IndexView index, - ResultHandle client) { // this client or containing client if we're in a subresource + // this client or containing client if we're in a subresource + ResultHandle client, + ResultHandle genericType, + ResultHandle paramAnnotations) { ResultHandle paramArray; String componentType = null; if (type.kind() == Type.Kind.ARRAY) { @@ -2242,8 +2325,8 @@ private ResultHandle addQueryParam(BytecodeCreator methodCreator, paramArray = methodCreator.invokeVirtualMethod( MethodDescriptor.ofMethod(RestClientBase.class, "convertParamArray", Object[].class, Object[].class, - Class.class), - client, paramArray, methodCreator.loadClassFromTCCL(componentType)); + Class.class, java.lang.reflect.Type.class, Annotation[].class), + client, paramArray, methodCreator.loadClassFromTCCL(componentType), genericType, paramAnnotations); return methodCreator.invokeInterfaceMethod( MethodDescriptor.ofMethod(WebTarget.class, "queryParam", WebTarget.class, @@ -2263,26 +2346,36 @@ private boolean isCollection(Type type, IndexView index) { } private void addHeaderParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder, - String paramName, ResultHandle headerParamHandle, String paramType, ResultHandle client) { + String paramName, ResultHandle headerParamHandle, String paramType, ResultHandle client, + FieldDescriptor methodGenericTypeField, FieldDescriptor methodParamAnnotationsField, int paramIdx) { + + ResultHandle genericType = invoBuilderEnricher.readArrayValue( + invoBuilderEnricher.readStaticField(methodGenericTypeField), paramIdx); + + ResultHandle parameterAnnotations = invoBuilderEnricher.readArrayValue( + invoBuilderEnricher.readStaticField(methodParamAnnotationsField), paramIdx); headerParamHandle = invoBuilderEnricher.invokeVirtualMethod( - MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, Object.class, Class.class), + MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, + Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class), client, headerParamHandle, - invoBuilderEnricher.loadClassFromTCCL(paramType)); + invoBuilderEnricher.loadClassFromTCCL(paramType), genericType, parameterAnnotations); invoBuilderEnricher.assign(invocationBuilder, invoBuilderEnricher.invokeInterfaceMethod( - MethodDescriptor.ofMethod(Invocation.Builder.class, "header", Invocation.Builder.class, String.class, - Object.class), + MethodDescriptor.ofMethod(Invocation.Builder.class, "header", Invocation.Builder.class, + String.class, Object.class), invocationBuilder, invoBuilderEnricher.load(paramName), headerParamHandle)); } private void addPathParam(BytecodeCreator methodCreator, AssignableResultHandle methodTarget, - String paramName, ResultHandle pathParamHandle, String parameterType, ResultHandle client) { + String paramName, ResultHandle pathParamHandle, String parameterType, ResultHandle client, + ResultHandle genericType, ResultHandle parameterAnnotations) { ResultHandle handle = methodCreator.invokeVirtualMethod( - MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, Object.class, Class.class), + MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, + Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class), client, pathParamHandle, - methodCreator.loadClassFromTCCL(parameterType)); + methodCreator.loadClassFromTCCL(parameterType), genericType, parameterAnnotations); methodCreator.assign(methodTarget, methodCreator.invokeInterfaceMethod(WEB_TARGET_RESOLVE_TEMPLATE_METHOD, methodTarget, @@ -2291,11 +2384,13 @@ private void addPathParam(BytecodeCreator methodCreator, AssignableResultHandle private void addFormParam(BytecodeCreator methodCreator, String paramName, ResultHandle formParamHandle, String parameterType, String restClientInterfaceClassName, - ResultHandle client, AssignableResultHandle formParams) { + ResultHandle client, AssignableResultHandle formParams, ResultHandle genericType, + ResultHandle parameterAnnotations) { ResultHandle convertedFormParam = methodCreator.invokeVirtualMethod( - MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, Object.class, Class.class), + MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, + Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class), client, formParamHandle, - methodCreator.loadClass(parameterType)); + methodCreator.loadClassFromTCCL(parameterType), genericType, parameterAnnotations); ResultHandle isString = methodCreator.instanceOf(convertedFormParam, String.class); BranchResult isStringBranch = methodCreator.ifTrue(isString); isStringBranch.falseBranch().throwException(IllegalStateException.class, @@ -2310,11 +2405,20 @@ private void addFormParam(BytecodeCreator methodCreator, String paramName, Resul } private void addCookieParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder, - String paramName, ResultHandle cookieParamHandle, String paramType, ResultHandle client) { + String paramName, ResultHandle cookieParamHandle, String paramType, ResultHandle client, + FieldDescriptor methodGenericTypeField, FieldDescriptor methodParamAnnotationsField, int paramIdx) { + + ResultHandle genericType = invoBuilderEnricher.readArrayValue( + invoBuilderEnricher.readStaticField(methodGenericTypeField), paramIdx); + + ResultHandle parameterAnnotations = invoBuilderEnricher.readArrayValue( + invoBuilderEnricher.readStaticField(methodParamAnnotationsField), paramIdx); + cookieParamHandle = invoBuilderEnricher.invokeVirtualMethod( - MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, Object.class, Class.class), + MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, + Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class), client, cookieParamHandle, - invoBuilderEnricher.loadClassFromTCCL(paramType)); + invoBuilderEnricher.loadClassFromTCCL(paramType), genericType, parameterAnnotations); invoBuilderEnricher.assign(invocationBuilder, invoBuilderEnricher.invokeInterfaceMethod( MethodDescriptor.ofMethod(Invocation.Builder.class, "cookie", Invocation.Builder.class, String.class, diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java index 57978766d7919b..343f9391c859e4 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java @@ -19,8 +19,8 @@ public RestClientBase(List providers) { } @SuppressWarnings("unused") // used by generated code - public Object[] convertParamArray(T[] value, Class type) { - ParamConverter converter = getConverter(type, null, null); + public Object[] convertParamArray(T[] value, Class type, Type genericType, Annotation[] annotations) { + ParamConverter converter = getConverter(type, genericType, annotations); if (converter == null) { return value; @@ -35,8 +35,8 @@ public Object[] convertParamArray(T[] value, Class type) { } @SuppressWarnings("unused") // used by generated code - public Object convertParam(T value, Class type) { - ParamConverter converter = getConverter(type, null, null); + public Object convertParam(T value, Class type, Type genericType, Annotation[] annotations) { + ParamConverter converter = getConverter(type, genericType, annotations); if (converter != null) { return converter.toString(value); } else { @@ -49,7 +49,7 @@ private ParamConverter getConverter(Class type, Type genericType, Anno if (converterProvider == null) { for (ParamConverterProvider provider : paramConverterProviders) { - ParamConverter converter = provider.getConverter(type, null, null); + ParamConverter converter = provider.getConverter(type, genericType, annotations); if (converter != null) { providerForClass.put(type, provider); return converter; @@ -57,7 +57,7 @@ private ParamConverter getConverter(Class type, Type genericType, Anno } providerForClass.put(type, NO_PROVIDER); } else if (converterProvider != NO_PROVIDER) { - return converterProvider.getConverter(type, null, null); + return converterProvider.getConverter(type, genericType, annotations); } return null; } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java index a0cac54126fab2..4890927cda9f52 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java @@ -8,7 +8,6 @@ import static org.objectweb.asm.Opcodes.ACC_STATIC; import java.lang.annotation.Annotation; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; @@ -132,9 +131,9 @@ public void forSubResourceMethod(ClassCreator subClassCreator, MethodCreator sub ClassInfo subInterfaceClass, MethodInfo subMethod, MethodInfo rootMethod, AssignableResultHandle invocationBuilder, // sub-level IndexView index, BuildProducer generatedClasses, - int methodIndex, int subMethodIndex) { - addJavaMethodToContext(subClassCreator, subClinit, subMethodCreator, subInterfaceClass, subMethod, - invocationBuilder, subMethodIndex); + int methodIndex, int subMethodIndex, FieldDescriptor javaMethodField) { + + addJavaMethodToContext(javaMethodField, subMethodCreator, invocationBuilder); Map headerFillersByName = new HashMap<>(); collectHeaderFillers(rootInterfaceClass, rootMethod, headerFillersByName); @@ -149,10 +148,9 @@ public void forSubResourceMethod(ClassCreator subClassCreator, MethodCreator sub public void forMethod(ClassCreator classCreator, MethodCreator constructor, MethodCreator clinit, MethodCreator methodCreator, ClassInfo interfaceClass, MethodInfo method, AssignableResultHandle invocationBuilder, IndexView index, - BuildProducer generatedClasses, int methodIndex) { + BuildProducer generatedClasses, int methodIndex, FieldDescriptor javaMethodField) { - addJavaMethodToContext(classCreator, clinit, methodCreator, interfaceClass, method, invocationBuilder, - methodIndex); + addJavaMethodToContext(javaMethodField, methodCreator, invocationBuilder); // header filler @@ -244,17 +242,13 @@ private void collectHeaderFillers(ClassInfo interfaceClass, MethodInfo method, * create a field in the stub class to contain (interface) java.lang.reflect.Method corresponding to this method * MP Rest Client spec says it has to be in the request context, keeping it in a field we don't have to * initialize it on each call - * - * @param classCreator client (or sub-resource client) class creator + * + * @param javaMethodField method reference in a static class field * @param methodCreator method for which we put the java.lang.reflect.Method to context (aka this method) - * @param interfaceClass class of the interface for this client - * @param method jandex counterpart of this method * @param invocationBuilder Invocation.Builder in this method - * @param methodIndex index of this method */ - private void addJavaMethodToContext(ClassCreator classCreator, MethodCreator clinit, MethodCreator methodCreator, - ClassInfo interfaceClass, MethodInfo method, AssignableResultHandle invocationBuilder, int methodIndex) { - FieldDescriptor javaMethodField = createJavaMethodField(classCreator, clinit, interfaceClass, method, methodIndex); + private void addJavaMethodToContext(FieldDescriptor javaMethodField, MethodCreator methodCreator, + AssignableResultHandle invocationBuilder) { ResultHandle javaMethod = methodCreator.readStaticField(javaMethodField); ResultHandle javaMethodAsObject = methodCreator.checkCast(javaMethod, Object.class); methodCreator.assign(invocationBuilder, @@ -262,26 +256,6 @@ private void addJavaMethodToContext(ClassCreator classCreator, MethodCreator cli methodCreator.load(INVOKED_METHOD), javaMethodAsObject)); } - private FieldDescriptor createJavaMethodField(ClassCreator classCreator, MethodCreator clinit, - ClassInfo interfaceClass, MethodInfo method, int methodIndex) { - ResultHandle interfaceClassHandle = clinit.loadClassFromTCCL(interfaceClass.toString()); - - ResultHandle parameterArray = clinit.newArray(Class.class, method.parameters().size()); - for (int i = 0; i < method.parameters().size(); i++) { - String parameterClass = method.parameters().get(i).name().toString(); - clinit.writeArrayValue(parameterArray, i, clinit.loadClassFromTCCL(parameterClass)); - } - - ResultHandle javaMethodHandle = clinit.invokeVirtualMethod( - MethodDescriptor.ofMethod(Class.class, "getMethod", Method.class, String.class, Class[].class), - interfaceClassHandle, clinit.load(method.name()), parameterArray); - FieldDescriptor javaMethodField = FieldDescriptor.of(classCreator.getClassName(), "javaMethod" + methodIndex, - Method.class); - classCreator.getFieldCreator(javaMethodField).setModifiers(Modifier.PRIVATE | Modifier.FINAL | Modifier.STATIC); - clinit.writeStaticField(javaMethodField, javaMethodHandle); - return javaMethodField; - } - private void putAllHeaderAnnotations(Map headerMap, ClassInfo interfaceClass, AnnotationInstance[] annotations) { for (AnnotationInstance annotation : annotations) { diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java index 76e277d68387f3..c7a211cdfd6990 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java @@ -1,6 +1,7 @@ package io.quarkus.rest.client.reactive.converter; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import java.lang.annotation.Annotation; import java.lang.reflect.Type; @@ -162,6 +163,14 @@ public static class ParamConverter implements ParamConverterProvider { @Override public javax.ws.rs.ext.ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { + if (genericType == null) { + fail("Generic Type cannot be null!"); + } + + if (annotations == null) { + fail("Annotations cannot be null!"); + } + if (rawType == Param.class) { return (javax.ws.rs.ext.ParamConverter) new javax.ws.rs.ext.ParamConverter() { @Override diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java index 617fd23c9e9f0c..9f244622de3ce8 100644 --- a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java @@ -118,6 +118,17 @@ void init(@Observes Router router) { rc.response().end(greeting); }); + router.post("/params/param").handler(rc -> rc.response().putHeader("content-type", MediaType.TEXT_PLAIN) + .end(getParam(rc))); + + router.route("/call-params-client-with-param-first").blockingHandler(rc -> { + String url = rc.getBody().toString(); + ParamClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + .build(ParamClient.class); + String result = client.getParam(Param.FIRST); + rc.response().end(result); + }); + router.route("/rest-response").blockingHandler(rc -> { String url = rc.getBody().toString(); RestResponseClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) @@ -166,6 +177,10 @@ private int getCount(io.vertx.ext.web.RoutingContext rc) { return Integer.parseInt(countQueryParam.get(0)); } + private String getParam(io.vertx.ext.web.RoutingContext rc) { + return rc.queryParam("param").get(0); + } + private void callGet(RoutingContext rc, ClientWithExceptionMapper client) { try { client.get(); diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/Param.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/Param.java new file mode 100644 index 00000000000000..e083c28042fdcc --- /dev/null +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/Param.java @@ -0,0 +1,6 @@ +package io.quarkus.it.rest.client.main; + +public enum Param { + FIRST, + SECOND +} diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ParamClient.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ParamClient.java new file mode 100644 index 00000000000000..8826ca59a7b679 --- /dev/null +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ParamClient.java @@ -0,0 +1,21 @@ +package io.quarkus.it.rest.client.main; + +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@Path("") +@RegisterRestClient +@RegisterProvider(ParamConverter.class) +public interface ParamClient { + + @POST + @Path("/param") + @Produces(MediaType.TEXT_PLAIN) + String getParam(@QueryParam("param") Param param); +} diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ParamConverter.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ParamConverter.java new file mode 100644 index 00000000000000..3513b41cc71dd4 --- /dev/null +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ParamConverter.java @@ -0,0 +1,46 @@ +package io.quarkus.it.rest.client.main; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.ext.ParamConverterProvider; + +public class ParamConverter implements ParamConverterProvider { + @SuppressWarnings("unchecked") + @Override + public javax.ws.rs.ext.ParamConverter getConverter(Class rawType, Type genericType, + Annotation[] annotations) { + if (genericType == null) { + throw new RuntimeException("Generic Type cannot be null!"); + } + + if (annotations == null) { + throw new RuntimeException("Annotations cannot be null!"); + } + + if (rawType == Param.class) { + return (javax.ws.rs.ext.ParamConverter) new javax.ws.rs.ext.ParamConverter() { + @Override + public Param fromString(String value) { + return null; + } + + @Override + public String toString(Param value) { + if (value == null) { + return null; + } + switch (value) { + case FIRST: + return "1"; + case SECOND: + return "2"; + default: + return "unexpected"; + } + } + }; + } + return null; + } +} diff --git a/integration-tests/rest-client-reactive/src/main/resources/application.properties b/integration-tests/rest-client-reactive/src/main/resources/application.properties index dca0a78fb80fcf..392722ce0c912d 100644 --- a/integration-tests/rest-client-reactive/src/main/resources/application.properties +++ b/integration-tests/rest-client-reactive/src/main/resources/application.properties @@ -1,3 +1,4 @@ w-exception-mapper/mp-rest/url=${test.url} w-fault-tolerance/mp-rest/url=${test.url} +io.quarkus.it.rest.client.main.ParamClient/mp-rest/url=${test.url} io.quarkus.it.rest.client.multipart.MultipartClient/mp-rest/url=${test.url} \ No newline at end of file diff --git a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java index 6ce6783e86963c..d9ef8c44460edd 100644 --- a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java +++ b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/BasicTest.java @@ -31,9 +31,10 @@ public class BasicTest { String appleUrl; @TestHTTPResource() String baseUrl; - @TestHTTPResource("/hello") String helloUrl; + @TestHTTPResource("/params") + String paramsUrl; @Test public void shouldMakeTextRequest() { @@ -209,6 +210,14 @@ void shouldCreateClientSpans() { Assertions.assertTrue(clientServerFound); } + @Test + public void shouldConvertParamFirstToOneUsingCustomConverter() { + RestAssured.with().body(paramsUrl).post("/call-params-client-with-param-first") + .then() + .statusCode(200) + .body(equalTo("1")); + } + private List> getSpans() { return get("/export").body().as(new TypeRef>>() { });