From 1a105333aa084650b415fc91a2ed51d09efc595e Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 16 Dec 2016 16:18:32 +0100 Subject: [PATCH] DATAMONGO-1565 - Polishing. Consider quoted/unquoted parameter use with the same parameter reference. Extend date range in license headers. --- .../ExpressionEvaluatingParameterBinder.java | 52 ++++++++++---- .../query/StringBasedMongoQueryUnitTests.java | 70 +++++++++++++------ 2 files changed, 87 insertions(+), 35 deletions(-) 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 e5f7faf79..c81afb625 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 72520b9c1..c9de1d253 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. @@ -50,6 +50,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat import org.springframework.data.repository.query.DefaultEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; +import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; @@ -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); DBObject dbObject = new BasicDBObject(); converter.write(address, dbObject); dbObject.removeField(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); BasicDBObject queryObject = new BasicDBObject("address", dbObject); 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); DBObject addressDbObject = new BasicDBObject(); converter.write(address, addressDbObject); @@ -128,7 +129,7 @@ public class StringBasedMongoQueryUnitTests { DBObject reference = new BasicDBObject("address", addressDbObject); reference.put("lastname", "Matthews"); - 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(reference)); } @@ -229,10 +230,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())); @@ -244,10 +245,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())); @@ -260,9 +261,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())); @@ -305,10 +306,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())); @@ -320,11 +321,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())); @@ -336,11 +337,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}}"); @@ -354,16 +355,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(), is(reference.getQueryObject())); } + /** + * @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); + BasicDBList or = new BasicDBList(); + or.add(new BasicDBObject("age", 3)); + or.add(new BasicDBObject("displayAge", "3")); + BasicDBObject queryObject = new BasicDBObject("$or", or); + org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(queryObject); + + assertThat(query.getQueryObject(), is(reference.getQueryObject())); + } + /** * @see DATAMONGO-1454 */ @@ -381,12 +402,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(JSON.parse("{ \"arg0\" : \"argWith?1andText\" , \"arg1\" : \"nothing-special\"}"))); } @@ -524,6 +545,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);