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 651d5483a..8e5f91252 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-2016 the original author or authors. + * Copyright 2015-2017 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,9 +15,9 @@ */ package org.springframework.data.mongodb.repository.query; -import com.mongodb.DBObject; import lombok.EqualsAndHashCode; import lombok.Value; +import lombok.experimental.UtilityClass; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -39,10 +39,11 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import com.mongodb.DBObject; import com.mongodb.util.JSON; /** - * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placholders within a + * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a * {@link String}. * * @author Christoph Strobl @@ -143,6 +144,8 @@ class ExpressionEvaluatingParameterBinder { * * @param buffer the {@link StringBuffer} to operate upon. * @param valueForBinding the actual binding value. + * @param raw the raw binding value + * @param isExpression {@literal true} if the binding value results from a SpEL expression. */ private void postProcessQuotedBinding(StringBuffer buffer, String valueForBinding, Object raw, boolean isExpression) { @@ -160,8 +163,8 @@ class ExpressionEvaluatingParameterBinder { quotationMark = buffer.charAt(quotationMarkIndex); } - if (valueForBinding.startsWith("{") && (raw instanceof DBObject || isExpression)) { // remove quotation char before - // the complex object string + // remove quotation char before the complex object string + if (valueForBinding.startsWith("{") && (raw instanceof DBObject || isExpression)) { buffer.deleteCharAt(quotationMarkIndex); @@ -196,7 +199,7 @@ class ExpressionEvaluatingParameterBinder { return (String) value; } - return ((String) value).replace("\\", "\\\\").replace("\"", "\\\""); + return QuotedString.unquote(JSON.serialize(value)); } if (value instanceof byte[]) { @@ -254,31 +257,32 @@ class ExpressionEvaluatingParameterBinder { * Extract the placeholder stripping any trailing trailing quotation mark that might have resulted from the * {@link #createReplacementPattern(List) pattern} used. * - * @param matcher The actual {@link Matcher#group() group}. + * @param parameterIndex The actual parameter index. + * @param matcher The actual {@link Matcher}. * @return */ private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) { if (matcher.groupCount() > 1) { - String suffix = matcher.group(parameterIndex * 2 + 2); String rawPlaceholder = matcher.group(parameterIndex * 2 + 1); + String suffix = matcher.group(parameterIndex * 2 + 2); if (!StringUtils.hasText(rawPlaceholder)) { rawPlaceholder = matcher.group(); - suffix = ""+rawPlaceholder.charAt(rawPlaceholder.length()-1); - if(rawPlaceholder.endsWith("'")) { - rawPlaceholder = rawPlaceholder.substring(0, rawPlaceholder.length()-1); + suffix = "" + rawPlaceholder.charAt(rawPlaceholder.length() - 1); + if (QuotedString.endsWithQuote(rawPlaceholder)) { + rawPlaceholder = QuotedString.unquoteSuffix(rawPlaceholder); } } if (StringUtils.hasText(suffix)) { - boolean quoted= (suffix.endsWith("'") || suffix.endsWith("\"")); + boolean quoted = QuotedString.endsWithQuote(suffix); return Placeholder.of(parameterIndex, rawPlaceholder, quoted, - quoted ? suffix.substring(0, suffix.length() - 1) : suffix); + quoted ? QuotedString.unquoteSuffix(suffix) : suffix); } } @@ -388,4 +392,41 @@ class ExpressionEvaluatingParameterBinder { } } + + /** + * Utility to handle quoted strings using single/double quotes. + * + * @author Mark Paluch + */ + @UtilityClass + static class QuotedString { + + /** + * @param string + * @return {@literal true} if {@literal string} ends with a single/double quote. + */ + static boolean endsWithQuote(String string) { + return string.endsWith("'") || string.endsWith("\""); + } + + /** + * Remove trailing quoting from {@literal quoted}. + * + * @param quoted + * @return {@literal quoted} with removed quotes. + */ + public static String unquoteSuffix(String quoted) { + return quoted.substring(0, quoted.length() - 1); + } + + /** + * Remove leading and trailing quoting from {@literal quoted}. + * + * @param quoted + * @return {@literal quoted} with removed quotes. + */ + public static String unquote(String quoted) { + return quoted.substring(1, quoted.length() - 1); + } + } } 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 86964b0c0..a66dcfeea 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 @@ -361,9 +361,9 @@ public class StringBasedMongoQueryUnitTests { public void shouldQuoteStringReplacementCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews', password: 'foo"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews', password: 'foo"); - 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(not(new Document().append("lastname", "Matthews").append("password", "foo")))); assertThat(query.getQueryObject(), is(new Document("lastname", "Matthews', password: 'foo"))); @@ -373,9 +373,9 @@ public class StringBasedMongoQueryUnitTests { public void shouldQuoteStringReplacementContainingQuotesCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews\", password: \"foo"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews\", password: \"foo"); - 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(not(new Document().append("lastname", "Matthews").append("password", "foo")))); assertThat(query.getQueryObject(), is(new Document("lastname", "Matthews\", password: \"foo"))); @@ -385,20 +385,20 @@ public class StringBasedMongoQueryUnitTests { public void shouldQuoteStringReplacementWithQuotationsCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "\"Dave Matthews\", password: 'foo"); - 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(new Document("lastname", "\"Dave Matthews\", password: 'foo"))); } @Test // DATAMONGO-1565, DATAMONGO-1575 - public void shouldQuoteComplexQueryStringCorreclty() throws Exception { + public void shouldQuoteComplexQueryStringCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"calamity\" }"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"calamity\" }"); - 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(new Document("lastname", "{ $ne : \"calamity\" }"))); } @@ -406,18 +406,15 @@ public class StringBasedMongoQueryUnitTests { public void shouldQuotationInQuotedComplexQueryString() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"\\\"calamity\\\"\" }"); - 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(new Document("lastname", "{ $ne : \"\\\"calamity\\\"\" }"))); } - /** - * @see DATAMONGO-1575 - */ - @Test + @Test // DATAMONGO-1575 public void shouldTakeBsonParameterAsIs() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByWithBsonArgument", Document.class); @@ -428,10 +425,7 @@ public class StringBasedMongoQueryUnitTests { assertThat(query.getQueryObject(), is(new Document("arg0", new BsonRegularExpression("^calamity$")))); } - /** - * @see DATAMONGO-1575 - */ - @Test + @Test // DATAMONGO-1575 public void shouldReplaceParametersInInQuotedExpressionOfNestedQueryOperator() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameRegex", String.class); diff --git a/src/main/asciidoc/reference/mongo-repositories.adoc b/src/main/asciidoc/reference/mongo-repositories.adoc index d0d4b289b..80f8c2f1f 100644 --- a/src/main/asciidoc/reference/mongo-repositories.adoc +++ b/src/main/asciidoc/reference/mongo-repositories.adoc @@ -401,7 +401,6 @@ public interface PersonRepository extends MongoRepository @Query("{'lastname': ?#{[0]} }") List findByQueryWithExpression(String param0); - } ---- @@ -414,7 +413,6 @@ public interface PersonRepository extends MongoRepository @Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}") List findByQueryWithExpressionAndNestedObject(boolean param0, String param1); - } ----