diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java index 60c3ca38cc..a6ff54ab21 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoConfigurationSupport.java @@ -46,6 +46,7 @@ * Base class for Spring Data MongoDB to be extended for JavaConfiguration usage. * * @author Mark Paluch + * @author Hyunsang Han * @since 2.0 */ public abstract class MongoConfigurationSupport { @@ -226,9 +227,15 @@ protected boolean autoIndexCreation() { protected MongoClientSettings mongoClientSettings() { MongoClientSettings.Builder builder = MongoClientSettings.builder(); - builder.uuidRepresentation(UuidRepresentation.STANDARD); configureClientSettings(builder); - return builder.build(); + + MongoClientSettings settings = builder.build(); + + if (settings.getUuidRepresentation() == null) { + throw new IllegalStateException("UUID representation must be explicitly configured."); + } + + return settings; } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java index 7c66396302..cfff0fe98b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoClientFactoryBean.java @@ -23,7 +23,6 @@ import java.util.function.Function; import java.util.stream.Collectors; -import org.bson.UuidRepresentation; import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.config.AbstractFactoryBean; import org.springframework.dao.DataAccessException; @@ -52,6 +51,7 @@ * * @author Christoph Strobl * @author Mark Paluch + * @author Hyunsang Han */ public class MongoClientFactoryBean extends AbstractFactoryBean implements PersistenceExceptionTranslator { @@ -162,7 +162,6 @@ protected MongoClientSettings computeClientSetting() { getOrDefault(port, "" + ServerAddress.defaultPort()))); Builder builder = MongoClientSettings.builder().applyConnectionString(connectionString); - builder.uuidRepresentation(UuidRepresentation.STANDARD); if (mongoClientSettings != null) { @@ -305,7 +304,13 @@ protected MongoClientSettings computeClientSetting() { }); } - return builder.build(); + MongoClientSettings settings = builder.build(); + + if (settings.getUuidRepresentation() == null) { + throw new IllegalStateException("UUID representation must be explicitly configured."); + } + + return settings; } private void applySettings(Consumer settingsBuilder, @Nullable T value) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java index f7af94728a..fff7a44f13 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java @@ -61,6 +61,7 @@ * * @author Mark Paluch * @author Christoph Strobl + * @author Hyunsang Han * @since 2.0 * @see org.springframework.data.convert.CustomConversions * @see org.springframework.data.mapping.model.SimpleTypeHolder @@ -159,7 +160,7 @@ public static class MongoConverterConfigurationAdapter { private static final Set> JAVA_DRIVER_TIME_SIMPLE_TYPES = Set.of(LocalDate.class, LocalTime.class, LocalDateTime.class); private boolean useNativeDriverJavaTimeCodecs = false; - private BigDecimalRepresentation bigDecimals = BigDecimalRepresentation.DECIMAL128; + private @Nullable BigDecimalRepresentation bigDecimals; private final List customConverters = new ArrayList<>(); private final PropertyValueConversions internalValueConversion = PropertyValueConversions.simple(it -> {}); @@ -313,8 +314,8 @@ public MongoConverterConfigurationAdapter useSpringDataJavaTimeCodecs() { } /** - * Configures the representation to for {@link java.math.BigDecimal} and {@link java.math.BigInteger} values in - * MongoDB. Defaults to {@link BigDecimalRepresentation#DECIMAL128}. + * Configures the representation for {@link java.math.BigDecimal} and {@link java.math.BigInteger} values in + * MongoDB. This configuration is required and must be explicitly set. * * @param representation the representation to use. * @return this. @@ -375,6 +376,10 @@ ConverterConfiguration createConverterConfiguration() { svc.init(); } + if (bigDecimals == null) { + throw new IllegalStateException("BigDecimal representation must be explicitly configured."); + } + List converters = new ArrayList<>(STORE_CONVERTERS.size() + 7); if (bigDecimals == BigDecimalRepresentation.STRING) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java index b16cb6961a..e9321e190d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java @@ -45,6 +45,9 @@ import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider; import org.springframework.test.util.ReflectionTestUtils; +import org.bson.UuidRepresentation; + +import com.mongodb.MongoClientSettings; import com.mongodb.client.MongoClient; /** @@ -53,6 +56,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Mark Paluch + * @author Hyunsang Han */ public class AbstractMongoConfigurationUnitTests { @@ -114,6 +118,40 @@ public void lifecycleCallbacksAreInvokedInAppropriateOrder() { context.close(); } + @Test // GH-5037 + public void requiresExplicitUuidRepresentationConfiguration() { + + assertThatThrownBy(() -> { + AbstractMongoClientConfiguration config = new AbstractMongoClientConfiguration() { + @Override + protected String getDatabaseName() { + return "test"; + } + }; + config.mongoClientSettings(); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("UUID representation must be explicitly configured"); + } + + @Test // GH-5037 + public void worksWithExplicitUuidRepresentationConfiguration() { + + AbstractMongoClientConfiguration config = new AbstractMongoClientConfiguration() { + @Override + protected String getDatabaseName() { + return "test"; + } + + @Override + protected void configureClientSettings(MongoClientSettings.Builder builder) { + builder.uuidRepresentation(UuidRepresentation.STANDARD); + } + }; + + MongoClientSettings settings = config.mongoClientSettings(); + assertThat(settings.getUuidRepresentation()).isEqualTo(UuidRepresentation.STANDARD); + } + @Test // DATAMONGO-725 public void shouldBeAbleToConfigureCustomTypeMapperViaJavaConfig() { @@ -158,6 +196,11 @@ protected String getDatabaseName() { return "database"; } + @Override + protected void configureClientSettings(MongoClientSettings.Builder builder) { + builder.uuidRepresentation(UuidRepresentation.STANDARD); + } + @Override public MongoClient mongoClient() { return Mockito.mock(MongoClient.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientFactoryBeanUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientFactoryBeanUnitTests.java index 868190db5d..f6d45dfe0b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientFactoryBeanUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientFactoryBeanUnitTests.java @@ -21,6 +21,8 @@ import org.junit.jupiter.api.Test; +import org.bson.UuidRepresentation; + import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; import com.mongodb.ServerAddress; @@ -29,6 +31,7 @@ * Unit tests for {@link MongoClientFactoryBean}. * * @author Christoph Strobl + * @author Hyunsang Han */ class MongoClientFactoryBeanUnitTests { @@ -89,4 +92,26 @@ void hostAndPortPlusConnectionStringError() { factoryBean.setPort(27017); assertThatExceptionOfType(IllegalStateException.class).isThrownBy(factoryBean::createInstance); } + + @Test // GH-5037 + void requiresExplicitUuidRepresentationConfiguration() { + + MongoClientFactoryBean factoryBean = new MongoClientFactoryBean(); + + assertThatThrownBy(factoryBean::computeClientSetting) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("UUID representation must be explicitly configured"); + } + + @Test // GH-5037 + void worksWithExplicitUuidRepresentationConfiguration() { + + MongoClientFactoryBean factoryBean = new MongoClientFactoryBean(); + factoryBean.setMongoClientSettings( + MongoClientSettings.builder().uuidRepresentation(UuidRepresentation.STANDARD).build()); + + MongoClientSettings settings = factoryBean.computeClientSetting(); + + assertThat(settings.getUuidRepresentation()).isEqualTo(UuidRepresentation.STANDARD); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index 1f0af2851e..dfdfc032cc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -123,6 +123,7 @@ * @author Roman Puchkovskiy * @author Heesu Jung * @author Julia Lee + * @author Hyunsang Han */ @ExtendWith(MockitoExtension.class) class MappingMongoConverterUnitTests { @@ -135,8 +136,10 @@ class MappingMongoConverterUnitTests { @BeforeEach void beforeEach() { - MongoCustomConversions conversions = new MongoCustomConversions( - Arrays.asList(new ByteBufferToDoubleHolderConverter())); + MongoCustomConversions conversions = MongoCustomConversions.create(config -> { + config.bigDecimal(MongoCustomConversions.BigDecimalRepresentation.DECIMAL128); + config.registerConverter(new ByteBufferToDoubleHolderConverter()); + }); mappingContext = new MongoMappingContext(); mappingContext.setApplicationContext(context); @@ -414,6 +417,30 @@ void readsClassWithBigDecimal() { assertThat(result.collection.get(0)).isEqualTo(BigDecimal.valueOf(2.5d)); } + @Test // GH-5037 + void requiresExplicitBigDecimalRepresentationConfiguration() { + + assertThatThrownBy(() -> { + MongoCustomConversions customConversions = new MongoCustomConversions(Collections.emptyList()); + MappingMongoConverter converter = new MappingMongoConverter(mock(DbRefResolver.class), mappingContext); + converter.setCustomConversions(customConversions); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("BigDecimal representation must be explicitly configured"); + } + + @Test // GH-5037 + void worksWithExplicitBigDecimalRepresentationConfiguration() { + + MongoCustomConversions customConversions = MongoCustomConversions.create(config -> { + config.bigDecimal(MongoCustomConversions.BigDecimalRepresentation.DECIMAL128); + }); + + MappingMongoConverter converter = new MappingMongoConverter(mock(DbRefResolver.class), mappingContext); + converter.setCustomConversions(customConversions); + assertThat(converter).isNotNull(); + assertThat(converter.getCustomConversions()).isEqualTo(customConversions); + } + @Test void writesNestedCollectionsCorrectly() { @@ -3407,6 +3434,26 @@ void usesStringNumericFormat() { assertThat(document).containsEntry("map.foo", "2.5"); } + @Test // GH-5037 + void requiresExplicitConfigurationForBothBigDecimalAndUuid() { + + MongoCustomConversions conversions = MongoCustomConversions.create( + it -> it.registerConverter(new ByteBufferToDoubleHolderConverter()) + .bigDecimal(MongoCustomConversions.BigDecimalRepresentation.DECIMAL128)); + + assertThat(conversions).isNotNull(); + + assertThatThrownBy(() -> { + new MongoCustomConversions(); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("BigDecimal representation must be explicitly configured"); + + assertThatThrownBy(() -> { + MongoCustomConversions.create(it -> it.registerConverter(new ByteBufferToDoubleHolderConverter())); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("BigDecimal representation must be explicitly configured"); + } + private MappingMongoConverter createConverter( MongoCustomConversions.BigDecimalRepresentation bigDecimalRepresentation) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java index 9382b835ea..00d7b37d4d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoCustomConversionsUnitTests.java @@ -27,20 +27,24 @@ import org.springframework.data.convert.PropertyValueConverter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mongodb.core.convert.MongoCustomConversions.BigDecimalRepresentation; import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.Foo; /** * Unit tests for {@link MongoCustomConversions}. * * @author Christoph Strobl + * @author Hyunsang Han */ class MongoCustomConversionsUnitTests { @Test // DATAMONGO-2349 void nonAnnotatedConverterForJavaTimeTypeShouldOnlyBeRegisteredAsReadingConverter() { - MongoCustomConversions conversions = new MongoCustomConversions( - Collections.singletonList(new DateToZonedDateTimeConverter())); + MongoCustomConversions conversions = MongoCustomConversions.create(config -> { + config.bigDecimal(BigDecimalRepresentation.DECIMAL128); + config.registerConverter(new DateToZonedDateTimeConverter()); + }); assertThat(conversions.hasCustomReadTarget(Date.class, ZonedDateTime.class)).isTrue(); assertThat(conversions.hasCustomWriteTarget(Date.class)).isFalse(); @@ -56,7 +60,7 @@ void propertyValueConverterRegistrationWorksAsExpected() { when(owner.getType()).thenReturn(Foo.class); MongoCustomConversions conversions = MongoCustomConversions.create(config -> { - + config.bigDecimal(BigDecimalRepresentation.DECIMAL128); config.configurePropertyConversions( registry -> registry.registerConverter(Foo.class, "name", mock(PropertyValueConverter.class))); }); @@ -68,12 +72,36 @@ void propertyValueConverterRegistrationWorksAsExpected() { void doesNotReturnConverterForNativeTimeTimeIfUsingDriverCodec() { MongoCustomConversions conversions = MongoCustomConversions.create(config -> { + config.bigDecimal(BigDecimalRepresentation.DECIMAL128); config.useNativeDriverJavaTimeCodecs(); }); assertThat(conversions.getCustomWriteTarget(Date.class)).isEmpty(); } + @Test // GH-5037 + void requiresExplicitBigDecimalRepresentationConfiguration() { + + assertThatThrownBy(() -> { + MongoCustomConversions.create(config -> { + config.useSpringDataJavaTimeCodecs(); + }); + }).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("BigDecimal representation must be explicitly configured"); + } + + @Test // GH-5037 + void worksWithExplicitBigDecimalRepresentationConfiguration() { + + MongoCustomConversions conversions = MongoCustomConversions.create(config -> { + config.bigDecimal(BigDecimalRepresentation.DECIMAL128); + config.useSpringDataJavaTimeCodecs(); + }); + + assertThat(conversions).isNotNull(); + assertThat(conversions.getSimpleTypeHolder()).isNotNull(); + } + static class DateToZonedDateTimeConverter implements Converter { @Override