From 307b5e73448feae92b5446ac7db5935db4695f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szynkiewicz?= Date: Mon, 20 Dec 2021 08:27:30 +0100 Subject: [PATCH 1/2] REST Client Reactive - param converter support Path params, query params, cookie params and header params are supported with this change --- .../JaxrsClientReactiveProcessor.java | 173 +++++++++---- .../reactive/deployment/RestClientBase.java | 71 +++++ .../runtime/JaxrsClientReactiveRecorder.java | 15 +- .../converter/ParamConverterProviderTest.java | 242 ++++++++++++++++++ .../runtime/RestClientBuilderImpl.java | 9 +- .../processor/beanparam/BeanParamParser.java | 18 +- .../processor/beanparam/CookieParamItem.java | 8 +- .../processor/beanparam/HeaderParamItem.java | 8 +- .../client/processor/beanparam/Item.java | 1 - .../processor/beanparam/PathParamItem.java | 8 +- .../reactive/client/impl/ClientProxies.java | 15 +- .../reactive/client/impl/WebTargetImpl.java | 10 +- 12 files changed, 501 insertions(+), 77 deletions(-) create mode 100644 extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/RestClientBase.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java 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 67dacc9029692..922cb057427ef 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 @@ -1,6 +1,8 @@ package io.quarkus.jaxrs.client.reactive.deployment; import static io.quarkus.deployment.Feature.JAXRS_CLIENT_REACTIVE; +import static org.jboss.jandex.Type.Kind.CLASS; +import static org.jboss.jandex.Type.Kind.PARAMETERIZED_TYPE; import static org.jboss.resteasy.reactive.common.processor.EndpointIndexer.extractProducesConsumesValues; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.COMPLETION_STAGE; import static org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames.CONSUMES; @@ -23,8 +25,8 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -41,6 +43,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.ParamConverterProvider; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; @@ -245,7 +248,7 @@ void setupClientProxies(JaxrsClientReactiveRecorder recorder, .setHasRuntimeConverters(false) .setDefaultProduces(defaultProducesType) .setSmartDefaultProduces(disableSmartDefaultProduces.isEmpty()) - .setResourceMethodCallback(new Consumer() { + .setResourceMethodCallback(new Consumer<>() { @Override public void accept(EndpointIndexer.ResourceMethodCallbackData entry) { MethodInfo method = entry.getMethodInfo(); @@ -269,7 +272,7 @@ public void accept(EndpointIndexer.ResourceMethodCallbackData entry) { (metricsCapability.isPresent() && metricsCapability.get().metricsSupported(MetricsFactory.MICROMETER))); - Map>> clientImplementations = new HashMap<>(); + Map, ?>>> clientImplementations = new HashMap<>(); Map failures = new HashMap<>(); for (Map.Entry i : result.getClientInterfaces().entrySet()) { ClassInfo clazz = index.getClassByName(i.getKey()); @@ -280,7 +283,8 @@ public void accept(EndpointIndexer.ResourceMethodCallbackData entry) { if (maybeClientProxy.exists()) { RestClientInterface clientProxy = maybeClientProxy.getRestClientInterface(); try { - RuntimeValue> proxyProvider = generateClientInvoker(recorderContext, clientProxy, + RuntimeValue, ?>> proxyProvider = generateClientInvoker( + recorderContext, clientProxy, enricherBuildItems, generatedClassBuildItemBuildProducer, clazz, index, defaultConsumesType, result.getHttpAnnotationToMethod(), observabilityIntegrationNeeded); if (proxyProvider != null) { @@ -470,16 +474,17 @@ public void close() { A more full example of generated client (with sub-resource) can is at the bottom of extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/subresource/SubResourceTest.java */ - private RuntimeValue> generateClientInvoker(RecorderContext recorderContext, + private RuntimeValue, ?>> generateClientInvoker( + RecorderContext recorderContext, RestClientInterface restClientInterface, List enrichers, BuildProducer generatedClasses, ClassInfo interfaceClass, IndexView index, String defaultMediaType, Map httpAnnotationToMethod, boolean observabilityIntegrationNeeded) { String name = restClientInterface.getClassName() + "$$QuarkusRestClientInterface"; - MethodDescriptor ctorDesc = MethodDescriptor.ofConstructor(name, WebTarget.class.getName()); + MethodDescriptor ctorDesc = MethodDescriptor.ofConstructor(name, WebTarget.class.getName(), List.class); try (ClassCreator c = new ClassCreator(new GeneratedClassGizmoAdaptor(generatedClasses, true), - name, null, Object.class.getName(), + name, null, RestClientBase.class.getName(), Closeable.class.getName(), restClientInterface.getClassName())) { // @@ -487,7 +492,8 @@ A more full example of generated client (with sub-resource) can is at the bottom // MethodCreator constructor = c.getMethodCreator(ctorDesc); - constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), constructor.getThis()); + 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); @@ -529,7 +535,7 @@ A more full example of generated client (with sub-resource) can is at the bottom if (method.getHttpMethod() == null) { Type returnType = jandexMethod.returnType(); - if (returnType.kind() != Type.Kind.CLASS) { + if (returnType.kind() != CLASS) { // sort of sub-resource method that returns a thing that isn't a class throw new IllegalArgumentException("Sub resource type is not a class: " + returnType.name().toString()); } @@ -564,6 +570,12 @@ A more full example of generated client (with sub-resource) can is at the bottom } Map paramFields = new HashMap<>(); + + FieldDescriptor clientField = sub.getFieldCreator("client", RestClientBase.class) + .setModifiers(Modifier.PUBLIC) + .getFieldDescriptor(); + methodCreator.writeInstanceField(clientField, subInstance, methodCreator.getThis()); + for (int i = 0; i < method.getParameters().length; i++) { FieldDescriptor paramField = sub.getFieldCreator("param" + i, method.getParameters()[i].type) .setModifiers(Modifier.PUBLIC) @@ -633,7 +645,9 @@ 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) subMethodCreator.assign(methodTarget, addQueryParam(subMethodCreator, methodTarget, param.name, - paramValue, jandexMethod.parameters().get(paramIdx), index)); + paramValue, jandexMethod.parameters().get(paramIdx), index, + subMethodCreator.readInstanceField(clientField, + subMethodCreator.getThis()))); } 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. @@ -652,16 +666,18 @@ A more full example of generated client (with sub-resource) can is at the bottom handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0)); addBeanParamData(subMethodCreator, handleBeanParamMethod, invocationBuilderRef, beanParam.getItems(), - paramValue, methodTarget, index); + paramValue, methodTarget, index, + subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), + handleBeanParamMethod.readInstanceField(clientField, + handleBeanParamMethod.getThis())); handleBeanParamMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleBeanParamDescriptor, paramValue); } else if (param.parameterType == ParameterType.PATH) { // methodTarget = methodTarget.resolveTemplate(paramname, paramvalue); - subMethodCreator.assign(methodTarget, - subMethodCreator.invokeInterfaceMethod(WEB_TARGET_RESOLVE_TEMPLATE_METHOD, - methodTarget, - subMethodCreator.load(param.name), paramValue)); + addPathParam(subMethodCreator, methodTarget, param.name, paramValue, + param.type, + subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis())); } else if (param.parameterType == ParameterType.BODY) { // just store the index of parameter used to create the body, we'll use it later bodyParameterValue = paramValue; @@ -677,7 +693,9 @@ A more full example of generated client (with sub-resource) can is at the bottom .createVariable(Invocation.Builder.class); handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0)); addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name, - handleHeaderMethod.getMethodParam(1)); + handleHeaderMethod.getMethodParam(1), + param.type, + handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis())); handleHeaderMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, paramValue); } else if (param.parameterType == ParameterType.COOKIE) { @@ -692,7 +710,9 @@ A more full example of generated client (with sub-resource) can is at the bottom .createVariable(Invocation.Builder.class); handleCookieMethod.assign(invocationBuilderRef, handleCookieMethod.getMethodParam(0)); addCookieParam(handleCookieMethod, invocationBuilderRef, param.name, - handleCookieMethod.getMethodParam(1)); + handleCookieMethod.getMethodParam(1), + param.type, + handleCookieMethod.readInstanceField(clientField, handleCookieMethod.getThis())); handleCookieMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleCookieDescriptor, paramValue); } else if (param.parameterType == ParameterType.FORM) { @@ -718,7 +738,9 @@ A more full example of generated client (with sub-resource) can is at the bottom subMethodCreator.assign(methodTarget, addQueryParam(subMethodCreator, methodTarget, param.name, subMethodCreator.getMethodParam(paramIdx), - jandexSubMethod.parameters().get(paramIdx), index)); + jandexSubMethod.parameters().get(paramIdx), index, + subMethodCreator.readInstanceField(clientField, + subMethodCreator.getThis()))); } 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. @@ -737,18 +759,18 @@ A more full example of generated client (with sub-resource) can is at the bottom handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0)); addBeanParamData(subMethodCreator, handleBeanParamMethod, invocationBuilderRef, beanParam.getItems(), - subMethodCreator.getMethodParam(paramIdx), methodTarget, index); + subMethodCreator.getMethodParam(paramIdx), methodTarget, index, + subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()), + handleBeanParamMethod.readInstanceField(clientField, + handleBeanParamMethod.getThis())); handleBeanParamMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleBeanParamDescriptor, subMethodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.PATH) { - // methodTarget = methodTarget.resolveTemplate(paramname, paramvalue); - subMethodCreator.assign(methodTarget, - subMethodCreator.invokeInterfaceMethod(WEB_TARGET_RESOLVE_TEMPLATE_METHOD, - methodTarget, - subMethodCreator.load(param.name), - subMethodCreator.getMethodParam(paramIdx))); + addPathParam(subMethodCreator, methodTarget, param.name, + subMethodCreator.getMethodParam(paramIdx), param.type, + subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis())); } 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); @@ -764,25 +786,28 @@ A more full example of generated client (with sub-resource) can is at the bottom .createVariable(Invocation.Builder.class); handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0)); addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name, - handleHeaderMethod.getMethodParam(1)); + handleHeaderMethod.getMethodParam(1), param.type, + handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis())); handleHeaderMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, subMethodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.COOKIE) { // cookies are added at the invocation builder level - MethodDescriptor handleHeaderDescriptor = MethodDescriptor.ofMethod(subName, + MethodDescriptor handleCookieDescriptor = MethodDescriptor.ofMethod(subName, subMethod.getName() + "$$" + subMethodIndex + "$$handleCookie$$" + paramIdx, Invocation.Builder.class, Invocation.Builder.class, param.type); - MethodCreator handleCookieMethod = sub.getMethodCreator(handleHeaderDescriptor); + MethodCreator handleCookieMethod = sub.getMethodCreator(handleCookieDescriptor); AssignableResultHandle invocationBuilderRef = handleCookieMethod .createVariable(Invocation.Builder.class); handleCookieMethod.assign(invocationBuilderRef, handleCookieMethod.getMethodParam(0)); addCookieParam(handleCookieMethod, invocationBuilderRef, param.name, - handleCookieMethod.getMethodParam(1)); + handleCookieMethod.getMethodParam(1), + param.type, + handleCookieMethod.readInstanceField(clientField, handleCookieMethod.getThis())); handleCookieMethod.returnValue(invocationBuilderRef); - invocationBuilderEnrichers.put(handleHeaderDescriptor, + invocationBuilderEnrichers.put(handleCookieDescriptor, subMethodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.FORM) { formParams = createIfAbsent(subMethodCreator, formParams); @@ -894,7 +919,7 @@ 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)); + jandexMethod.parameters().get(paramIdx), index, methodCreator.getThis())); } 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. @@ -913,15 +938,16 @@ A more full example of generated client (with sub-resource) can is at the bottom handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0)); addBeanParamData(methodCreator, handleBeanParamMethod, invocationBuilderRef, beanParam.getItems(), - methodCreator.getMethodParam(paramIdx), methodTarget, index); + methodCreator.getMethodParam(paramIdx), methodTarget, index, + methodCreator.getThis(), + handleBeanParamMethod.getThis()); handleBeanParamMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleBeanParamDescriptor, methodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.PATH) { // methodTarget = methodTarget.resolveTemplate(paramname, paramvalue); - methodCreator.assign(methodTarget, - methodCreator.invokeInterfaceMethod(WEB_TARGET_RESOLVE_TEMPLATE_METHOD, methodTarget, - methodCreator.load(param.name), methodCreator.getMethodParam(paramIdx))); + addPathParam(methodCreator, methodTarget, param.name, methodCreator.getMethodParam(paramIdx), + param.type, methodCreator.getThis()); } else if (param.parameterType == ParameterType.BODY) { // just store the index of parameter used to create the body, we'll use it later bodyParameterIdx = paramIdx; @@ -937,7 +963,8 @@ A more full example of generated client (with sub-resource) can is at the bottom .createVariable(Invocation.Builder.class); handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0)); addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name, - handleHeaderMethod.getMethodParam(1)); + handleHeaderMethod.getMethodParam(1), param.type, + handleHeaderMethod.getThis()); handleHeaderMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.COOKIE) { @@ -952,7 +979,8 @@ A more full example of generated client (with sub-resource) can is at the bottom .createVariable(Invocation.Builder.class); handleCookieMethod.assign(invocationBuilderRef, handleCookieMethod.getMethodParam(0)); addCookieParam(handleCookieMethod, invocationBuilderRef, param.name, - handleCookieMethod.getMethodParam(1)); + handleCookieMethod.getMethodParam(1), param.type, + handleCookieMethod.getThis()); handleCookieMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx)); } else if (param.parameterType == ParameterType.FORM) { @@ -1018,11 +1046,12 @@ A more full example of generated client (with sub-resource) can is at the bottom } String creatorName = restClientInterface.getClassName() + "$$QuarkusRestClientInterfaceCreator"; try (ClassCreator c = new ClassCreator(new GeneratedClassGizmoAdaptor(generatedClasses, true), - creatorName, null, Object.class.getName(), Function.class.getName())) { + creatorName, null, Object.class.getName(), BiFunction.class.getName())) { MethodCreator apply = c - .getMethodCreator(MethodDescriptor.ofMethod(creatorName, "apply", Object.class, Object.class)); - apply.returnValue(apply.newInstance(ctorDesc, apply.getMethodParam(0))); + .getMethodCreator( + MethodDescriptor.ofMethod(creatorName, "apply", Object.class, Object.class, Object.class)); + apply.returnValue(apply.newInstance(ctorDesc, apply.getMethodParam(0), apply.getMethodParam(1))); } return recorderContext.newInstance(creatorName); @@ -1370,7 +1399,7 @@ private void handleReturn(ClassInfo restClientInterface, String defaultMediaType String simpleReturnType = returnType.name().toString(); ResultHandle genericReturnType = null; - if (returnType.kind() == Type.Kind.PARAMETERIZED_TYPE) { + if (returnType.kind() == PARAMETERIZED_TYPE) { ParameterizedType paramType = returnType.asParameterizedType(); if (ASYNC_RETURN_TYPES.contains(paramType.name())) { returnCategory = paramType.name().equals(COMPLETION_STAGE) @@ -1384,7 +1413,7 @@ private void handleReturn(ClassInfo restClientInterface, String defaultMediaType simpleReturnType = Object.class.getName(); } else { Type type = paramType.arguments().get(0); - if (type.kind() == Type.Kind.PARAMETERIZED_TYPE) { + if (type.kind() == PARAMETERIZED_TYPE) { genericReturnType = createGenericTypeFromParameterizedType(methodCreator, type.asParameterizedType()); } else { @@ -1417,7 +1446,7 @@ private void handleReturn(ClassInfo restClientInterface, String defaultMediaType //we infer the return type from the param type of the continuation Type type = lastParamType.asParameterizedType().arguments().get(0); for (;;) { - if (type.kind() == Type.Kind.PARAMETERIZED_TYPE) { + if (type.kind() == PARAMETERIZED_TYPE) { genericReturnType = createGenericTypeFromParameterizedType(methodCreator, type.asParameterizedType()); break; @@ -1737,7 +1766,9 @@ private void addBeanParamData(BytecodeCreator methodCreator, List beanParamItems, ResultHandle param, AssignableResultHandle target, // can only be used in the current method, not in `invocationBuilderEnricher` - IndexView index) { + IndexView index, + ResultHandle client, + ResultHandle invocationEnricherClient) { // this client or containing client if this is a sub-client BytecodeCreator creator = methodCreator.ifNotNull(param).trueBranch(); BytecodeCreator invoEnricher = invocationBuilderEnricher.ifNotNull(invocationBuilderEnricher.getMethodParam(1)) .trueBranch(); @@ -1747,7 +1778,7 @@ private void addBeanParamData(BytecodeCreator methodCreator, BeanParamItem beanParamItem = (BeanParamItem) item; ResultHandle beanParamElementHandle = beanParamItem.extract(creator, param); addBeanParamData(creator, invoEnricher, invocationBuilder, beanParamItem.items(), - beanParamElementHandle, target, index); + beanParamElementHandle, target, index, client, invocationEnricherClient); break; case QUERY_PARAM: QueryParamItem queryParam = (QueryParamItem) item; @@ -1755,25 +1786,27 @@ private void addBeanParamData(BytecodeCreator methodCreator, addQueryParam(creator, target, queryParam.name(), queryParam.extract(creator, param), queryParam.getValueType(), - index)); + index, client)); break; case COOKIE: CookieParamItem cookieParam = (CookieParamItem) item; addCookieParam(invoEnricher, invocationBuilder, cookieParam.getCookieName(), - cookieParam.extract(invoEnricher, invoEnricher.getMethodParam(1))); + cookieParam.extract(invoEnricher, invoEnricher.getMethodParam(1)), + cookieParam.getParamType(), invocationEnricherClient); break; case HEADER_PARAM: HeaderParamItem headerParam = (HeaderParamItem) item; addHeaderParam(invoEnricher, invocationBuilder, headerParam.getHeaderName(), - headerParam.extract(invoEnricher, invoEnricher.getMethodParam(1))); + headerParam.extract(invoEnricher, invoEnricher.getMethodParam(1)), + headerParam.getParamType(), invocationEnricherClient); break; case PATH_PARAM: PathParamItem pathParam = (PathParamItem) item; addPathParam(creator, target, pathParam.getPathParamName(), - pathParam.extract(creator, param)); + pathParam.extract(creator, param), pathParam.getParamType(), client); break; default: throw new IllegalStateException("Unimplemented"); // TODO form params, etc @@ -1787,20 +1820,38 @@ private ResultHandle addQueryParam(BytecodeCreator methodCreator, String paramName, ResultHandle queryParamHandle, Type type, - IndexView index) { + IndexView index, + ResultHandle client) { // this client or containing client if we're in a subresource ResultHandle paramArray; + String componentType = null; if (type.kind() == Type.Kind.ARRAY) { + componentType = type.asArrayType().component().name().toString(); paramArray = methodCreator.checkCast(queryParamHandle, Object[].class); } else if (isCollection(type, index)) { + if (type.kind() == PARAMETERIZED_TYPE) { + Type paramType = type.asParameterizedType().arguments().get(0); + if (paramType.kind() == CLASS) { + componentType = paramType.name().toString(); + } + } + if (componentType == null) { + componentType = DotNames.OBJECT.toString(); + } paramArray = methodCreator.invokeStaticMethod( MethodDescriptor.ofMethod(ToObjectArray.class, "collection", Object[].class, Collection.class), queryParamHandle); } else { + componentType = type.name().toString(); paramArray = methodCreator.invokeStaticMethod( MethodDescriptor.ofMethod(ToObjectArray.class, "value", Object[].class, Object.class), queryParamHandle); } + paramArray = methodCreator.invokeVirtualMethod( + MethodDescriptor.ofMethod(RestClientBase.class, "convertParamArray", Object[].class, Object[].class, + Class.class), + client, paramArray, methodCreator.loadClass(componentType)); + return methodCreator.invokeInterfaceMethod( MethodDescriptor.ofMethod(WebTarget.class, "queryParam", WebTarget.class, String.class, Object[].class), @@ -1819,7 +1870,13 @@ private boolean isCollection(Type type, IndexView index) { } private void addHeaderParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder, - String paramName, ResultHandle headerParamHandle) { + String paramName, ResultHandle headerParamHandle, String paramType, ResultHandle client) { + + headerParamHandle = invoBuilderEnricher.invokeVirtualMethod( + MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, Object.class, Class.class), + client, headerParamHandle, + invoBuilderEnricher.loadClass(paramType)); + invoBuilderEnricher.assign(invocationBuilder, invoBuilderEnricher.invokeInterfaceMethod( MethodDescriptor.ofMethod(Invocation.Builder.class, "header", Invocation.Builder.class, String.class, @@ -1828,15 +1885,23 @@ private void addHeaderParam(BytecodeCreator invoBuilderEnricher, AssignableResul } private void addPathParam(BytecodeCreator methodCreator, AssignableResultHandle methodTarget, - String paramName, ResultHandle pathParamHandle) { + String paramName, ResultHandle pathParamHandle, String parameterType, ResultHandle client) { + ResultHandle handle = methodCreator.invokeVirtualMethod( + MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, Object.class, Class.class), + client, pathParamHandle, + methodCreator.loadClass(parameterType)); methodCreator.assign(methodTarget, methodCreator.invokeInterfaceMethod(WEB_TARGET_RESOLVE_TEMPLATE_METHOD, methodTarget, - methodCreator.load(paramName), pathParamHandle)); + methodCreator.load(paramName), handle)); } private void addCookieParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder, - String paramName, ResultHandle cookieParamHandle) { + String paramName, ResultHandle cookieParamHandle, String paramType, ResultHandle client) { + cookieParamHandle = invoBuilderEnricher.invokeVirtualMethod( + MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, Object.class, Class.class), + client, cookieParamHandle, + invoBuilderEnricher.loadClass(paramType)); 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/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/RestClientBase.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/RestClientBase.java new file mode 100644 index 0000000000000..8893a808b5957 --- /dev/null +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/RestClientBase.java @@ -0,0 +1,71 @@ +package io.quarkus.jaxrs.client.reactive.deployment; + +import java.io.Closeable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.ext.ParamConverter; +import javax.ws.rs.ext.ParamConverterProvider; + +public abstract class RestClientBase implements Closeable { + private final List paramConverterProviders; + private final Map, ParamConverterProvider> providerForClass = new ConcurrentHashMap<>(); + + public RestClientBase(List providers) { + this.paramConverterProviders = providers; + } + + @SuppressWarnings("unused") // used by generated code + public Object[] convertParamArray(T[] value, Class type) { + ParamConverter converter = getConverter(type, null, null); + + if (converter == null) { + return value; + } else { + Object[] result = new Object[value.length]; + + for (int i = 0; i < value.length; i++) { + result[i] = converter.toString(value[i]); + } + return result; + } + } + + @SuppressWarnings("unused") // used by generated code + public Object convertParam(T value, Class type) { + ParamConverter converter = getConverter(type, null, null); + if (converter != null) { + return converter.toString(value); + } else { + return value; + } + } + + private ParamConverter getConverter(Class type, Type genericType, Annotation[] annotations) { + ParamConverterProvider converterProvider = providerForClass.get(type); + + if (converterProvider == null) { + for (ParamConverterProvider provider : paramConverterProviders) { + ParamConverter converter = provider.getConverter(type, null, null); + if (converter != null) { + providerForClass.put(type, provider); + return converter; + } + } + providerForClass.put(type, NO_PROVIDER); + } else if (converterProvider != NO_PROVIDER) { + return converterProvider.getConverter(type, null, null); + } + return null; + } + + private static final ParamConverterProvider NO_PROVIDER = new ParamConverterProvider() { + @Override + public ParamConverter getConverter(Class rawType, Type genericType, Annotation[] annotations) { + return null; + } + }; +} diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/JaxrsClientReactiveRecorder.java b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/JaxrsClientReactiveRecorder.java index 7316187f915ea..013e77739c8de 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/JaxrsClientReactiveRecorder.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/JaxrsClientReactiveRecorder.java @@ -2,11 +2,13 @@ import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.BiFunction; import javax.ws.rs.RuntimeType; import javax.ws.rs.client.WebTarget; +import javax.ws.rs.ext.ParamConverterProvider; import org.jboss.resteasy.reactive.client.impl.ClientProxies; import org.jboss.resteasy.reactive.client.impl.ClientSerialisers; @@ -37,7 +39,8 @@ public static GenericTypeMapping getGenericTypeMapping() { return genericTypeMapping; } - public void setupClientProxies(Map>> clientImplementations, + public void setupClientProxies( + Map, ?>>> clientImplementations, Map failures) { clientProxies = createClientImpls(clientImplementations, failures); } @@ -49,10 +52,12 @@ public Serialisers createSerializers() { return s; } - private ClientProxies createClientImpls(Map>> clientImplementations, + private ClientProxies createClientImpls( + Map, ?>>> clientImplementations, Map failureMessages) { - Map, Function> map = new HashMap<>(); - for (Map.Entry>> entry : clientImplementations.entrySet()) { + Map, BiFunction, ?>> map = new HashMap<>(); + for (Map.Entry, ?>>> entry : clientImplementations + .entrySet()) { map.put(loadClass(entry.getKey()), entry.getValue().getValue()); } Map, String> failures = new HashMap<>(); 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 new file mode 100644 index 0000000000000..76e277d68387f --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/converter/ParamConverterProviderTest.java @@ -0,0 +1,242 @@ +package io.quarkus.rest.client.reactive.converter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.net.URI; + +import javax.ws.rs.BeanParam; +import javax.ws.rs.CookieParam; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.ext.ParamConverterProvider; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.http.TestHTTPResource; + +public class ParamConverterProviderTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest(); + + @TestHTTPResource + URI baseUri; + + @Test + void shouldConvertPathParam() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri) + .build(Client.class); + assertThat(client.get(Param.FIRST)).isEqualTo("1"); + assertThat(client.sub().get(Param.SECOND)).isEqualTo("2"); + + Bean bean = new Bean(); + bean.param = Param.FIRST; + assertThat(client.get(bean)).isEqualTo("1"); + } + + @Test + void shouldConvertQueryParams() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri) + .build(Client.class); + assertThat(client.getWithQuery(Param.FIRST)).isEqualTo("1"); + assertThat(client.sub().getWithQuery(Param.SECOND)).isEqualTo("2"); + + Bean bean = new Bean(); + bean.param = Param.SECOND; + bean.queryParam = Param.FIRST; + assertThat(client.getWithQuery(bean)).isEqualTo("1"); + } + + @Test + void shouldConvertHeaderParams() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri) + .build(Client.class); + assertThat(client.getWithHeader(Param.FIRST)).isEqualTo("1"); + assertThat(client.sub().getWithHeader(Param.SECOND)).isEqualTo("2"); + + Bean bean = new Bean(); + bean.param = Param.SECOND; + bean.queryParam = Param.SECOND; + bean.headerParam = Param.FIRST; + assertThat(client.getWithHeader(bean)).isEqualTo("1"); + } + + @Test + void shouldConvertCookieParams() { + Client client = RestClientBuilder.newBuilder().baseUri(baseUri) + .build(Client.class); + assertThat(client.getWithHeader(Param.FIRST)).isEqualTo("1"); + assertThat(client.sub().getWithCookie(Param.SECOND)).isEqualTo("2"); + + Bean bean = new Bean(); + bean.param = Param.SECOND; + bean.queryParam = Param.SECOND; + bean.headerParam = Param.SECOND; + bean.cookieParam = Param.FIRST; + assertThat(client.getWithCookie(bean)).isEqualTo("1"); + } + + @Path("/echo") + @RegisterProvider(ParamConverter.class) + interface Client { + @Path("/sub") + SubClient sub(); + + @GET + @Path("/param/{param}") + String get(@PathParam("param") Param param); + + @GET + @Path("/param/{param}") + String get(@BeanParam Bean beanParam); + + @GET + @Path("/query") + String getWithQuery(@QueryParam("param") Param param); + + @GET + @Path("/query") + String getWithQuery(@BeanParam Bean beanParam); + + @GET + @Path("/header") + String getWithHeader(@HeaderParam("param") Param param); + + @GET + @Path("/header") + String getWithHeader(@BeanParam Bean beanParam); + + @GET + @Path("/cookie") + String getWithCookie(@HeaderParam("cookie-param") Param param); + + @GET + @Path("/cookie") + String getWithCookie(@BeanParam Bean beanParam); + } + + interface SubClient { + @GET + @Path("/param/{param}") + String get(@PathParam("param") Param param); + + @GET + @Path("/query") + String getWithQuery(@QueryParam("param") Param param); + + @GET + @Path("/header") + String getWithHeader(@HeaderParam("param") Param param); + + @GET + @Path("cookie") + String getWithCookie(@CookieParam("cookie-param") Param param); + } + + public static class Bean { + @PathParam("param") + public Param param; + @QueryParam("param") + public Param queryParam; + @HeaderParam("param") + public Param headerParam; + @CookieParam("cookie-param") + public Param cookieParam; + } + + enum Param { + FIRST, + SECOND + } + + public static class ParamConverter implements ParamConverterProvider { + @SuppressWarnings("unchecked") + @Override + public javax.ws.rs.ext.ParamConverter getConverter(Class rawType, Type genericType, + Annotation[] annotations) { + 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; + } + } + + @Path("/echo") + public static class EchoEndpoint { + @Path("/param/{param}") + @GET + public String echoPath(@PathParam("param") String param) { + return param; + } + + @Path("/sub/param/{param}") + @GET + public String echoSubPath(@PathParam("param") String param) { + return param; + } + + @GET + @Path("/query") + public String get(@QueryParam("param") String param) { + return param; + } + + @Path("/sub/query") + @GET + public String getSub(@QueryParam("param") String param) { + return param; + } + + @GET + @Path("/header") + public String getHeader(@HeaderParam("param") String param) { + return param; + } + + @Path("/sub/header") + @GET + public String getSubHeader(@HeaderParam("param") String param) { + return param; + } + + @GET + @Path("/cookie") + public String getCookie(@CookieParam("cookie-param") String param) { + return param; + } + + @Path("/sub/cookie") + @GET + public String getSubCookie(@CookieParam("cookie-param") String param) { + return param; + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java index c2d1499cea048..01eb033eab22e 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java @@ -16,6 +16,7 @@ import javax.net.ssl.SSLContext; import javax.ws.rs.RuntimeType; import javax.ws.rs.core.Configuration; +import javax.ws.rs.ext.ParamConverterProvider; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.rest.client.RestClientBuilder; @@ -46,6 +47,7 @@ public class RestClientBuilderImpl implements RestClientBuilder { private final ClientBuilderImpl clientBuilder = (ClientBuilderImpl) new ClientBuilderImpl() .withConfig(new ConfigurationImpl(RuntimeType.CLIENT)); private final List> exceptionMappers = new ArrayList<>(); + private final List paramConverterProviders = new ArrayList<>(); private URI uri; private boolean followRedirects; @@ -221,7 +223,8 @@ public RestClientBuilder baseUri(URI uri) { } private void registerMpSpecificProvider(Class componentClass) { - if (ResponseExceptionMapper.class.isAssignableFrom(componentClass)) { + if (ResponseExceptionMapper.class.isAssignableFrom(componentClass) + || ParamConverterProvider.class.isAssignableFrom(componentClass)) { try { registerMpSpecificProvider(componentClass.getDeclaredConstructor().newInstance()); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { @@ -235,6 +238,9 @@ private void registerMpSpecificProvider(Object component) { if (component instanceof ResponseExceptionMapper) { exceptionMappers.add((ResponseExceptionMapper) component); } + if (component instanceof ParamConverterProvider) { + paramConverterProviders.add((ParamConverterProvider) component); + } } @Override @@ -287,6 +293,7 @@ public T build(Class aClass) throws IllegalStateException, RestClientDefi ClientImpl client = clientBuilder.build(); WebTargetImpl target = (WebTargetImpl) client.target(uri); + target.setParamConverterProviders(paramConverterProviders); try { return target.proxy(aClass); } catch (InvalidRestClientDefinitionException e) { diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamParser.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamParser.java index a09a7d7e12a23..4bc0d551afdcf 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamParser.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/BeanParamParser.java @@ -79,11 +79,13 @@ public static List parse(ClassInfo beanParamClass, IndexView index) { if (target.kind() == AnnotationTarget.Kind.FIELD) { FieldInfo fieldInfo = target.asField(); resultList.add(new CookieParamItem(annotation.value().asString(), - new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()))); + new FieldExtractor(null, fieldInfo.name(), + fieldInfo.declaringClass().name().toString()), + fieldInfo.type().name().toString())); } else if (target.kind() == AnnotationTarget.Kind.METHOD) { MethodInfo getterMethod = getGetterMethod(beanParamClass, target.asMethod()); resultList.add(new CookieParamItem(annotation.value().asString(), - new GetterExtractor(getterMethod))); + new GetterExtractor(getterMethod), getterMethod.returnType().name().toString())); } } } @@ -94,12 +96,14 @@ public static List parse(ClassInfo beanParamClass, IndexView index) { AnnotationTarget target = headerParamAnnotation.target(); if (target.kind() == AnnotationTarget.Kind.FIELD) { FieldInfo fieldInfo = target.asField(); + String paramType = fieldInfo.type().name().toString(); resultList.add(new HeaderParamItem(headerParamAnnotation.value().asString(), - new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()))); + new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()), + paramType)); } else if (target.kind() == AnnotationTarget.Kind.METHOD) { MethodInfo getterMethod = getGetterMethod(beanParamClass, target.asMethod()); resultList.add(new HeaderParamItem(headerParamAnnotation.value().asString(), - new GetterExtractor(getterMethod))); + new GetterExtractor(getterMethod), getterMethod.returnType().name().toString())); } } } @@ -110,11 +114,13 @@ public static List parse(ClassInfo beanParamClass, IndexView index) { AnnotationTarget target = pathParamAnnotation.target(); if (target.kind() == AnnotationTarget.Kind.FIELD) { FieldInfo fieldInfo = target.asField(); - resultList.add(new PathParamItem(pathParamAnnotation.value().asString(), + String fieldType = fieldInfo.type().name().toString(); + resultList.add(new PathParamItem(pathParamAnnotation.value().asString(), fieldType, new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()))); } else if (target.kind() == AnnotationTarget.Kind.METHOD) { MethodInfo getterMethod = getGetterMethod(beanParamClass, target.asMethod()); - resultList.add(new PathParamItem(pathParamAnnotation.value().asString(), + String paramType = getterMethod.returnType().name().toString(); + resultList.add(new PathParamItem(pathParamAnnotation.value().asString(), paramType, new GetterExtractor(getterMethod))); } } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/CookieParamItem.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/CookieParamItem.java index 6f8dc72a5c47b..c865b7dc475e9 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/CookieParamItem.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/CookieParamItem.java @@ -2,13 +2,19 @@ public class CookieParamItem extends Item { private final String cookieName; + private final String paramType; - public CookieParamItem(String cookieName, ValueExtractor extractor) { + public CookieParamItem(String cookieName, ValueExtractor extractor, String paramType) { super(ItemType.COOKIE, extractor); this.cookieName = cookieName; + this.paramType = paramType; } public String getCookieName() { return cookieName; } + + public String getParamType() { + return paramType; + } } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/HeaderParamItem.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/HeaderParamItem.java index b4e37d4430e7b..b47110b960d4b 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/HeaderParamItem.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/HeaderParamItem.java @@ -2,13 +2,19 @@ public class HeaderParamItem extends Item { private final String headerName; + private final String paramType; - public HeaderParamItem(String headerName, ValueExtractor extractor) { + public HeaderParamItem(String headerName, ValueExtractor extractor, String paramType) { super(ItemType.HEADER_PARAM, extractor); this.headerName = headerName; + this.paramType = paramType; } public String getHeaderName() { return headerName; } + + public String getParamType() { + return paramType; + } } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/Item.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/Item.java index 2cdd440b48e99..4bda26033130d 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/Item.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/Item.java @@ -20,5 +20,4 @@ public ItemType type() { public ResultHandle extract(BytecodeCreator methodCreator, ResultHandle param) { return valueExtractor.extract(methodCreator, param); } - } diff --git a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/PathParamItem.java b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/PathParamItem.java index c4504962fd246..4db45bd16baaf 100644 --- a/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/PathParamItem.java +++ b/independent-projects/resteasy-reactive/client/processor/src/main/java/org/jboss/resteasy/reactive/client/processor/beanparam/PathParamItem.java @@ -3,13 +3,19 @@ public class PathParamItem extends Item { private final String pathParamName; + private final String paramType; - public PathParamItem(String pathParamName, ValueExtractor valueExtractor) { + public PathParamItem(String pathParamName, String paramType, ValueExtractor valueExtractor) { super(ItemType.PATH_PARAM, valueExtractor); this.pathParamName = pathParamName; + this.paramType = paramType; } public String getPathParamName() { return pathParamName; } + + public String getParamType() { + return paramType; + } } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientProxies.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientProxies.java index 2ac86b06c4ce4..0390e606269dc 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientProxies.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientProxies.java @@ -1,23 +1,26 @@ package org.jboss.resteasy.reactive.client.impl; import java.util.Collection; +import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.BiFunction; import javax.ws.rs.client.WebTarget; +import javax.ws.rs.ext.ParamConverterProvider; import org.jboss.resteasy.reactive.client.api.InvalidRestClientDefinitionException; public class ClientProxies { - final Map, Function> clientProxies; + final Map, BiFunction, ?>> clientProxies; private final Map, String> failures; - public ClientProxies(Map, Function> clientProxies, Map, String> failures) { + public ClientProxies(Map, BiFunction, ?>> clientProxies, + Map, String> failures) { this.clientProxies = clientProxies; this.failures = failures; } - public T get(Class clazz, WebTarget webTarget) { - Function function = clientProxies.get(clazz); + public T get(Class clazz, WebTarget webTarget, List providers) { + BiFunction, ?> function = clientProxies.get(clazz); if (function == null) { String failure = failures.get(clazz); if (failure != null) { @@ -30,7 +33,7 @@ public T get(Class clazz, WebTarget webTarget) { } } //noinspection unchecked - return (T) function.apply(webTarget); + return (T) function.apply(webTarget, providers); } // for dev console diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java index 3c25ddd03b2cb..b48e8fdf05d8e 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/WebTargetImpl.java @@ -2,6 +2,7 @@ import io.vertx.core.http.HttpClient; import java.net.URI; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -10,6 +11,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.ext.ParamConverterProvider; import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; import org.jboss.resteasy.reactive.common.core.Serialisers; import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl; @@ -29,6 +31,7 @@ public class WebTargetImpl implements WebTarget { // an additional handler that is passed to the handlerChain // used to support observability features private ClientRestHandler preClientSendHandler = null; + private List paramConverterProviders = Collections.emptyList(); public WebTargetImpl(ClientImpl restClient, HttpClient client, UriBuilder uriBuilder, ConfigurationImpl configuration, @@ -378,8 +381,13 @@ public WebTargetImpl setChunked(boolean chunked) { return this; } + public WebTargetImpl setParamConverterProviders(List providers) { + this.paramConverterProviders = providers; + return this; + } + public T proxy(Class clazz) { - return restClient.getClientContext().getClientProxies().get(clazz, this); + return restClient.getClientContext().getClientProxies().get(clazz, this, paramConverterProviders); } public ClientImpl getRestClient() { From f23d1f27d49123e45760ba47bd0823996533fa8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szynkiewicz?= Date: Mon, 20 Dec 2021 16:28:13 +0100 Subject: [PATCH 2/2] REST Client Reactive - param converter support Path params, query params, cookie params and header params are supported with this change --- .../reactive/deployment/JaxrsClientReactiveProcessor.java | 1 + .../quarkus/jaxrs/client/reactive/runtime}/RestClientBase.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename extensions/resteasy-reactive/jaxrs-client-reactive/{deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment => runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime}/RestClientBase.java (97%) 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 922cb057427ef..09d4caf98b8dc 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 @@ -129,6 +129,7 @@ import io.quarkus.gizmo.TryBlock; import io.quarkus.jaxrs.client.reactive.runtime.ClientResponseBuilderFactory; import io.quarkus.jaxrs.client.reactive.runtime.JaxrsClientReactiveRecorder; +import io.quarkus.jaxrs.client.reactive.runtime.RestClientBase; import io.quarkus.jaxrs.client.reactive.runtime.ToObjectArray; import io.quarkus.resteasy.reactive.common.deployment.ApplicationResultBuildItem; import io.quarkus.resteasy.reactive.common.deployment.QuarkusFactoryCreator; diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/RestClientBase.java b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java similarity index 97% rename from extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/RestClientBase.java rename to extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java index 8893a808b5957..57978766d7919 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/RestClientBase.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/RestClientBase.java @@ -1,4 +1,4 @@ -package io.quarkus.jaxrs.client.reactive.deployment; +package io.quarkus.jaxrs.client.reactive.runtime; import java.io.Closeable; import java.lang.annotation.Annotation;