@ -17,9 +17,15 @@ package org.springframework.data.mongodb.core.index;
import java.util.ArrayList ;
import java.util.ArrayList ;
import java.util.Collections ;
import java.util.Collections ;
import java.util.LinkedHashMap ;
import java.util.List ;
import java.util.List ;
import java.util.Map ;
import java.util.concurrent.TimeUnit ;
import java.util.concurrent.TimeUnit ;
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import org.springframework.core.annotation.AnnotationUtils ;
import org.springframework.core.annotation.AnnotationUtils ;
import org.springframework.dao.InvalidDataAccessApiUsageException ;
import org.springframework.dao.InvalidDataAccessApiUsageException ;
import org.springframework.data.domain.Sort ;
import org.springframework.data.domain.Sort ;
@ -48,6 +54,8 @@ import com.mongodb.util.JSON;
* /
* /
public class MongoPersistentEntityIndexResolver implements IndexResolver {
public class MongoPersistentEntityIndexResolver implements IndexResolver {
private static final Logger LOGGER = LoggerFactory . getLogger ( MongoPersistentEntityIndexResolver . class ) ;
private final MongoMappingContext mappingContext ;
private final MongoMappingContext mappingContext ;
/ * *
/ * *
@ -88,6 +96,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
final List < IndexDefinitionHolder > indexInformation = new ArrayList < MongoPersistentEntityIndexResolver . IndexDefinitionHolder > ( ) ;
final List < IndexDefinitionHolder > indexInformation = new ArrayList < MongoPersistentEntityIndexResolver . IndexDefinitionHolder > ( ) ;
indexInformation . addAll ( potentiallyCreateCompoundIndexDefinitions ( "" , root . getCollection ( ) , root . getType ( ) ) ) ;
indexInformation . addAll ( potentiallyCreateCompoundIndexDefinitions ( "" , root . getCollection ( ) , root . getType ( ) ) ) ;
final CycleGuard guard = new CycleGuard ( ) ;
root . doWithProperties ( new PropertyHandler < MongoPersistentProperty > ( ) {
root . doWithProperties ( new PropertyHandler < MongoPersistentProperty > ( ) {
@Override
@Override
@ -95,7 +105,7 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
if ( persistentProperty . isEntity ( ) ) {
if ( persistentProperty . isEntity ( ) ) {
indexInformation . addAll ( resolveIndexForClass ( persistentProperty . getActualType ( ) ,
indexInformation . addAll ( resolveIndexForClass ( persistentProperty . getActualType ( ) ,
persistentProperty . getFieldName ( ) , root . getCollection ( ) ) ) ;
persistentProperty . getFieldName ( ) , root . getCollection ( ) , guard ) ) ;
}
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty (
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty (
@ -118,7 +128,8 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
* @return List of { @link IndexDefinitionHolder } representing indexes for given type and its referenced property
* @return List of { @link IndexDefinitionHolder } representing indexes for given type and its referenced property
* types . Will never be { @code null } .
* types . Will never be { @code null } .
* /
* /
private List < IndexDefinitionHolder > resolveIndexForClass ( Class < ? > type , final String path , final String collection ) {
private List < IndexDefinitionHolder > resolveIndexForClass ( final Class < ? > type , final String path ,
final String collection , final CycleGuard guard ) {
final List < IndexDefinitionHolder > indexInformation = new ArrayList < MongoPersistentEntityIndexResolver . IndexDefinitionHolder > ( ) ;
final List < IndexDefinitionHolder > indexInformation = new ArrayList < MongoPersistentEntityIndexResolver . IndexDefinitionHolder > ( ) ;
indexInformation . addAll ( potentiallyCreateCompoundIndexDefinitions ( path , collection , type ) ) ;
indexInformation . addAll ( potentiallyCreateCompoundIndexDefinitions ( path , collection , type ) ) ;
@ -130,9 +141,15 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
public void doWithPersistentProperty ( MongoPersistentProperty persistentProperty ) {
public void doWithPersistentProperty ( MongoPersistentProperty persistentProperty ) {
String propertyDotPath = ( StringUtils . hasText ( path ) ? path + "." : "" ) + persistentProperty . getFieldName ( ) ;
String propertyDotPath = ( StringUtils . hasText ( path ) ? path + "." : "" ) + persistentProperty . getFieldName ( ) ;
guard . protect ( persistentProperty , path ) ;
if ( persistentProperty . isEntity ( ) ) {
if ( persistentProperty . isEntity ( ) ) {
indexInformation . addAll ( resolveIndexForClass ( persistentProperty . getActualType ( ) , propertyDotPath , collection ) ) ;
try {
indexInformation . addAll ( resolveIndexForClass ( persistentProperty . getActualType ( ) , propertyDotPath ,
collection , guard ) ) ;
} catch ( CyclicPropertyReferenceException e ) {
LOGGER . warn ( e . getMessage ( ) ) ;
}
}
}
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty ( propertyDotPath ,
IndexDefinitionHolder indexDefinitionHolder = createIndexDefinitionHolderForProperty ( propertyDotPath ,
@ -320,6 +337,115 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
return new IndexDefinitionHolder ( dotPath , indexDefinition , collection ) ;
return new IndexDefinitionHolder ( dotPath , indexDefinition , collection ) ;
}
}
/ * *
* { @link CycleGuard } holds information about properties and the paths for accessing those . This information is used
* to detect potential cycles within the references .
*
* @author Christoph Strobl
* /
private static class CycleGuard {
private final Map < String , List < Path > > propertyTypeMap ;
CycleGuard ( ) {
this . propertyTypeMap = new LinkedHashMap < String , List < Path > > ( ) ;
}
/ * *
* @param property The property to inspect
* @param path The path under which the property can be reached .
* @throws CyclicPropertyReferenceException in case a potential cycle is detected .
* /
void protect ( MongoPersistentProperty property , String path ) throws CyclicPropertyReferenceException {
String propertyTypeKey = createMapKey ( property ) ;
if ( propertyTypeMap . containsKey ( propertyTypeKey ) ) {
List < Path > paths = propertyTypeMap . get ( propertyTypeKey ) ;
for ( Path existingPath : paths ) {
if ( existingPath . cycles ( property ) ) {
paths . add ( new Path ( property , path ) ) ;
throw new CyclicPropertyReferenceException ( property . getFieldName ( ) , property . getOwner ( ) . getType ( ) ,
existingPath . getPath ( ) ) ;
}
}
paths . add ( new Path ( property , path ) ) ;
} else {
ArrayList < Path > paths = new ArrayList < Path > ( ) ;
paths . add ( new Path ( property , path ) ) ;
propertyTypeMap . put ( propertyTypeKey , paths ) ;
}
}
private String createMapKey ( MongoPersistentProperty property ) {
return property . getOwner ( ) . getType ( ) . getSimpleName ( ) + ":" + property . getFieldName ( ) ;
}
private static class Path {
private final MongoPersistentProperty property ;
private final String path ;
Path ( MongoPersistentProperty property , String path ) {
this . property = property ;
this . path = path ;
}
public String getPath ( ) {
return path ;
}
boolean cycles ( MongoPersistentProperty property ) {
Pattern pattern = Pattern . compile ( "\\p{Punct}?" + Pattern . quote ( property . getFieldName ( ) ) + "(\\p{Punct}|\\w)?" ) ;
Matcher matcher = pattern . matcher ( path ) ;
int count = 0 ;
while ( matcher . find ( ) ) {
count + + ;
}
return count > = 1 & & property . getOwner ( ) . getType ( ) . equals ( this . property . getOwner ( ) . getType ( ) ) ;
}
}
}
/ * *
* @author Christoph Strobl
* @since 1 . 5
* /
public static class CyclicPropertyReferenceException extends RuntimeException {
private static final long serialVersionUID = - 3762979307658772277L ;
private final String propertyName ;
private final Class < ? > type ;
private final String dotPath ;
public CyclicPropertyReferenceException ( String propertyName , Class < ? > type , String dotPath ) {
this . propertyName = propertyName ;
this . type = type ;
this . dotPath = dotPath ;
}
/ *
* ( non - Javadoc )
* @see java . lang . Throwable # getMessage ( )
* /
@Override
public String getMessage ( ) {
return String . format ( "Found cycle for field '%s' in type '%s' for path '%s'" , propertyName , type . getSimpleName ( ) ,
dotPath ) ;
}
}
/ * *
/ * *
* Implementation of { @link IndexDefinition } holding additional ( property ) path information used for creating the
* Implementation of { @link IndexDefinition } holding additional ( property ) path information used for creating the
* index . The path itself is the properties { @literal "dot" } path representation from its root document .
* index . The path itself is the properties { @literal "dot" } path representation from its root document .
@ -329,9 +455,9 @@ public class MongoPersistentEntityIndexResolver implements IndexResolver {
* /
* /
public static class IndexDefinitionHolder implements IndexDefinition {
public static class IndexDefinitionHolder implements IndexDefinition {
private String path ;
private final String path ;
private IndexDefinition indexDefinition ;
private final IndexDefinition indexDefinition ;
private String collection ;
private final String collection ;
/ * *
/ * *
* Create
* Create