DATAMONGO-1911 - Fix UUID serialization in String-based queries.

We now render to the correct UUID representation in String-based queries. Unquoted values render to $binary representation, quoted UUIDs are rendered with their toString() value.

Previously we used JSON.serialize() to encode values to JSON. The com.mongodb.util.JSON serializer does not produce JSON that is compatible with Document.parse. It uses an older JSON format that preceded the MongoDB Extended JSON specification.

Original Pull Request: #544
This commit is contained in:
Mark Paluch
2018-03-26 09:52:39 +02:00
committed by Christoph Strobl
parent 56b6748068
commit 51d5c52193
5 changed files with 91 additions and 0 deletions

View File

@@ -19,11 +19,14 @@ import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.experimental.UtilityClass;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -221,9 +224,33 @@ class ExpressionEvaluatingParameterBinder {
return base64representation;
}
if (value instanceof UUID) {
UUID uuid = (UUID) value;
if (binding.isQuoted()) {
return uuid.toString();
}
String base64representation = DatatypeConverter.printBase64Binary(asBinary(uuid));
return "{ '$binary' : '" + base64representation + "', '$type' : '" + BSON.B_UUID + "'}";
}
return JSON.serialize(value);
}
private static byte[] asBinary(UUID uuid) {
byte[] bytes = new byte[16];
ByteBuffer bb = ByteBuffer.wrap(bytes) //
.order(ByteOrder.LITTLE_ENDIAN) //
.putLong(uuid.getMostSignificantBits()) //
.putLong(uuid.getLeastSignificantBits());
return bb.array();
}
/**
* Evaluates the given {@code expressionString}.
*

View File

@@ -27,6 +27,7 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -1094,6 +1095,17 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
assertThat(users.get(0), is(dave));
}
@Test // DATAMONGO-1911
public void findByUUIDShouldReturnCorrectResult() {
dave.setUniqueId(UUID.randomUUID());
repository.save(dave);
Person dave = repository.findByUniqueId(this.dave.getUniqueId());
assertThat(dave, is(equalTo(dave)));
}
@Test // DATAMONGO-1245
public void findByExampleShouldResolveStuffCorrectly() {

View File

@@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
@@ -34,6 +35,7 @@ import org.springframework.data.mongodb.core.mapping.Field;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
@Document
public class Person extends Contact {
@@ -56,6 +58,8 @@ public class Person extends Contact {
private @Field("add") Address address;
private Set<Address> shippingAddresses;
private UUID uniqueId;
@DBRef User creator;
@DBRef(lazy = true) User coworker;
@@ -196,6 +200,14 @@ public class Person extends Contact {
this.shippingAddresses = addresses;
}
public UUID getUniqueId() {
return uniqueId;
}
public void setUniqueId(UUID uniqueId) {
this.uniqueId = uniqueId;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.Contact#getName()
*/

View File

@@ -19,6 +19,7 @@ import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
import org.springframework.data.domain.Page;
@@ -318,6 +319,10 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
@Query("{ firstname : :#{#firstname}}")
List<Person> findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname);
// DATAMONGO-1911
@Query("{ uniqueId: ?0}")
Person findByUniqueId(UUID uniqueId);
/**
* Returns the count of {@link Person} with the given firstname. Uses {@link CountQuery} annotation to define the
* query to be executed.

View File

@@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.xml.bind.DatatypeConverter;
@@ -321,6 +322,34 @@ public class StringBasedMongoQueryUnitTests {
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-1911
public void shouldSupportNonQuotedUUIDReplacement() {
UUID uuid = UUID.fromString("864de43b-e3ea-f1e4-3663-fb8240b659b9");
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, (Object) uuid);
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsUUID", UUID.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(
"{'lastname' : { $binary:\"5PHq4zvkTYa5WbZAgvtjNg==\", $type: \"03\"}}");
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-1911
public void shouldSupportQuotedUUIDReplacement() {
UUID uuid = UUID.randomUUID();
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, (Object) uuid);
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsStringUUID", UUID.class);
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(
"{'lastname' : '" + uuid.toString() + "'}");
assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson()));
}
@Test // DATAMONGO-1454
public void shouldSupportExistsProjection() {
@@ -551,6 +580,12 @@ public class StringBasedMongoQueryUnitTests {
@Query("{ 'lastname' : ?0 }")
Person findByLastnameAsBinary(byte[] lastname);
@Query("{ 'lastname' : ?0 }")
Person findByLastnameAsUUID(UUID lastname);
@Query("{ 'lastname' : '?0' }")
Person findByLastnameAsStringUUID(UUID lastname);
@Query("{ 'lastname' : '?0' }")
Person findByLastnameQuoted(String lastname);