diff --git a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java index 5d44d077b7d74..09cdac5a52d79 100644 --- a/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java +++ b/extensions/keycloak-admin-client-reactive/runtime/src/main/java/io/quarkus/keycloak/admin/client/reactive/runtime/ResteasyReactiveClientProvider.java @@ -1,9 +1,15 @@ package io.quarkus.keycloak.admin.client.reactive.runtime; +import java.util.List; + +import javax.enterprise.inject.Instance; import javax.net.ssl.SSLContext; +import javax.ws.rs.Priorities; import javax.ws.rs.client.Client; import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.resteasy.reactive.client.impl.ClientBuilderImpl; import org.jboss.resteasy.reactive.client.impl.WebTargetImpl; import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; @@ -14,10 +20,14 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; import io.quarkus.arc.InstanceHandle; +import io.quarkus.jackson.ObjectMapperCustomizer; import io.quarkus.rest.client.reactive.jackson.runtime.serialisers.ClientJacksonMessageBodyWriter; public class ResteasyReactiveClientProvider implements ResteasyClientProvider { + private static final List HANDLED_MEDIA_TYPES = List.of(MediaType.APPLICATION_JSON); + private static final int PROVIDER_PRIORITY = Priorities.USER + 100; // ensures that it will be used first + @Override public Client newRestEasyClient(Object messageHandler, SSLContext sslContext, boolean disableTrustManager) { ClientBuilderImpl clientBuilder = new ClientBuilderImpl(); @@ -32,29 +42,65 @@ private ClientBuilderImpl registerJacksonProviders(ClientBuilderImpl clientBuild throw new IllegalStateException(this.getClass().getName() + " should only be used in a Quarkus application"); } else { InstanceHandle objectMapperInstance = arcContainer.instance(ObjectMapper.class); - ObjectMapper objectMapper = null; + boolean canReuseObjectMapper = canReuseObjectMapper(objectMapperInstance, arcContainer); + if (canReuseObjectMapper) { - InstanceHandle readerInstance = arcContainer - .instance(JacksonBasicMessageBodyReader.class); - if (readerInstance.isAvailable()) { - clientBuilder = clientBuilder.register(readerInstance.get()); - } else { - objectMapper = getObjectMapper(objectMapper, objectMapperInstance); - clientBuilder = clientBuilder.register(new JacksonBasicMessageBodyReader(objectMapper)); - } + ObjectMapper objectMapper = null; - InstanceHandle writerInstance = arcContainer - .instance(ClientJacksonMessageBodyWriter.class); - if (writerInstance.isAvailable()) { - clientBuilder = clientBuilder.register(writerInstance.get()); + InstanceHandle readerInstance = arcContainer + .instance(JacksonBasicMessageBodyReader.class); + if (readerInstance.isAvailable()) { + clientBuilder = clientBuilder.register(readerInstance.get()); + } else { + objectMapper = getObjectMapper(objectMapper, objectMapperInstance); + clientBuilder = clientBuilder.register(new JacksonBasicMessageBodyReader(objectMapper)); + } + + InstanceHandle writerInstance = arcContainer + .instance(ClientJacksonMessageBodyWriter.class); + if (writerInstance.isAvailable()) { + clientBuilder = clientBuilder.register(writerInstance.get()); + } else { + objectMapper = getObjectMapper(objectMapper, objectMapperInstance); + clientBuilder = clientBuilder.register(new ClientJacksonMessageBodyWriter(objectMapper)); + } } else { - objectMapper = getObjectMapper(objectMapper, objectMapperInstance); - clientBuilder = clientBuilder.register(new ClientJacksonMessageBodyWriter(objectMapper)); + ObjectMapper newObjectMapper = new ObjectMapper(); + clientBuilder = clientBuilder + .registerMessageBodyReader(new JacksonBasicMessageBodyReader(newObjectMapper), Object.class, + HANDLED_MEDIA_TYPES, true, + PROVIDER_PRIORITY) + .registerMessageBodyWriter(new ClientJacksonMessageBodyWriter(newObjectMapper), Object.class, + HANDLED_MEDIA_TYPES, true, PROVIDER_PRIORITY); } + } return clientBuilder; } + // the idea is to only reuse the ObjectMapper if no known customizations would break Keycloak + // TODO: in the future we could also look into checking the ObjectMapper bean itself to see how it has been configured + private boolean canReuseObjectMapper(InstanceHandle objectMapperInstance, ArcContainer arcContainer) { + if (objectMapperInstance.isAvailable() && !objectMapperInstance.getBean().isDefaultBean()) { + // in this case a user provided a completely custom ObjectMapper, so we can't use it + return false; + } + + Instance customizers = arcContainer.beanManager().createInstance() + .select(ObjectMapperCustomizer.class); + if (!customizers.isUnsatisfied()) { + // ObjectMapperCustomizer can make arbitrary changes, so in order to be safe we won't allow reuse + return false; + } + // if any Jackson properties were configured, disallow reuse - this is done in order to provide forward compatibility with new Jackson configuration options + for (String propertyName : ConfigProvider.getConfig().getPropertyNames()) { + if (propertyName.startsWith("io.quarkus.jackson")) { + return false; + } + } + return true; + } + // the whole idea here is to reuse the ObjectMapper instance private ObjectMapper getObjectMapper(ObjectMapper value, InstanceHandle objectMapperInstance) { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java index c133d6a33f444..365ae79cd4be8 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java @@ -12,6 +12,7 @@ import java.util.Set; import javax.ws.rs.Priorities; +import javax.ws.rs.RuntimeType; import javax.ws.rs.core.MediaType; import org.jboss.jandex.AnnotationInstance; @@ -137,13 +138,14 @@ void additionalProviders(ContextResolversBuildItem contextResolversBuildItem, new MessageBodyReaderBuildItem.Builder(ServerJacksonMessageBodyReader.class.getName(), Object.class.getName()) .setMediaTypeStrings(HANDLED_MEDIA_TYPES) - .setBuiltin(true).build()); + .setBuiltin(true).setRuntimeType(RuntimeType.SERVER).build()); additionalReaders .produce( new MessageBodyReaderBuildItem.Builder(VertxJsonArrayMessageBodyReader.class.getName(), JsonArray.class.getName()) .setMediaTypeStrings(HANDLED_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.SERVER) .build()); additionalReaders .produce( @@ -151,6 +153,7 @@ void additionalProviders(ContextResolversBuildItem contextResolversBuildItem, JsonObject.class.getName()) .setMediaTypeStrings(HANDLED_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.SERVER) .build()); additionalWriters .produce( @@ -160,6 +163,7 @@ void additionalProviders(ContextResolversBuildItem contextResolversBuildItem, Object.class.getName()) .setMediaTypeStrings(HANDLED_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.SERVER) .build()); additionalWriters .produce( @@ -167,6 +171,7 @@ void additionalProviders(ContextResolversBuildItem contextResolversBuildItem, JsonArray.class.getName()) .setMediaTypeStrings(HANDLED_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.SERVER) .build()); additionalWriters .produce( @@ -174,6 +179,7 @@ void additionalProviders(ContextResolversBuildItem contextResolversBuildItem, JsonObject.class.getName()) .setMediaTypeStrings(HANDLED_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.SERVER) .build()); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java index bc82769dd1d7a..215a4101a087c 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/streams/StreamTestCase.java @@ -24,11 +24,14 @@ import org.apache.http.HttpStatus; import org.jboss.resteasy.reactive.client.impl.MultiInvoker; import org.jboss.resteasy.reactive.common.util.RestMediaType; +import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; 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 com.fasterxml.jackson.databind.ObjectMapper; + import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; import io.smallrye.mutiny.Multi; @@ -137,7 +140,8 @@ public void testStreamJsonMultiFromMulti() { } private void testJsonMulti(String path) { - Client client = ClientBuilder.newBuilder().build(); + Client client = ClientBuilder.newBuilder().register(new JacksonBasicMessageBodyReader(new ObjectMapper())).build(); + ; WebTarget target = client.target(uri.toString() + path); Multi multi = target.request().rx(MultiInvoker.class).get(Message.class); List list = multi.collect().asList().await().atMost(Duration.ofSeconds(30)); diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/main/java/io/quarkus/rest/client/reactive/jackson/deployment/RestClientReactiveJacksonProcessor.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/main/java/io/quarkus/rest/client/reactive/jackson/deployment/RestClientReactiveJacksonProcessor.java index e057f19afa33e..7b7d967cab8aa 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/main/java/io/quarkus/rest/client/reactive/jackson/deployment/RestClientReactiveJacksonProcessor.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/main/java/io/quarkus/rest/client/reactive/jackson/deployment/RestClientReactiveJacksonProcessor.java @@ -7,6 +7,7 @@ import java.util.Collections; import java.util.List; +import javax.ws.rs.RuntimeType; import javax.ws.rs.core.MediaType; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -42,9 +43,6 @@ void additionalProviders( BuildProducer additionalBean, BuildProducer additionalReaders, BuildProducer additionalWriters) { - if (!jacksonProviderDefined.isEmpty()) { - return; - } // make these beans to they can get instantiated with the Quarkus CDI configured Jsonb object additionalBean.produce(AdditionalBeanBuildItem.builder() .addBeanClass(ClientJacksonMessageBodyReader.class.getName()) @@ -57,6 +55,7 @@ void additionalProviders( Object.class.getName()) .setMediaTypeStrings(HANDLED_READ_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.CLIENT) .build()); additionalReaders .produce( @@ -64,6 +63,7 @@ void additionalProviders( JsonArray.class.getName()) .setMediaTypeStrings(HANDLED_READ_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.CLIENT) .build()); additionalReaders .produce( @@ -71,6 +71,7 @@ void additionalProviders( JsonObject.class.getName()) .setMediaTypeStrings(HANDLED_READ_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.CLIENT) .build()); additionalWriters .produce( @@ -78,6 +79,7 @@ void additionalProviders( Object.class.getName()) .setMediaTypeStrings(HANDLED_WRITE_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.CLIENT) .build()); additionalWriters .produce( @@ -85,6 +87,7 @@ void additionalProviders( JsonArray.class.getName()) .setMediaTypeStrings(HANDLED_WRITE_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.CLIENT) .build()); additionalWriters .produce( @@ -92,6 +95,7 @@ void additionalProviders( JsonObject.class.getName()) .setMediaTypeStrings(HANDLED_WRITE_MEDIA_TYPES) .setBuiltin(true) + .setRuntimeType(RuntimeType.CLIENT) .build()); } } diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java index 70473305b438e..53e2147f4146f 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/deployment/src/test/java/io/quarkus/rest/client/reactive/jackson/test/MultiSseTest.java @@ -16,9 +16,12 @@ import javax.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import com.fasterxml.jackson.databind.ObjectMapper; + import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; import io.smallrye.mutiny.Multi; @@ -34,7 +37,7 @@ public class MultiSseTest { @Test void shouldConsume() { var resultList = new CopyOnWriteArrayList<>(); - RestClientBuilder.newBuilder().baseUri(uri).build(SseClient.class) + createClient() .get() .subscribe().with(resultList::add); await().atMost(5, TimeUnit.SECONDS) @@ -45,7 +48,7 @@ void shouldConsume() { @Test void shouldConsumeJsonEntity() { var resultList = new CopyOnWriteArrayList<>(); - RestClientBuilder.newBuilder().baseUri(uri).build(SseClient.class) + createClient() .getJson() .subscribe().with(resultList::add); await().atMost(5, TimeUnit.SECONDS) @@ -56,7 +59,7 @@ void shouldConsumeJsonEntity() { @Test void shouldConsumeAsParametrizedType() { var resultList = new CopyOnWriteArrayList<>(); - RestClientBuilder.newBuilder().baseUri(uri).build(SseClient.class) + createClient() .getJsonAsMap() .subscribe().with(resultList::add); await().atMost(5, TimeUnit.SECONDS) @@ -68,7 +71,7 @@ void shouldConsumeAsParametrizedType() { @Test void shouldSendPayloadAndConsume() { var resultList = new CopyOnWriteArrayList<>(); - RestClientBuilder.newBuilder().baseUri(uri).build(SseClient.class) + createClient() .post("test") .subscribe().with(resultList::add); await().atMost(5, TimeUnit.SECONDS) @@ -79,7 +82,7 @@ void shouldSendPayloadAndConsume() { @Test void shouldSendPayloadAndConsumeAsParametrizedType() { var resultList = new CopyOnWriteArrayList<>(); - RestClientBuilder.newBuilder().baseUri(uri).build(SseClient.class) + createClient() .postAndReadAsMap("test") .subscribe().with(resultList::add); await().atMost(5, TimeUnit.SECONDS) @@ -90,6 +93,11 @@ void shouldSendPayloadAndConsumeAsParametrizedType() { Map.of("name", "foo", "value", "test"))); } + private SseClient createClient() { + return RestClientBuilder.newBuilder().baseUri(uri).register(new JacksonBasicMessageBodyReader(new ObjectMapper())) + .build(SseClient.class); + } + @Path("/sse") public interface SseClient { @GET diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BasicRestClientTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BasicRestClientTest.java index 3148c53acf512..d6ad8b08a1900 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BasicRestClientTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/BasicRestClientTest.java @@ -22,7 +22,8 @@ public class BasicRestClientTest { static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar .addClasses(HelloClient.class, HelloResource.class, TestBean.class, HelloClient2.class, - HelloNonSimpleClient.class)) + HelloNonSimpleClient.class, TestJacksonBasicMessageBodyReader.class, + TestJacksonBasicMessageBodyWriter.class)) .withConfigurationResource("basic-test-application.properties"); @Inject diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloNonSimpleClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloNonSimpleClient.java index 513694dd032e4..09b99baee4b79 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloNonSimpleClient.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/HelloNonSimpleClient.java @@ -10,6 +10,7 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import io.smallrye.mutiny.Uni; @@ -20,6 +21,8 @@ // https://github.com/quarkusio/quarkus/issues/21375 // @RegisterRestClient(configKey = "hello2") +@RegisterProvider(TestJacksonBasicMessageBodyReader.class) +@RegisterProvider(TestJacksonBasicMessageBodyWriter.class) public interface HelloNonSimpleClient { @POST @Consumes(MediaType.TEXT_PLAIN) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MediaTypeSuffixTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MediaTypeSuffixTest.java index 87eeae28e6a13..b6be853724c35 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MediaTypeSuffixTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MediaTypeSuffixTest.java @@ -9,6 +9,7 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.junit.jupiter.api.Test; @@ -23,7 +24,7 @@ public class MediaTypeSuffixTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() .withApplicationRoot((jar) -> jar - .addClasses(HelloResource.class, Client.class)) + .addClasses(HelloResource.class, Client.class, TestJacksonBasicMessageBodyReader.class)) .withConfigurationResource("media-type-suffix-application.properties"); @Test @@ -37,6 +38,7 @@ public void test() { @RegisterRestClient(configKey = "test") @Path("/hello") + @RegisterProvider(TestJacksonBasicMessageBodyReader.class) public interface Client { @GET diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultiNdjsonTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultiNdjsonTest.java index d515892f4fa02..beb02bc6c1707 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultiNdjsonTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/MultiNdjsonTest.java @@ -37,15 +37,15 @@ public class MultiNdjsonTest { @RegisterExtension - static final QuarkusUnitTest TEST = new QuarkusUnitTest(); + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(TestJacksonBasicMessageBodyReader.class)); @TestHTTPResource URI uri; @Test void shouldReadNdjsonStringAsMulti() throws InterruptedException { - var client = RestClientBuilder.newBuilder().baseUri(uri) - .build(Client.class); + var client = createClient(uri); var collected = new CopyOnWriteArrayList(); var completionLatch = new CountDownLatch(1); client.readString().onCompletion().invoke(completionLatch::countDown) @@ -60,8 +60,7 @@ void shouldReadNdjsonStringAsMulti() throws InterruptedException { @Test void shouldReadNdjsonPojoAsMulti() throws InterruptedException { - var client = RestClientBuilder.newBuilder().baseUri(uri) - .build(Client.class); + var client = createClient(uri); var collected = new CopyOnWriteArrayList(); var completionLatch = new CountDownLatch(1); client.readPojo().onCompletion().invoke(completionLatch::countDown) @@ -79,8 +78,7 @@ void shouldReadNdjsonPojoAsMulti() throws InterruptedException { @Test void shouldReadNdjsonPojoFromReactiveRoutes() throws InterruptedException { URI reactiveRoutesBaseUri = URI.create(uri.toString() + "/rr"); - var client = RestClientBuilder.newBuilder().baseUri(reactiveRoutesBaseUri) - .build(Client.class); + var client = createClient(reactiveRoutesBaseUri); var collected = new CopyOnWriteArrayList(); var completionLatch = new CountDownLatch(1); client.readPojo().onCompletion().invoke(completionLatch::countDown) @@ -96,8 +94,7 @@ void shouldReadNdjsonPojoFromReactiveRoutes() throws InterruptedException { @Test void shouldReadNdjsonFromSingleMessage() throws InterruptedException { - var client = RestClientBuilder.newBuilder().baseUri(uri) - .build(Client.class); + var client = createClient(uri); var collected = new CopyOnWriteArrayList(); var completionLatch = new CountDownLatch(1); client.readPojoSingle().onCompletion().invoke(completionLatch::countDown) @@ -112,8 +109,14 @@ void shouldReadNdjsonFromSingleMessage() throws InterruptedException { assertThat(collected).hasSize(4).containsAll(expected); } + private Client createClient(URI uri) { + return RestClientBuilder.newBuilder().baseUri(uri).register(new TestJacksonBasicMessageBodyReader()) + .build(Client.class); + } + @Path("/stream") public interface Client { + @GET @Path("/string") @Produces(RestMediaType.APPLICATION_NDJSON) @@ -131,9 +134,11 @@ public interface Client { @Produces(RestMediaType.APPLICATION_NDJSON) @RestStreamElementType(MediaType.APPLICATION_JSON) Multi readPojoSingle(); + } public static class ReactiveRoutesResource { + @Route(path = "/rr/stream/pojo", produces = ReactiveRoutes.ND_JSON) Multi people(RoutingContext context) { return Multi.createFrom().items( diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/StreamJsonTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/StreamJsonTest.java index c859aaf1dfece..a8b12e9a5f47c 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/StreamJsonTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/StreamJsonTest.java @@ -37,15 +37,15 @@ public class StreamJsonTest { @RegisterExtension - static final QuarkusUnitTest TEST = new QuarkusUnitTest(); + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar.addClasses(TestJacksonBasicMessageBodyReader.class)); @TestHTTPResource URI uri; @Test void shouldReadStreamJsonStringAsMulti() throws InterruptedException { - var client = RestClientBuilder.newBuilder().baseUri(uri) - .build(Client.class); + var client = createClient(uri); var collected = new CopyOnWriteArrayList(); var completionLatch = new CountDownLatch(1); client.readString().onCompletion().invoke(completionLatch::countDown) @@ -60,8 +60,7 @@ void shouldReadStreamJsonStringAsMulti() throws InterruptedException { @Test void shouldReadNdjsonPojoAsMulti() throws InterruptedException { - var client = RestClientBuilder.newBuilder().baseUri(uri) - .build(Client.class); + var client = createClient(uri); var collected = new CopyOnWriteArrayList(); var completionLatch = new CountDownLatch(1); client.readPojo().onCompletion().invoke(completionLatch::countDown) @@ -79,8 +78,7 @@ void shouldReadNdjsonPojoAsMulti() throws InterruptedException { @Test void shouldReadNdjsonPojoFromReactiveRoutes() throws InterruptedException { URI reactiveRoutesBaseUri = URI.create(uri.toString() + "/rr"); - var client = RestClientBuilder.newBuilder().baseUri(reactiveRoutesBaseUri) - .build(Client.class); + var client = createClient(reactiveRoutesBaseUri); var collected = new CopyOnWriteArrayList(); var completionLatch = new CountDownLatch(1); client.readPojo().onCompletion().invoke(completionLatch::countDown) @@ -96,8 +94,7 @@ void shouldReadNdjsonPojoFromReactiveRoutes() throws InterruptedException { @Test void shouldReadNdjsonFromSingleMessage() throws InterruptedException { - var client = RestClientBuilder.newBuilder().baseUri(uri) - .build(Client.class); + var client = createClient(uri); var collected = new CopyOnWriteArrayList(); var completionLatch = new CountDownLatch(1); client.readPojoSingle().onCompletion().invoke(completionLatch::countDown) @@ -112,6 +109,11 @@ void shouldReadNdjsonFromSingleMessage() throws InterruptedException { assertThat(collected).hasSize(4).containsAll(expected); } + private Client createClient(URI uri) { + return RestClientBuilder.newBuilder().baseUri(uri).register(new TestJacksonBasicMessageBodyReader()) + .build(Client.class); + } + @Path("/stream") public interface Client { @GET diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestJacksonBasicMessageBodyReader.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestJacksonBasicMessageBodyReader.java new file mode 100644 index 0000000000000..e00d824ebc95d --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestJacksonBasicMessageBodyReader.java @@ -0,0 +1,14 @@ +package io.quarkus.rest.client.reactive; + +import javax.ws.rs.Consumes; + +import org.jboss.resteasy.reactive.server.jackson.JacksonBasicMessageBodyReader; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Consumes({ "application/json", "application/x-ndjson", "application/stream+json" }) +public class TestJacksonBasicMessageBodyReader extends JacksonBasicMessageBodyReader { + public TestJacksonBasicMessageBodyReader() { + super(new ObjectMapper()); + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestJacksonBasicMessageBodyWriter.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestJacksonBasicMessageBodyWriter.java new file mode 100644 index 0000000000000..d4d4c494bdfda --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/TestJacksonBasicMessageBodyWriter.java @@ -0,0 +1,41 @@ +package io.quarkus.rest.client.reactive; + +import static org.jboss.resteasy.reactive.server.jackson.JacksonMessageBodyWriterUtil.doLegacyWrite; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; + +@Produces("application/json") +public class TestJacksonBasicMessageBodyWriter implements MessageBodyWriter { + + private final ObjectWriter wWriter; + + public TestJacksonBasicMessageBodyWriter() { + this.wWriter = new ObjectMapper().writer(); + } + + @Override + public final boolean isWriteable(Class type, Type genericType, + Annotation[] annotations, MediaType mediaType) { + return true; + } + + @Override + public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, + WebApplicationException { + doLegacyWrite(o, annotations, httpHeaders, entityStream, wWriter); + } + +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromProviderTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromProviderTest.java index 150ae65fa87cb..e0c9eca9ca16c 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromProviderTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/ReactiveClientHeadersFromProviderTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.rest.client.reactive.ReactiveClientHeadersFactory; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; import io.smallrye.common.annotation.Blocking; @@ -46,7 +47,7 @@ public class ReactiveClientHeadersFromProviderTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar.addClasses(ReactiveClientHeadersFromProviderTest.Client.class) + .withApplicationRoot((jar) -> jar.addClasses(Client.class, TestJacksonBasicMessageBodyReader.class) .addAsResource( new StringAsset("my.property-value=" + HEADER_VALUE), "application.properties")); @@ -86,6 +87,7 @@ public Map> returnHeaderValues(@Context HttpHeaders headers @POST public Map> callClient(String uri) { ReactiveClientHeadersFromProviderTest.Client client = RestClientBuilder.newBuilder().baseUri(URI.create(uri)) + .register(new TestJacksonBasicMessageBodyReader()) .build(ReactiveClientHeadersFromProviderTest.Client.class); return client.getWithHeader(DIRECT_HEADER_PARAM_VAL); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartDetectionTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartDetectionTest.java index 58bd4d6ceb106..d07cb2b0c1733 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartDetectionTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartDetectionTest.java @@ -15,12 +15,15 @@ import javax.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.jboss.resteasy.reactive.PartType; import org.jboss.resteasy.reactive.RestForm; import org.jboss.resteasy.reactive.multipart.FileUpload; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyWriter; import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; import io.smallrye.mutiny.Multi; @@ -33,7 +36,9 @@ public class MultipartDetectionTest { @RegisterExtension static final QuarkusUnitTest TEST = new QuarkusUnitTest() - .withApplicationRoot(jar -> jar.addClasses(Resource.class, Client.class, Person.class)); + .withApplicationRoot( + jar -> jar.addClasses(Resource.class, Client.class, Person.class, TestJacksonBasicMessageBodyReader.class, + TestJacksonBasicMessageBodyWriter.class)); @Test void shouldCallExplicitEndpoints() throws IOException { @@ -104,6 +109,8 @@ public String uploadMultipart(@RestForm String name) { } @Path("form") + @RegisterProvider(TestJacksonBasicMessageBodyReader.class) + @RegisterProvider(TestJacksonBasicMessageBodyWriter.class) public interface Client { @Path("multipart") @POST diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartResponseTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartResponseTest.java index ad94920c4bbf3..f89eae747a690 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartResponseTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/multipart/MultipartResponseTest.java @@ -25,6 +25,7 @@ import javax.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.jboss.resteasy.reactive.ClientWebApplicationException; import org.jboss.resteasy.reactive.MultipartForm; import org.jboss.resteasy.reactive.PartType; @@ -33,6 +34,8 @@ import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyWriter; import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.http.TestHTTPResource; import io.smallrye.mutiny.Uni; @@ -46,11 +49,13 @@ public class MultipartResponseTest { URI baseUri; @RegisterExtension - static final QuarkusUnitTest TEST = new QuarkusUnitTest(); + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot( + (jar) -> jar.addClasses(TestJacksonBasicMessageBodyReader.class, TestJacksonBasicMessageBodyWriter.class)); @Test void shouldParseMultipartResponse() { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = createClient(); MultipartData data = client.getFile(); assertThat(data.file).exists(); verifyWooHooFile(data.file, 10000); @@ -64,7 +69,7 @@ void shouldParseMultipartResponse() { @Test void shouldParseUniMultipartResponse() { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = createClient(); MultipartData data = client.uniGetFile().await().atMost(Duration.ofSeconds(10)); assertThat(data.file).exists(); verifyWooHooFile(data.file, 10000); @@ -78,7 +83,7 @@ void shouldParseUniMultipartResponse() { @Test void shouldParseCompletionStageMultipartResponse() throws ExecutionException, InterruptedException, TimeoutException { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = createClient(); MultipartData data = client.csGetFile().toCompletableFuture().get(10, TimeUnit.SECONDS); assertThat(data.file).exists(); verifyWooHooFile(data.file, 10000); @@ -92,7 +97,7 @@ void shouldParseCompletionStageMultipartResponse() throws ExecutionException, In @Test void shouldParseMultipartResponseWithSmallFile() { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = createClient(); MultipartData data = client.getSmallFile(); assertThat(data.file).exists(); verifyWooHooFile(data.file, 1); @@ -105,7 +110,7 @@ void shouldParseMultipartResponseWithSmallFile() { @EnabledIfSystemProperty(named = "test-resteasy-reactive-large-files", matches = "true") @Test void shouldParseMultipartResponseWithLargeFile() { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = createClient(); MultipartData data = client.getLargeFile(); assertThat(data.file).exists(); assertThat(data.file.length()).isEqualTo(ONE_GIGA); @@ -113,7 +118,7 @@ void shouldParseMultipartResponseWithLargeFile() { @Test void shouldParseMultipartResponseWithNulls() { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = createClient(); MultipartData data = client.getFileEmpty(); assertThat(data.file).isNull(); assertThat(data.name).isNull(); @@ -122,7 +127,7 @@ void shouldParseMultipartResponseWithNulls() { @Test void shouldParseMultipartResponseWithSetters() { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = createClient(); MultipartDataWithSetters data = client.getFileWithSetters(); assertThat(data.file).exists(); assertThat(data.name).isEqualTo("foo"); @@ -133,13 +138,13 @@ void shouldParseMultipartResponseWithSetters() { @Test void shouldBeSaneOnServerError() { - Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class); + Client client = createClient(); assertThatThrownBy(client::error).isInstanceOf(ClientWebApplicationException.class); } @Test void shouldParseMultipartResponseWithClientBuilderApi() { - javax.ws.rs.client.Client client = ClientBuilder.newBuilder().build(); + javax.ws.rs.client.Client client = ClientBuilder.newBuilder().register(new TestJacksonBasicMessageBodyReader()).build(); MultipartDataForClientBuilder data = client.target(baseUri) .path("/give-me-file") .request(MediaType.MULTIPART_FORM_DATA) @@ -152,6 +157,11 @@ void shouldParseMultipartResponseWithClientBuilderApi() { assertThat(data.numbers).containsSequence(2008, 2011, 2014); } + private Client createClient() { + return RestClientBuilder.newBuilder().baseUri(baseUri).register(new TestJacksonBasicMessageBodyReader()) + .build(Client.class); + } + void verifyWooHooFile(File file, int expectedTimes) { int position = 0; try (FileReader reader = new FileReader(file)) { @@ -167,6 +177,8 @@ void verifyWooHooFile(File file, int expectedTimes) { } @Path("/give-me-file") + @RegisterProvider(TestJacksonBasicMessageBodyReader.class) + @RegisterProvider(TestJacksonBasicMessageBodyWriter.class) public interface Client { @GET @Produces(MediaType.MULTIPART_FORM_DATA) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderNoPassingClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderNoPassingClient.java index eea43ac6e10a7..6bd57afebe60c 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderNoPassingClient.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderNoPassingClient.java @@ -3,9 +3,13 @@ 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.rest.client.reactive.TestJacksonBasicMessageBodyReader; + @RegisterRestClient +@RegisterProvider(TestJacksonBasicMessageBodyReader.class) public interface HeaderNoPassingClient { @GET diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderPassingClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderPassingClient.java index b0a379c1e1020..c5f9683dc65a6 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderPassingClient.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderPassingClient.java @@ -4,10 +4,14 @@ import javax.ws.rs.Path; import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; + @RegisterRestClient @RegisterClientHeaders +@RegisterProvider(TestJacksonBasicMessageBodyReader.class) public interface HeaderPassingClient { @GET diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderSettingClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderSettingClient.java index d31b35ef8f1fb..ef1df1794e015 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderSettingClient.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/HeaderSettingClient.java @@ -4,9 +4,13 @@ import javax.ws.rs.HeaderParam; import javax.ws.rs.Path; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; + @RegisterRestClient +@RegisterProvider(TestJacksonBasicMessageBodyReader.class) public interface HeaderSettingClient { String HEADER = "my-header"; diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MultipleHeadersBindingClient.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MultipleHeadersBindingClient.java index 7a4e8012169dd..ebf9fc793e101 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MultipleHeadersBindingClient.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/MultipleHeadersBindingClient.java @@ -6,12 +6,16 @@ import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; + @RegisterRestClient @RegisterClientHeaders(MyHeadersFactory.class) @ClientHeaderParam(name = "my-header", value = "constant-header-value") @ClientHeaderParam(name = "computed-header", value = "{io.quarkus.rest.client.reactive.registerclientheaders.ComputedHeader.get}") +@RegisterProvider(TestJacksonBasicMessageBodyReader.class) public interface MultipleHeadersBindingClient { @GET @Path("/describe-request") diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/RegisterClientHeadersTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/RegisterClientHeadersTest.java index 7d9488644b613..410507cee276e 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/RegisterClientHeadersTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/registerclientheaders/RegisterClientHeadersTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkus.rest.client.reactive.TestJacksonBasicMessageBodyReader; import io.quarkus.test.QuarkusUnitTest; public class RegisterClientHeadersTest { @@ -24,6 +25,7 @@ public class RegisterClientHeadersTest { .setArchiveProducer(() -> { return ShrinkWrap.create(JavaArchive.class) .addPackage(EchoClient.class.getPackage()) + .addClass(TestJacksonBasicMessageBodyReader.class) .addAsResource( new StringAsset( setUrlForClass(EchoClient.class) + 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 9d323b012a410..2c51b18d2dd3f 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 @@ -9,6 +9,7 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ExecutorService; @@ -21,6 +22,8 @@ import javax.ws.rs.RuntimeType; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.Configuration; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; import org.jboss.logging.Logger; import org.jboss.resteasy.reactive.client.api.ClientLogger; @@ -344,6 +347,23 @@ public ClientBuilderImpl register(Object component, Map, Integer> contr return this; } + /* + * Add some custom methods that allow registering MessageBodyReader and MessageBodyWriter classes with all the necessary + * information + */ + + public ClientBuilderImpl registerMessageBodyReader(MessageBodyReader reader, Class handledType, List consumes, + boolean builtin, Integer priority) { + configuration.registerMessageBodyReader(reader, handledType, consumes, RuntimeType.CLIENT, builtin, priority); + return this; + } + + public ClientBuilderImpl registerMessageBodyWriter(MessageBodyWriter writer, Class handledType, List consumes, + boolean builtin, Integer priority) { + configuration.registerMessageBodyWriter(writer, handledType, consumes, RuntimeType.CLIENT, builtin, priority); + return this; + } + public ClientBuilderImpl trustAll(boolean trustAll) { this.trustAll = trustAll; return this; diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java index d3887b548a248..f8985ea9c5dea 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/ConfigurationImpl.java @@ -429,6 +429,41 @@ public void register(Object component, Map, Integer> componentContracts } + /* + * Add some custom methods that allow registering MessageBodyReader and MessageBodyWriter classes with all the necessary + * information + */ + + public void registerMessageBodyReader(MessageBodyReader reader, Class handledType, List consumes, + RuntimeType runtimeType, boolean builtin, Integer priority) { + if (isRegistered(reader)) { + return; + } + ResourceReader resourceReader = new ResourceReader(); + resourceReader.setFactory(new UnmanagedBeanFactory<>(reader)); + resourceReader.setMediaTypeStrings(consumes); + resourceReader.setBuiltin(builtin); + resourceReader.setPriority(priority); + resourceReader.setConstraint(runtimeType); + resourceReaders.add(handledType, resourceReader); + allInstances.put(reader.getClass(), reader); + } + + public void registerMessageBodyWriter(MessageBodyWriter messageBodyWriter, Class handledType, List consumes, + RuntimeType runtimeType, boolean builtin, Integer priority) { + if (isRegistered(messageBodyWriter)) { + return; + } + ResourceWriter resourceWriter = new ResourceWriter(); + resourceWriter.setFactory(new UnmanagedBeanFactory<>(messageBodyWriter)); + resourceWriter.setMediaTypeStrings(consumes); + resourceWriter.setBuiltin(builtin); + resourceWriter.setPriority(priority); + resourceWriter.setConstraint(runtimeType); + resourceWriters.add(handledType, resourceWriter); + allInstances.put(messageBodyWriter.getClass(), messageBodyWriter); + } + public void register(Object component, int priority) { register(component, Integer.valueOf(priority)); } diff --git a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/AdminClientResource.java b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/AdminClientResource.java index 970a88280b747..fa99da14b2a70 100644 --- a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/AdminClientResource.java +++ b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/AdminClientResource.java @@ -29,22 +29,22 @@ public class AdminClientResource { Keycloak keycloak; @GET - @Produces(MediaType.APPLICATION_JSON) + @Produces(MediaType.TEXT_PLAIN) @Path("realm") - public RealmRepresentation getRealm() { - return keycloak.realm("quarkus").toRepresentation(); + public String getRealm() { + return keycloak.realm("quarkus").toRepresentation().getRealm(); } @GET - @Produces(MediaType.APPLICATION_JSON) + @Produces(MediaType.TEXT_PLAIN) @Path("newrealm") - public RealmRepresentation createRealm() { + public String createRealm() { RealmRepresentation newRealm = createRealm("quarkus2"); newRealm.getClients().add(createClient("quarkus-app2")); newRealm.getUsers().add(createUser("alice", "user")); keycloak.realms().create(newRealm); - return keycloak.realm("quarkus2").toRepresentation(); + return keycloak.realm("quarkus2").toRepresentation().getRealm(); } private static RealmRepresentation createRealm(String name) { diff --git a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/SnakeCaseObjectMapperCustomizer.java b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/SnakeCaseObjectMapperCustomizer.java new file mode 100644 index 0000000000000..56f297f6d39ef --- /dev/null +++ b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/SnakeCaseObjectMapperCustomizer.java @@ -0,0 +1,21 @@ +package io.quarkus.it.keycloak; + +import javax.inject.Singleton; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; + +import io.quarkus.jackson.ObjectMapperCustomizer; + +/** + * This class is used to alter the global ObjectMapper quarkus uses. + * We ensure that KeyCloak Admin Client continues to work despite this. + */ +@Singleton +public class SnakeCaseObjectMapperCustomizer implements ObjectMapperCustomizer { + + @Override + public void customize(ObjectMapper mapper) { + mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); + } +} diff --git a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/UsersResource.java b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/UsersResource.java index 8424d2b447709..9ac559a75da9a 100644 --- a/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/UsersResource.java +++ b/integration-tests/keycloak-authorization/src/main/java/io/quarkus/it/keycloak/UsersResource.java @@ -1,9 +1,17 @@ package io.quarkus.it.keycloak; +import java.lang.reflect.Type; +import java.util.function.BiFunction; + import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; + +import io.quarkus.resteasy.reactive.jackson.CustomSerialization; import io.quarkus.security.identity.SecurityIdentity; @Path("/api/users") @@ -14,6 +22,7 @@ public class UsersResource { @GET @Path("/me") + @CustomSerialization(ProperCaseFunction.class) // needed because otherwise SnakeCaseObjectMapperCustomizer causes the result to not be usable by Keycloak public User me() { return new User(keycloakSecurityContext); } @@ -30,4 +39,12 @@ public String getUserName() { return userName; } } + + public static class ProperCaseFunction implements BiFunction { + + @Override + public ObjectWriter apply(ObjectMapper objectMapper, Type type) { + return objectMapper.copy().setPropertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE).writer(); + } + } } diff --git a/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/AdminClientTestCase.java b/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/AdminClientTestCase.java index 3762f3af8c246..ad1b1b41cad20 100644 --- a/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/AdminClientTestCase.java +++ b/integration-tests/keycloak-authorization/src/test/java/io/quarkus/it/keycloak/AdminClientTestCase.java @@ -1,10 +1,9 @@ package io.quarkus.it.keycloak; -import static io.restassured.RestAssured.given; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.equalTo; import org.junit.jupiter.api.Test; -import org.keycloak.representations.idm.RealmRepresentation; import io.quarkus.test.junit.QuarkusTest; @@ -13,15 +12,17 @@ public class AdminClientTestCase { @Test public void testGetExistingRealm() { - RealmRepresentation realm = given() - .when().get("/admin-client/realm").as(RealmRepresentation.class); - assertEquals("quarkus", realm.getRealm()); + when().get("/admin-client/realm") + .then() + .statusCode(200) + .body(equalTo("quarkus")); } @Test public void testGetNewRealm() { - RealmRepresentation realm = given() - .when().get("/admin-client/newrealm").as(RealmRepresentation.class); - assertEquals("quarkus2", realm.getRealm()); + when().get("/admin-client/newrealm") + .then() + .statusCode(200) + .body(equalTo("quarkus2")); } }