From c4af78d81d6755538b19101cfec28ccb9ac7410a Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 18 Aug 2017 12:59:23 +0200 Subject: [PATCH] DATAMONGO-1768 - Allow ignoring type restriction when issuing QBE. We now allow to remove the type restriction inferred by the QBE mapping via an ignored path expression on the ExampleMatcher. This allows to create untyped QBE expressions returning all entities matching the query without limiting the result to types assignable to the probe itself. Original pull request: #496. --- .../core/convert/MongoExampleMapper.java | 45 ++++++++++++++-- .../mongodb/core/QueryByExampleTests.java | 34 ++++++++++++ .../convert/MongoExampleMapperUnitTests.java | 52 ++++++++++++++++++- 3 files changed, 124 insertions(+), 7 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java index 50726ba72..55a2b8217 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java @@ -28,6 +28,7 @@ import java.util.Stack; import java.util.regex.Pattern; import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.ExampleMatcher.NullHandler; import org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer; import org.springframework.data.domain.ExampleMatcher.StringMatcher; @@ -41,6 +42,7 @@ import org.springframework.data.repository.core.support.ExampleMatcherAccessor; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -99,8 +101,8 @@ public class MongoExampleMapper { DBObject reference = (DBObject) converter.convertToMongoType(example.getProbe()); - if (entity.hasIdProperty() && entity.getIdentifierAccessor(example.getProbe()).getIdentifier() == null) { - reference.removeField(entity.getIdProperty().getFieldName()); + if (entity.hasIdProperty() && ClassUtils.isAssignable(entity.getType(), example.getProbeType())) { + if (entity.getIdentifierAccessor(example.getProbe()).getIdentifier() == null) {reference.removeField(entity.getIdProperty().getFieldName());} } ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher()); @@ -111,9 +113,7 @@ public class MongoExampleMapper { : new BasicDBObject(SerializationUtils.flattenMap(reference)); DBObject result = example.getMatcher().isAllMatching() ? flattened : orConcatenate(flattened); - this.converter.getTypeMapper().writeTypeRestrictions(result, getTypesToMatch(example)); - - return result; + return updateTypeRestrictions(result, example); } private static DBObject orConcatenate(DBObject source) { @@ -272,4 +272,39 @@ public class MongoExampleMapper { dbo.put("$options", "i"); } } + + private DBObject updateTypeRestrictions(DBObject query, Example example) { + + DBObject result = new BasicDBObject(); + + if (isTypeRestricting(example.getMatcher())) { + + result.putAll(query); + this.converter.getTypeMapper().writeTypeRestrictions(result, getTypesToMatch(example)); + return result; + } + + for (String key : query.keySet()) { + if (!this.converter.getTypeMapper().isTypeKey(key)) { + result.put(key, query.get(key)); + } + } + + return result; + } + + private boolean isTypeRestricting(ExampleMatcher matcher) { + + if (matcher.getIgnoredPaths().isEmpty()) { + return true; + } + + for (String path : matcher.getIgnoredPaths()) { + if (this.converter.getTypeMapper().isTypeKey(path)) { + return false; + } + } + + return true; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java index 11e2be573..c0eeec632 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java @@ -166,6 +166,32 @@ public class QueryByExampleTests { assertThat(result, hasItems(p1, p2)); } + @Test // DATAMONGO-1768 + public void typedExampleMatchesNothingIfTypesDoNotMatch() { + + NotAPersonButStillMatchingFields probe = new NotAPersonButStillMatchingFields(); + probe.lastname = "stark"; + + Query query = new Query(new Criteria().alike(Example.of(probe))); + List result = operations.find(query, Person.class); + + assertThat(result, hasSize(0)); + } + + @Test // DATAMONGO-1768 + public void untypedExampleMatchesCorrectly() { + + NotAPersonButStillMatchingFields probe = new NotAPersonButStillMatchingFields(); + probe.lastname = "stark"; + + Query query = new Query( + new Criteria().alike(Example.of(probe, ExampleMatcher.matching().withIgnorePaths("_class")))); + List result = operations.find(query, Person.class); + + assertThat(result, hasSize(2)); + assertThat(result, hasItems(p1, p3)); + } + @Document(collection = "dramatis-personae") @EqualsAndHashCode @ToString @@ -175,4 +201,12 @@ public class QueryByExampleTests { String firstname, middlename; @Field("last_name") String lastname; } + + @EqualsAndHashCode + @ToString + static class NotAPersonButStillMatchingFields { + + String firstname, middlename; + @Field("last_name") String lastname; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java index b95f77a07..470c9b75f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java @@ -24,6 +24,7 @@ import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import org.bson.BSONObject; @@ -36,8 +37,7 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.annotation.Id; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; -import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers; -import org.springframework.data.domain.ExampleMatcher.StringMatcher; +import org.springframework.data.domain.ExampleMatcher.*; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.convert.QueryMapperUnitTests.ClassWithGeoTypes; @@ -47,6 +47,7 @@ import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.test.util.IsBsonObject; +import org.springframework.data.util.TypeInformation; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; @@ -426,6 +427,53 @@ public class MongoExampleMapperUnitTests { assertThat(mapper.getMappedExample(example), isBsonObject().containing("$or").containing("_class")); } + @Test // DATAMONGO-1768 + public void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPath() { + + WrapperDocument probe = new WrapperDocument(); + probe.flatDoc = new FlatDocument(); + probe.flatDoc.stringValue = "conflux"; + + DBObject dbo = mapper + .getMappedExample(Example.of(probe, ExampleMatcher.matching().withIgnorePaths("_class"))); + + assertThat(dbo, isBsonObject().notContaining("_class")); + } + + @Test // DATAMONGO-1768 + public void allowIgnoringTypeRestrictionBySettingUpTypeKeyAsAnIgnoredPathWhenUsingCustomTypeMapper() { + + WrapperDocument probe = new WrapperDocument(); + probe.flatDoc = new FlatDocument(); + probe.flatDoc.stringValue = "conflux"; + + MappingMongoConverter mappingMongoConverter = new MappingMongoConverter(new DefaultDbRefResolver(factory), context); + mappingMongoConverter.setTypeMapper(new DefaultMongoTypeMapper() { + + @Override + public boolean isTypeKey(String key) { + return "_foo".equals(key); + } + + @Override + public void writeTypeRestrictions(DBObject dbo, Set> restrictedTypes) { + dbo.put("_foo", "bar"); + } + + @Override + public void writeType(TypeInformation info, DBObject sink) { + sink.put("_foo", "bar"); + + } + }); + mappingMongoConverter.afterPropertiesSet(); + + DBObject dbo = new MongoExampleMapper(mappingMongoConverter) + .getMappedExample(Example.of(probe, ExampleMatcher.matching().withIgnorePaths("_foo"))); + + assertThat(dbo, isBsonObject().notContaining("_class").notContaining("_foo")); + } + static class FlatDocument { @Id String id;