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 8c3c71e24..0de9eac29 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 @@ -45,10 +45,12 @@ class DateTimeFormatter { static { FormatterImpl dateTimeHelper; try { - dateTimeHelper = loadDateTimeFormatter("org.bson.json.DateTimeFormatter$Java8DateTimeFormatter"); + 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.bson.json.DateTimeFormatter$JaxbDateTimeFormatter"); + dateTimeHelper = loadDateTimeFormatter( + "org.springframework.data.mongodb.util.json.DateTimeFormatter$JaxbDateTimeFormatter"); } FORMATTER_IMPL = dateTimeHelper; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java index 2edde9433..2ec51a79b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingDocumentCodec.java @@ -21,6 +21,7 @@ import static org.bson.codecs.configuration.CodecRegistries.*; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.UUID; @@ -35,22 +36,13 @@ import org.bson.BsonValue; import org.bson.BsonWriter; import org.bson.Document; import org.bson.Transformer; -import org.bson.codecs.BsonTypeClassMap; -import org.bson.codecs.BsonTypeCodecMap; -import org.bson.codecs.BsonValueCodecProvider; -import org.bson.codecs.Codec; -import org.bson.codecs.CollectibleCodec; -import org.bson.codecs.DecoderContext; -import org.bson.codecs.DocumentCodecProvider; -import org.bson.codecs.EncoderContext; -import org.bson.codecs.IdGenerator; -import org.bson.codecs.ObjectIdGenerator; -import org.bson.codecs.ValueCodecProvider; +import org.bson.codecs.*; import org.bson.codecs.configuration.CodecRegistry; - import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; +import org.springframework.util.NumberUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -278,6 +270,17 @@ public class ParameterBindingDocumentCodec implements CollectibleCodec if (bindingReader.currentValue != null) { Object value = bindingReader.currentValue; + + if (ObjectUtils.nullSafeEquals(BsonType.DATE_TIME, bindingReader.getCurrentBsonType()) + && !(value instanceof Date)) { + + if (value instanceof Number) { + value = new Date(NumberUtils.convertNumberToTargetClass((Number) value, Long.class)); + } else if (value instanceof String) { + value = new Date(DateTimeFormatter.parse((String) value)); + } + } + bindingReader.setState(State.TYPE); bindingReader.currentValue = null; return value; 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 ba61e487c..a5801031c 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 @@ -394,7 +394,7 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { matched = true; String group = matcher.group(); int index = computeParameterIndex(group); - computedValue = computedValue.replace(group, ObjectUtils.nullSafeToString(getBindableValueForIndex(index))); + computedValue = computedValue.replace(group, nullSaveToString(getBindableValueForIndex(index))); } if (!matched) { @@ -406,7 +406,7 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { String binding = regexMatcher.group(); String expression = binding.substring(3, binding.length() - 1); - computedValue = computedValue.replace(binding, ObjectUtils.nullSafeToString(evaluateExpression(expression))); + computedValue = computedValue.replace(binding, nullSaveToString(evaluateExpression(expression))); } } @@ -416,6 +416,15 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { return bindableValue; } + private static String nullSaveToString(Object value) { + + if (value instanceof Date) { + return DateTimeFormatter.format(((Date) value).getTime()); + } + + return ObjectUtils.nullSafeToString(value); + } + private static int computeParameterIndex(String parameter) { return NumberUtils.parseNumber(parameter.replace("?", ""), Integer.class); } @@ -1244,12 +1253,20 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { } else { if (valueToken.getType() == JsonTokenType.INT32 || valueToken.getType() == JsonTokenType.INT64) { value = valueToken.getValue(Long.class); - } else if (valueToken.getType() == JsonTokenType.STRING) { - String dateTimeString = valueToken.getValue(String.class); - try { - value = DateTimeFormatter.parse(dateTimeString); - } catch (IllegalArgumentException e) { - throw new JsonParseException("Failed to parse string as a date", e); + } else if (valueToken.getType() == JsonTokenType.STRING + || valueToken.getType() == JsonTokenType.UNQUOTED_STRING) { + + Object dt = bindableValueFor(valueToken).getValue(); + if (dt instanceof Date) { + value = ((Date) dt).getTime(); + } else if (dt instanceof Number) { + value = NumberUtils.convertNumberToTargetClass((Number) dt, Long.class); + } else { + try { + value = DateTimeFormatter.parse(dt.toString()); + } catch (IllegalArgumentException e) { + throw new JsonParseException(String.format("Failed to parse string '%s' as a date", dt), e); + } } } else { throw new JsonParseException("JSON reader expected an integer or string but found '%s'.", 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 0026fbc74..15ee1133e 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 @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import org.bson.Document; @@ -158,6 +159,42 @@ public class ParameterBindingJsonReaderUnitTests { assertThat(target).isEqualTo(new Document("arg0", 100.01D)); } + @Test // DATAMONGO-2315 + public void bindDateAsDate() { + + Date date = new Date(); + Document target = parse("{ 'end_date' : { $gte : { $date : ?0 } } }", date); + + assertThat(target).isEqualTo(Document.parse("{ 'end_date' : { $gte : { $date : " + date.getTime() + " } } } ")); + } + + @Test // DATAMONGO-2315 + public void bindQuotedDateAsDate() { + + Date date = new Date(); + Document target = parse("{ 'end_date' : { $gte : { $date : '?0' } } }", date); + + assertThat(target).isEqualTo(Document.parse("{ 'end_date' : { $gte : { $date : " + date.getTime() + " } } } ")); + } + + @Test // DATAMONGO-2315 + public void bindStringAsDate() { + + Date date = new Date(); + Document target = parse("{ 'end_date' : { $gte : { $date : ?0 } } }", "2019-07-04T12:19:23.000Z"); + + assertThat(target).isEqualTo(Document.parse("{ 'end_date' : { $gte : { $date : '2019-07-04T12:19:23.000Z' } } } ")); + } + + @Test // DATAMONGO-2315 + public void bindNumberAsDate() { + + Long time = new Date().getTime(); + Document target = parse("{ 'end_date' : { $gte : { $date : ?0 } } }", time); + + assertThat(target).isEqualTo(Document.parse("{ 'end_date' : { $gte : { $date : " + time + " } } } ")); + } + private static Document parse(String json, Object... args) { ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, args);