From 17820d1d52b624dccf053820726a63f6f6053ff8 Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 14 Jun 2023 13:41:19 -0700 Subject: [PATCH] Allow users to customize the internal ObjectMapper created by GenericJackson2JsonRedisSerializer. We now allow the internally created Jackson ObjectMapper to be customized and further configured after construction of the GenericJackson2JsonRedisSerializer when a user does not explicitly provide a custom ObjectMapper during construction. Even when providing a custom ObjectMapper, not all configuration applied by the GenericJackson2JsonRedisSerialzier (such as (standard) type resolution) to the internal ObjectMapper would get applied to the user-provided ObjectMapper as well. Closes #2601 --- .../GenericJackson2JsonRedisSerializer.java | 43 +++++++++++-- ...cJackson2JsonRedisSerializerUnitTests.java | 64 +++++++++++++++---- 2 files changed, 91 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java b/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java index c3922d5f7..b8711abb4 100644 --- a/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java +++ b/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java @@ -17,6 +17,7 @@ package org.springframework.data.redis.serializer; import java.io.IOException; import java.util.Collections; +import java.util.function.Consumer; import java.util.function.Supplier; import org.springframework.cache.support.NullValue; @@ -54,6 +55,7 @@ import com.fasterxml.jackson.databind.type.TypeFactory; * @author Christoph Strobl * @author Mark Paluch * @author Mao Shuai + * @author John Blum * @since 1.6 */ public class GenericJackson2JsonRedisSerializer implements RedisSerializer { @@ -197,6 +199,16 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer objectMapperConfigurer) { + + Assert.notNull(objectMapperConfigurer, + "Consumer used to configure and customize ObjectMapper must not be null"); + + objectMapperConfigurer.accept(getObjectMapper()); + + return this; + } + protected JavaType resolveType(byte[] source, Class type) throws IOException { if (!type.equals(Object.class) || !defaultTypingEnabled.get()) { diff --git a/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerUnitTests.java b/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerUnitTests.java index f1538eeaa..7ebbb615f 100644 --- a/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerUnitTests.java +++ b/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerUnitTests.java @@ -15,17 +15,20 @@ */ package org.springframework.data.redis.serializer; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; -import static org.springframework.test.util.ReflectionTestUtils.*; -import static org.springframework.util.ObjectUtils.*; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; -import lombok.Data; -import lombok.ToString; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.springframework.test.util.ReflectionTestUtils.getField; +import static org.springframework.util.ObjectUtils.nullSafeEquals; +import static org.springframework.util.ObjectUtils.nullSafeHashCode; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -33,11 +36,14 @@ import java.time.LocalDate; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.mockito.Mockito; + import org.springframework.beans.BeanUtils; import org.springframework.cache.support.NullValue; +import org.springframework.lang.Nullable; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; @@ -47,15 +53,22 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import com.fasterxml.jackson.databind.type.TypeFactory; -import org.springframework.lang.Nullable; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; + +import lombok.Data; +import lombok.ToString; /** * Unit tests for {@link GenericJackson2JsonRedisSerializer}. * * @author Christoph Strobl * @author Mark Paluch + * @author John Blum */ class GenericJackson2JsonRedisSerializerUnitTests { @@ -407,6 +420,33 @@ class GenericJackson2JsonRedisSerializerUnitTests { assertThat(serializer.deserialize(source, WithJsr310.class).myDate).isEqualTo(java.time.LocalDate.of(2022,9,2)); } + @Test // GH-2601 + public void internalObjectMapperCustomization() { + + GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(); + + com.fasterxml.jackson.databind.Module mockModule = mock(com.fasterxml.jackson.databind.Module.class); + + ObjectMapper mockObjectMapper = mock(ObjectMapper.class); + + Consumer configurer = objectMapper -> mockObjectMapper.registerModule(mockModule); + + assertThat(serializer.configure(configurer)).isSameAs(serializer); + + verify(mockObjectMapper, times(1)).registerModule(eq(mockModule)); + verifyNoMoreInteractions(mockObjectMapper); + verifyNoInteractions(mockModule); + } + + @Test // GH-2601 + public void configureWithNullConsumerThrowsIllegalArgumentException() { + + assertThatIllegalArgumentException() + .isThrownBy(() -> new GenericJackson2JsonRedisSerializer().configure(null)) + .withMessage("Consumer used to configure and customize ObjectMapper must not be null") + .withNoCause(); + } + private static void serializeAndDeserializeNullValue(GenericJackson2JsonRedisSerializer serializer) { NullValue nv = BeanUtils.instantiateClass(NullValue.class);