From b7e77fdf1aab1b6301e733f173ff359b4632b110 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 11 Dec 2023 17:19:26 -0500 Subject: [PATCH] GH-2516: Introduce `JacksonUtils` Fixes: #2516 * The new `JacksonUtils` registers some well-known Jackson modules. * Use `JacksonUtils.enhancedObjectMapper()` in the `Jackson2JsonMessageConverter` for more straightforward configuration from target projects --- build.gradle | 8 +- .../Jackson2JsonMessageConverter.java | 7 +- .../amqp/support/converter/JacksonUtils.java | 127 ++++++++++++++++++ 3 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 spring-amqp/src/main/java/org/springframework/amqp/support/converter/JacksonUtils.java diff --git a/build.gradle b/build.gradle index ca3919b8..22d021a2 100644 --- a/build.gradle +++ b/build.gradle @@ -379,7 +379,13 @@ project('spring-amqp') { optionalApi 'com.fasterxml.jackson.core:jackson-databind' optionalApi 'com.fasterxml.jackson.core:jackson-annotations' optionalApi 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml' - + optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' + optionalApi 'com.fasterxml.jackson.module:jackson-module-parameter-names' + optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-joda' + optionalApi ('com.fasterxml.jackson.module:jackson-module-kotlin') { + exclude group: 'org.jetbrains.kotlin' + } // Spring Data projection message binding support optionalApi ('org.springframework.data:spring-data-commons') { exclude group: 'org.springframework' diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverter.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverter.java index 9e1c3c72..3045ecd0 100644 --- a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverter.java +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/Jackson2JsonMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ public class Jackson2JsonMessageConverter extends AbstractJackson2MessageConvert * Construct with an internal {@link ObjectMapper} instance * and trusted packed to all ({@code *}). * @since 1.6.11 + * @see JacksonUtils#enhancedObjectMapper() */ public Jackson2JsonMessageConverter() { this("*"); @@ -53,10 +54,10 @@ public class Jackson2JsonMessageConverter extends AbstractJackson2MessageConvert * @param trustedPackages the trusted Java packages for deserialization * @since 1.6.11 * @see DefaultJackson2JavaTypeMapper#setTrustedPackages(String...) + * @see JacksonUtils#enhancedObjectMapper() */ public Jackson2JsonMessageConverter(String... trustedPackages) { - this(new ObjectMapper(), trustedPackages); - this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this(JacksonUtils.enhancedObjectMapper(), trustedPackages); } /** diff --git a/spring-amqp/src/main/java/org/springframework/amqp/support/converter/JacksonUtils.java b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/JacksonUtils.java new file mode 100644 index 00000000..f988ed5b --- /dev/null +++ b/spring-amqp/src/main/java/org/springframework/amqp/support/converter/JacksonUtils.java @@ -0,0 +1,127 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.amqp.support.converter; + +import org.springframework.util.ClassUtils; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; + + +/** + * The utilities for Jackson {@link ObjectMapper} instances. + * + * @author Artem Bilan + * + * @since 3.1.1 + */ +public final class JacksonUtils { + + private static final boolean JDK8_MODULE_PRESENT = + ClassUtils.isPresent("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", null); + + private static final boolean PARAMETER_NAMES_MODULE_PRESENT = + ClassUtils.isPresent("com.fasterxml.jackson.module.paramnames.ParameterNamesModule", null); + + private static final boolean JAVA_TIME_MODULE_PRESENT = + ClassUtils.isPresent("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", null); + + private static final boolean JODA_MODULE_PRESENT = + ClassUtils.isPresent("com.fasterxml.jackson.datatype.joda.JodaModule", null); + + private static final boolean KOTLIN_MODULE_PRESENT = + ClassUtils.isPresent("kotlin.Unit", null) && + ClassUtils.isPresent("com.fasterxml.jackson.module.kotlin.KotlinModule", null); + + /** + * Factory for {@link ObjectMapper} instances with registered well-known modules + * and disabled {@link MapperFeature#DEFAULT_VIEW_INCLUSION} and + * {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} features. + * @return the {@link ObjectMapper} instance. + */ + public static ObjectMapper enhancedObjectMapper() { + ObjectMapper objectMapper = JsonMapper.builder() + .configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .build(); + registerWellKnownModulesIfAvailable(objectMapper); + return objectMapper; + } + + private static void registerWellKnownModulesIfAvailable(ObjectMapper objectMapper) { + if (JDK8_MODULE_PRESENT) { + objectMapper.registerModule(Jdk8ModuleProvider.MODULE); + } + + if (PARAMETER_NAMES_MODULE_PRESENT) { + objectMapper.registerModule(ParameterNamesProvider.MODULE); + } + + if (JAVA_TIME_MODULE_PRESENT) { + objectMapper.registerModule(JavaTimeModuleProvider.MODULE); + } + + if (JODA_MODULE_PRESENT) { + objectMapper.registerModule(JodaModuleProvider.MODULE); + } + + if (KOTLIN_MODULE_PRESENT) { + objectMapper.registerModule(KotlinModuleProvider.MODULE); + } + } + + private JacksonUtils() { + } + + private static final class Jdk8ModuleProvider { + + static final com.fasterxml.jackson.databind.Module MODULE = + new com.fasterxml.jackson.datatype.jdk8.Jdk8Module(); + + } + + private static final class ParameterNamesProvider { + + static final com.fasterxml.jackson.databind.Module MODULE = + new com.fasterxml.jackson.module.paramnames.ParameterNamesModule(); + + } + + private static final class JavaTimeModuleProvider { + + static final com.fasterxml.jackson.databind.Module MODULE = + new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule(); + + } + + private static final class JodaModuleProvider { + + static final com.fasterxml.jackson.databind.Module MODULE = + new com.fasterxml.jackson.datatype.joda.JodaModule(); + + } + + private static final class KotlinModuleProvider { + + static final com.fasterxml.jackson.databind.Module MODULE = + new com.fasterxml.jackson.module.kotlin.KotlinModule.Builder().build(); + + } + +}