From 571fa923f75998ad6f9e377abef840bc4f31c76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szynkiewicz?= Date: Wed, 24 Mar 2021 15:08:27 +0000 Subject: [PATCH] Rest Client Reactive - MicroProfile 2.0 features fixes #15970 --- .../JaxrsClientReactiveProcessor.java | 41 +++-- .../deployment/beanparam/BeanParamParser.java | 8 +- .../deployment/beanparam/QueryParamItem.java | 10 +- .../reactive/runtime/ToObjectArray.java | 21 +++ ...va => MicroProfileRestClientEnricher.java} | 46 ++++-- .../reactive/redirect/RedirectTest.java | 50 ++++++ .../redirect/RedirectingResource.java | 21 +++ .../redirect/RedirectingResourceClient.java | 12 ++ .../rest/client/reactive/BeanGrabber.java | 18 ++ ...MicroprofileRestClientExceptionMapper.java | 27 +++ .../MicroProfileRestClientRequestFilter.java | 91 ++++++++++ .../MicroProfileRestClientResponseFilter.java | 43 +++++ .../{runtime => }/RestClientBuilderImpl.java | 119 ++++++++----- .../client/reactive/RestClientListeners.java | 24 +++ .../reactive/runtime/BuilderResolver.java | 1 + .../reactive/runtime/HeaderContainer.java | 2 +- .../runtime/RestClientCDIDelegateBuilder.java | 80 +++++++-- .../api/QuarkusRestClientProperties.java | 4 + .../handlers/ClientSendRequestHandler.java | 21 ++- .../client/impl/ClientBuilderImpl.java | 35 +++- .../reactive/client/impl/ClientImpl.java | 25 ++- .../reactive/client/impl/WebTargetImpl.java | 4 +- .../common/jaxrs/MultiQueryParamMode.java | 16 ++ .../reactive/common/jaxrs/UriBuilderImpl.java | 29 +++- .../microprofile-rest-client-reactive/pom.xml | 8 +- .../CustomInvokeWithJsonBProviderTest.java | 92 ----------- .../CustomInvokeWithJsonPProviderTest.java | 156 ------------------ 27 files changed, 662 insertions(+), 342 deletions(-) create mode 100644 extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ToObjectArray.java rename extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/{RestClientReactiveEnricher.java => MicroProfileRestClientEnricher.java} (91%) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectTest.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectingResource.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectingResourceClient.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/BeanGrabber.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/DefaultMicroprofileRestClientExceptionMapper.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/MicroProfileRestClientRequestFilter.java create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/MicroProfileRestClientResponseFilter.java rename extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/{runtime => }/RestClientBuilderImpl.java (72%) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/RestClientListeners.java create mode 100644 independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/MultiQueryParamMode.java delete mode 100644 tcks/microprofile-rest-client-reactive/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonBProviderTest.java delete mode 100644 tcks/microprofile-rest-client-reactive/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonPProviderTest.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 79b25f6c51a89d..eaeef3953e5bb2 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 @@ -93,6 +93,7 @@ import io.quarkus.jaxrs.client.reactive.deployment.beanparam.QueryParamItem; import io.quarkus.jaxrs.client.reactive.runtime.ClientResponseBuilderFactory; import io.quarkus.jaxrs.client.reactive.runtime.JaxrsClientReactiveRecorder; +import io.quarkus.resteasy.reactive.client.runtime.ToObjectArray; import io.quarkus.resteasy.reactive.common.deployment.ApplicationResultBuildItem; import io.quarkus.resteasy.reactive.common.deployment.QuarkusFactoryCreator; import io.quarkus.resteasy.reactive.common.deployment.ResourceScanningResultBuildItem; @@ -451,7 +452,8 @@ public void close() { // 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))); + methodCreator.getMethodParam(paramIdx), + jandexMethod.parameters().get(paramIdx), index)); } 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. @@ -470,7 +472,7 @@ public void close() { handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0)); addBeanParamData(methodCreator, handleBeanParamMethod, invocationBuilderRef, beanParam.getItems(), - methodCreator.getMethodParam(paramIdx), methodTarget); + methodCreator.getMethodParam(paramIdx), methodTarget, index); handleBeanParamMethod.returnValue(invocationBuilderRef); invocationBuilderEnrichers.put(handleBeanParamDescriptor, methodCreator.getMethodParam(paramIdx)); @@ -809,8 +811,8 @@ private void addBeanParamData(BytecodeCreator methodCreator, AssignableResultHandle invocationBuilder, List beanParamItems, ResultHandle param, - AssignableResultHandle target // can only be used in the current method, not in `invocationBuilderEnricher` - ) { + AssignableResultHandle target, // can only be used in the current method, not in `invocationBuilderEnricher` + IndexView index) { BytecodeCreator creator = methodCreator.ifNotNull(param).trueBranch(); BytecodeCreator invoEnricher = invocationBuilderEnricher.ifNotNull(invocationBuilderEnricher.getMethodParam(1)) .trueBranch(); @@ -820,12 +822,15 @@ private void addBeanParamData(BytecodeCreator methodCreator, BeanParamItem beanParamItem = (BeanParamItem) item; ResultHandle beanParamElementHandle = beanParamItem.extract(creator, param); addBeanParamData(creator, invoEnricher, invocationBuilder, beanParamItem.items(), - beanParamElementHandle, target); + beanParamElementHandle, target, index); break; case QUERY_PARAM: QueryParamItem queryParam = (QueryParamItem) item; creator.assign(target, - addQueryParam(creator, target, queryParam.name(), queryParam.extract(creator, param))); + addQueryParam(creator, target, queryParam.name(), + queryParam.extract(creator, param), + queryParam.getValueType(), + index)); break; case COOKIE: CookieParamItem cookieParam = (CookieParamItem) item; @@ -848,13 +853,29 @@ private void addBeanParamData(BytecodeCreator methodCreator, // takes a result handle to target as one of the parameters, returns a result handle to a modified target private ResultHandle addQueryParam(BytecodeCreator methodCreator, ResultHandle target, - String paramName, ResultHandle queryParamHandle) { - ResultHandle array = methodCreator.newArray(Object.class, 1); - methodCreator.writeArrayValue(array, 0, queryParamHandle); + String paramName, + ResultHandle queryParamHandle, + Type type, + IndexView index) { + ResultHandle paramArray; + if (type.kind() == Type.Kind.ARRAY) { + paramArray = methodCreator.checkCast(queryParamHandle, Object[].class); + } else if (index + .getClassByName(type.name()).interfaceNames().stream() + .anyMatch(DotName.createSimple(Collection.class.getName())::equals)) { + paramArray = methodCreator.invokeStaticMethod( + MethodDescriptor.ofMethod(ToObjectArray.class, "collection", Object[].class, Collection.class), + queryParamHandle); + } else { + paramArray = methodCreator.invokeStaticMethod( + MethodDescriptor.ofMethod(ToObjectArray.class, "value", Object[].class, Object.class), + queryParamHandle); + } + ResultHandle alteredTarget = methodCreator.invokeInterfaceMethod( MethodDescriptor.ofMethod(WebTarget.class, "queryParam", WebTarget.class, String.class, Object[].class), - target, methodCreator.load(paramName), array); + target, methodCreator.load(paramName), paramArray); return alteredTarget; } diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/beanparam/BeanParamParser.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/beanparam/BeanParamParser.java index 4dbf8c4725a0f4..6243bdcd7b4ce9 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/beanparam/BeanParamParser.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/beanparam/BeanParamParser.java @@ -29,11 +29,12 @@ public static List parse(ClassInfo beanParamClass, IndexView index) { if (target.kind() == AnnotationTarget.Kind.FIELD) { FieldInfo fieldInfo = target.asField(); resultList.add(new QueryParamItem(annotation.value().asString(), - new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()))); + new FieldExtractor(null, fieldInfo.name(), fieldInfo.declaringClass().name().toString()), + fieldInfo.type())); } else if (target.kind() == AnnotationTarget.Kind.METHOD) { MethodInfo getterMethod = getGetterMethod(beanParamClass, target.asMethod()); resultList.add(new QueryParamItem(annotation.value().asString(), - new GetterExtractor(getterMethod))); + new GetterExtractor(getterMethod), getterMethod.returnType())); } } } @@ -116,4 +117,7 @@ private static MethodInfo getGetterMethod(ClassInfo beanParamClass, MethodInfo m } return getter; } + + private BeanParamParser() { + } } diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/beanparam/QueryParamItem.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/beanparam/QueryParamItem.java index bbf492556ea4d3..fb58bdb948aff9 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/beanparam/QueryParamItem.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/beanparam/QueryParamItem.java @@ -1,15 +1,23 @@ package io.quarkus.jaxrs.client.reactive.deployment.beanparam; +import org.jboss.jandex.Type; + public class QueryParamItem extends Item { private final String name; + private final Type valueType; - public QueryParamItem(String name, ValueExtractor extractor) { + public QueryParamItem(String name, ValueExtractor extractor, Type valueType) { super(ItemType.QUERY_PARAM, extractor); this.name = name; + this.valueType = valueType; } public String name() { return name; } + + public Type getValueType() { + return valueType; + } } diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ToObjectArray.java b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ToObjectArray.java new file mode 100644 index 00000000000000..6dc6c53e074bf8 --- /dev/null +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/runtime/src/main/java/io/quarkus/jaxrs/client/reactive/runtime/ToObjectArray.java @@ -0,0 +1,21 @@ +package io.quarkus.jaxrs.client.reactive.runtime; + +import java.util.Collection; + +/** + * used by query param handling mechanism, in generated code + */ +@SuppressWarnings("unused") +public class ToObjectArray { + + public static Object[] collection(Collection collection) { + return collection.toArray(); + } + + public static Object[] value(Object value) { + return new Object[] { value }; + } + + private ToObjectArray() { + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveEnricher.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java similarity index 91% rename from extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveEnricher.java rename to extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java index cdcf60e81570b7..ce67e9ab2557ed 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveEnricher.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java @@ -23,6 +23,9 @@ import javax.ws.rs.core.Configurable; import javax.ws.rs.core.MultivaluedMap; +import io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveEnricher; +import io.quarkus.rest.client.reactive.MicroProfileRestClientRequestFilter; +import io.quarkus.rest.client.reactive.runtime.NoOpHeaderFiller; import org.eclipse.microprofile.rest.client.RestClientDefinitionException; import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; import org.eclipse.microprofile.rest.client.ext.DefaultClientHeadersFactoryImpl; @@ -42,6 +45,7 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.gizmo.AssignableResultHandle; +import io.quarkus.gizmo.BranchResult; import io.quarkus.gizmo.BytecodeCreator; import io.quarkus.gizmo.CatchBlockCreator; import io.quarkus.gizmo.ClassCreator; @@ -52,10 +56,8 @@ import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.gizmo.TryBlock; -import io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveEnricher; +import io.quarkus.rest.client.reactive.BeanGrabber; import io.quarkus.rest.client.reactive.HeaderFiller; -import io.quarkus.rest.client.reactive.runtime.NoOpHeaderFiller; -import io.quarkus.rest.client.reactive.runtime.RestClientReactiveRequestFilter; import io.quarkus.runtime.util.HashUtil; /** @@ -64,8 +66,8 @@ * Used mostly to handle the `@RegisterProvider` annotation that e.g. registers filters * and to add support for `@ClientHeaderParam` annotations for specifying (possibly) computed headers via annotations */ -class RestClientReactiveEnricher implements JaxrsClientReactiveEnricher { - private static final Logger log = Logger.getLogger(RestClientReactiveEnricher.class); +class MicroProfileRestClientEnricher implements JaxrsClientReactiveEnricher { + private static final Logger log = Logger.getLogger(MicroProfileRestClientEnricher.class); public static final String DEFAULT_HEADERS_FACTORY = DefaultClientHeadersFactoryImpl.class.getName(); @@ -128,7 +130,7 @@ public void forClass(MethodCreator constructor, AssignableResultHandle webTarget } ResultHandle restClientFilter = constructor.newInstance( - MethodDescriptor.ofConstructor(RestClientReactiveRequestFilter.class, ClientHeadersFactory.class), + MethodDescriptor.ofConstructor(MicroProfileRestClientRequestFilter.class, ClientHeadersFactory.class), clientHeadersFactory); constructor.assign(webTargetBase, constructor.invokeInterfaceMethod( @@ -433,13 +435,35 @@ private AnnotationInstance[] extractAnnotations(AnnotationInstance groupAnnotati private void addProvider(MethodCreator ctor, AssignableResultHandle target, IndexView index, AnnotationInstance registerProvider) { - ResultHandle provider = ctor.newInstance(MethodDescriptor.ofConstructor(registerProvider.value().asString())); - ResultHandle alteredTarget = ctor.invokeInterfaceMethod( + // if a registered provider is a cdi bean, it has to be reused + // take the name of the provider class from the annotation: + String providerClass = registerProvider.value().asString(); + + // get bean, or null, with BeanGrabber.getBeanIfDefined(providerClass) + ResultHandle providerBean = ctor.invokeStaticMethod( + MethodDescriptor.ofMethod(BeanGrabber.class, "getBeanIfDefined", Object.class, Class.class), + ctor.loadClass(providerClass)); + + // if bean != null, register the bean + BranchResult branchResult = ctor.ifNotNull(providerBean); + BytecodeCreator beanProviderAvailable = branchResult.trueBranch(); + + ResultHandle alteredTarget = beanProviderAvailable.invokeInterfaceMethod( + MethodDescriptor.ofMethod(Configurable.class, "register", Configurable.class, Object.class, + int.class), + target, providerBean, + beanProviderAvailable.load(registerProvider.valueWithDefault(index, "priority").asInt())); + beanProviderAvailable.assign(target, alteredTarget); + + // else, create a new instance of the provider class + BytecodeCreator beanProviderNotAvailable = branchResult.falseBranch(); + ResultHandle provider = beanProviderNotAvailable.newInstance(MethodDescriptor.ofConstructor(providerClass)); + alteredTarget = beanProviderNotAvailable.invokeInterfaceMethod( MethodDescriptor.ofMethod(Configurable.class, "register", Configurable.class, Object.class, int.class), target, provider, - ctor.load(registerProvider.valueWithDefault(index, "priority").asInt())); - ctor.assign(target, alteredTarget); + beanProviderNotAvailable.load(registerProvider.valueWithDefault(index, "priority").asInt())); + beanProviderNotAvailable.assign(target, alteredTarget); } -} +} \ No newline at end of file diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectTest.java new file mode 100644 index 00000000000000..5f494ea76a96c3 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectTest.java @@ -0,0 +1,50 @@ +package io.quarkus.rest.client.reactive.redirect; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; + +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +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 RedirectTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(RedirectingResourceClient.class, RedirectingResource.class)); + + @TestHTTPResource + URI uri; + + @Test + void shouldRedirect3Times_whenMax4() { + RedirectingResourceClient client = RestClientBuilder.newBuilder() + .baseUri(uri) + .followRedirects(true) + .property(QuarkusRestClientProperties.MAX_REDIRECTS, 4) + .build(RedirectingResourceClient.class); + Response call = client.call(3); + assertThat(call.getStatus()).isEqualTo(200); + } + + @Test + void shouldNotRedirect3Times_whenMax2() { + RedirectingResourceClient client = RestClientBuilder.newBuilder() + .baseUri(uri) + .followRedirects(true) + .property(QuarkusRestClientProperties.MAX_REDIRECTS, 2) + .build(RedirectingResourceClient.class); + assertThat(client.call(3).getStatus()).isEqualTo(307); + + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectingResource.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectingResource.java new file mode 100644 index 00000000000000..c6f04fa55e96ed --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectingResource.java @@ -0,0 +1,21 @@ +package io.quarkus.rest.client.reactive.redirect; + +import java.net.URI; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +@Path("/redirect") +public class RedirectingResource { + + @GET + public Response redirectedResponse(@QueryParam("redirects") Integer number) { + if (number == null || 0 == number) { + return Response.ok().build(); + } else { + return Response.temporaryRedirect(URI.create("/redirect?redirects=" + (number - 1))).build(); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectingResourceClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectingResourceClient.java new file mode 100644 index 00000000000000..88c1054c30d01d --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/redirect/RedirectingResourceClient.java @@ -0,0 +1,12 @@ +package io.quarkus.rest.client.reactive.redirect; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +@Path("/redirect") +public interface RedirectingResourceClient { + @GET + Response call(@QueryParam("redirects") Integer numberOfRedirects); +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/BeanGrabber.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/BeanGrabber.java new file mode 100644 index 00000000000000..68ff83305f1f6d --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/BeanGrabber.java @@ -0,0 +1,18 @@ +package io.quarkus.rest.client.reactive; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; + +@SuppressWarnings("unused") +public class BeanGrabber { + public static T getBeanIfDefined(Class beanClass) { + InstanceHandle instance = Arc.container().instance(beanClass); + if (instance.isAvailable()) { + return instance.get(); + } + return null; + } + + private BeanGrabber() { + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/DefaultMicroprofileRestClientExceptionMapper.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/DefaultMicroprofileRestClientExceptionMapper.java new file mode 100644 index 00000000000000..bfe2af82808619 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/DefaultMicroprofileRestClientExceptionMapper.java @@ -0,0 +1,27 @@ +package io.quarkus.rest.client.reactive; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; + +public class DefaultMicroprofileRestClientExceptionMapper implements ResponseExceptionMapper { + + public Throwable toThrowable(Response response) { + try { + response.bufferEntity(); + } catch (Exception var3) { + } + + return new WebApplicationException("Unknown error, status code " + response.getStatus(), response); + } + + public boolean handles(int status, MultivaluedMap headers) { + return status >= 400; + } + + public int getPriority() { + return Integer.MAX_VALUE; + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/MicroProfileRestClientRequestFilter.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/MicroProfileRestClientRequestFilter.java new file mode 100644 index 00000000000000..aa2987ce24777d --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/MicroProfileRestClientRequestFilter.java @@ -0,0 +1,91 @@ +package io.quarkus.rest.client.reactive; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.annotation.Priority; +import javax.enterprise.context.RequestScoped; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; + +import io.quarkus.rest.client.reactive.runtime.HeaderContainer; +import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; +import org.eclipse.microprofile.rest.client.ext.DefaultClientHeadersFactoryImpl; + +import io.quarkus.arc.Arc; + +@Priority(Integer.MIN_VALUE) +public class MicroProfileRestClientRequestFilter implements ClientRequestFilter { + + private static final MultivaluedMap EMPTY_MAP = new MultivaluedHashMap<>(); + + private final ClientHeadersFactory clientHeadersFactory; + + public MicroProfileRestClientRequestFilter(ClientHeadersFactory clientHeadersFactory) { + this.clientHeadersFactory = clientHeadersFactory; + } + + @Override + public void filter(ClientRequestContext requestContext) { + HeaderFiller headerFiller = (HeaderFiller) requestContext.getProperty(HeaderFiller.class.getName()); + + // mutable collection of headers + MultivaluedMap headers = new MultivaluedHashMap<>(); + + // gather original headers + for (Map.Entry> headerEntry : requestContext.getHeaders().entrySet()) { + headers.put(headerEntry.getKey(), castToListOfStrings(headerEntry.getValue())); + } + + // add headers from MP annotations + if (headerFiller != null) { + // add headers to a mutable headers collection + headerFiller.addHeaders(headers); + } + + MultivaluedMap incomingHeaders = MicroProfileRestClientRequestFilter.EMPTY_MAP; + if (Arc.container().getActiveContext(RequestScoped.class) != null) { + HeaderContainer headerContainer = Arc.container().instance(HeaderContainer.class).get(); + if (headerContainer != null) { + incomingHeaders = headerContainer.getHeaders(); + } + } + + if (clientHeadersFactory instanceof DefaultClientHeadersFactoryImpl) { + // When using the default factory, pass the proposed outgoing headers onto the request context. + // Propagation with the default factory will then overwrite any values if required. + for (Map.Entry> headerEntry : headers.entrySet()) { + requestContext.getHeaders().put(headerEntry.getKey(), castToListOfObjects(headerEntry.getValue())); + } + } + + if (clientHeadersFactory != null) { + incomingHeaders = clientHeadersFactory.update(incomingHeaders, headers); + } + + for (Map.Entry> headerEntry : incomingHeaders.entrySet()) { + requestContext.getHeaders().put(headerEntry.getKey(), castToListOfObjects(headerEntry.getValue())); + } + } + + private static List castToListOfStrings(List values) { + List result = new ArrayList<>(); + for (Object value : values) { + if (value instanceof String) { + result.add((String) value); + } else { + result.add(String.valueOf(value)); + } + } + return result; + } + + @SuppressWarnings("unchecked") + private static List castToListOfObjects(List values) { + return (List) (List) values; + } + +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/MicroProfileRestClientResponseFilter.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/MicroProfileRestClientResponseFilter.java new file mode 100644 index 00000000000000..3ccce69c3e2a09 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/MicroProfileRestClientResponseFilter.java @@ -0,0 +1,43 @@ +package io.quarkus.rest.client.reactive; + +import java.io.IOException; +import java.util.List; + +import javax.ws.rs.ProcessingException; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientResponseContext; +import javax.ws.rs.client.ClientResponseFilter; + +import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; +import org.jboss.resteasy.reactive.client.handlers.ClientResponseRestHandler; +import org.jboss.resteasy.reactive.client.impl.ClientRequestContextImpl; +import org.jboss.resteasy.reactive.client.impl.ClientResponseContextImpl; +import org.jboss.resteasy.reactive.common.jaxrs.ResponseImpl; + +public class +MicroProfileRestClientResponseFilter implements ClientResponseFilter { + private final List> exceptionMappers; + + public MicroProfileRestClientResponseFilter(List> exceptionMappers) { + if (exceptionMappers == null) { + throw new NullPointerException("exceptionMappers cannot be null"); + } + this.exceptionMappers = exceptionMappers; + } + + @Override + public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { + for (ResponseExceptionMapper exceptionMapper : exceptionMappers) { + if (exceptionMapper.handles(responseContext.getStatus(), responseContext.getHeaders())) { + // we have an exception mapper, we don't need the response anymore, we can map it to response right away (I hope :D) + ResponseImpl response = ClientResponseRestHandler.mapToResponse( + ((ClientRequestContextImpl) requestContext).getRestClientRequestContext(), + (ClientResponseContextImpl) responseContext); + Throwable throwable = exceptionMapper.toThrowable(response); + if (throwable != null) { + throw new ProcessingException(throwable); + } + } + } + } +} 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/RestClientBuilderImpl.java similarity index 72% rename from extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java rename to extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/RestClientBuilderImpl.java index 8feb73f1690a5e..62a0d27677e909 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/RestClientBuilderImpl.java @@ -1,4 +1,4 @@ -package io.quarkus.rest.client.reactive.runtime; +package io.quarkus.rest.client.reactive; import java.lang.reflect.InvocationTargetException; import java.net.URISyntaxException; @@ -14,7 +14,6 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.ws.rs.RuntimeType; -import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.Configuration; import org.eclipse.microprofile.config.ConfigProvider; @@ -27,6 +26,10 @@ import org.jboss.resteasy.reactive.client.impl.ClientImpl; import org.jboss.resteasy.reactive.client.impl.WebTargetImpl; import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl; +import org.jboss.resteasy.reactive.common.jaxrs.MultiQueryParamMode; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; /** * Builder implementation for MicroProfile Rest Client @@ -35,14 +38,12 @@ public class RestClientBuilderImpl implements RestClientBuilder { private static final String DEFAULT_MAPPER_DISABLED = "microprofile.rest.client.disable.default.mapper"; - private final ClientBuilder clientBuilder = new ClientBuilderImpl().withConfig(new ConfigurationImpl(RuntimeType.CLIENT)); + private final ClientBuilderImpl clientBuilder = (ClientBuilderImpl) new ClientBuilderImpl() + .withConfig(new ConfigurationImpl(RuntimeType.CLIENT)); private final List> exceptionMappers = new ArrayList<>(); private URL url; - // TODO - MP4 - Require Implementation - private boolean followRedirect; - private String host; - private Integer port; + private boolean followRedirects; private QueryParamStyle queryParamStyle; @Override @@ -89,7 +90,7 @@ public RestClientBuilder hostnameVerifier(HostnameVerifier hostnameVerifier) { @Override public RestClientBuilder followRedirects(final boolean follow) { - this.followRedirect = follow; + this.followRedirects = follow; return this; } @@ -102,8 +103,7 @@ public RestClientBuilder proxyAddress(final String proxyHost, final int proxyPor throw new IllegalArgumentException("Invalid port number"); } - this.host = proxyHost; - this.port = proxyPort; + clientBuilder.proxy(proxyHost, proxyPort); return this; } @@ -127,46 +127,53 @@ public RestClientBuilder property(String name, Object value) { @Override public RestClientBuilder register(Class componentClass) { - registerMpSpecificProvider(componentClass); - clientBuilder.register(componentClass); + Object bean = BeanGrabber.getBeanIfDefined(componentClass); + if (bean != null) { + registerMpSpecificProvider(bean); + clientBuilder.register(bean); + } else { + registerMpSpecificProvider(componentClass); + clientBuilder.register(componentClass); + } return this; } @Override public RestClientBuilder register(Class componentClass, int priority) { - registerMpSpecificProvider(componentClass); - clientBuilder.register(componentClass, priority); - return this; - } - - private void registerMpSpecificProvider(Class componentClass) { - if (ResponseExceptionMapper.class.isAssignableFrom(componentClass)) { - try { - registerMpSpecificProvider(componentClass.getDeclaredConstructor().newInstance()); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new IllegalArgumentException("Failed to instantiate exception mapper " + componentClass - + ". Does it have a public no-arg constructor?", e); - } + InstanceHandle instance = Arc.container().instance(componentClass); + if (instance.isAvailable()) { + registerMpSpecificProvider(instance.get()); + clientBuilder.register(instance.get(), priority); + } else { + registerMpSpecificProvider(componentClass); + clientBuilder.register(componentClass, priority); } + return this; } - private void registerMpSpecificProvider(Object component) { - if (component instanceof ResponseExceptionMapper) { - exceptionMappers.add((ResponseExceptionMapper) component); - } - }; - @Override public RestClientBuilder register(Class componentClass, Class... contracts) { - registerMpSpecificProvider(componentClass); - clientBuilder.register(componentClass, contracts); + InstanceHandle instance = Arc.container().instance(componentClass); + if (instance.isAvailable()) { + registerMpSpecificProvider(instance.get()); + clientBuilder.register(instance.get(), contracts); + } else { + registerMpSpecificProvider(componentClass); + clientBuilder.register(componentClass, contracts); + } return this; } @Override public RestClientBuilder register(Class componentClass, Map, Integer> contracts) { - registerMpSpecificProvider(componentClass); - clientBuilder.register(componentClass, contracts); + InstanceHandle instance = Arc.container().instance(componentClass); + if (instance.isAvailable()) { + registerMpSpecificProvider(instance.get()); + clientBuilder.register(instance.get(), contracts); + } else { + registerMpSpecificProvider(componentClass); + clientBuilder.register(componentClass, contracts); + } return this; } @@ -198,9 +205,26 @@ public RestClientBuilder register(Object component, Map, Integer> contr return this; } + private void registerMpSpecificProvider(Class componentClass) { + if (ResponseExceptionMapper.class.isAssignableFrom(componentClass)) { + try { + registerMpSpecificProvider(componentClass.getDeclaredConstructor().newInstance()); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new IllegalArgumentException("Failed to instantiate exception mapper " + componentClass + + ". Does it have a public no-arg constructor?", e); + } + } + } + + private void registerMpSpecificProvider(Object component) { + if (component instanceof ResponseExceptionMapper) { + exceptionMappers.add((ResponseExceptionMapper) component); + } + } + @Override public RestClientBuilder queryParamStyle(final QueryParamStyle style) { - this.queryParamStyle = style; + queryParamStyle = style; return this; } @@ -217,11 +241,15 @@ public T build(Class aClass) throws IllegalStateException, RestClientDefi Boolean globallyDisabledMapper = ConfigProvider.getConfig() .getOptionalValue(DEFAULT_MAPPER_DISABLED, Boolean.class).orElse(false); if (!globallyDisabledMapper && !(defaultMapperDisabled instanceof Boolean && (Boolean) defaultMapperDisabled)) { - exceptionMappers.add(new DefaultRestClientReactiveExceptionMapper()); + exceptionMappers.add(new DefaultMicroprofileRestClientExceptionMapper()); } exceptionMappers.sort(Comparator.comparingInt(ResponseExceptionMapper::getPriority)); - clientBuilder.register(new RestClientReactiveResponseFilter(exceptionMappers)); + clientBuilder.register(new MicroProfileRestClientResponseFilter(exceptionMappers)); + clientBuilder.followRedirects(followRedirects); + + clientBuilder.multiQueryParamMode(toMultiQueryParamMode(queryParamStyle)); + ClientImpl client = (ClientImpl) clientBuilder.build(); WebTargetImpl target = null; try { @@ -235,4 +263,19 @@ public T build(Class aClass) throws IllegalStateException, RestClientDefi throw new RestClientDefinitionException(e); } } + + private MultiQueryParamMode toMultiQueryParamMode(QueryParamStyle queryParamStyle) { + if (queryParamStyle == null) { + return null; + } + switch (queryParamStyle) { + case MULTI_PAIRS: + return MultiQueryParamMode.MULTI_PAIRS; + case COMMA_SEPARATED: + return MultiQueryParamMode.COMMA_SEPARATED; + case ARRAY_PAIRS: + return MultiQueryParamMode.ARRAY_PAIRS; + } + return null; + } } diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/RestClientListeners.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/RestClientListeners.java new file mode 100644 index 00000000000000..bf3c6ea6dfa42e --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/RestClientListeners.java @@ -0,0 +1,24 @@ +package io.quarkus.rest.client.reactive; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.ServiceLoader; + +import org.eclipse.microprofile.rest.client.spi.RestClientListener; + +public class RestClientListeners { + + private RestClientListeners() { + } + + static Collection get() { + List result = new ArrayList<>(); + ServiceLoader listeners = ServiceLoader.load(RestClientListener.class, + RestClientListeners.class.getClassLoader()); + for (RestClientListener listener : listeners) { + result.add(listener); + } + return result; + } +} \ No newline at end of file diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/BuilderResolver.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/BuilderResolver.java index cfdb54f7a4868d..4cb2c438d1ae75 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/BuilderResolver.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/BuilderResolver.java @@ -1,5 +1,6 @@ package io.quarkus.rest.client.reactive.runtime; +import io.quarkus.rest.client.reactive.RestClientBuilderImpl; import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver; diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/HeaderContainer.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/HeaderContainer.java index bcc3eacd30a454..dbbe78a54c5b00 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/HeaderContainer.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/HeaderContainer.java @@ -15,7 +15,7 @@ void setContainerRequestContext(ContainerRequestContext requestContext) { this.requestContext = requestContext; } - MultivaluedMap getHeaders() { + public MultivaluedMap getHeaders() { if (requestContext == null) { return EMPTY_MAP; } else { diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java index 54ca15c258162f..0f304465390681 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java @@ -20,26 +20,35 @@ import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; +import org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties; public class RestClientCDIDelegateBuilder { private static final String MP_REST = "mp-rest"; - private static final String REST_URL_FORMAT = "%s/" + MP_REST + "/url"; - private static final String REST_URI_FORMAT = "%s/" + MP_REST + "/uri"; - private static final String REST_CONNECT_TIMEOUT_FORMAT = "%s/" + MP_REST + "/connectTimeout"; - private static final String REST_READ_TIMEOUT_FORMAT = "%s/" + MP_REST + "/readTimeout"; - public static final String REST_SCOPE_FORMAT = "%s/" + MP_REST + "/scope"; + private static final String REST_FOLLOW_REDIRECTS = "%s/" + MP_REST + "/followRedirects"; + private static final String REST_HOSTNAME_VERIFIER = "%s/" + MP_REST + "/hostnameVerifier"; + private static final String REST_KEY_STORE = "%s/" + MP_REST + "/keyStore"; + private static final String REST_KEY_STORE_PASSWORD = "%s/" + MP_REST + "/keyStorePassword"; + private static final String REST_KEY_STORE_TYPE = "%s/" + MP_REST + "/keyStoreType"; private static final String REST_PROVIDERS = "%s/" + MP_REST + "/providers"; + private static final String REST_PROXY_ADDRESS = "%s/" + MP_REST + "/proxyAddress"; + private static final String REST_QUERY_PARAM_STYLE = "%s/" + MP_REST + "/queryParamStyle"; + public static final String REST_SCOPE_FORMAT = "%s/" + MP_REST + "/scope"; + private static final String REST_TIMEOUT_CONNECT = "%s/" + MP_REST + "/connectTimeout"; + private static final String REST_TIMEOUT_READ = "%s/" + MP_REST + "/readTimeout"; private static final String REST_TRUST_STORE = "%s/" + MP_REST + "/trustStore"; private static final String REST_TRUST_STORE_PASSWORD = "%s/" + MP_REST + "/trustStorePassword"; private static final String REST_TRUST_STORE_TYPE = "%s/" + MP_REST + "/trustStoreType"; - private static final String REST_KEY_STORE = "%s/" + MP_REST + "/keyStore"; - private static final String REST_KEY_STORE_PASSWORD = "%s/" + MP_REST + "/keyStorePassword"; - private static final String REST_KEY_STORE_TYPE = "%s/" + MP_REST + "/keyStoreType"; - private static final String REST_HOSTNAME_VERIFIER = "%s/" + MP_REST + "/hostnameVerifier"; - private static final String REST_NOOP_HOSTNAME_VERIFIER = "io.quarkus.restclient.NoopHostnameVerifier"; + private static final String REST_URL_FORMAT = "%s/" + MP_REST + "/url"; + private static final String REST_URI_FORMAT = "%s/" + MP_REST + "/uri"; + + private static final String MAX_REDIRECTS = "quarkus.rest.client.max-redirects"; + private static final String TLS_TRUST_ALL = "quarkus.tls.trust-all"; + private static final String REST_NOOP_HOSTNAME_VERIFIER = "io.quarkus.restclient.NoopHostnameVerifier"; + private final Class jaxrsInterface; private final String baseUriFromAnnotation; private final String propertyPrefix; @@ -62,10 +71,57 @@ private Object build() { configureTimeouts(builder); configureProviders(builder); configureSsl(builder); + configureRedirects(builder); + configureQueryParamStyle(builder); + configureProxy(builder); Object result = builder.build(jaxrsInterface); return result; } + private void configureProxy(RestClientBuilder builder) { + Optional maybeProxy = getOptionalDynamicProperty(REST_PROXY_ADDRESS, String.class); + if (maybeProxy.isPresent()) { + String proxyString = maybeProxy.get(); + + int lastColonIndex = proxyString.lastIndexOf(':'); + + if (lastColonIndex <= 0 || lastColonIndex == proxyString.length() - 1) { + throw new RuntimeException("Invalid proxy string. Expected :, found '" + proxyString + "'"); + } + + String host = proxyString.substring(0, lastColonIndex); + int port = 0; + try { + port = Integer.valueOf(proxyString.substring(lastColonIndex + 1)); + } catch (NumberFormatException e) { + throw new RuntimeException("Invalid proxy setting. The port is not a number in '" + proxyString + "'", e); + } + + builder.proxyAddress(host, port); + } + } + + private void configureQueryParamStyle(RestClientBuilder builder) { + Optional maybeQueryParamStyle = getOptionalDynamicProperty(REST_QUERY_PARAM_STYLE, + QueryParamStyle.class); + if (maybeQueryParamStyle.isPresent()) { + QueryParamStyle queryParamStyle = maybeQueryParamStyle.get(); + builder.queryParamStyle(queryParamStyle); + } + } + + private void configureRedirects(RestClientBuilder builder) { + Optional maxRedirects = getOptionalProperty(MAX_REDIRECTS, Integer.class); + if (maxRedirects.isPresent()) { + builder.property(QuarkusRestClientProperties.MAX_REDIRECTS, maxRedirects.get()); + } + + Optional maybeFollowRedirects = getOptionalDynamicProperty(REST_FOLLOW_REDIRECTS, Boolean.class); + if (maybeFollowRedirects.isPresent()) { + builder.followRedirects(maybeFollowRedirects.get()); + } + } + private void configureSsl(RestClientBuilder builder) { Optional trustAll = getOptionalProperty(TLS_TRUST_ALL, Boolean.class); @@ -204,12 +260,12 @@ private Class providerClassForName(String name) { } private void configureTimeouts(RestClientBuilder builder) { - Optional connectTimeout = getOptionalDynamicProperty(REST_CONNECT_TIMEOUT_FORMAT, Long.class); + Optional connectTimeout = getOptionalDynamicProperty(REST_TIMEOUT_CONNECT, Long.class); if (connectTimeout.isPresent()) { builder.connectTimeout(connectTimeout.get(), TimeUnit.MILLISECONDS); } - Optional readTimeout = getOptionalDynamicProperty(REST_READ_TIMEOUT_FORMAT, Long.class); + Optional readTimeout = getOptionalDynamicProperty(REST_TIMEOUT_READ, Long.class); if (readTimeout.isPresent()) { builder.readTimeout(readTimeout.get(), TimeUnit.MILLISECONDS); } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/api/QuarkusRestClientProperties.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/api/QuarkusRestClientProperties.java index aa85a9222767fd..10a45e5f3e2bee 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/api/QuarkusRestClientProperties.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/api/QuarkusRestClientProperties.java @@ -2,5 +2,9 @@ public class QuarkusRestClientProperties { public static final String CONNECT_TIMEOUT = "io.quarkus.rest.client.connect-timeout"; + /** + * maximum number of redirects for a client call. Works only if the client has `followingRedirects enabled + */ + public static final String MAX_REDIRECTS = "io.quarkus.rest.client.max-redirects"; public static final String READ_TIMEOUT = "io.quarkus.rest.client.read-timeout"; } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java index 8246283472fc5f..a8ddc3f3301eeb 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientSendRequestHandler.java @@ -19,12 +19,18 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Variant; + import org.jboss.resteasy.reactive.client.impl.AsyncInvokerImpl; import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext; import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; import org.jboss.resteasy.reactive.common.core.Serialisers; public class ClientSendRequestHandler implements ClientRestHandler { + private final boolean followRedirects; + + public ClientSendRequestHandler(boolean followRedirects) { + this.followRedirects = followRedirects; + } @Override public void handle(RestClientRequestContext requestContext) { @@ -114,13 +120,14 @@ public Future createRequest(RestClientRequestContext state) { URI uri = state.getUri(); boolean isHttps = "https".equals(uri.getScheme()); int port = uri.getPort() != -1 ? uri.getPort() : (isHttps ? 443 : 80); - return httpClient.request( - new RequestOptions() - .setMethod(HttpMethod.valueOf(state.getHttpMethod())) - .setHost(uri.getHost()) - .setURI(uri.getPath() + (uri.getQuery() == null ? "" : "?" + uri.getQuery())) - .setPort(port) - .setSsl(isHttps)); + RequestOptions requestOptions = new RequestOptions(); + requestOptions.setHost(uri.getHost()); + requestOptions.setPort(port); + requestOptions.setMethod(HttpMethod.valueOf(state.getHttpMethod())); + requestOptions.setURI(uri.getPath() + (uri.getQuery() == null ? "" : "?" + uri.getQuery())); + requestOptions.setFollowRedirects(followRedirects); + requestOptions.setSsl(isHttps); + return httpClient.request(requestOptions); } private Buffer setRequestHeadersAndPrepareBody(HttpClientRequest httpClientRequest, diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java index 9e66a2e8586881..1033ead88c8fb9 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java @@ -6,6 +6,7 @@ import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.net.JksOptions; +import io.vertx.core.net.ProxyOptions; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.KeyStore; @@ -23,6 +24,7 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.client.spi.ClientContextResolver; import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl; +import org.jboss.resteasy.reactive.common.jaxrs.MultiQueryParamMode; public class ClientBuilderImpl extends ClientBuilder { @@ -37,6 +39,12 @@ public class ClientBuilderImpl extends ClientBuilder { private char[] keystorePassword; private SSLContext sslContext; private KeyStore trustStore; + + private String proxyHost; + private int proxyPort; + private boolean followRedirects; + private MultiQueryParamMode multiQueryParamMode; + private HttpClientOptions httpClientOptions = new HttpClientOptions(); @Override @@ -92,11 +100,27 @@ public ClientBuilder readTimeout(long timeout, TimeUnit unit) { return this; } + public ClientBuilder proxy(String proxyHost, int proxyPort) { + this.proxyPort = proxyPort; + this.proxyHost = proxyHost; + return this; + } + public ClientBuilder httpClientOptions(HttpClientOptions httpClientOptions) { this.httpClientOptions = httpClientOptions; return this; } + public ClientBuilder followRedirects(boolean followRedirects) { + this.followRedirects = followRedirects; + return this; + } + + public ClientBuilder multiQueryParamMode(MultiQueryParamMode multiQueryParamMode) { + this.multiQueryParamMode = multiQueryParamMode; + return this; + } + @Override public ClientImpl build() { Buffer keyStore = asBuffer(this.keyStore, keystorePassword); @@ -120,11 +144,20 @@ public ClientImpl build() { } } + if (proxyHost != null) { + options.setProxyOptions( + new ProxyOptions() + .setHost(proxyHost) + .setPort(proxyPort)); + } + return new ClientImpl(httpClientOptions, configuration, CLIENT_CONTEXT_RESOLVER.resolve(Thread.currentThread().getContextClassLoader()), hostnameVerifier, - sslContext); + sslContext, + followRedirects, + multiQueryParamMode); } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java index 86cf7adbe675a7..4052ae52fa472c 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java @@ -1,6 +1,7 @@ package org.jboss.resteasy.reactive.client.impl; import static org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties.CONNECT_TIMEOUT; +import static org.jboss.resteasy.reactive.client.api.QuarkusRestClientProperties.MAX_REDIRECTS; import io.netty.channel.EventLoopGroup; import io.vertx.core.AsyncResult; @@ -63,6 +64,8 @@ import org.jboss.resteasy.reactive.client.spi.ClientContext; import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl; +import org.jboss.resteasy.reactive.common.jaxrs.MultiQueryParamMode; +import org.jboss.resteasy.reactive.common.jaxrs.UriBuilderImpl; public class ClientImpl implements Client { @@ -78,16 +81,20 @@ public class ClientImpl implements Client { final ClientRestHandler[] handlerChain; final ClientRestHandler[] abortHandlerChain; final Vertx vertx; + private final MultiQueryParamMode multiQueryParamMode; public ClientImpl(HttpClientOptions options, ConfigurationImpl configuration, ClientContext clientContext, HostnameVerifier hostnameVerifier, - SSLContext sslContext) { + SSLContext sslContext, boolean followRedirects, + MultiQueryParamMode multiQueryParamMode) { + configuration = configuration != null ? configuration : new ConfigurationImpl(RuntimeType.CLIENT); // TODO: ssl context // TODO: hostnameVerifier - this.configuration = configuration != null ? configuration : new ConfigurationImpl(RuntimeType.CLIENT); + this.configuration = configuration; this.clientContext = clientContext; this.hostnameVerifier = hostnameVerifier; this.sslContext = sslContext; + this.multiQueryParamMode = multiQueryParamMode; Supplier vertx = clientContext.getVertx(); if (vertx != null) { this.vertx = vertx.get(); @@ -101,16 +108,21 @@ public Vertx get() { }); closeVertx = true; } - Object connectTimeoutMs = configuration == null ? null : configuration.getProperty(CONNECT_TIMEOUT); + Object connectTimeoutMs = configuration.getProperty(CONNECT_TIMEOUT); if (connectTimeoutMs == null) { options.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT); } else { options.setConnectTimeout((int) connectTimeoutMs); } + + Object maxRedirects = configuration.getProperty(MAX_REDIRECTS); + if (maxRedirects != null) { + options.setMaxRedirects((Integer) maxRedirects); + } this.httpClient = this.vertx.createHttpClient(options); abortHandlerChain = new ClientRestHandler[] { new ClientErrorHandler() }; - handlerChain = new ClientRestHandler[] { new ClientRequestFiltersRestHandler(), new ClientSendRequestHandler(), - new ClientResponseRestHandler() }; + handlerChain = new ClientRestHandler[] { new ClientRequestFiltersRestHandler(), + new ClientSendRequestHandler(followRedirects), new ClientResponseRestHandler() }; } public ClientContext getClientContext() { @@ -151,6 +163,9 @@ public WebTarget target(URI uri) { public WebTarget target(UriBuilder uriBuilder) { abortIfClosed(); Objects.requireNonNull(uriBuilder); + if (uriBuilder instanceof UriBuilderImpl && multiQueryParamMode != null) { + ((UriBuilderImpl) uriBuilder).multiQueryParamMode(multiQueryParamMode); + } return new WebTargetImpl(this, httpClient, uriBuilder, new ConfigurationImpl(configuration), handlerChain, abortHandlerChain, null); } 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 9dec50d39b725b..2168f2d6b78422 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 @@ -29,7 +29,9 @@ public class WebTargetImpl implements WebTarget { public WebTargetImpl(ClientImpl restClient, HttpClient client, UriBuilder uriBuilder, ConfigurationImpl configuration, - ClientRestHandler[] handlerChain, ClientRestHandler[] abortHandlerChain, ThreadSetupAction requestContext) { + ClientRestHandler[] handlerChain, + ClientRestHandler[] abortHandlerChain, + ThreadSetupAction requestContext) { this.restClient = restClient; this.client = client; this.uriBuilder = uriBuilder; diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/MultiQueryParamMode.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/MultiQueryParamMode.java new file mode 100644 index 00000000000000..f8c509a6be1439 --- /dev/null +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/MultiQueryParamMode.java @@ -0,0 +1,16 @@ +package org.jboss.resteasy.reactive.common.jaxrs; + +public enum MultiQueryParamMode { + /** + * foo=v1&foo=v2&foo=v3 + */ + MULTI_PAIRS, + /** + * foo=v1,v2,v3 + */ + COMMA_SEPARATED, + /** + * foo[]=v1&foo[]=v2&foo[]=v3 + */ + ARRAY_PAIRS +} diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/UriBuilderImpl.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/UriBuilderImpl.java index 2883d861ab424d..10e11b4060f189 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/UriBuilderImpl.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/UriBuilderImpl.java @@ -65,6 +65,8 @@ public boolean containsKey(Object key) { private String ssp; private String authority; + private MultiQueryParamMode queryParamMode = MultiQueryParamMode.MULTI_PAIRS; + public UriBuilder clone() { UriBuilderImpl impl = new UriBuilderImpl(); impl.host = host; @@ -76,6 +78,7 @@ public UriBuilder clone() { impl.fragment = fragment; impl.ssp = ssp; impl.authority = authority; + impl.queryParamMode = queryParamMode; return impl; } @@ -901,12 +904,29 @@ public UriBuilder queryParam(String name, Object... values) throws IllegalArgume throw new IllegalArgumentException("Name parameter is null"); if (values == null) throw new IllegalArgumentException("Values parameter is null"); + + if (queryParamMode == MultiQueryParamMode.COMMA_SEPARATED) { + sb.append(Encode.encodeQueryParam(name)).append("="); + } for (Object value : values) { if (value == null) throw new IllegalArgumentException("Value is null"); + sb.append(prefix); - prefix = "&"; - sb.append(Encode.encodeQueryParam(name)).append("=").append(Encode.encodeQueryParam(value.toString())); + switch (queryParamMode) { + case MULTI_PAIRS: + prefix = "&"; + sb.append(Encode.encodeQueryParam(name)).append("=").append(Encode.encodeQueryParam(value.toString())); + break; + case COMMA_SEPARATED: + prefix = ","; + sb.append(Encode.encodeQueryParam(value.toString())); + break; + case ARRAY_PAIRS: + prefix = "&"; + sb.append(Encode.encodeQueryParam(name)).append("[]=").append(Encode.encodeQueryParam(value.toString())); + break; + } } query = sb.toString(); @@ -1067,4 +1087,9 @@ public UriBuilder resolveTemplatesFromEncoded(Map templateValues throw new IllegalArgumentException("map key null"); return uriTemplate(buildCharSequence(templateValues, true, true, true)); } + + public UriBuilder multiQueryParamMode(MultiQueryParamMode mode) { + queryParamMode = mode; + return this; + } } diff --git a/tcks/microprofile-rest-client-reactive/pom.xml b/tcks/microprofile-rest-client-reactive/pom.xml index 11347774ff663a..6e0935d92d3e87 100644 --- a/tcks/microprofile-rest-client-reactive/pom.xml +++ b/tcks/microprofile-rest-client-reactive/pom.xml @@ -49,11 +49,13 @@ org.eclipse.microprofile.rest.client.tck.cditests.CDIInterceptorTest - - org.eclipse.microprofile.rest.client.tck.InvokeWithJsonBProviderTest - org.eclipse.microprofile.rest.client.tck.InvokeWithJsonPProviderTest + + org.eclipse.microprofile.rest.client.tck.InvalidInterfaceTest + + org.eclipse.microprofile.rest.client.tck.InvokeWithJsonPProviderTest + org.eclipse.microprofile.rest.client.tck.ssl.SslContextTest org.eclipse.microprofile.rest.client.tck.ssl.SslHostnameVerifierTest diff --git a/tcks/microprofile-rest-client-reactive/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonBProviderTest.java b/tcks/microprofile-rest-client-reactive/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonBProviderTest.java deleted file mode 100644 index b84edd39ff2149..00000000000000 --- a/tcks/microprofile-rest-client-reactive/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonBProviderTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package io.quarkus.tck.restclient; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.reset; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static org.testng.Assert.assertEquals; - -import java.time.LocalDate; - -import javax.inject.Inject; - -import org.eclipse.microprofile.rest.client.RestClientBuilder; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.eclipse.microprofile.rest.client.tck.WiremockArquillianTest; -import org.eclipse.microprofile.rest.client.tck.interfaces.JsonBClient; -import org.eclipse.microprofile.rest.client.tck.interfaces.MyJsonBObject; -import org.eclipse.microprofile.rest.client.tck.jsonb.InvokeWithJsonBProviderTest; -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.EmptyAsset; -import org.jboss.shrinkwrap.api.asset.StringAsset; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.testng.SkipException; -import org.testng.annotations.Test; - -/** - * copied from - * https://github.com/eclipse/microprofile-rest-client/blob/1.4.X-service/tck/src/main/java/org/eclipse/microprofile/rest/client/tck/InvokeWithJsonBProviderTest.java - */ -public class CustomInvokeWithJsonBProviderTest extends WiremockArquillianTest { - - private static final String CDI = "cdi"; - private static final String BUILT = "built"; - - @Deployment - public static WebArchive createDeployment() { - StringAsset mpConfig = new StringAsset(JsonBClient.class.getName() + "/mp-rest/uri=" + getStringURL()); - return ShrinkWrap.create(WebArchive.class, InvokeWithJsonBProviderTest.class.getSimpleName() + ".war") - .addClasses(JsonBClient.class, WiremockArquillianTest.class, MyJsonBObject.class, - InvokeWithJsonBProviderTest.class) - .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml") - .addAsWebInfResource(mpConfig, "classes/META-INF/microprofile-config.properties"); - } - - private static void assumeJsonbApiExists() throws SkipException { - try { - Class.forName("javax.json.bind.annotation.JsonbProperty"); - } catch (Throwable t) { - throw new SkipException("Skipping since JSON-B APIs were not found."); - } - } - - @RestClient - @Inject - private JsonBClient cdiJsonBClient; - - private JsonBClient builtJsonBClient; - - public void setupClient() { - builtJsonBClient = RestClientBuilder.newBuilder() - .baseUri(getServerURI()) - .build(JsonBClient.class); - } - - @Test - public void testGetExecutesForBothClients() throws Exception { - setupClient(); - assumeJsonbApiExists(); - testGet(builtJsonBClient, BUILT); - testGet(cdiJsonBClient, CDI); - } - - private void testGet(JsonBClient client, String clientType) { - reset(); - stubFor(get(urlEqualTo("/myObject")) - .willReturn(aResponse() - .withHeader("Content-Type", "application/json") - .withBody("{" + - "\"objectName\": \"myObject\"," + - "\"quantity\": 17," + - "\"date\": \"2018-12-04\"" + - "}"))); - - MyJsonBObject obj = client.get("myObject"); - assertEquals(obj.getName(), "myObject"); - assertEquals(obj.getQty(), 17); - assertEquals(obj.getIgnoredField(), "CTOR"); - assertEquals(obj.getDate(), LocalDate.of(2018, 12, 4)); - } -} diff --git a/tcks/microprofile-rest-client-reactive/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonPProviderTest.java b/tcks/microprofile-rest-client-reactive/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonPProviderTest.java deleted file mode 100644 index 2526d3b874586d..00000000000000 --- a/tcks/microprofile-rest-client-reactive/src/test/java/io/quarkus/tck/restclient/CustomInvokeWithJsonPProviderTest.java +++ /dev/null @@ -1,156 +0,0 @@ -package io.quarkus.tck.restclient; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.put; -import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.reset; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.client.WireMock.verify; -import static org.testng.Assert.assertEquals; - -import java.util.List; - -import javax.inject.Inject; -import javax.json.Json; -import javax.json.JsonArray; -import javax.json.JsonObject; -import javax.ws.rs.core.Response; - -import org.eclipse.microprofile.rest.client.RestClientBuilder; -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.eclipse.microprofile.rest.client.tck.WiremockArquillianTest; -import org.eclipse.microprofile.rest.client.tck.interfaces.JsonPClient; -import org.jboss.arquillian.container.test.api.Deployment; -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.EmptyAsset; -import org.jboss.shrinkwrap.api.asset.StringAsset; -import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.testng.annotations.Test; - -/* -copied from https://github.com/eclipse/microprofile-rest-client/blob/1.4.X-service/tck/src/main/java/org/eclipse/microprofile/rest/client/tck/InvokeWithJsonPProviderTest.java - */ -public class CustomInvokeWithJsonPProviderTest extends WiremockArquillianTest { - - private static final String CDI = "cdi"; - private static final String BUILT = "built"; - - @Deployment - public static WebArchive createDeployment() { - StringAsset mpConfig = new StringAsset(JsonPClient.class.getName() + "/mp-rest/url=" + getStringURL()); - return ShrinkWrap.create(WebArchive.class, CustomInvokeWithJsonPProviderTest.class.getSimpleName() + ".war") - .addClasses(JsonPClient.class, WiremockArquillianTest.class) - .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml") - .addAsWebInfResource(mpConfig, "classes/META-INF/microprofile-config.properties"); - } - - @Inject - @RestClient - private JsonPClient cdiJsonPClient; - - private JsonPClient builtJsonPClient; - - public void setupClient() { - builtJsonPClient = RestClientBuilder.newBuilder() - .baseUri(getServerURI()) - .build(JsonPClient.class); - } - - @Test - public void testGetExecutesForBothClients() { - setupClient(); - testGet(builtJsonPClient, BUILT); - testGet(cdiJsonPClient, CDI); - } - - @Test - public void testGetSingleExecutesForBothClients() { - setupClient(); - testGetSingle(builtJsonPClient, BUILT); - testGetSingle(cdiJsonPClient, CDI); - } - - @Test - public void testPostExecutes() { - setupClient(); - testPost(builtJsonPClient, BUILT); - testPost(cdiJsonPClient, CDI); - } - - @Test - public void testPutExecutes() { - setupClient(); - testPut(builtJsonPClient, BUILT); - testPut(cdiJsonPClient, CDI); - } - - private void testGet(JsonPClient client, String clientType) { - reset(); - stubFor(get(urlEqualTo("/")) - .willReturn(aResponse() - .withHeader("Content-Type", "application/json") - .withBody("[{\"key\": \"value\"}, {\"key\": \"anotherValue\"}]"))); - JsonArray jsonArray = client.get(); - assertEquals(jsonArray.size(), 2, "Expected 2 values in the array for client " + clientType); - List jsonObjects = jsonArray.getValuesAs(JsonObject.class); - JsonObject one = jsonObjects.get(0); - assertEquals(one.keySet().size(), 1, "There should only be one key in object 1 for client " + clientType); - assertEquals(one.getString("key"), "value", "The value of 'key' on object 1 should be 'value' in client " + clientType); - - JsonObject two = jsonObjects.get(1); - assertEquals(two.keySet().size(), 1, "There should only be one key in object 2 for client " + clientType); - assertEquals(two.getString("key"), "anotherValue", - "The value of 'key' on object 2 should be 'anotherValue' in client " + clientType); - - } - - private void testGetSingle(JsonPClient client, String clientType) { - reset(); - stubFor(get(urlEqualTo("/id")) - .willReturn(aResponse() - .withHeader("Content-Type", "application/json") - .withBody("{\"key\": \"value\"}"))); - JsonObject jsonObject = client.get("id"); - assertEquals(jsonObject.keySet().size(), 1, "There should only be one key in object for client " + clientType); - assertEquals(jsonObject.getString("key"), "value", - "The value of 'key' on object should be 'value' in client " + clientType); - - } - - private void testPost(JsonPClient client, String clientType) { - reset(); - stubFor(post(urlEqualTo("/")) - .willReturn(aResponse() - .withHeader("Content-Type", "application/json") - .withStatus(200))); - - JsonObject jsonObject = Json.createObjectBuilder().add("someKey", "newValue").build(); - String jsonObjectAsString = jsonObject.toString(); - Response response = client.post(jsonObject); - response.close(); - assertEquals(response.getStatus(), 200, "Expected a 200 OK on client " + clientType); - - verify(1, postRequestedFor(urlEqualTo("/")).withRequestBody(equalTo(jsonObjectAsString))); - } - - private void testPut(JsonPClient client, String clientType) { - reset(); - stubFor(put(urlEqualTo("/id")) - .willReturn(aResponse() - .withHeader("Content-Type", "application/json") - .withStatus(200).withBody("{\"someOtherKey\":\"newValue\"}"))); - - JsonObject jsonObject = Json.createObjectBuilder().add("someKey", "newValue").build(); - String jsonObjectAsString = jsonObject.toString(); - JsonObject response = client.update("id", jsonObject); - assertEquals(response.getString("someOtherKey"), "newValue", - "The value of 'someOtherKey' on response should be 'someOtherKey' in client " + clientType); - - verify(1, putRequestedFor(urlEqualTo("/id")).withRequestBody(equalTo(jsonObjectAsString))); - } -}