@ -48,16 +48,26 @@ import org.springframework.util.ObjectUtils;
@@ -48,16 +48,26 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils ;
/ * *
* Mapper from { @link Example } to a query { @link Document } .
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Jens Schauder
* @since 1 . 8
* @see Example
* @see org . springframework . data . domain . ExampleMatcher
* @see UntypedExampleMatcher
* /
public class MongoExampleMapper {
private final MappingContext < ? extends MongoPersistentEntity < ? > , MongoPersistentProperty > mappingContext ;
private final MongoConverter converter ;
/ * *
* Create a new { @link MongoTypeMapper } given { @link MongoConverter } .
*
* @param converter must not be { @literal null } .
* /
public MongoExampleMapper ( MongoConverter converter ) {
this . converter = converter ;
@ -112,28 +122,63 @@ public class MongoExampleMapper {
@@ -112,28 +122,63 @@ public class MongoExampleMapper {
return updateTypeRestrictions ( result , example ) ;
}
private static Document orConcatenate ( Document source ) {
List < Document > foo = new ArrayList < Document > ( source . keySet ( ) . size ( ) ) ;
private void applyPropertySpecs ( String path , Document source , Class < ? > probeType ,
ExampleMatcherAccessor exampleSpecAccessor ) {
for ( String key : source . keySet ( ) ) {
foo . add ( new Document ( key , source . get ( key ) ) ) ;
if ( source = = null ) {
return ;
}
return new Document ( "$or" , foo ) ;
}
Iterator < Map . Entry < String , Object > > iter = source . entrySet ( ) . iterator ( ) ;
private Set < Class < ? > > getTypesToMatch ( Example < ? > example ) {
while ( iter . hasNext ( ) ) {
Set < Class < ? > > types = new HashSet < Class < ? > > ( ) ;
Map . Entry < String , Object > entry = iter . next ( ) ;
String propertyPath = StringUtils . hasText ( path ) ? path + "." + entry . getKey ( ) : entry . getKey ( ) ;
String mappedPropertyPath = getMappedPropertyPath ( propertyPath , probeType ) ;
for ( TypeInformation < ? > reference : mappingContext . getManagedTypes ( ) ) {
if ( example . getProbeType ( ) . isAssignableFrom ( reference . getType ( ) ) ) {
types . add ( reference . getType ( ) ) ;
if ( isEmptyIdProperty ( entry ) ) {
iter . remove ( ) ;
continue ;
}
}
return types ;
if ( exampleSpecAccessor . isIgnoredPath ( propertyPath ) | | exampleSpecAccessor . isIgnoredPath ( mappedPropertyPath ) ) {
iter . remove ( ) ;
continue ;
}
StringMatcher stringMatcher = exampleSpecAccessor . getDefaultStringMatcher ( ) ;
Object value = entry . getValue ( ) ;
boolean ignoreCase = exampleSpecAccessor . isIgnoreCaseEnabled ( ) ;
if ( exampleSpecAccessor . hasPropertySpecifiers ( ) ) {
mappedPropertyPath = exampleSpecAccessor . hasPropertySpecifier ( propertyPath ) ? propertyPath
: getMappedPropertyPath ( propertyPath , probeType ) ;
stringMatcher = exampleSpecAccessor . getStringMatcherForPath ( mappedPropertyPath ) ;
ignoreCase = exampleSpecAccessor . isIgnoreCaseForPath ( mappedPropertyPath ) ;
}
// TODO: should a PropertySpecifier outrule the later on string matching?
if ( exampleSpecAccessor . hasPropertySpecifier ( mappedPropertyPath ) ) {
PropertyValueTransformer valueTransformer = exampleSpecAccessor . getValueTransformerForPath ( mappedPropertyPath ) ;
value = valueTransformer . convert ( value ) ;
if ( value = = null ) {
iter . remove ( ) ;
continue ;
}
entry . setValue ( value ) ;
}
if ( entry . getValue ( ) instanceof String ) {
applyStringMatcher ( entry , stringMatcher , ignoreCase ) ;
} else if ( entry . getValue ( ) instanceof Document ) {
applyPropertySpecs ( propertyPath , ( Document ) entry . getValue ( ) , probeType , exampleSpecAccessor ) ;
}
}
}
private String getMappedPropertyPath ( String path , Class < ? > probeType ) {
@ -142,9 +187,9 @@ public class MongoExampleMapper {
@@ -142,9 +187,9 @@ public class MongoExampleMapper {
Iterator < String > parts = Arrays . asList ( path . split ( "\\." ) ) . iterator ( ) ;
final Stack < MongoPersistentProperty > stack = new Stack < MongoPersistentProperty > ( ) ;
final Stack < MongoPersistentProperty > stack = new Stack < > ( ) ;
List < String > resultParts = new ArrayList < String > ( ) ;
List < String > resultParts = new ArrayList < > ( ) ;
while ( parts . hasNext ( ) ) {
@ -178,70 +223,64 @@ public class MongoExampleMapper {
@@ -178,70 +223,64 @@ public class MongoExampleMapper {
return StringUtils . collectionToDelimitedString ( resultParts , "." ) ;
}
private void applyPropertySpecs ( String path , Document source , Class < ? > probeType ,
ExampleMatcherAccessor exampleSpecAccessor ) {
if ( ! ( source instanceof Document ) ) {
return ;
}
private Document updateTypeRestrictions ( Document query , Example example ) {
Iterator < Map . Entry < String , Object > > iter = ( ( Document ) source ) . entrySet ( ) . iterator ( ) ;
Document result = new Document ( ) ;
while ( iter . hasNext ( ) ) {
if ( isTypeRestricting ( example ) ) {
Map . Entry < String , Object > entry = iter . next ( ) ;
String propertyPath = StringUtils . hasText ( path ) ? path + "." + entry . getKey ( ) : entry . getKey ( ) ;
String mappedPropertyPath = getMappedPropertyPath ( propertyPath , probeType ) ;
result . putAll ( query ) ;
this . converter . getTypeMapper ( ) . writeTypeRestrictions ( result , getTypesToMatch ( example ) ) ;
return result ;
}
if ( isEmptyIdProperty ( entry ) ) {
iter . remove ( ) ;
continue ;
for ( Map . Entry < String , Object > entry : query . entrySet ( ) ) {
if ( ! this . converter . getTypeMapper ( ) . isTypeKey ( entry . getKey ( ) ) ) {
result . put ( entry . getKey ( ) , entry . getValue ( ) ) ;
}
}
if ( exampleSpecAccessor . isIgnoredPath ( propertyPath ) | | exampleSpecAccessor . isIgnoredPath ( mappedPropertyPath ) ) {
iter . remove ( ) ;
continue ;
}
return result ;
}
StringMatcher stringMatcher = exampleSpecAccessor . getDefaultStringMatcher ( ) ;
Object value = entry . getValue ( ) ;
boolean ignoreCase = exampleSpecAccessor . isIgnoreCaseEnabled ( ) ;
private boolean isTypeRestricting ( Example example ) {
if ( exampleSpecAccessor . hasPropertySpecifiers ( ) ) {
if ( example . getMatcher ( ) instanceof UntypedExampleMatcher ) {
return false ;
}
mappedPropertyPath = exampleSpecAccessor . hasPropertySpecifier ( propertyPath ) ? propertyPath
: getMappedPropertyPath ( propertyPath , probeType ) ;
if ( example . getMatcher ( ) . getIgnoredPaths ( ) . isEmpty ( ) ) {
return true ;
}
stringMatcher = exampleSpecAccessor . getStringMatcherForPath ( mappedPropertyPath ) ;
ignoreCase = exampleSpecAccessor . isIgnoreCaseForPath ( mappedPropertyPath ) ;
for ( String path : example . getMatcher ( ) . getIgnoredPaths ( ) ) {
if ( this . converter . getTypeMapper ( ) . isTypeKey ( path ) ) {
return false ;
}
}
// TODO: should a PropertySpecifier outrule the later on string matching?
if ( exampleSpecAccessor . hasPropertySpecifier ( mappedPropertyPath ) ) {
return true ;
}
PropertyValueTransformer valueTransformer = exampleSpecAccessor . getValueTransformerForPath ( mappedPropertyPath ) ;
value = valueTransformer . convert ( value ) ;
if ( value = = null ) {
iter . remove ( ) ;
continue ;
}
private Set < Class < ? > > getTypesToMatch ( Example < ? > example ) {
entry . setValue ( value ) ;
}
Set < Class < ? > > types = new HashSet < > ( ) ;
if ( entry . getValue ( ) instanceof String ) {
applyStringMatcher ( entry , stringMatcher , ignoreCase ) ;
} else if ( entry . getValue ( ) instanceof Document ) {
applyPropertySpecs ( propertyPath , ( Document ) entry . getValue ( ) , probeType , exampleSpecAccessor ) ;
for ( TypeInformation < ? > reference : mappingContext . getManagedTypes ( ) ) {
if ( example . getProbeType ( ) . isAssignableFrom ( reference . getType ( ) ) ) {
types . add ( reference . getType ( ) ) ;
}
}
return types ;
}
private boolean isEmptyIdProperty ( Entry < String , Object > entry ) {
private static boolean isEmptyIdProperty ( Entry < String , Object > entry ) {
return entry . getKey ( ) . equals ( "_id" ) & & entry . getValue ( ) = = null | | entry . getValue ( ) . equals ( Optional . empty ( ) ) ;
}
private void applyStringMatcher ( Map . Entry < String , Object > entry , StringMatcher stringMatcher , boolean ignoreCase ) {
private static void applyStringMatcher ( Map . Entry < String , Object > entry , StringMatcher stringMatcher ,
boolean ignoreCase ) {
Document document = new Document ( ) ;
@ -264,9 +303,20 @@ public class MongoExampleMapper {
@@ -264,9 +303,20 @@ public class MongoExampleMapper {
}
}
private static Document orConcatenate ( Document source ) {
List < Document > or = new ArrayList < > ( source . keySet ( ) . size ( ) ) ;
for ( String key : source . keySet ( ) ) {
or . add ( new Document ( key , source . get ( key ) ) ) ;
}
return new Document ( "$or" , or ) ;
}
/ * *
* Return the { @link MatchMode } for the given { @link StringMatcher } .
*
*
* @param matcher must not be { @literal null } .
* @return
* /
@ -288,43 +338,4 @@ public class MongoExampleMapper {
@@ -288,43 +338,4 @@ public class MongoExampleMapper {
return MatchMode . DEFAULT ;
}
}
private Document updateTypeRestrictions ( Document query , Example example ) {
Document result = new Document ( ) ;
if ( isTypeRestricting ( example ) ) {
result . putAll ( query ) ;
this . converter . getTypeMapper ( ) . writeTypeRestrictions ( result , getTypesToMatch ( example ) ) ;
return result ;
}
for ( Map . Entry < String , Object > entry : query . entrySet ( ) ) {
if ( ! this . converter . getTypeMapper ( ) . isTypeKey ( entry . getKey ( ) ) ) {
result . put ( entry . getKey ( ) , entry . getValue ( ) ) ;
}
}
return result ;
}
private boolean isTypeRestricting ( Example example ) {
if ( example . getMatcher ( ) instanceof UntypedExampleMatcher ) {
return false ;
}
if ( example . getMatcher ( ) . getIgnoredPaths ( ) . isEmpty ( ) ) {
return true ;
}
for ( String path : example . getMatcher ( ) . getIgnoredPaths ( ) ) {
if ( this . converter . getTypeMapper ( ) . isTypeKey ( path ) ) {
return false ;
}
}
return true ;
}
}