From 05577623d3dc604bf9f9bf67529416b64fff67bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szynkiewicz?= Date: Fri, 23 Apr 2021 14:27:10 +0200 Subject: [PATCH] Rest Client Reactive - fixed @RegisterProvider support --- .../MicroProfileRestClientEnricher.java | 58 +-------------- ....java => RestClientReactiveProcessor.java} | 71 ++++++++++++++++++- .../AnnotationRegisteredProviders.java | 19 +++++ .../runtime/RestClientBuilderImpl.java | 6 ++ .../it/rest/client/{ => main}/Apple.java | 2 +- .../AppleClient.java} | 4 +- .../{ => main}/ClientCallingResource.java | 42 +++++++++-- .../main/ClientWithExceptionMapper.java | 17 +++++ .../{ => main}/DefaultCtorTestFilter.java | 2 +- .../main/MyResponseExceptionMapper.java | 22 ++++++ .../{ => main}/NonDefaultCtorTestFilter.java | 2 +- .../src/main/resources/application.properties | 1 + .../io/quarkus/it/rest/client/BasicTest.java | 20 +++++- 13 files changed, 194 insertions(+), 72 deletions(-) rename extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/{ReactiveResteasyMpClientProcessor.java => RestClientReactiveProcessor.java} (82%) create mode 100644 extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/AnnotationRegisteredProviders.java rename integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/{ => main}/Apple.java (91%) rename integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/{SimpleClient.java => main/AppleClient.java} (95%) rename integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/{ => main}/ClientCallingResource.java (63%) create mode 100644 integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientWithExceptionMapper.java rename integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/{ => main}/DefaultCtorTestFilter.java (88%) create mode 100644 integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/MyResponseExceptionMapper.java rename integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/{ => main}/NonDefaultCtorTestFilter.java (93%) create mode 100644 integration-tests/resteasy-reactive-rest-client/src/main/resources/application.properties diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java index dc30836adead9..f5e90dc889c70 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java @@ -4,8 +4,6 @@ import static io.quarkus.rest.client.reactive.deployment.DotNames.CLIENT_HEADER_PARAM; import static io.quarkus.rest.client.reactive.deployment.DotNames.CLIENT_HEADER_PARAMS; import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_CLIENT_HEADERS; -import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDER; -import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDERS; import static org.jboss.resteasy.reactive.common.processor.HashUtil.sha1; import static org.objectweb.asm.Opcodes.ACC_STATIC; @@ -43,7 +41,6 @@ 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; @@ -56,7 +53,6 @@ import io.quarkus.gizmo.TryBlock; import io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveEnricher; import io.quarkus.rest.client.reactive.HeaderFiller; -import io.quarkus.rest.client.reactive.runtime.BeanGrabber; import io.quarkus.rest.client.reactive.runtime.MicroProfileRestClientRequestFilter; import io.quarkus.rest.client.reactive.runtime.NoOpHeaderFiller; import io.quarkus.runtime.util.HashUtil; @@ -91,16 +87,6 @@ class MicroProfileRestClientEnricher implements JaxrsClientReactiveEnricher { public void forClass(MethodCreator constructor, AssignableResultHandle webTargetBase, ClassInfo interfaceClass, IndexView index) { - AnnotationInstance annotation = interfaceClass.classAnnotation(REGISTER_PROVIDER); - AnnotationInstance groupAnnotation = interfaceClass.classAnnotation(REGISTER_PROVIDERS); - - if (annotation != null) { - addProvider(constructor, webTargetBase, index, annotation); - } - for (AnnotationInstance annotationInstance : extractAnnotations(groupAnnotation)) { - addProvider(constructor, webTargetBase, index, annotationInstance); - } - ResultHandle clientHeadersFactory = null; AnnotationInstance registerClientHeaders = interfaceClass.classAnnotation(REGISTER_CLIENT_HEADERS); @@ -493,54 +479,14 @@ private AnnotationInstance[] extractAnnotations(AnnotationInstance groupAnnotati return EMPTY_ANNOTATION_INSTANCES; } - private void addProvider(MethodCreator ctor, AssignableResultHandle target, IndexView index, - AnnotationInstance registerProvider) { - // 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 - ClassInfo providerClassInfo = index.getClassByName(DotName.createSimple(providerClass)); - BytecodeCreator beanProviderNotAvailable = branchResult.falseBranch(); - if ((providerClassInfo != null) && providerClassInfo.hasNoArgsConstructor()) { // if the filter has a no-args constructor, use it - ResultHandle provider = beanProviderNotAvailable.newInstance(MethodDescriptor.ofConstructor(providerClass)); - alteredTarget = beanProviderNotAvailable.invokeInterfaceMethod( - MethodDescriptor.ofMethod(Configurable.class, "register", Configurable.class, Object.class, - int.class), - target, provider, - beanProviderNotAvailable.load(registerProvider.valueWithDefault(index, "priority").asInt())); - beanProviderNotAvailable.assign(target, alteredTarget); - } else { // the filter does not have a no-args constructor, so we can do nothing but fail - beanProviderNotAvailable.throwException(IllegalStateException.class, - "Provider " + providerClass + " must either be a CDI bean or have a no-args constructor"); - } - - } - /** * ClientHeaderParam annotations can be defined on a JAX-RS interface or a sub-client (sub-resource). * If we're filling headers for a sub-client, we need to know the defining class of the ClientHeaderParam * to properly resolve default methods of the "root" client */ private static class HeaderData { - private AnnotationInstance annotation; - private ClassInfo definingClass; + private final AnnotationInstance annotation; + private final ClassInfo definingClass; public HeaderData(AnnotationInstance annotation, ClassInfo definingClass) { this.annotation = annotation; diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ReactiveResteasyMpClientProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java similarity index 82% rename from extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ReactiveResteasyMpClientProcessor.java rename to extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java index 054a487bea67d..304e5fe382c4c 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ReactiveResteasyMpClientProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.rest.client.reactive.deployment; +import static io.quarkus.arc.processor.MethodDescriptors.MAP_PUT; import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_CLIENT_HEADERS; import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDER; import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDERS; @@ -11,13 +12,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import javax.enterprise.context.SessionScoped; import javax.enterprise.inject.Typed; +import javax.inject.Singleton; import javax.ws.rs.core.MediaType; import org.eclipse.microprofile.config.Config; @@ -40,6 +44,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.arc.processor.ScopeInfo; import io.quarkus.deployment.Capabilities; @@ -61,6 +66,7 @@ import io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveEnricherBuildItem; import io.quarkus.jaxrs.client.reactive.deployment.RestClientDefaultConsumesBuildItem; import io.quarkus.jaxrs.client.reactive.deployment.RestClientDefaultProducesBuildItem; +import io.quarkus.rest.client.reactive.runtime.AnnotationRegisteredProviders; import io.quarkus.rest.client.reactive.runtime.HeaderCapturingServerFilter; import io.quarkus.rest.client.reactive.runtime.HeaderContainer; import io.quarkus.rest.client.reactive.runtime.RestClientCDIDelegateBuilder; @@ -68,9 +74,9 @@ import io.quarkus.rest.client.reactive.runtime.RestClientRecorder; import io.quarkus.resteasy.reactive.spi.ContainerRequestFilterBuildItem; -class ReactiveResteasyMpClientProcessor { +class RestClientReactiveProcessor { - private static final Logger log = Logger.getLogger(ReactiveResteasyMpClientProcessor.class); + private static final Logger log = Logger.getLogger(RestClientReactiveProcessor.class); private static final DotName REGISTER_REST_CLIENT = DotName.createSimple(RegisterRestClient.class.getName()); private static final DotName SESSION_SCOPED = DotName.createSimple(SessionScoped.class.getName()); @@ -151,6 +157,67 @@ void registerHeaderFactoryBeans(CombinedIndexBuildItem index, } } + /** + * Creates an implementation of `AnnotationRegisteredProviders` class with a constructor that + * puts all the providers registered by the @RegisterProvider annotation in a + * map using the {@link AnnotationRegisteredProviders#addProviders(String, Map)} method + * + * @param indexBuildItem index + * @param generatedBeans build producer for generated beans + */ + @BuildStep + void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem, + BuildProducer generatedBeans, + BuildProducer unremovableBeans) { + String annotationRegisteredProvidersImpl = AnnotationRegisteredProviders.class.getName() + "Implementation"; + IndexView index = indexBuildItem.getIndex(); + Map> annotationsByClassName = new HashMap<>(); + + for (AnnotationInstance annotation : index.getAnnotations(REGISTER_PROVIDER)) { + String targetClass = annotation.target().asClass().name().toString(); + annotationsByClassName.computeIfAbsent(targetClass, key -> new ArrayList<>()) + .add(annotation); + } + + for (AnnotationInstance annotation : index.getAnnotations(REGISTER_PROVIDERS)) { + String targetClass = annotation.target().asClass().name().toString(); + annotationsByClassName.computeIfAbsent(targetClass, key -> new ArrayList<>()) + .addAll(Arrays.asList(annotation.value().asNestedArray())); + } + + try (ClassCreator classCreator = ClassCreator.builder() + .className(annotationRegisteredProvidersImpl) + .classOutput(new GeneratedBeanGizmoAdaptor(generatedBeans)) + .superClass(AnnotationRegisteredProviders.class) + .build()) { + + classCreator.addAnnotation(Singleton.class.getName()); + MethodCreator constructor = classCreator + .getMethodCreator(MethodDescriptor.ofConstructor(annotationRegisteredProvidersImpl)); + constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(AnnotationRegisteredProviders.class), + constructor.getThis()); + + for (Map.Entry> annotationsForClass : annotationsByClassName.entrySet()) { + ResultHandle map = constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class)); + for (AnnotationInstance value : annotationsForClass.getValue()) { + String className = value.value().asString(); + AnnotationValue priority = value.value("priority"); + + constructor.invokeInterfaceMethod(MAP_PUT, map, constructor.loadClass(className), + constructor.load(priority == null ? -1 : priority.asInt())); + } + constructor.invokeVirtualMethod( + MethodDescriptor.ofMethod(AnnotationRegisteredProviders.class, "addProviders", void.class, String.class, + Map.class), + constructor.getThis(), constructor.load(annotationsForClass.getKey()), map); + } + + constructor.returnValue(null); + } + + unremovableBeans.produce(UnremovableBeanBuildItem.beanClassNames(annotationRegisteredProvidersImpl)); + } + @BuildStep AdditionalBeanBuildItem registerProviderBeans(CombinedIndexBuildItem combinedIndex) { IndexView index = combinedIndex.getIndex(); diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/AnnotationRegisteredProviders.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/AnnotationRegisteredProviders.java new file mode 100644 index 0000000000000..72d50cebc9fd0 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/AnnotationRegisteredProviders.java @@ -0,0 +1,19 @@ +package io.quarkus.rest.client.reactive.runtime; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public abstract class AnnotationRegisteredProviders { + private final Map, Integer>> providers = new HashMap<>(); + + public Map, Integer> getProviders(Class clientClass) { + Map, Integer> providersForClass = providers.get(clientClass.getName()); + return providersForClass == null ? Collections.emptyMap() : providersForClass; + } + + // used by generated code + public void addProviders(String className, Map, Integer> providersForClass) { + this.providers.put(className, providersForClass); + } +} 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 16ae42ee2aed6..c24806223c8df 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 @@ -238,6 +238,12 @@ public T build(Class aClass) throws IllegalStateException, RestClientDefi RestClientListeners.get().forEach(listener -> listener.onNewClient(aClass, this)); + AnnotationRegisteredProviders annotationRegisteredProviders = Arc.container() + .instance(AnnotationRegisteredProviders.class).get(); + for (Map.Entry, Integer> mapper : annotationRegisteredProviders.getProviders(aClass).entrySet()) { + register(mapper.getKey(), mapper.getValue()); + } + Object defaultMapperDisabled = getConfiguration().getProperty(DEFAULT_MAPPER_DISABLED); Boolean globallyDisabledMapper = ConfigProvider.getConfig() .getOptionalValue(DEFAULT_MAPPER_DISABLED, Boolean.class).orElse(false); diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/Apple.java b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/Apple.java similarity index 91% rename from integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/Apple.java rename to integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/Apple.java index c814467d957ab..02634a15cc321 100644 --- a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/Apple.java +++ b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/Apple.java @@ -1,4 +1,4 @@ -package io.quarkus.it.rest.client; +package io.quarkus.it.rest.client.main; import io.quarkus.runtime.annotations.RegisterForReflection; diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/SimpleClient.java b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/AppleClient.java similarity index 95% rename from integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/SimpleClient.java rename to integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/AppleClient.java index b6bedb8810873..d03ae8cdc61f2 100644 --- a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/SimpleClient.java +++ b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/AppleClient.java @@ -1,4 +1,4 @@ -package io.quarkus.it.rest.client; +package io.quarkus.it.rest.client.main; import java.util.concurrent.CompletionStage; @@ -15,7 +15,7 @@ @Path("") @RegisterProvider(DefaultCtorTestFilter.class) @RegisterProvider(NonDefaultCtorTestFilter.class) -public interface SimpleClient { +public interface AppleClient { @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/ClientCallingResource.java b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java similarity index 63% rename from integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/ClientCallingResource.java rename to integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java index 3ec2cd5830e68..a90422414360d 100644 --- a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/ClientCallingResource.java +++ b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java @@ -1,4 +1,4 @@ -package io.quarkus.it.rest.client; +package io.quarkus.it.rest.client.main; import java.net.URI; import java.util.concurrent.atomic.AtomicInteger; @@ -7,11 +7,13 @@ import javax.enterprise.event.Observes; import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.inject.RestClient; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; +import io.quarkus.it.rest.client.main.MyResponseExceptionMapper.MyException; import io.smallrye.mutiny.Uni; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; @@ -21,12 +23,27 @@ public class ClientCallingResource { private static final ObjectMapper mapper = new JsonMapper(); - private static final String[] RESPONSES = { "cortland", "cortland2", "cortland3" }; + private static final String[] RESPONSES = { "cortland", "lobo", "golden delicious" }; private final AtomicInteger count = new AtomicInteger(0); + @RestClient + ClientWithExceptionMapper clientWithExceptionMapper; + void init(@Observes Router router) { router.post().handler(BodyHandler.create()); + router.get("/unprocessable").handler(rc -> rc.response().setStatusCode(422).end("the entity was unprocessable")); + + router.post("/call-client-with-exception-mapper").blockingHandler(rc -> { + String url = rc.getBody().toString(); + ClientWithExceptionMapper client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + .register(MyResponseExceptionMapper.class) + .build(ClientWithExceptionMapper.class); + callGet(rc, client); + }); + + router.post("/call-cdi-client-with-exception-mapper").blockingHandler(rc -> callGet(rc, clientWithExceptionMapper)); + router.post("/apples").handler(rc -> { int count = this.count.getAndIncrement(); rc.response().putHeader("content-type", "application/json") @@ -35,8 +52,8 @@ void init(@Observes Router router) { router.route("/call-client").blockingHandler(rc -> { String url = rc.getBody().toString(); - SimpleClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) - .build(SimpleClient.class); + AppleClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + .build(AppleClient.class); Uni apple1 = Uni.createFrom().item(client.swapApple(new Apple("lobo"))); Uni apple2 = Uni.createFrom().completionStage(client.completionSwapApple(new Apple("lobo2"))); Uni apple3 = client.uniSwapApple(new Apple("lobo3")); @@ -56,12 +73,23 @@ void init(@Observes Router router) { } catch (JsonProcessingException e) { fail(rc, e.getMessage()); } - }, t -> { - fail(rc, t.getMessage()); - }); + }, t -> fail(rc, t.getMessage())); }); } + private void callGet(RoutingContext rc, ClientWithExceptionMapper client) { + try { + client.get(); + } catch (MyException expected) { + rc.response().setStatusCode(200).end(); + return; + } catch (Exception unexpected) { + rc.response().setStatusCode(500).end("Expected MyException to be thrown, got " + unexpected.getClass()); + return; + } + rc.response().setStatusCode(500).end("Expected MyException to be thrown but no exception has been thrown"); + } + private void fail(RoutingContext rc, String message) { rc.response().putHeader("content-type", "text/plain").setStatusCode(500).end(message); } diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientWithExceptionMapper.java b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientWithExceptionMapper.java new file mode 100644 index 0000000000000..b663cb54a2285 --- /dev/null +++ b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientWithExceptionMapper.java @@ -0,0 +1,17 @@ +package io.quarkus.it.rest.client.main; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.quarkus.it.rest.client.main.MyResponseExceptionMapper.MyException; + +@Path("/unprocessable") +@RegisterProvider(MyResponseExceptionMapper.class) +@RegisterRestClient(configKey = "w-exception-mapper") +public interface ClientWithExceptionMapper { + @GET + String get() throws MyException; +} diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/DefaultCtorTestFilter.java b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/DefaultCtorTestFilter.java similarity index 88% rename from integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/DefaultCtorTestFilter.java rename to integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/DefaultCtorTestFilter.java index a68355f7fc6bb..5ca8adb4bab54 100644 --- a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/DefaultCtorTestFilter.java +++ b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/DefaultCtorTestFilter.java @@ -1,4 +1,4 @@ -package io.quarkus.it.rest.client; +package io.quarkus.it.rest.client.main; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/MyResponseExceptionMapper.java b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/MyResponseExceptionMapper.java new file mode 100644 index 0000000000000..0eac6781c46b5 --- /dev/null +++ b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/MyResponseExceptionMapper.java @@ -0,0 +1,22 @@ +package io.quarkus.it.rest.client.main; + +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; + +public class MyResponseExceptionMapper implements ResponseExceptionMapper { + @Override + public Exception toThrowable(Response response) { + if (response.getStatus() == 422) { + return new MyException("expected"); + } else { + return new IllegalStateException(""); + } + } + + public static class MyException extends Exception { + public MyException(String message) { + super(message); + } + } +} diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/NonDefaultCtorTestFilter.java b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/NonDefaultCtorTestFilter.java similarity index 93% rename from integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/NonDefaultCtorTestFilter.java rename to integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/NonDefaultCtorTestFilter.java index 104c8f389bf8b..f5e3e88d47c52 100644 --- a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/NonDefaultCtorTestFilter.java +++ b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/NonDefaultCtorTestFilter.java @@ -1,4 +1,4 @@ -package io.quarkus.it.rest.client; +package io.quarkus.it.rest.client.main; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/resources/application.properties b/integration-tests/resteasy-reactive-rest-client/src/main/resources/application.properties new file mode 100644 index 0000000000000..6ca0250ca07cb --- /dev/null +++ b/integration-tests/resteasy-reactive-rest-client/src/main/resources/application.properties @@ -0,0 +1 @@ +w-exception-mapper/mp-rest/url=${test.url} \ No newline at end of file diff --git a/integration-tests/resteasy-reactive-rest-client/src/test/java/io/quarkus/it/rest/client/BasicTest.java b/integration-tests/resteasy-reactive-rest-client/src/test/java/io/quarkus/it/rest/client/BasicTest.java index 6b0b985d0cf3b..03e1c8d87d29c 100644 --- a/integration-tests/resteasy-reactive-rest-client/src/test/java/io/quarkus/it/rest/client/BasicTest.java +++ b/integration-tests/resteasy-reactive-rest-client/src/test/java/io/quarkus/it/rest/client/BasicTest.java @@ -19,10 +19,12 @@ public class BasicTest { @TestHTTPResource("/apples") String appleUrl; + @TestHTTPResource() + String baseUrl; @SuppressWarnings({ "rawtypes", "unchecked" }) @Test - public void shouldWork() { + void shouldWork() { List results = RestAssured.with().body(appleUrl).post("/call-client") .then() .statusCode(200) @@ -32,6 +34,20 @@ public void shouldWork() { assertThat(m).containsOnlyKeys("cultivar"); }); Map valueByCount = results.stream().collect(Collectors.groupingBy(m -> m.get("cultivar"), counting())); - assertThat(valueByCount).containsOnly(entry("cortland", 3L), entry("cortland2", 3L), entry("cortland3", 3L)); + assertThat(valueByCount).containsOnly(entry("cortland", 3L), entry("lobo", 3L), entry("golden delicious", 3L)); + } + + @Test + void shouldMapException() { + RestAssured.with().body(baseUrl).post("/call-client-with-exception-mapper") + .then() + .statusCode(200); + } + + @Test + void shouldMapExceptionCdi() { + RestAssured.with().body(baseUrl).post("/call-cdi-client-with-exception-mapper") + .then() + .statusCode(200); } }