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 fcab6fd76..82faf4a59 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,9 @@ */ package org.springframework.data.mongodb.repository.query; +import lombok.Data; +import lombok.RequiredArgsConstructor; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -44,6 +47,7 @@ import com.mongodb.util.JSON; * @author Christoph Strobl * @author Thomas Darimont * @author Oliver Gierke + * @author Mark Paluch * @since 1.9 */ class ExpressionEvaluatingParameterBinder { @@ -90,7 +94,7 @@ class ExpressionEvaluatingParameterBinder { * * @param input must not be {@literal null} or empty. * @param accessor must not be {@literal null}. - * @param bindings must not be {@literal null}. + * @param bindingContext must not be {@literal null}. * @return */ private String replacePlaceholders(String input, MongoParameterAccessor accessor, BindingContext bindingContext) { @@ -232,22 +236,23 @@ class ExpressionEvaluatingParameterBinder { * @param groupName The actual {@link Matcher#group() group}. * @return */ - private String extractPlaceholder(String groupName) { + private Placeholder extractPlaceholder(String groupName) { if (!groupName.endsWith("'") && !groupName.endsWith("\"")) { - return groupName; + return new Placeholder(groupName, false); } - return groupName.substring(0, groupName.length() - 1); + return new Placeholder(groupName.substring(0, groupName.length() - 1), true); } /** * @author Christoph Strobl + * @author Mark Paluch * @since 1.9 */ static class BindingContext { final MongoParameters parameters; - final Map bindings; + final Map bindings; /** * Creates new {@link BindingContext}. @@ -279,13 +284,13 @@ class ExpressionEvaluatingParameterBinder { /** * Get the concrete {@link ParameterBinding} for a given {@literal placeholder}. - * + * * @param placeholder must not be {@literal null}. * @return * @throws java.util.NoSuchElementException * @since 1.10 */ - ParameterBinding getBindingFor(String placeholder) { + ParameterBinding getBindingFor(Placeholder placeholder) { if (!bindings.containsKey(placeholder)) { throw new NoSuchElementException(String.format("Could not to find binding for placeholder '%s'.", placeholder)); @@ -296,20 +301,43 @@ class ExpressionEvaluatingParameterBinder { /** * Get the associated {@link MongoParameters}. - * + * * @return */ public MongoParameters getParameters() { return parameters; } - private static Map mapBindings(List bindings) { + private static Map mapBindings(List bindings) { - Map map = new LinkedHashMap(bindings.size(), 1); + Map map = new LinkedHashMap(bindings.size(), 1); for (ParameterBinding binding : bindings) { - map.put(binding.getParameter(), binding); + map.put(new Placeholder(binding.getParameter(), binding.isQuoted()), binding); } return map; } } + + /** + * Encapsulates a quoted/unquoted parameter placeholder. + * + * @author Mark Paluch + * @since 1.9 + */ + @Data + @RequiredArgsConstructor + static class Placeholder { + + private final String parameter; + private final boolean quoted; + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return quoted ? String.format("'%s'", parameter) : parameter; + } + } } 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 441d10822..a53e3525b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -86,9 +87,9 @@ public class StringBasedMongoQueryUnitTests { public void bindsSimplePropertyCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -100,13 +101,13 @@ public class StringBasedMongoQueryUnitTests { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByAddress", Address.class); Address address = new Address("Foo", "0123", "Bar"); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, address); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, address); Document document = new Document(); converter.write(address, document); document.remove(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); Document queryObject = new Document("address", document); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(queryObject); @@ -119,7 +120,7 @@ public class StringBasedMongoQueryUnitTests { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAndAddress", String.class, Address.class); Address address = new Address("Foo", "0123", "Bar"); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews", address); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews", address); Document addressDocument = new Document(); converter.write(address, addressDocument); @@ -128,7 +129,7 @@ public class StringBasedMongoQueryUnitTests { Document reference = new Document("lastname", "Matthews"); reference.append("address", addressDocument); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject().toJson(), is(reference.toJson())); } @@ -232,10 +233,10 @@ public class StringBasedMongoQueryUnitTests { @Test public void bindsSimplePropertyAlreadyQuotedCorrectly() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -247,10 +248,10 @@ public class StringBasedMongoQueryUnitTests { @Test public void bindsSimplePropertyAlreadyQuotedWithRegexCorrectly() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -263,9 +264,9 @@ public class StringBasedMongoQueryUnitTests { public void bindsSimplePropertyWithRegexCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -307,10 +308,10 @@ public class StringBasedMongoQueryUnitTests { @Test public void shouldSupportExpressionsInCustomQueries() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpression", String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -322,11 +323,11 @@ public class StringBasedMongoQueryUnitTests { @Test public void shouldSupportExpressionsInCustomQueriesWithNestedObject() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndNestedObject", boolean.class, String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{ \"id\" : { \"$exists\" : true}}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -338,11 +339,11 @@ public class StringBasedMongoQueryUnitTests { @Test public void shouldSupportExpressionsInCustomQueriesWithMultipleNestedObjects() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndMultipleNestedObjects", boolean.class, String.class, String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery( "{ \"id\" : { \"$exists\" : true} , \"foo\" : 42 , \"bar\" : { \"$exists\" : false}}"); @@ -356,16 +357,36 @@ public class StringBasedMongoQueryUnitTests { public void shouldSupportNonQuotedBinaryDataReplacement() throws Exception { byte[] binaryData = "Matthews".getBytes("UTF-8"); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, binaryData); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, binaryData); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsBinary", byte[].class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : { '$binary' : '" + DatatypeConverter.printBase64Binary(binaryData) + "', '$type' : '" + BSON.B_GENERAL + "'}}"); assertThat(query.getQueryObject().toJson(), is(reference.getQueryObject().toJson())); } + /** + * @see DATAMONGO-1565 + */ + @Test + public void bindsPropertyReferenceMultipleTimesCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByAgeQuotedAndUnquoted", Integer.TYPE); + + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, 3); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + List or = new ArrayList<>(); + or.add(new Document("age", 3)); + or.add(new Document("displayAge", "3")); + Document queryObject = new Document("$or", or); + org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(queryObject); + + assertThat(query.getQueryObject(), is(reference.getQueryObject())); + } + /** * @see DATAMONGO-1454 */ @@ -383,12 +404,12 @@ public class StringBasedMongoQueryUnitTests { @Test public void shouldIgnorePlaceholderPatternInReplacementValue() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "argWith?1andText", + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "argWith?1andText", "nothing-special"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByStringWithWildcardChar", String.class, String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(Document.parse("{ \"arg0\" : \"argWith?1andText\" , \"arg1\" : \"nothing-special\"}"))); } @@ -527,6 +548,9 @@ public class StringBasedMongoQueryUnitTests { @Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}") List findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2); + @Query(value = "{ $or : [{'age' : ?0 }, {'displayAge' : '?0'}] }") + boolean findByAgeQuotedAndUnquoted(int age); + @Query(value = "{ 'lastname' : ?0 }", exists = true) boolean existsByLastname(String lastname);