From 5c4d84365632e8b20489cd089f23ba6f5b965332 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 20 Mar 2023 10:16:51 +0100 Subject: [PATCH 1/4] Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-benchmarks/pom.xml | 2 +- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index d864b2e4e6..252d6f2235 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 4.1.0-SNAPSHOT + 4.1.x-GH-3750-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index 1b2a1390e6..a3ca7196a6 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 4.1.0-SNAPSHOT + 4.1.x-GH-3750-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 8db8d798fb..7caab7779e 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 - 4.1.0-SNAPSHOT + 4.1.x-GH-3750-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 597ca94f38..e2177b93a6 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 4.1.0-SNAPSHOT + 4.1.x-GH-3750-SNAPSHOT ../pom.xml From 68fceac5e75aee4986bd75934f94aaade4b618d9 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 3 Mar 2022 09:12:59 +0100 Subject: [PATCH 2/4] Follow changes in uuid processing. --- .../util/json/ParameterBindingJsonReader.java | 46 ++++++++++++------- .../ParameterBindingJsonReaderUnitTests.java | 19 ++++++++ 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java index da6bf8cb7a..b8258529c9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java @@ -27,6 +27,7 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import java.util.UUID; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -289,10 +290,9 @@ public BsonType readBsonType() { } else if ("DBPointer".equals(value)) { setCurrentBsonType(BsonType.DB_POINTER); currentValue = visitDBPointerConstructor(); - } else if ("UUID".equals(value) || "GUID".equals(value) || "CSUUID".equals(value) || "CSGUID".equals(value) - || "JUUID".equals(value) || "JGUID".equals(value) || "PYUUID".equals(value) || "PYGUID".equals(value)) { + } else if ("UUID".equals(value)) { setCurrentBsonType(BsonType.BINARY); - currentValue = visitUUIDConstructor(value); + currentValue = visitUUIDConstructor(); } else if ("new".equals(value)) { visitNew(); } else { @@ -840,9 +840,8 @@ private void visitNew() { } else if ("DBPointer".equals(value)) { currentValue = visitDBPointerConstructor(); setCurrentBsonType(BsonType.DB_POINTER); - } else if ("UUID".equals(value) || "GUID".equals(value) || "CSUUID".equals(value) || "CSGUID".equals(value) - || "JUUID".equals(value) || "JGUID".equals(value) || "PYUUID".equals(value) || "PYGUID".equals(value)) { - currentValue = visitUUIDConstructor(value); + } else if ("UUID".equals(value)) { + currentValue = visitUUIDConstructor(); setCurrentBsonType(BsonType.BINARY); } else { throw new JsonParseException("JSON reader expected a type name but found '%s'.", value); @@ -862,7 +861,13 @@ private void visitExtendedJSON() { setCurrentBsonType(BsonType.BINARY); return; } - } else if ("$regex".equals(value) || "$options".equals(value)) { + } + if ("$uuid".equals(value)) { + currentValue = visitUuidExtendedJson(); + setCurrentBsonType(BsonType.BINARY); + return; + } + else if ("$regex".equals(value) || "$options".equals(value)) { currentValue = visitRegularExpressionExtendedJson(value); if (currentValue != null) { setCurrentBsonType(BsonType.REGULAR_EXPRESSION); @@ -956,16 +961,12 @@ private BsonBinary visitBinDataConstructor() { return new BsonBinary(subTypeToken.getValue(Integer.class).byteValue(), bytes); } - private BsonBinary visitUUIDConstructor(final String uuidConstructorName) { - verifyToken(JsonTokenType.LEFT_PAREN); - String hexString = readStringFromExtendedJson().replaceAll("\\{", "").replaceAll("}", "").replaceAll("-", ""); - verifyToken(JsonTokenType.RIGHT_PAREN); - byte[] bytes = decodeHex(hexString); - BsonBinarySubType subType = BsonBinarySubType.UUID_STANDARD; - if (!"UUID".equals(uuidConstructorName) || !"GUID".equals(uuidConstructorName)) { - subType = BsonBinarySubType.UUID_LEGACY; - } - return new BsonBinary(subType, bytes); + private BsonBinary visitUUIDConstructor() { + this.verifyToken(JsonTokenType.LEFT_PAREN); + String hexString = this.readStringFromExtendedJson().replace("-", ""); + + this.verifyToken(JsonTokenType.RIGHT_PAREN); + return new BsonBinary(BsonBinarySubType.UUID_STANDARD, decodeHex(hexString)); } private BsonRegularExpression visitRegularExpressionConstructor() { @@ -1484,6 +1485,17 @@ private int readIntFromExtendedJson() { return value; } + private BsonBinary visitUuidExtendedJson() { + verifyToken(JsonTokenType.COLON); + String hexString = this.readStringFromExtendedJson().replace("-", ""); + verifyToken(JsonTokenType.END_OBJECT); + try { + return new BsonBinary(BsonBinarySubType.UUID_STANDARD, decodeHex(hexString)); + } catch (IllegalArgumentException e) { + throw new JsonParseException(e); + } + } + private void visitJavaScriptExtendedJson() { verifyToken(JsonTokenType.COLON); String code = readStringFromExtendedJson(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java index bd82d7d7d1..cf2dceed74 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java @@ -27,6 +27,8 @@ import java.util.List; import java.util.UUID; +import org.bson.BsonBinary; +import org.bson.BsonBinarySubType; import org.bson.Document; import org.bson.codecs.DecoderContext; import org.junit.jupiter.api.Test; @@ -547,6 +549,23 @@ void retainsSpelArgumentTypeViaParameterPlaceholderWhenValueContainsDoubleQuotes assertThat(target.get("arg0")).isEqualTo(source); } + @Test // GH-3750 + void shouldParseUUIDasStandardRepresentation() { + + String json = "{ 'value' : UUID(\"b5f21e0c-2a0d-42d6-ad03-d827008d8ab6\") }"; + + BsonBinary value = parse(json).get("value", BsonBinary.class); + assertThat(value.getType()).isEqualTo(BsonBinarySubType.UUID_STANDARD.getValue()); + } + + @Test // GH-3750 + public void shouldParse$uuidAsStandardRepresentation() { + + String json = "{ 'value' : { '$uuid' : \"73ff-d26444b-34c6-990e8e-7d1dfc035d4\" } } }"; + BsonBinary value = parse(json).get("value", BsonBinary.class); + assertThat(value.getType()).isEqualTo(BsonBinarySubType.UUID_STANDARD.getValue()); + } + private static Document parse(String json, Object... args) { ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, args); From b959b492f8261af5b8406b9949467a8585ff90bd Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 20 Mar 2023 11:25:45 +0100 Subject: [PATCH 3/4] Update date time parser --- .../mongodb/util/json/DateTimeFormatter.java | 134 ++---------------- .../util/json/ParameterBindingJsonReader.java | 40 ++---- .../ParameterBindingJsonReaderUnitTests.java | 33 ++++- 3 files changed, 57 insertions(+), 150 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/DateTimeFormatter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/DateTimeFormatter.java index 30735069f0..7fddd2bb6c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/DateTimeFormatter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/DateTimeFormatter.java @@ -29,7 +29,7 @@ import java.util.TimeZone; /** - * JsonBuffer implementation borrowed from MongoDB * Inc. licensed under the Apache License, Version 2.0.
* Formatted and modified. @@ -40,133 +40,23 @@ */ class DateTimeFormatter { - private static final FormatterImpl FORMATTER_IMPL; - - static { - FormatterImpl dateTimeHelper; - try { - dateTimeHelper = loadDateTimeFormatter( - "org.springframework.data.mongodb.util.json.DateTimeFormatter$Java8DateTimeFormatter"); - } catch (LinkageError e) { - // this is expected if running on a release prior to Java 8: fallback to JAXB. - dateTimeHelper = loadDateTimeFormatter( - "org.springframework.data.mongodb.util.json.DateTimeFormatter$JaxbDateTimeFormatter"); - } - - FORMATTER_IMPL = dateTimeHelper; - } - - private static FormatterImpl loadDateTimeFormatter(final String className) { - + static long parse(final String dateTimeString) { try { - return (FormatterImpl) Class.forName(className).getDeclaredConstructor().newInstance(); - } catch (ClassNotFoundException e) { - // this is unexpected as it means the class itself is not found - throw new ExceptionInInitializerError(e); - } catch (InstantiationException e) { - // this is unexpected as it means the class can't be instantiated - throw new ExceptionInInitializerError(e); - } catch (IllegalAccessException e) { - // this is unexpected as it means the no-args constructor isn't accessible - throw new ExceptionInInitializerError(e); - } catch (NoSuchMethodException e) { - throw new ExceptionInInitializerError(e); - } catch (InvocationTargetException e) { - throw new ExceptionInInitializerError(e); + return ISO_OFFSET_DATE_TIME.parse(dateTimeString, new TemporalQuery() { + @Override + public Instant queryFrom(final TemporalAccessor temporal) { + return Instant.from(temporal); + } + }).toEpochMilli(); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException(e.getMessage()); } } - static long parse(final String dateTimeString) { - return FORMATTER_IMPL.parse(dateTimeString); - } - static String format(final long dateTime) { - return FORMATTER_IMPL.format(dateTime); - } - - private interface FormatterImpl { - long parse(String dateTimeString); - - String format(long dateTime); - } - - // Reflective use of DatatypeConverter avoids a compile-time dependency on the java.xml.bind module in Java 9 - static class JaxbDateTimeFormatter implements FormatterImpl { - - private static final Method DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD; - private static final Method DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD; - - static { - try { - DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD = Class.forName("jakarta.xml.bind.DatatypeConverter") - .getDeclaredMethod("parseDateTime", String.class); - DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD = Class.forName("jakarta.xml.bind.DatatypeConverter") - .getDeclaredMethod("printDateTime", Calendar.class); - } catch (NoSuchMethodException e) { - throw new ExceptionInInitializerError(e); - } catch (ClassNotFoundException e) { - throw new ExceptionInInitializerError(e); - } - } - - @Override - public long parse(final String dateTimeString) { - try { - return ((Calendar) DATATYPE_CONVERTER_PARSE_DATE_TIME_METHOD.invoke(null, dateTimeString)).getTimeInMillis(); - } catch (IllegalAccessException e) { - throw new IllegalStateException(e); - } catch (InvocationTargetException e) { - throw (RuntimeException) e.getCause(); - } - } - - @Override - public String format(final long dateTime) { - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(dateTime); - calendar.setTimeZone(TimeZone.getTimeZone("Z")); - try { - return (String) DATATYPE_CONVERTER_PRINT_DATE_TIME_METHOD.invoke(null, calendar); - } catch (IllegalAccessException e) { - throw new IllegalStateException(); - } catch (InvocationTargetException e) { - throw (RuntimeException) e.getCause(); - } - } + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateTime), ZoneId.of("Z")).format(ISO_OFFSET_DATE_TIME); } - static class Java8DateTimeFormatter implements FormatterImpl { - - // if running on Java 8 or above then java.time.format.DateTimeFormatter will be available and initialization will - // succeed. - // Otherwise it will fail. - static { - try { - Class.forName("java.time.format.DateTimeFormatter"); - } catch (ClassNotFoundException e) { - throw new ExceptionInInitializerError(e); - } - } - - @Override - public long parse(final String dateTimeString) { - try { - return ISO_OFFSET_DATE_TIME.parse(dateTimeString, new TemporalQuery() { - @Override - public Instant queryFrom(final TemporalAccessor temporal) { - return Instant.from(temporal); - } - }).toEpochMilli(); - } catch (DateTimeParseException e) { - throw new IllegalArgumentException(e.getMessage()); - } - } - - @Override - public String format(final long dateTime) { - return ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateTime), ZoneId.of("Z")).format(ISO_OFFSET_DATE_TIME); - } + private DateTimeFormatter() { } - - private DateTimeFormatter() {} } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java index b8258529c9..c7c3c6076d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java @@ -20,6 +20,8 @@ import java.text.DateFormat; import java.text.ParsePosition; import java.text.SimpleDateFormat; +import java.time.format.DateTimeParseException; +import java.util.Base64; import java.util.Calendar; import java.util.Collections; import java.util.Date; @@ -27,7 +29,6 @@ import java.util.Locale; import java.util.Map; import java.util.TimeZone; -import java.util.UUID; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -42,7 +43,6 @@ import org.springframework.expression.EvaluationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; -import org.springframework.util.Base64Utils; import org.springframework.util.ClassUtils; import org.springframework.util.NumberUtils; import org.springframework.util.ObjectUtils; @@ -957,7 +957,7 @@ private BsonBinary visitBinDataConstructor() { } verifyToken(JsonTokenType.RIGHT_PAREN); - byte[] bytes = Base64Utils.decodeFromString(bytesToken.getValue(String.class)); + byte[] bytes = Base64.getDecoder().decode(bytesToken.getValue(String.class)); return new BsonBinary(subTypeToken.getValue(Integer.class).byteValue(), bytes); } @@ -1080,28 +1080,14 @@ private long visitISODateTimeConstructor() { } verifyToken(JsonTokenType.RIGHT_PAREN); - String[] patterns = { "yyyy-MM-dd", "yyyy-MM-dd'T'HH:mm:ssz", "yyyy-MM-dd'T'HH:mm:ss.SSSz" }; + + String dateTimeString = token.getValue(String.class); - SimpleDateFormat format = new SimpleDateFormat(patterns[0], Locale.ENGLISH); - ParsePosition pos = new ParsePosition(0); - String s = token.getValue(String.class); - - if (s.endsWith("Z")) { - s = s.substring(0, s.length() - 1) + "GMT-00:00"; - } - - for (final String pattern : patterns) { - format.applyPattern(pattern); - format.setLenient(true); - pos.setIndex(0); - - Date date = format.parse(s, pos); - - if (date != null && pos.getIndex() == s.length()) { - return date.getTime(); - } + try { + return DateTimeFormatter.parse(dateTimeString); + } catch (DateTimeParseException e) { + throw new JsonParseException("Failed to parse string as a date: " + dateTimeString, e); } - throw new JsonParseException("Invalid date format."); } private BsonBinary visitHexDataConstructor() { @@ -1219,7 +1205,7 @@ private BsonBinary visitBinDataExtendedJson(final String firstKey) { byte type; if (firstNestedKey.equals("base64")) { verifyToken(JsonTokenType.COLON); - data = Base64Utils.decodeFromString(readStringFromExtendedJson()); + data = Base64.getDecoder().decode(readStringFromExtendedJson()); verifyToken(JsonTokenType.COMMA); verifyString("subType"); verifyToken(JsonTokenType.COLON); @@ -1230,7 +1216,7 @@ private BsonBinary visitBinDataExtendedJson(final String firstKey) { verifyToken(JsonTokenType.COMMA); verifyString("base64"); verifyToken(JsonTokenType.COLON); - data = Base64Utils.decodeFromString(readStringFromExtendedJson()); + data = Base64.getDecoder().decode(readStringFromExtendedJson()); } else { throw new JsonParseException("Unexpected key for $binary: " + firstNestedKey); } @@ -1258,7 +1244,7 @@ private BsonBinary visitLegacyBinaryExtendedJson(final String firstKey) { byte type; if (firstKey.equals("$binary")) { - data = Base64Utils.decodeFromString(readStringFromExtendedJson()); + data = Base64.getDecoder().decode(readStringFromExtendedJson()); verifyToken(JsonTokenType.COMMA); verifyString("$type"); verifyToken(JsonTokenType.COLON); @@ -1268,7 +1254,7 @@ private BsonBinary visitLegacyBinaryExtendedJson(final String firstKey) { verifyToken(JsonTokenType.COMMA); verifyString("$binary"); verifyToken(JsonTokenType.COLON); - data = Base64Utils.decodeFromString(readStringFromExtendedJson()); + data = Base64.getDecoder().decode(readStringFromExtendedJson()); } verifyToken(JsonTokenType.END_OBJECT); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java index cf2dceed74..8519f4f76e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java @@ -211,6 +211,38 @@ void bindNumberAsDate() { assertThat(target).isEqualTo(Document.parse("{ 'end_date' : { $gte : { $date : " + time + " } } } ")); } + @Test // GH-3750 + public void shouldParseISODate() { + + String json = "{ 'value' : ISODate(\"1970-01-01T00:00:00Z\") }"; + Date value = parse(json).get("value", Date.class); + assertThat(value.getTime()).isZero(); + } + + @Test // GH-3750 + public void shouldParseISODateWith24HourTimeSpecification() { + + String json = "{ 'value' : ISODate(\"2013-10-04T12:07:30.443Z\") }"; + Date value = parse(json).get("value", Date.class); + assertThat(value.getTime()).isEqualTo(1380888450443L); + } + + @Test // GH-3750 + public void shouldParse$date() { + + String json = "{ 'value' : { \"$date\" : \"2015-04-16T14:55:57.626Z\" } }"; + Date value = parse(json).get("value", Date.class); + assertThat(value.getTime()).isEqualTo(1429196157626L); + } + + @Test // GH-3750 + public void shouldParse$dateWithTimeOffset() { + + String json = "{ 'value' :{ \"$date\" : \"2015-04-16T16:55:57.626+02:00\" } }"; + Date value = parse(json).get("value", Date.class); + assertThat(value.getTime()).isEqualTo(1429196157626L); + } + @Test // DATAMONGO-2418 void shouldNotAccessSpElEvaluationContextWhenNoSpElPresentInBindableTarget() { @@ -486,7 +518,6 @@ void parsesNullValue() { assertThat(target).isEqualTo(new Document("parent", null)); } - @Test // GH-4089 void retainsSpelArgumentTypeViaArgumentIndex() { From a76cf38ba9c2d4c17d5af5c3439453aeb5a265a5 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 21 Mar 2023 08:24:48 +0100 Subject: [PATCH 4/4] Fix null value handling in ParameterBindingJsonReader#readStringFromExtendedJson This commit makes sure to return null for a null parameter value avoiding a potential NPE when parsing data. In doing so we can ensure object creation is done with the intended value that may or may not lead to a downstream error eg. when trying to create an ObjectId with a null hexString. Closes: #4282 --- .../mongodb/util/json/ParameterBindingJsonReader.java | 3 ++- .../util/json/ParameterBindingJsonReaderUnitTests.java | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java index c7c3c6076d..e52d66730f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java @@ -1412,7 +1412,8 @@ private String readStringFromExtendedJson() { // Spring Data Customization START if (patternToken.getType() == JsonTokenType.STRING || patternToken.getType() == JsonTokenType.UNQUOTED_STRING) { - return bindableValueFor(patternToken).getValue().toString(); + Object value = bindableValueFor(patternToken).getValue(); + return value != null ? value.toString() : null; } throw new JsonParseException("JSON reader expected a string but found '%s'.", patternToken.getValue()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java index 8519f4f76e..37115e5221 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java @@ -243,6 +243,14 @@ public void shouldParseISODateWith24HourTimeSpecification() { assertThat(value.getTime()).isEqualTo(1429196157626L); } + @Test // GH-4282 + public void shouldReturnNullAsSuch() { + + String json = "{ 'value' : ObjectId(?0) }"; + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> parse(json, new Object[] { null })) + .withMessageContaining("hexString"); + } + @Test // DATAMONGO-2418 void shouldNotAccessSpElEvaluationContextWhenNoSpElPresentInBindableTarget() {