From 65a4a35200a5ef16aeececa011923ed9bbf3980b Mon Sep 17 00:00:00 2001 From: Chas Honton Date: Mon, 28 Feb 2022 11:39:01 -0800 Subject: [PATCH] limit scope of alternate ObjectManagers to resteasy reactive extensions --- docs/src/main/asciidoc/rest-json.adoc | 9 ++-- .../deployment/DefaultObjectMappersTest.java | 38 +++++++++++++ .../DistinctObjectMappersCustomizerTest.java | 31 +++++++++++ .../jackson/runtime/ObjectMapperProducer.java | 54 ++++++------------- .../resteasy-common/runtime/pom.xml | 5 -- .../QuarkusObjectMapperContextResolver.java | 3 -- ...eaturedServerJacksonMessageBodyWriter.java | 8 +-- .../ClientJacksonMessageBodyWriter.java | 7 +-- extensions/vertx/runtime/pom.xml | 5 -- .../jackson/QuarkusJacksonJsonCodec.java | 3 +- 10 files changed, 102 insertions(+), 61 deletions(-) create mode 100644 extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/DefaultObjectMappersTest.java diff --git a/docs/src/main/asciidoc/rest-json.adoc b/docs/src/main/asciidoc/rest-json.adoc index 20c7acbedd47e..0c9f6666e8646 100644 --- a/docs/src/main/asciidoc/rest-json.adoc +++ b/docs/src/main/asciidoc/rest-json.adoc @@ -245,9 +245,11 @@ public class CustomObjectMapper { } ---- -There are three `ObjectMapper` instances provided by the quarkus-jackson extension: unqualified, client, and server. The client -instance is used for REST client serialization/deserialization. The server instance is used for REST server serialization/deserialization. -The unqualified instance is used for general purpose Object to json serialization/deserialization and Object to Object mapping. +There is one unqualified `ObjectMapper` instances provided by the quarkus-jackson extension. You may produce two additional +`ObjectMapper` instances qualified by the `ClientObjectMapper` and `ServerObjectMapper` qualifier annotations. If provided, the +client instance is used by rest-client-reactive extension for serialization/deserialization. If provided, the server +instance is used by quarkus-resteasy-reactive extension for serialization/deserialization. If not a qualified instance is not +provided, the fallback is the unqualified instance. You can produce, inject, and customize each of these instances independently. The following snippet demonstrates how to inject each instance using the `ClientObjectMapper` and `ServerObjectMapper` qualifier annotations. @@ -345,6 +347,7 @@ import java.util.stream.Stream; @ApplicationScoped public class DistinctObjectMapperProducer { + // This producer overrides the default producer provided by resteasy-jackson extension @Singleton @Produces public ObjectMapper unqualifiedObjectMapper(Instance unqualifiedCustomizers) { diff --git a/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/DefaultObjectMappersTest.java b/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/DefaultObjectMappersTest.java new file mode 100644 index 0000000000000..73b85cd330ba8 --- /dev/null +++ b/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/DefaultObjectMappersTest.java @@ -0,0 +1,38 @@ +package io.quarkus.jackson.deployment; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.jackson.runtime.ClientObjectMapper; +import io.quarkus.jackson.runtime.ServerObjectMapper; +import io.quarkus.test.QuarkusUnitTest; + +public class DefaultObjectMappersTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest(); + + @Inject + @ClientObjectMapper + Instance clientObjectMapper; + + @Inject + @ServerObjectMapper + Instance serverObjectMapper; + + @Test + public void noClientObjectMapper() { + Assertions.assertTrue(clientObjectMapper.isUnsatisfied()); + } + + @Test + public void noServerObjectMapper() { + Assertions.assertTrue(serverObjectMapper.isUnsatisfied()); + } +} diff --git a/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/DistinctObjectMappersCustomizerTest.java b/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/DistinctObjectMappersCustomizerTest.java index 8097264ba0565..6857caa31a3be 100644 --- a/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/DistinctObjectMappersCustomizerTest.java +++ b/extensions/jackson/deployment/src/test/java/io/quarkus/jackson/deployment/DistinctObjectMappersCustomizerTest.java @@ -4,7 +4,10 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.Produces; import javax.inject.Inject; import javax.inject.Singleton; @@ -81,4 +84,32 @@ public void customize(ObjectMapper objectMapper) { objectMapper.registerModule(new SimpleModule("server")); } } + + @Singleton + public static class Factory { + + private ObjectMapper createMapper(Instance unqualifiedCustomizers, + Instance qualifiedCustomizers) { + ObjectMapper objectMapper = new ObjectMapper(); + // apply customizers in priority order + Stream.concat(unqualifiedCustomizers.stream(), qualifiedCustomizers.stream()) + .sorted() + .forEach(c -> c.customize(objectMapper)); + return objectMapper; + } + + @Produces + @ClientObjectMapper + ObjectMapper clientObjectMapper(Instance unqualifiedCustomizers, + @ClientObjectMapper Instance clientCustomizers) { + return createMapper(unqualifiedCustomizers, clientCustomizers); + } + + @Produces + @ServerObjectMapper + ObjectMapper serverObjectMapper(Instance unqualifiedCustomizers, + @ServerObjectMapper Instance serverCustomizers) { + return createMapper(unqualifiedCustomizers, serverCustomizers); + } + } } diff --git a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java index 46dcdcee57c92..1c09d63e8b9ca 100644 --- a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java +++ b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/runtime/ObjectMapperProducer.java @@ -1,8 +1,10 @@ package io.quarkus.jackson.runtime; import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.TimeZone; -import java.util.stream.Stream; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Instance; @@ -24,34 +26,8 @@ public class ObjectMapperProducer { @DefaultBean @Singleton @Produces - public ObjectMapper unqualifiedObjectMapper(JacksonBuildTimeConfig jacksonBuildTimeConfig, - Instance unqualifiedCustomizers) { - return objectMapper(jacksonBuildTimeConfig, unqualifiedCustomizers, null); - } - - @DefaultBean - @ClientObjectMapper - @Singleton - @Produces - public ObjectMapper clientObjectMapper(JacksonBuildTimeConfig jacksonBuildTimeConfig, - Instance unqualifiedCustomizers, - @ClientObjectMapper Instance clientCustomizers) { - return objectMapper(jacksonBuildTimeConfig, unqualifiedCustomizers, clientCustomizers); - } - - @DefaultBean - @ServerObjectMapper - @Singleton - @Produces - public ObjectMapper serverObjectMapper(JacksonBuildTimeConfig jacksonBuildTimeConfig, - Instance unqualifiedCustomizers, - @ServerObjectMapper Instance serverSustomizers) { - return objectMapper(jacksonBuildTimeConfig, unqualifiedCustomizers, serverSustomizers); - } - - private ObjectMapper objectMapper(JacksonBuildTimeConfig jacksonBuildTimeConfig, - Instance unqualifiedCustomizers, Instance qualifiedCustomizers) { - + public ObjectMapper objectMapper(Instance customizers, + JacksonBuildTimeConfig jacksonBuildTimeConfig) { ObjectMapper objectMapper = new ObjectMapper(); if (!jacksonBuildTimeConfig.failOnUnknownProperties) { // this feature is enabled by default, so we disable it @@ -76,16 +52,20 @@ private ObjectMapper objectMapper(JacksonBuildTimeConfig jacksonBuildTimeConfig, if ((zoneId != null) && !zoneId.getId().equals("UTC")) { // Jackson uses UTC as the default, so let's not reset it objectMapper.setTimeZone(TimeZone.getTimeZone(zoneId)); } - - // concat all customizers - Stream customizers = unqualifiedCustomizers.stream(); - if (qualifiedCustomizers != null) { - customizers = Stream.concat(customizers, qualifiedCustomizers.stream()); + List sortedCustomizers = sortCustomizersInDescendingPriorityOrder(customizers); + for (ObjectMapperCustomizer customizer : sortedCustomizers) { + customizer.customize(objectMapper); } - - // apply customizers in priority order - customizers.sorted().forEach(c -> c.customize(objectMapper)); return objectMapper; } + private List sortCustomizersInDescendingPriorityOrder( + Instance customizers) { + List sortedCustomizers = new ArrayList<>(); + for (ObjectMapperCustomizer customizer : customizers) { + sortedCustomizers.add(customizer); + } + Collections.sort(sortedCustomizers); + return sortedCustomizers; + } } diff --git a/extensions/resteasy-classic/resteasy-common/runtime/pom.xml b/extensions/resteasy-classic/resteasy-common/runtime/pom.xml index 7981ee2fbd751..67310591a3513 100644 --- a/extensions/resteasy-classic/resteasy-common/runtime/pom.xml +++ b/extensions/resteasy-classic/resteasy-common/runtime/pom.xml @@ -78,11 +78,6 @@ resteasy-json-binding-provider true - - io.quarkus - quarkus-jackson - provided - diff --git a/extensions/resteasy-classic/resteasy-common/runtime/src/main/java/io/quarkus/resteasy/common/runtime/jackson/QuarkusObjectMapperContextResolver.java b/extensions/resteasy-classic/resteasy-common/runtime/src/main/java/io/quarkus/resteasy/common/runtime/jackson/QuarkusObjectMapperContextResolver.java index eea5e35d39561..338831749d370 100644 --- a/extensions/resteasy-classic/resteasy-common/runtime/src/main/java/io/quarkus/resteasy/common/runtime/jackson/QuarkusObjectMapperContextResolver.java +++ b/extensions/resteasy-classic/resteasy-common/runtime/src/main/java/io/quarkus/resteasy/common/runtime/jackson/QuarkusObjectMapperContextResolver.java @@ -9,14 +9,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import io.quarkus.jackson.runtime.ServerObjectMapper; - @Provider @ApplicationScoped @Priority(Priorities.USER + 10) // give it a priority that ensures that user supplied ContextResolver classes override this one public class QuarkusObjectMapperContextResolver implements ContextResolver { - @ServerObjectMapper @Inject ObjectMapper objectMapper; diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java index ba9ba26a6cb6d..422064f681471 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/serialisers/FullyFeaturedServerJacksonMessageBodyWriter.java @@ -15,6 +15,7 @@ import java.util.function.BiFunction; import java.util.function.Function; +import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; @@ -37,9 +38,10 @@ public class FullyFeaturedServerJacksonMessageBodyWriter extends ServerMessageBo private final ConcurrentMap perMethodWriter = new ConcurrentHashMap<>(); @Inject - public FullyFeaturedServerJacksonMessageBodyWriter(@ServerObjectMapper ObjectMapper mapper) { - this.originalMapper = mapper; - this.defaultWriter = createDefaultWriter(mapper); + public FullyFeaturedServerJacksonMessageBodyWriter(@ServerObjectMapper Instance serverInstance, + ObjectMapper mapper) { + this.originalMapper = serverInstance.isUnsatisfied() ? mapper : serverInstance.get(); + this.defaultWriter = createDefaultWriter(originalMapper); } @Override diff --git a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyWriter.java b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyWriter.java index 5bc3ed860eb99..f006f72399c86 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyWriter.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jackson/runtime/src/main/java/io/quarkus/rest/client/reactive/jackson/runtime/serialisers/ClientJacksonMessageBodyWriter.java @@ -8,6 +8,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; @@ -25,9 +26,9 @@ public class ClientJacksonMessageBodyWriter implements MessageBodyWriter protected final ObjectWriter defaultWriter; @Inject - public ClientJacksonMessageBodyWriter(@ClientObjectMapper ObjectMapper mapper) { - this.originalMapper = mapper; - this.defaultWriter = createDefaultWriter(mapper); + public ClientJacksonMessageBodyWriter(@ClientObjectMapper Instance clientInstance, ObjectMapper mapper) { + this.originalMapper = clientInstance.isUnsatisfied() ? mapper : clientInstance.get(); + this.defaultWriter = createDefaultWriter(originalMapper); } @Override diff --git a/extensions/vertx/runtime/pom.xml b/extensions/vertx/runtime/pom.xml index f230b1d364580..5970735688cf6 100644 --- a/extensions/vertx/runtime/pom.xml +++ b/extensions/vertx/runtime/pom.xml @@ -73,11 +73,6 @@ assertj-core test - - io.quarkus - quarkus-jackson - provided - diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/jackson/QuarkusJacksonJsonCodec.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/jackson/QuarkusJacksonJsonCodec.java index 4c901466823f4..c686aeecacf27 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/jackson/QuarkusJacksonJsonCodec.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/jackson/QuarkusJacksonJsonCodec.java @@ -16,7 +16,6 @@ import io.netty.buffer.ByteBufInputStream; import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; -import io.quarkus.jackson.runtime.ServerObjectMapper; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.DecodeException; import io.vertx.core.json.EncodeException; @@ -41,7 +40,7 @@ class QuarkusJacksonJsonCodec implements JsonCodec { // this can happen in QuarkusUnitTest mapper = new ObjectMapper(); } else { - ObjectMapper managedMapper = container.instance(ObjectMapper.class, ServerObjectMapper.Literal.INSTANCE).get(); + ObjectMapper managedMapper = container.instance(ObjectMapper.class).get(); if (managedMapper == null) { // TODO: is this too heavy handed? It should never happen but even if it does, it's a mostly recoverable state throw new IllegalStateException("There was no ObjectMapper bean configured");