From 2658af1ac5082b5e003fb152f29498b5d8a01f36 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 26 Mar 2018 09:52:39 +0200 Subject: [PATCH] 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 --- .../ExpressionEvaluatingParameterBinder.java | 27 ++++++++++++++ ...tractPersonRepositoryIntegrationTests.java | 12 +++++++ .../data/mongodb/repository/Person.java | 12 +++++++ .../mongodb/repository/PersonRepository.java | 5 +++ .../query/StringBasedMongoQueryUnitTests.java | 35 +++++++++++++++++++ 5 files changed, 91 insertions(+) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index a43b3b027..9e1eb7392 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -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}. * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index f81c05100..e650c3837 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -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() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java index 172e3a65f..d08d0814f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java @@ -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
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() */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index 8a6f35d70..44ca0f0b5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -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, Query @Query("{ firstname : :#{#firstname}}") List 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. diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index 0860ed7ae..6e6a53bbe 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -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);