17
17
18
18
import java .util .ArrayList ;
19
19
import java .util .Collections ;
20
+ import java .util .LinkedHashMap ;
20
21
import java .util .List ;
22
+ import java .util .Map ;
21
23
import java .util .concurrent .TimeUnit ;
24
+ import java .util .regex .Matcher ;
25
+ import java .util .regex .Pattern ;
22
26
27
+ import org .slf4j .Logger ;
28
+ import org .slf4j .LoggerFactory ;
23
29
import org .springframework .core .annotation .AnnotationUtils ;
24
30
import org .springframework .dao .InvalidDataAccessApiUsageException ;
25
31
import org .springframework .data .domain .Sort ;
48
54
*/
49
55
public class MongoPersistentEntityIndexResolver implements IndexResolver {
50
56
57
+ private static final Logger LOGGER = LoggerFactory .getLogger (MongoPersistentEntityIndexResolver .class );
58
+
51
59
private final MongoMappingContext mappingContext ;
52
60
53
61
/**
@@ -88,14 +96,16 @@ public List<IndexDefinitionHolder> resolveIndexForEntity(final MongoPersistentEn
88
96
final List <IndexDefinitionHolder > indexInformation = new ArrayList <MongoPersistentEntityIndexResolver .IndexDefinitionHolder >();
89
97
indexInformation .addAll (potentiallyCreateCompoundIndexDefinitions ("" , root .getCollection (), root .getType ()));
90
98
99
+ final CycleGuard guard = new CycleGuard ();
100
+
91
101
root .doWithProperties (new PropertyHandler <MongoPersistentProperty >() {
92
102
93
103
@ Override
94
104
public void doWithPersistentProperty (MongoPersistentProperty persistentProperty ) {
95
105
96
106
if (persistentProperty .isEntity ()) {
97
107
indexInformation .addAll (resolveIndexForClass (persistentProperty .getActualType (),
98
- persistentProperty .getFieldName (), root .getCollection ()));
108
+ persistentProperty .getFieldName (), root .getCollection (), guard ));
99
109
}
100
110
101
111
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty (
@@ -118,7 +128,8 @@ public void doWithPersistentProperty(MongoPersistentProperty persistentProperty)
118
128
* @return List of {@link IndexDefinitionHolder} representing indexes for given type and its referenced property
119
129
* types. Will never be {@code null}.
120
130
*/
121
- private List <IndexDefinitionHolder > resolveIndexForClass (Class <?> type , final String path , final String collection ) {
131
+ private List <IndexDefinitionHolder > resolveIndexForClass (final Class <?> type , final String path ,
132
+ final String collection , final CycleGuard guard ) {
122
133
123
134
final List <IndexDefinitionHolder > indexInformation = new ArrayList <MongoPersistentEntityIndexResolver .IndexDefinitionHolder >();
124
135
indexInformation .addAll (potentiallyCreateCompoundIndexDefinitions (path , collection , type ));
@@ -130,9 +141,15 @@ private List<IndexDefinitionHolder> resolveIndexForClass(Class<?> type, final St
130
141
public void doWithPersistentProperty (MongoPersistentProperty persistentProperty ) {
131
142
132
143
String propertyDotPath = (StringUtils .hasText (path ) ? path + "." : "" ) + persistentProperty .getFieldName ();
144
+ guard .protect (persistentProperty , path );
133
145
134
146
if (persistentProperty .isEntity ()) {
135
- indexInformation .addAll (resolveIndexForClass (persistentProperty .getActualType (), propertyDotPath , collection ));
147
+ try {
148
+ indexInformation .addAll (resolveIndexForClass (persistentProperty .getActualType (), propertyDotPath ,
149
+ collection , guard ));
150
+ } catch (CyclicPropertyReferenceException e ) {
151
+ LOGGER .warn (e .getMessage ());
152
+ }
136
153
}
137
154
138
155
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty (propertyDotPath ,
@@ -320,6 +337,115 @@ protected IndexDefinitionHolder createGeoSpatialIndexDefinition(String dotPath,
320
337
return new IndexDefinitionHolder (dotPath , indexDefinition , collection );
321
338
}
322
339
340
+ /**
341
+ * {@link CycleGuard} holds information about properties and the paths for accessing those. This information is used
342
+ * to detect potential cycles within the references.
343
+ *
344
+ * @author Christoph Strobl
345
+ */
346
+ private static class CycleGuard {
347
+
348
+ private final Map <String , List <Path >> propertyTypeMap ;
349
+
350
+ CycleGuard () {
351
+ this .propertyTypeMap = new LinkedHashMap <String , List <Path >>();
352
+ }
353
+
354
+ /**
355
+ * @param property The property to inspect
356
+ * @param path The path under which the property can be reached.
357
+ * @throws CyclicPropertyReferenceException in case a potential cycle is detected.
358
+ */
359
+ void protect (MongoPersistentProperty property , String path ) throws CyclicPropertyReferenceException {
360
+
361
+ String propertyTypeKey = createMapKey (property );
362
+ if (propertyTypeMap .containsKey (propertyTypeKey )) {
363
+
364
+ List <Path > paths = propertyTypeMap .get (propertyTypeKey );
365
+
366
+ for (Path existingPath : paths ) {
367
+
368
+ if (existingPath .cycles (property )) {
369
+ paths .add (new Path (property , path ));
370
+ throw new CyclicPropertyReferenceException (property .getFieldName (), property .getOwner ().getType (),
371
+ existingPath .getPath ());
372
+ }
373
+ }
374
+
375
+ paths .add (new Path (property , path ));
376
+
377
+ } else {
378
+
379
+ ArrayList <Path > paths = new ArrayList <Path >();
380
+ paths .add (new Path (property , path ));
381
+ propertyTypeMap .put (propertyTypeKey , paths );
382
+ }
383
+ }
384
+
385
+ private String createMapKey (MongoPersistentProperty property ) {
386
+ return property .getOwner ().getType ().getSimpleName () + ":" + property .getFieldName ();
387
+ }
388
+
389
+ private static class Path {
390
+
391
+ private final MongoPersistentProperty property ;
392
+ private final String path ;
393
+
394
+ Path (MongoPersistentProperty property , String path ) {
395
+
396
+ this .property = property ;
397
+ this .path = path ;
398
+ }
399
+
400
+ public String getPath () {
401
+ return path ;
402
+ }
403
+
404
+ boolean cycles (MongoPersistentProperty property ) {
405
+
406
+ Pattern pattern = Pattern .compile ("\\ p{Punct}?" + Pattern .quote (property .getFieldName ()) + "(\\ p{Punct}|\\ w)?" );
407
+ Matcher matcher = pattern .matcher (path );
408
+
409
+ int count = 0 ;
410
+ while (matcher .find ()) {
411
+ count ++;
412
+ }
413
+
414
+ return count >= 1 && property .getOwner ().getType ().equals (this .property .getOwner ().getType ());
415
+ }
416
+ }
417
+ }
418
+
419
+ /**
420
+ * @author Christoph Strobl
421
+ * @since 1.5
422
+ */
423
+ public static class CyclicPropertyReferenceException extends RuntimeException {
424
+
425
+ private static final long serialVersionUID = -3762979307658772277L ;
426
+
427
+ private final String propertyName ;
428
+ private final Class <?> type ;
429
+ private final String dotPath ;
430
+
431
+ public CyclicPropertyReferenceException (String propertyName , Class <?> type , String dotPath ) {
432
+
433
+ this .propertyName = propertyName ;
434
+ this .type = type ;
435
+ this .dotPath = dotPath ;
436
+ }
437
+
438
+ /*
439
+ * (non-Javadoc)
440
+ * @see java.lang.Throwable#getMessage()
441
+ */
442
+ @ Override
443
+ public String getMessage () {
444
+ return String .format ("Found cycle for field '%s' in type '%s' for path '%s'" , propertyName , type .getSimpleName (),
445
+ dotPath );
446
+ }
447
+ }
448
+
323
449
/**
324
450
* Implementation of {@link IndexDefinition} holding additional (property)path information used for creating the
325
451
* index. The path itself is the properties {@literal "dot"} path representation from its root document.
@@ -329,9 +455,9 @@ protected IndexDefinitionHolder createGeoSpatialIndexDefinition(String dotPath,
329
455
*/
330
456
public static class IndexDefinitionHolder implements IndexDefinition {
331
457
332
- private String path ;
333
- private IndexDefinition indexDefinition ;
334
- private String collection ;
458
+ private final String path ;
459
+ private final IndexDefinition indexDefinition ;
460
+ private final String collection ;
335
461
336
462
/**
337
463
* Create
0 commit comments