From d119be2f7d946fefda3eed68167d100c42124c24 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis <geoand@gmail.com> Date: Tue, 7 Sep 2021 22:31:26 +0300 Subject: [PATCH] Support RestResponse<T> as a return type for reactive rest client methods Closes: #19966 --- .../ClientResponseCompleteRestHandler.java | 6 ++++- .../client/impl/AsyncInvokerImpl.java | 23 ++++++++++++++----- .../client/impl/RestClientRequestContext.java | 22 +++++++++++++++--- .../it/rest/client/main/AppleClient.java | 9 ++++++++ .../client/main/ClientCallingResource.java | 20 +++++++++++++--- .../rest/client/main/RestResponseClient.java | 16 +++++++++++++ .../io/quarkus/it/rest/client/BasicTest.java | 10 ++++++-- 7 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/RestResponseClient.java diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientResponseCompleteRestHandler.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientResponseCompleteRestHandler.java index e10c4dcf01e80..7879d8e2e088e 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientResponseCompleteRestHandler.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/handlers/ClientResponseCompleteRestHandler.java @@ -2,6 +2,7 @@ import java.io.IOException; import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; import org.jboss.resteasy.reactive.client.impl.ClientResponseBuilderImpl; import org.jboss.resteasy.reactive.client.impl.ClientResponseContextImpl; import org.jboss.resteasy.reactive.client.impl.RestClientRequestContext; @@ -22,7 +23,10 @@ public static ResponseImpl mapToResponse(RestClientRequestContext context, boole builder.status(responseContext.getStatus(), responseContext.getReasonPhrase()); builder.setAllHeaders(responseContext.getHeaders()); builder.invocationState(context); - if (context.isResponseTypeSpecified() && parseContent) { // this case means that a specific response type was requested + if (context.isResponseTypeSpecified() + // when we are returning a RestResponse, we don't want to do any parsing + && (Response.Status.Family.familyOf(context.getResponseStatus()) == Response.Status.Family.SUCCESSFUL) + && parseContent) { // this case means that a specific response type was requested Object entity = context.readEntity(responseContext.getEntityStream(), context.getResponseType(), responseContext.getMediaType(), diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/AsyncInvokerImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/AsyncInvokerImpl.java index 2b84187250995..b5ef7e9d2b3ec 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/AsyncInvokerImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/AsyncInvokerImpl.java @@ -16,6 +16,7 @@ import javax.ws.rs.client.InvocationCallback; import javax.ws.rs.core.GenericType; import javax.ws.rs.core.Response; +import org.jboss.resteasy.reactive.RestResponse; import org.jboss.resteasy.reactive.common.jaxrs.ConfigurationImpl; import org.jboss.resteasy.reactive.common.util.types.Types; import org.jboss.resteasy.reactive.spi.ThreadSetupAction; @@ -280,12 +281,22 @@ private <T> Type getInvocationCallbackType(InvocationCallback<T> callback) { public <T> CompletableFuture<T> mapResponse(CompletableFuture<Response> res, Class<?> responseType) { if (responseType.equals(Response.class)) { return (CompletableFuture<T>) res; + } else if (responseType.equals(RestResponse.class)) { + return res.thenApply(new Function<>() { + @Override + public T apply(Response response) { + return (T) RestResponse.ResponseBuilder.create(response.getStatusInfo(), response.getEntity()) + .replaceAll(response.getHeaders()).build(); + } + }); + } else { + return res.thenApply(new Function<>() { + @Override + public T apply(Response response) { + return (T) response.getEntity(); + } + }); } - return res.thenApply(new Function<Response, T>() { - @Override - public T apply(Response response) { - return (T) response.getEntity(); - } - }); + } } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java index 467166cdf2dc9..6ceb958ca79e4 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/RestClientRequestContext.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.URI; import java.util.Arrays; @@ -29,6 +30,7 @@ import javax.ws.rs.ext.ReaderInterceptor; import javax.ws.rs.ext.WriterInterceptor; import org.jboss.resteasy.reactive.ClientWebApplicationException; +import org.jboss.resteasy.reactive.RestResponse; import org.jboss.resteasy.reactive.client.spi.ClientRestHandler; import org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext; import org.jboss.resteasy.reactive.common.core.Serialisers; @@ -102,9 +104,23 @@ public RestClientRequestContext(ClientImpl restClient, this.responseTypeSpecified = false; } else { this.responseType = responseType; - boolean isJaxResponse = responseType.getRawType().equals(Response.class); - this.checkSuccessfulFamily = !isJaxResponse; - this.responseTypeSpecified = !isJaxResponse; + if (responseType.getRawType().equals(Response.class)) { + this.checkSuccessfulFamily = false; + this.responseTypeSpecified = false; + } else if (responseType.getRawType().equals(RestResponse.class)) { + if (responseType.getType() instanceof ParameterizedType) { + ParameterizedType type = (ParameterizedType) responseType.getType(); + if (type.getActualTypeArguments().length == 1) { + Type restResponseType = type.getActualTypeArguments()[0]; + this.responseType = new GenericType<>(restResponseType); + } + } + this.checkSuccessfulFamily = false; + this.responseTypeSpecified = true; + } else { + this.checkSuccessfulFamily = true; + this.responseTypeSpecified = true; + } } this.registerBodyHandler = registerBodyHandler; this.result = new CompletableFuture<>(); diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/AppleClient.java b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/AppleClient.java index d03ae8cdc61f2..608df268226a2 100644 --- a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/AppleClient.java +++ b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/AppleClient.java @@ -9,6 +9,7 @@ import javax.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.jboss.resteasy.reactive.RestResponse; import io.smallrye.mutiny.Uni; @@ -54,4 +55,12 @@ public interface AppleClient { @POST @Produces(MediaType.APPLICATION_JSON) Uni<String> uniStringApple(); + + @POST + @Produces(MediaType.APPLICATION_JSON) + RestResponse<Apple> restResponseApple(); + + @POST + @Produces(MediaType.APPLICATION_JSON) + Uni<RestResponse<Apple>> uniRestResponseApple(); } diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java index 7dee0e453e1ee..2c67f67c4507c 100644 --- a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java +++ b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java @@ -3,6 +3,7 @@ import java.net.URI; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; @@ -12,6 +13,7 @@ import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.resteasy.reactive.RestResponse; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -74,12 +76,15 @@ void init(@Observes Router router) { Uni<Apple> apple8 = Uni.createFrom().completionStage(client.completionStringApple()).onItem() .transform(this::toApple); Uni<Apple> apple9 = client.uniStringApple().onItem().transform(this::toApple); - Uni.combine().all().unis(apple1, apple2, apple3, apple4, apple5, apple6, apple7, apple8, apple9).asTuple() + Uni<Apple> apple10 = Uni.createFrom().item(client.restResponseApple().getEntity()); + Uni<Apple> apple11 = client.uniRestResponseApple().onItem().transform(RestResponse::getEntity); + Uni.combine().all().unis(apple1, apple2, apple3, apple4, apple5, apple6, apple7, apple8, apple9, apple10, apple11) + .combinedWith(Function.identity()) .subscribe() - .with(tuple -> { + .with(list -> { try { rc.response().putHeader("content-type", "application/json") - .end(mapper.writeValueAsString(tuple.asList())); + .end(mapper.writeValueAsString(list)); } catch (JsonProcessingException e) { fail(rc, e.getMessage()); } @@ -107,6 +112,15 @@ void init(@Observes Router router) { rc.response().end(greeting); }); + router.route("/rest-response").blockingHandler(rc -> { + String url = rc.getBody().toString(); + RestResponseClient client = RestClientBuilder.newBuilder().baseUri(URI.create(url)) + .property("microprofile.rest.client.disable.default.mapper", true) + .build(RestResponseClient.class); + RestResponse<String> restResponse = client.response(); + rc.response().end("" + restResponse.getStatus()); + }); + router.route("/export-clear").blockingHandler(rc -> { inMemorySpanExporter.reset(); rc.response().end(); diff --git a/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/RestResponseClient.java b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/RestResponseClient.java new file mode 100644 index 0000000000000..49ed4092650c4 --- /dev/null +++ b/integration-tests/resteasy-reactive-rest-client/src/main/java/io/quarkus/it/rest/client/main/RestResponseClient.java @@ -0,0 +1,16 @@ +package io.quarkus.it.rest.client.main; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.jboss.resteasy.reactive.RestResponse; + +@Path("") +public interface RestResponseClient { + + @Produces(MediaType.TEXT_PLAIN) + @GET + RestResponse<String> response(); +} 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 78724c6e10cc5..82f666e6bad9e 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 @@ -41,6 +41,12 @@ public void shouldMakeTextRequest() { assertThat(response.asString()).isEqualTo("Hello, JohnJohn"); } + @Test + public void restResponseShouldWorkWithNonSuccessfulResponse() { + Response response = RestAssured.with().body(helloUrl).post("/rest-response"); + assertThat(response.asString()).isEqualTo("405"); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Test void shouldMakeJsonRequest() { @@ -49,11 +55,11 @@ void shouldMakeJsonRequest() { .statusCode(200) .contentType("application/json") .extract().body().jsonPath().getList(".", Map.class); - assertThat(results).hasSize(9).allSatisfy(m -> { + assertThat(results).hasSize(11).allSatisfy(m -> { assertThat(m).containsOnlyKeys("cultivar"); }); Map<Object, Long> valueByCount = results.stream().collect(Collectors.groupingBy(m -> m.get("cultivar"), counting())); - assertThat(valueByCount).containsOnly(entry("cortland", 3L), entry("lobo", 3L), entry("golden delicious", 3L)); + assertThat(valueByCount).containsOnly(entry("cortland", 4L), entry("lobo", 4L), entry("golden delicious", 3L)); } @Test