15
15
*/
16
16
package org .springframework .data .mongodb .repository .query ;
17
17
18
+ import static java .util .regex .Pattern .*;
19
+
20
+ import java .util .ArrayList ;
21
+ import java .util .Collections ;
22
+ import java .util .List ;
18
23
import java .util .regex .Matcher ;
19
24
import java .util .regex .Pattern ;
20
25
23
28
import org .springframework .data .mongodb .core .MongoOperations ;
24
29
import org .springframework .data .mongodb .core .query .BasicQuery ;
25
30
import org .springframework .data .mongodb .core .query .Query ;
31
+ import org .springframework .util .StringUtils ;
26
32
27
33
import com .mongodb .util .JSON ;
28
34
31
37
*
32
38
* @author Oliver Gierke
33
39
* @author Christoph Strobl
40
+ * @author Thomas Darimont
34
41
*/
35
42
public class StringBasedMongoQuery extends AbstractMongoQuery {
36
43
37
44
private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!" ;
38
- private static final Pattern PLACEHOLDER = Pattern .compile ("\\ ?(\\ d+)" );
39
45
private static final Logger LOG = LoggerFactory .getLogger (StringBasedMongoQuery .class );
40
46
41
47
private final String query ;
42
48
private final String fieldSpec ;
43
49
private final boolean isCountQuery ;
44
50
private final boolean isDeleteQuery ;
51
+ private final List <ParameterBinding > queryParameterBindings ;
52
+ private final List <ParameterBinding > fieldSpecParameterBindings ;
45
53
46
54
/**
47
55
* Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod} and {@link MongoOperations}.
@@ -65,7 +73,12 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati
65
73
super (method , mongoOperations );
66
74
67
75
this .query = query ;
76
+ this .queryParameterBindings = ParameterBindingParser .INSTANCE .parseParameterBindingsFrom (query );
77
+
68
78
this .fieldSpec = method .getFieldSpecification ();
79
+ this .fieldSpecParameterBindings = ParameterBindingParser .INSTANCE .parseParameterBindingsFrom (method
80
+ .getFieldSpecification ());
81
+
69
82
this .isCountQuery = method .hasAnnotatedQuery () ? method .getQueryAnnotation ().count () : false ;
70
83
this .isDeleteQuery = method .hasAnnotatedQuery () ? method .getQueryAnnotation ().delete () : false ;
71
84
@@ -81,12 +94,12 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati
81
94
@ Override
82
95
protected Query createQuery (ConvertingParameterAccessor accessor ) {
83
96
84
- String queryString = replacePlaceholders (query , accessor );
97
+ String queryString = replacePlaceholders (query , accessor , queryParameterBindings );
85
98
86
99
Query query = null ;
87
100
88
101
if (fieldSpec != null ) {
89
- String fieldString = replacePlaceholders (fieldSpec , accessor );
102
+ String fieldString = replacePlaceholders (fieldSpec , accessor , fieldSpecParameterBindings );
90
103
query = new BasicQuery (queryString , fieldString );
91
104
} else {
92
105
query = new BasicQuery (queryString );
@@ -119,21 +132,147 @@ protected boolean isDeleteQuery() {
119
132
return this .isDeleteQuery ;
120
133
}
121
134
122
- private String replacePlaceholders (String input , ConvertingParameterAccessor accessor ) {
135
+ /**
136
+ * Replaced the parameter place-holders with the actual parameter values from the given {@link ParameterBinding}s.
137
+ *
138
+ * @param input
139
+ * @param accessor
140
+ * @param bindings
141
+ * @return
142
+ */
143
+ private String replacePlaceholders (String input , ConvertingParameterAccessor accessor , List <ParameterBinding > bindings ) {
144
+
145
+ if (bindings .isEmpty ()) {
146
+ return input ;
147
+ }
148
+
149
+ StringBuilder result = new StringBuilder (input );
150
+
151
+ for (ParameterBinding binding : bindings ) {
152
+
153
+ String parameter = binding .getParameter ();
154
+ int idx = result .indexOf (parameter );
155
+ if (idx != -1 ) {
156
+ result .replace (idx , idx + parameter .length (), getParameterValueForBinding (accessor , binding ));
157
+ }
158
+ }
159
+
160
+ return result .toString ();
161
+ }
162
+
163
+ /**
164
+ * Returns the serialized value to be used for the given {@link ParameterBinding}.
165
+ *
166
+ * @param accessor
167
+ * @param binding
168
+ * @return
169
+ */
170
+ private String getParameterValueForBinding (ConvertingParameterAccessor accessor , ParameterBinding binding ) {
123
171
124
- Matcher matcher = PLACEHOLDER .matcher (input );
125
- String result = input ;
172
+ Object value = accessor .getBindableValue (binding .getParameterIndex ());
126
173
127
- while (matcher .find ()) {
128
- String group = matcher .group ();
129
- int index = Integer .parseInt (matcher .group (1 ));
130
- result = result .replace (group , getParameterWithIndex (accessor , index ));
174
+ if (value instanceof String && binding .isQuoted ()) {
175
+ return (String ) value ;
131
176
}
132
177
133
- return result ;
178
+ return JSON . serialize ( value ) ;
134
179
}
135
180
136
- private String getParameterWithIndex (ConvertingParameterAccessor accessor , int index ) {
137
- return JSON .serialize (accessor .getBindableValue (index ));
181
+ /**
182
+ * A parser that extracts the parameter bindings from a given query string.
183
+ *
184
+ * @author Thomas Darimont
185
+ */
186
+ static enum ParameterBindingParser {
187
+
188
+ INSTANCE ;
189
+
190
+ private static final Pattern PARAMETER_BINDING_PATTERN ;
191
+
192
+ private final static int PARAMETER_INDEX_GROUP = 1 ;
193
+
194
+ static {
195
+
196
+ StringBuilder builder = new StringBuilder ();
197
+ builder .append ("\\ ?(\\ d+)" ); // position parameter and parameter index
198
+ builder .append ("[^,'\" ]*" ); // followed by non quotes, non field separators
199
+ builder .append ("[,\" '}]?" );
200
+
201
+ PARAMETER_BINDING_PATTERN = Pattern .compile (builder .toString (), CASE_INSENSITIVE );
202
+ }
203
+
204
+ /**
205
+ * Returns a list of {@link ParameterBinding}s found in the given {@code input} or an
206
+ * {@link Collections#emptyList()}.
207
+ *
208
+ * @param input
209
+ * @return
210
+ */
211
+ public List <ParameterBinding > parseParameterBindingsFrom (String input ) {
212
+
213
+ if (!StringUtils .hasText (input )) {
214
+ return Collections .emptyList ();
215
+ }
216
+
217
+ List <ParameterBinding > bindings = new ArrayList <ParameterBinding >();
218
+
219
+ Matcher matcher = PARAMETER_BINDING_PATTERN .matcher (input );
220
+
221
+ while (matcher .find ()) {
222
+
223
+ String group = matcher .group ();
224
+
225
+ boolean parameterIsQuoted = group .endsWith ("'" ) || group .endsWith ("\" " );
226
+ int parameterIndex = Integer .parseInt (matcher .group (PARAMETER_INDEX_GROUP ));
227
+
228
+ bindings .add (new ParameterBinding (parameterIndex , parameterIsQuoted ));
229
+ }
230
+
231
+ return bindings ;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * A generic parameter binding with name or position information.
237
+ *
238
+ * @author Thomas Darimont
239
+ */
240
+ static class ParameterBinding {
241
+
242
+ private final int parameterIndex ;
243
+ private final boolean quoted ;
244
+
245
+ /**
246
+ * Creates a new {@link ParameterBinding} with the given {@code parameterIndex}.
247
+ *
248
+ * @param parameterIndex
249
+ */
250
+ public ParameterBinding (int parameterIndex ) {
251
+ this (parameterIndex , false );
252
+ }
253
+
254
+ /**
255
+ * Creates a new {@link ParameterBinding} with the given {@code parameterIndex} and {@code quoted} information.
256
+ *
257
+ * @param parameterIndex
258
+ * @param quoted whether or not the parameter is already quoted.
259
+ */
260
+ public ParameterBinding (int parameterIndex , boolean quoted ) {
261
+
262
+ this .parameterIndex = parameterIndex ;
263
+ this .quoted = quoted ;
264
+ }
265
+
266
+ public boolean isQuoted () {
267
+ return quoted ;
268
+ }
269
+
270
+ public int getParameterIndex () {
271
+ return parameterIndex ;
272
+ }
273
+
274
+ public String getParameter () {
275
+ return "?" + parameterIndex ;
276
+ }
138
277
}
139
278
}
0 commit comments