diff --git a/pom.xml b/pom.xml
index a6dc46e10e..bf6fe831ab 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 5.1.0-SNAPSHOT
+ 5.1.x-GH-5092-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index cab02fe276..04f93b421d 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-mongodb-parent
- 5.1.0-SNAPSHOT
+ 5.1.x-GH-5092-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 0a758111af..513b3a453d 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -13,7 +13,7 @@
org.springframework.data
spring-data-mongodb-parent
- 5.1.0-SNAPSHOT
+ 5.1.x-GH-5092-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
index e7e43d9e0f..9e2ce96ed8 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
@@ -173,6 +173,7 @@ public class MappingMongoConverter extends AbstractMongoConverter
expressionParser);
private final CachingValueExpressionEvaluatorFactory expressionEvaluatorFactory = new CachingValueExpressionEvaluatorFactory(
expressionParser, this, o -> spELContext.getEvaluationContext(o));
+ private ObjectIdConversion> objectIdConversion = ObjectIdConversion.enforceHexStringAsObjectId();
/**
* Creates a new {@link MappingMongoConverter} given the new {@link DbRefResolver} and {@link MappingContext}.
@@ -303,6 +304,10 @@ public MappingContext extends MongoPersistentEntity>, MongoPersistentPropert
return mappingContext;
}
+ public void setObjectIdConversion(ObjectIdConversion> objectIdConversion) {
+ this.objectIdConversion = objectIdConversion;
+ }
+
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
@@ -333,6 +338,11 @@ public Environment getEnvironment() {
return environment;
}
+ @Override
+ public ObjectIdConversion> objectIdConversion() {
+ return objectIdConversion;
+ }
+
/**
* Set the {@link EntityCallbacks} instance to use when invoking
* {@link org.springframework.data.mapping.callback.EntityCallback callbacks} like the {@link AfterConvertCallback}.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java
index dcb21ec899..f21e1df420 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java
@@ -21,7 +21,6 @@
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.jspecify.annotations.Nullable;
-
import org.springframework.core.convert.ConversionException;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityConverter;
@@ -102,7 +101,7 @@ public interface MongoConverter
* @throws IllegalArgumentException if {@literal targetType} is {@literal null}.
* @since 2.1
*/
- @SuppressWarnings({"unchecked","NullAway"})
+ @SuppressWarnings({ "unchecked", "NullAway" })
default @Nullable T mapValueToTargetType(S source, Class targetType, DbRefResolver dbRefResolver) {
Assert.notNull(targetType, "TargetType must not be null");
@@ -158,6 +157,7 @@ public interface MongoConverter
* @since 2.2
*/
@Nullable
+ @SuppressWarnings({ "unchecked", "rawtypes" })
default Object convertId(@Nullable Object id, Class> targetType) {
if (id == null || ClassUtils.isAssignableValue(targetType, id)) {
@@ -166,14 +166,10 @@ default Object convertId(@Nullable Object id, Class> targetType) {
if (ClassUtils.isAssignable(ObjectId.class, targetType)) {
- if (id instanceof String) {
-
- if (ObjectId.isValid(id.toString())) {
- return new ObjectId(id.toString());
- }
+ if (objectIdConversion().isApplicable(id)) {
- // avoid ConversionException as convertToMongoType will return String anyways.
- return id;
+ Object converted = ((ObjectIdConversion) objectIdConversion()).convert(id);
+ return converted != null ? converted : id;
}
}
@@ -182,7 +178,93 @@ default Object convertId(@Nullable Object id, Class> targetType) {
? getConversionService().convert(id, targetType)
: convertToMongoType(id, (TypeInformation>) null);
} catch (ConversionException o_O) {
- return convertToMongoType(id,(TypeInformation>) null);
+ return convertToMongoType(id, (TypeInformation>) null);
+ }
+ }
+
+ /**
+ * @return the IdConversion to apply for {@link #convertId(Object, Class)}. A hex string to {@link ObjectId} enforcing
+ * conversion by default.
+ * @since 5.1
+ */
+ default ObjectIdConversion> objectIdConversion() {
+ return ObjectIdConversion.enforceHexStringAsObjectId();
+ }
+
+ /**
+ * Conversion function to apply to non {@literal null} id values of {@link #sourceType()} that do not match the
+ * desired store id type.
+ *
+ * @param
+ * @since 5.1
+ */
+ interface ObjectIdConversion {
+
+ /**
+ * Converts a {@link String} value that represent a {@link ObjectId#isValid(String) valid ObjectId} into the
+ * according {@link ObjectId}.
+ */
+ ObjectIdConversion hexToString = new ObjectIdConversion<>() {
+
+ @Override
+ public Class sourceType() {
+ return String.class;
+ }
+
+ @Override
+ public @Nullable ObjectId convert(String source) {
+
+ if (!ObjectId.isValid(source)) {
+ return null;
+ }
+ return new ObjectId(source);
+ }
+ };
+
+ /**
+ * Uses a raw {@link String} value no matter what by always returning {@literal null} for {@link #convert(Object)}.
+ */
+ ObjectIdConversion rawString = new ObjectIdConversion<>() {
+
+ @Override
+ public Class sourceType() {
+ return String.class;
+ }
+
+ @Override
+ public @Nullable ObjectId convert(String source) {
+ return null;
+ }
+ };
+
+ static ObjectIdConversion enforceHexStringAsObjectId() {
+ return hexToString;
+ }
+
+ static ObjectIdConversion retainRawString() {
+ return rawString;
+ }
+
+ /**
+ * @param source must not be {@literal null}.
+ * @return {@literal null} to indicate (though {@link #isApplicable(Object)) that no conversion happened.
+ */
+ @Nullable
+ ObjectId convert(S source);
+
+ /**
+ * The source type for which a potential conversion should happen.
+ *
+ * @return must not be {@literal null}.
+ */
+ Class sourceType();
+
+ default boolean isApplicable(Object probe) {
+ return isApplicable(probe.getClass());
+ }
+
+ default boolean isApplicable(Class> probe) {
+ return ClassUtils.isAssignable(sourceType(), probe);
}
}
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 931b9a6cea..b37401236a 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
@@ -2095,6 +2095,20 @@ void convertStringIdThatIsAnObjectIdHexToObjectIdIfTargetIsObjectId() {
assertThat(converter.convertId(source.toHexString(), ObjectId.class)).isEqualTo(source);
}
+ @Test // GH-5092
+ void doNotConvertStringIdThatIsAnObjectIdHexToObjectIdIfTargetIsObjectIdButConfigTellsOtherwise() {
+
+ ObjectId source = new ObjectId();
+ converter = new MappingMongoConverter(resolver, mappingContext) {
+ @Override
+ public ObjectIdConversion> objectIdConversion() {
+ return ObjectIdConversion.retainRawString();
+ }
+ };
+ converter.afterPropertiesSet();
+ assertThat(converter.convertId(source.toHexString(), ObjectId.class)).isEqualTo(source.toHexString());
+ }
+
@Test // DATAMONGO-1798
void donNotConvertStringIdThatIsAnObjectIdHexToObjectIdIfTargetIsString() {