@ -15,8 +15,13 @@
@@ -15,8 +15,13 @@
* /
package org.springframework.data.mongodb.repository.query ;
import java.util.Collections ;
import java.util.ArrayList ;
import java.util.LinkedHashMap ;
import java.util.List ;
import java.util.Map ;
import java.util.NoSuchElementException ;
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
import javax.xml.bind.DatatypeConverter ;
@ -94,42 +99,62 @@ class ExpressionEvaluatingParameterBinder {
@@ -94,42 +99,62 @@ class ExpressionEvaluatingParameterBinder {
return input ;
}
boolean isCompletlyParameterizedQuery = input . matches ( "^\\?\\d+$" ) ;
StringBuilder result = new StringBuilder ( input ) ;
for ( ParameterBinding binding : bindingContext . getBindings ( ) ) {
if ( input . matches ( "^\\?\\d+$" ) ) {
return getParameterValueForBinding ( accessor , bindingContext . getParameters ( ) ,
bindingContext . getBindings ( ) . iterator ( ) . next ( ) ) ;
}
String parameter = binding . getParameter ( ) ;
int idx = result . indexOf ( parameter ) ;
Matcher matcher = createReplacementPattern ( bindingContext . getBindings ( ) ) . matcher ( input ) ;
StringBuffer buffer = new StringBuffer ( ) ;
if ( idx = = - 1 ) {
continue ;
}
while ( matcher . find ( ) ) {
ParameterBinding binding = bindingContext . getBindingFor ( extractPlaceholder ( matcher . group ( ) ) ) ;
String valueForBinding = getParameterValueForBinding ( accessor , bindingContext . getParameters ( ) , binding ) ;
int start = idx ;
int end = idx + parameter . length ( ) ;
// appendReplacement does not like unescaped $ sign and others, so we need to quote that stuff first
matcher . appendReplacement ( buffer , Matcher . quoteReplacement ( valueForBinding ) ) ;
if ( binding . isQuoted ( ) ) {
postProcessQuotedBinding ( buffer , valueForBinding ) ;
}
}
// If the value to bind is an object literal we need to remove the quoting around the expression insertion point.
if ( valueForBinding . startsWith ( "{" ) & & ! isCompletlyParameterizedQuery ) {
matcher . appendTail ( buffer ) ;
return buffer . toString ( ) ;
}
/ * *
* Sanitize String binding by replacing single quoted values with double quotes which prevents potential single quotes
* contained in replacement to interfere with the Json parsing . Also take care of complex objects by removing the
* quotation entirely .
*
* @param buffer the { @link StringBuffer } to operate upon .
* @param valueForBinding the actual binding value .
* /
private void postProcessQuotedBinding ( StringBuffer buffer , String valueForBinding ) {
// Is the insertion point actually surrounded by quotes?
char beforeStart = result . charAt ( start - 1 ) ;
char afterEnd = result . charAt ( end ) ;
int quotationMarkIndex = buffer . length ( ) - valueForBinding . length ( ) - 1 ;
char quotationMark = buffer . charAt ( quotationMarkIndex ) ;
if ( ( beforeStart = = '\'' | | beforeStart = = '"' ) & & ( afterEnd = = '\'' | | afterEnd = = '"' ) ) {
while ( quotationMark ! = '\'' & & quotationMark ! = '"' ) {
// Skip preceding and following quote
start - = 1 ;
end + = 1 ;
quotationMarkIndex - - ;
if ( quotationMarkIndex < 0 ) {
throw new IllegalArgumentException ( "Could not find opening quotes for quoted parameter" ) ;
}
quotationMark = buffer . charAt ( quotationMarkIndex ) ;
}
result . replace ( start , end , valueForBinding ) ;
}
if ( valueForBinding . startsWith ( "{" ) ) { // remove quotation char before the complex object string
buffer . deleteCharAt ( quotationMarkIndex ) ;
} else {
return result . toString ( ) ;
if ( quotationMark = = '\'' ) {
buffer . replace ( quotationMarkIndex , quotationMarkIndex + 1 , "\"" ) ;
}
buffer . append ( "\"" ) ;
}
}
/ * *
@ -148,7 +173,7 @@ class ExpressionEvaluatingParameterBinder {
@@ -148,7 +173,7 @@ class ExpressionEvaluatingParameterBinder {
: accessor . getBindableValue ( binding . getParameterIndex ( ) ) ;
if ( value instanceof String & & binding . isQuoted ( ) ) {
return ( String ) value ;
return ( ( String ) value ) . startsWith ( "{" ) ? ( String ) value : ( ( String ) value ) . replace ( "\"" , "\\\"" ) ;
}
if ( value instanceof byte [ ] ) {
@ -156,7 +181,7 @@ class ExpressionEvaluatingParameterBinder {
@@ -156,7 +181,7 @@ class ExpressionEvaluatingParameterBinder {
String base64representation = DatatypeConverter . printBase64Binary ( ( byte [ ] ) value ) ;
if ( ! binding . isQuoted ( ) ) {
return "{ '$binary' : '" + base64representation + "', '$type' : " + BSON . B_GENERAL + " }";
return "{ '$binary' : '" + base64representation + "', '$type' : '" + BSON . B_GENERAL + "' }";
}
return base64representation ;
@ -181,6 +206,40 @@ class ExpressionEvaluatingParameterBinder {
@@ -181,6 +206,40 @@ class ExpressionEvaluatingParameterBinder {
return expression . getValue ( evaluationContext , Object . class ) ;
}
/ * *
* Creates a replacement { @link Pattern } for all { @link ParameterBinding # getParameter ( ) binding parameters } including
* a potentially trailing quotation mark .
*
* @param bindings
* @return
* /
private Pattern createReplacementPattern ( List < ParameterBinding > bindings ) {
StringBuilder regex = new StringBuilder ( ) ;
for ( ParameterBinding binding : bindings ) {
regex . append ( "|" ) ;
regex . append ( Pattern . quote ( binding . getParameter ( ) ) ) ;
regex . append ( "['\"]?" ) ; // potential quotation char (as in { foo : '?0' }).
}
return Pattern . compile ( regex . substring ( 1 ) ) ;
}
/ * *
* Extract the placeholder stripping any trailing trailing quotation mark that might have resulted from the
* { @link # createReplacementPattern ( List ) pattern } used .
*
* @param groupName The actual { @link Matcher # group ( ) group } .
* @return
* /
private String extractPlaceholder ( String groupName ) {
if ( ! groupName . endsWith ( "'" ) & & ! groupName . endsWith ( "\"" ) ) {
return groupName ;
}
return groupName . substring ( 0 , groupName . length ( ) - 1 ) ;
}
/ * *
* @author Christoph Strobl
* @since 1 . 9
@ -188,7 +247,7 @@ class ExpressionEvaluatingParameterBinder {
@@ -188,7 +247,7 @@ class ExpressionEvaluatingParameterBinder {
static class BindingContext {
final MongoParameters parameters ;
final List < ParameterBinding > bindings ;
final Map < String , ParameterBinding > bindings ;
/ * *
* Creates new { @link BindingContext } .
@ -199,7 +258,7 @@ class ExpressionEvaluatingParameterBinder {
@@ -199,7 +258,7 @@ class ExpressionEvaluatingParameterBinder {
public BindingContext ( MongoParameters parameters , List < ParameterBinding > bindings ) {
this . parameters = parameters ;
this . bindings = bindings ;
this . bindings = mapBindings ( bindings ) ;
}
/ * *
@ -215,7 +274,24 @@ class ExpressionEvaluatingParameterBinder {
@@ -215,7 +274,24 @@ class ExpressionEvaluatingParameterBinder {
* @return never { @literal null } .
* /
public List < ParameterBinding > getBindings ( ) {
return Collections . unmodifiableList ( bindings ) ;
return new ArrayList < ParameterBinding > ( bindings . values ( ) ) ;
}
/ * *
* 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 ) {
if ( ! bindings . containsKey ( placeholder ) ) {
throw new NoSuchElementException ( String . format ( "Could not to find binding for placeholder '%s'." , placeholder ) ) ;
}
return bindings . get ( placeholder ) ;
}
/ * *
@ -227,5 +303,13 @@ class ExpressionEvaluatingParameterBinder {
@@ -227,5 +303,13 @@ class ExpressionEvaluatingParameterBinder {
return parameters ;
}
private static Map < String , ParameterBinding > mapBindings ( List < ParameterBinding > bindings ) {
Map < String , ParameterBinding > map = new LinkedHashMap < String , ParameterBinding > ( bindings . size ( ) , 1 ) ;
for ( ParameterBinding binding : bindings ) {
map . put ( binding . getParameter ( ) , binding ) ;
}
return map ;
}
}
}