Browse Source

DATAMONGO-1768 - Polishing.

Use Lombok to generate constructors. Extend javadocs. Make methods static/reorder methods where possible. Use diamond syntax where possible. Formatting.

Original pull request: #496.
pull/691/head
Mark Paluch 8 years ago
parent
commit
e28bede416
  1. 207
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java
  2. 27
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/UntypedExampleMatcher.java
  3. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java

207
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoExampleMapper.java

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

27
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/UntypedExampleMatcher.java

@ -15,36 +15,29 @@
*/ */
package org.springframework.data.mongodb.core.query; package org.springframework.data.mongodb.core.query;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.Set; import java.util.Set;
import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.ExampleMatcher;
import org.springframework.util.Assert;
/** /**
* {@link ExampleMatcher} implementation for query by example (QBE). Unlike plain {@link ExampleMatcher} this untyped * {@link ExampleMatcher} implementation for query by example (QBE). Unlike plain {@link ExampleMatcher} this untyped
* counterpart does not enforce a strict type match when executing the query. This allows to use totally unrelated * counterpart does not enforce type matching when executing the query. This allows to query unrelated example documents
* example documents as references for querying collections as long as the used field/property names match. * as references for querying collections as long as the used field/property names match.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @author Mark Paluch
* @since 2.0 * @since 2.0
*/ */
@EqualsAndHashCode @EqualsAndHashCode
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class UntypedExampleMatcher implements ExampleMatcher { public class UntypedExampleMatcher implements ExampleMatcher {
private final ExampleMatcher delegate; private final @NonNull ExampleMatcher delegate;
/**
* Creates new {@link UntypedExampleMatcher}.
*
* @param delegate must not be {@literal null}.
*/
private UntypedExampleMatcher(ExampleMatcher delegate) {
Assert.notNull(delegate, "Delegate must not be null!");
this.delegate = delegate;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
@ -167,7 +160,7 @@ public class UntypedExampleMatcher implements ExampleMatcher {
return delegate.getNullHandler(); return delegate.getNullHandler();
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.domain.ExampleMatcher#getDefaultStringMatcher() * @see org.springframework.data.domain.ExampleMatcher#getDefaultStringMatcher()
*/ */
@ -175,7 +168,7 @@ public class UntypedExampleMatcher implements ExampleMatcher {
return delegate.getDefaultStringMatcher(); return delegate.getDefaultStringMatcher();
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.domain.ExampleMatcher#isIgnoreCaseEnabled() * @see org.springframework.data.domain.ExampleMatcher#isIgnoreCaseEnabled()
*/ */

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java

@ -484,7 +484,7 @@ public class MongoExampleMapperUnitTests {
} }
@Test // DATAMONGO-1768 @Test // DATAMONGO-1768
public void untypedExampleShouldNotInfereTypeRestriction() { public void untypedExampleShouldNotInferTypeRestriction() {
WrapperDocument probe = new WrapperDocument(); WrapperDocument probe = new WrapperDocument();
probe.flatDoc = new FlatDocument(); probe.flatDoc = new FlatDocument();

Loading…
Cancel
Save