From b9aa410ac176e4f051af7d79987387cf7c1f4b9a Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 2 Jan 2017 10:18:01 +0100 Subject: [PATCH] DATAMONGO-1575 - Polishing. Extend year range in license headers. Use MongoDB JSON serializer for String escaping. Move unquoting/quote checking to inner QuotedString utility class. Reformat code. --- .../ExpressionEvaluatingParameterBinder.java | 67 +++++++++++++++---- .../query/StringBasedMongoQueryUnitTests.java | 41 +++++------- .../reference/mongo-repositories.adoc | 2 - 3 files changed, 71 insertions(+), 39 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 279fe937c..154ecefdf 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 d7ed7737c..8d9237bcb 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 @@ -363,9 +363,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 BasicDBObjectBuilder().add("lastname", "Matthews").add("password", "foo").get()))); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews', password: 'foo"))); @@ -375,9 +375,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 BasicDBObjectBuilder().add("lastname", "Matthews").add("password", "foo").get()))); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews\", password: \"foo"))); @@ -387,54 +387,47 @@ 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((DBObject) new BasicDBObject("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); - assertThat(query.getQueryObject(), - is((DBObject) new BasicDBObject("lastname", "{ $ne : \"calamity\" }"))); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "{ $ne : \"calamity\" }"))); } @Test // DATAMONGO-1565, DATAMONGO-1575 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); - assertThat(query.getQueryObject(), - is((DBObject) new BasicDBObject("lastname", "{ $ne : \"\\\"calamity\\\"\" }"))); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "{ $ne : \"\\\"calamity\\\"\" }"))); } - /** - * @see DATAMONGO-1575 - */ - @Test + @Test // DATAMONGO-1575 public void shouldTakeBsonParameterAsIs() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByWithBsonArgument", DBObject.class); - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new BasicDBObject("$regex", "^calamity$")); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, + new BasicDBObject("$regex", "^calamity$")); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("arg0", Pattern.compile("^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 e5a8a7cd7..40d12e2ac 100644 --- a/src/main/asciidoc/reference/mongo-repositories.adoc +++ b/src/main/asciidoc/reference/mongo-repositories.adoc @@ -399,7 +399,6 @@ public interface PersonRepository extends MongoRepository @Query("{'lastname': ?#{[0]} }") List findByQueryWithExpression(String param0); - } ---- @@ -412,7 +411,6 @@ public interface PersonRepository extends MongoRepository @Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}") List findByQueryWithExpressionAndNestedObject(boolean param0, String param1); - } ----