From 51d5c52193e2d37f9892a6763ad4262d7ae93ffb 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 21aa12cec..b8fd95c1e 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 e33219fd3..141d5f17b 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 4c165f739..ccf4489b5 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 ad355258b..601abc09b 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 a08c95c6d..d7cf599fc 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);