Skip to content

Update ParameterBindingJsonReader #4334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.x-GH-3750-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.x-GH-3750-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.x-GH-3750-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.1.0-SNAPSHOT</version>
<version>4.1.x-GH-3750-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import java.util.TimeZone;

/**
* JsonBuffer implementation borrowed from <a href=
* DateTimeFormatter implementation borrowed from <a href=
* "https://github.com/mongodb/mongo-java-driver/blob/master/bson/src/main/org/bson/json/DateTimeFormatter.java">MongoDB
* Inc.</a> licensed under the Apache License, Version 2.0. <br />
* Formatted and modified.
Expand All @@ -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<Instant>() {
@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<Instant>() {
@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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -952,20 +957,16 @@ 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);
}

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() {
Expand Down Expand Up @@ -1079,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" };

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";
}

String dateTimeString = token.getValue(String.class);

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() {
Expand Down Expand Up @@ -1218,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);
Expand All @@ -1229,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);
}
Expand Down Expand Up @@ -1257,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);
Expand All @@ -1267,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);

Expand Down Expand Up @@ -1425,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());
Expand Down Expand Up @@ -1484,6 +1472,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();
Expand Down
Loading