20
20
import java .util .Collections ;
21
21
import java .util .Iterator ;
22
22
import java .util .List ;
23
+ import java .util .Map ;
23
24
import java .util .Map .Entry ;
24
25
import java .util .Set ;
26
+ import java .util .regex .Pattern ;
25
27
26
28
import org .bson .types .ObjectId ;
27
29
import org .springframework .core .convert .ConversionException ;
28
30
import org .springframework .core .convert .ConversionService ;
29
31
import org .springframework .core .convert .converter .Converter ;
32
+ import org .springframework .data .domain .Example ;
33
+ import org .springframework .data .domain .Example .ObjectMatchMode ;
34
+ import org .springframework .data .domain .Example .StringMatchMode ;
30
35
import org .springframework .data .mapping .Association ;
31
36
import org .springframework .data .mapping .PersistentEntity ;
32
37
import org .springframework .data .mapping .PropertyPath ;
39
44
import org .springframework .data .mongodb .core .mapping .MongoPersistentProperty ;
40
45
import org .springframework .data .mongodb .core .mapping .MongoPersistentProperty .PropertyToFieldNameConverter ;
41
46
import org .springframework .data .mongodb .core .query .Query ;
47
+ import org .springframework .data .mongodb .core .query .SerializationUtils ;
42
48
import org .springframework .data .util .ClassTypeInformation ;
43
49
import org .springframework .data .util .TypeInformation ;
44
50
import org .springframework .util .Assert ;
51
+ import org .springframework .util .ObjectUtils ;
45
52
46
53
import com .mongodb .BasicDBList ;
47
54
import com .mongodb .BasicDBObject ;
@@ -239,9 +246,93 @@ protected DBObject getMappedKeyword(Keyword keyword, MongoPersistentEntity<?> en
239
246
return new BasicDBObject (keyword .getKey (), newConditions );
240
247
}
241
248
249
+ if (keyword .isSample ()) {
250
+ return getMappedExample (keyword .<Example <?>> getValue (), entity );
251
+ }
252
+
242
253
return new BasicDBObject (keyword .getKey (), convertSimpleOrDBObject (keyword .getValue (), entity ));
243
254
}
244
255
256
+ /**
257
+ * Returns the given {@link Example} as {@link DBObject} holding matching values extracted from
258
+ * {@link Example#getProbe()}.
259
+ *
260
+ * @param example
261
+ * @param entity
262
+ * @return
263
+ * @since 1.8
264
+ */
265
+ protected DBObject getMappedExample (Example <?> example , MongoPersistentEntity <?> entity ) {
266
+
267
+ DBObject reference = (DBObject ) converter .convertToMongoType (example .getProbe ());
268
+
269
+ if (entity .hasIdProperty () && entity .getIdentifierAccessor (example .getProbe ()).getIdentifier () == null ) {
270
+ reference .removeField (entity .getIdProperty ().getFieldName ());
271
+ }
272
+
273
+ if (!ObjectUtils .nullSafeEquals (StringMatchMode .DEFAULT , example .getStringMatchMode ())
274
+ || example .isIngnoreCaseEnabled ()) {
275
+ applyStringPattern (reference , example );
276
+ }
277
+
278
+ return ObjectUtils .nullSafeEquals (ObjectMatchMode .STRICT , example .getObjectMatchMode ()) ? reference
279
+ : new BasicDBObject (SerializationUtils .flatMap (reference ));
280
+ }
281
+
282
+ private void applyStringPattern (DBObject source , Example <?> example ) {
283
+
284
+ if (!(source instanceof BasicDBObject )) {
285
+ return ;
286
+ }
287
+ Iterator <Map .Entry <String , Object >> iter = ((BasicDBObject ) source ).entrySet ().iterator ();
288
+
289
+ while (iter .hasNext ()) {
290
+
291
+ Map .Entry <String , Object > entry = iter .next ();
292
+ if (entry .getValue () instanceof String ) {
293
+
294
+ // TODO: extract common stuff from MongoQueryCreator
295
+ BasicDBObject dbo = new BasicDBObject ();
296
+ switch (example .getStringMatchMode ()) {
297
+
298
+ case REGEX :
299
+ dbo .put ("$regex" , entry .getValue ());
300
+ entry .setValue (dbo );
301
+ break ;
302
+ case DEFAULT :
303
+ dbo .put ("$regex" , Pattern .quote ((String ) entry .getValue ()));
304
+ entry .setValue (dbo );
305
+ break ;
306
+ case CONTAINING :
307
+ dbo .put ("$regex" , ".*" + entry .getValue () + ".*" );
308
+ entry .setValue (dbo );
309
+ break ;
310
+ case STARTING :
311
+ dbo .put ("$regex" , "^" + entry .getValue ());
312
+ entry .setValue (dbo );
313
+ break ;
314
+ case ENDING :
315
+ dbo .put ("$regex" , entry .getValue () + "$" );
316
+ entry .setValue (dbo );
317
+ break ;
318
+ case EXACT :
319
+ dbo .put ("$regex" , "^" + entry .getValue () + "$" );
320
+ entry .setValue (dbo );
321
+ break ;
322
+ default :
323
+ }
324
+
325
+ // sometimes order matters in MongoDB so make sure to add $options after $regex.
326
+ if (example .isIngnoreCaseEnabled ()) {
327
+ dbo .put ("$options" , "i" );
328
+ }
329
+
330
+ } else if (entry .getValue () instanceof BasicDBObject ) {
331
+ applyStringPattern ((BasicDBObject ) entry .getValue (), example );
332
+ }
333
+ }
334
+ }
335
+
245
336
/**
246
337
* Returns the mapped keyword considered defining a criteria for the given property.
247
338
*
@@ -254,8 +345,8 @@ protected DBObject getMappedKeyword(Field property, Keyword keyword) {
254
345
boolean needsAssociationConversion = property .isAssociation () && !keyword .isExists ();
255
346
Object value = keyword .getValue ();
256
347
257
- Object convertedValue = needsAssociationConversion ? convertAssociation (value , property )
258
- : getMappedValue ( property .with (keyword .getKey ()), value );
348
+ Object convertedValue = needsAssociationConversion ? convertAssociation (value , property ) : getMappedValue (
349
+ property .with (keyword .getKey ()), value );
259
350
260
351
return new BasicDBObject (keyword .key , convertedValue );
261
352
}
@@ -477,8 +568,8 @@ public Object convertId(Object id) {
477
568
}
478
569
479
570
try {
480
- return conversionService .canConvert (id .getClass (), ObjectId .class ) ? conversionService . convert ( id , ObjectId . class )
481
- : delegateConvertToMongoType (id , null );
571
+ return conversionService .canConvert (id .getClass (), ObjectId .class ) ? conversionService
572
+ . convert ( id , ObjectId . class ) : delegateConvertToMongoType (id , null );
482
573
} catch (ConversionException o_O ) {
483
574
return delegateConvertToMongoType (id , null );
484
575
}
@@ -566,6 +657,16 @@ public boolean isGeometry() {
566
657
return "$geometry" .equalsIgnoreCase (key );
567
658
}
568
659
660
+ /**
661
+ * Returns wheter the current keyword indicates a sample object.
662
+ *
663
+ * @return
664
+ * @since 1.8
665
+ */
666
+ public boolean isSample () {
667
+ return "$sample" .equalsIgnoreCase (key );
668
+ }
669
+
569
670
public boolean hasIterableValue () {
570
671
return value instanceof Iterable ;
571
672
}
0 commit comments