Polishing

Rename ReferenceReader to ReferenceLookupDelegate.
Rename LazyLoadingProxyGenerator to LazyLoadingProxyFactory.
Rename DefaultReferenceLoader to MongoDatabaseFactoryReferenceLoader.

Reduce scope of LookupFunction and move it to ReferenceLookupDelegate.

Extract some checks into methods to reflect the underlying concepts. Simplify code, convert variables to constants where possible.

Original pull request: #3647.
Closes #3602.
This commit is contained in:
Mark Paluch
2021-05-18 10:43:13 +02:00
parent 6ed274bd9b
commit 82af678cab
15 changed files with 204 additions and 166 deletions

View File

@@ -47,6 +47,7 @@ import org.springframework.data.mongodb.LazyLoadingException;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoDatabaseUtils;
import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery;
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.lang.Nullable;
import org.springframework.objenesis.ObjenesisStd;
@@ -83,7 +84,7 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db
*/
public DefaultDbRefResolver(MongoDatabaseFactory mongoDbFactory) {
super(new DefaultReferenceLoader(mongoDbFactory));
super(new MongoDatabaseFactoryReferenceLoader(mongoDbFactory));
Assert.notNull(mongoDbFactory, "MongoDbFactory translator must not be null!");
@@ -117,7 +118,7 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db
*/
@Override
public Document fetch(DBRef dbRef) {
return getReferenceLoader().fetch(DocumentReferenceQuery.singleReferenceFilter(Filters.eq("_id", dbRef.getId())),
return getReferenceLoader().fetchOne(DocumentReferenceQuery.forSingleDocument(Filters.eq("_id", dbRef.getId())),
ReferenceCollection.fromDBRef(dbRef));
}
@@ -159,7 +160,7 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db
}
List<Document> result = mongoCollection //
.find(new Document("_id", new Document("$in", ids))) //
.find(new Document(BasicMongoPersistentProperty.ID_FIELD_NAME, new Document("$in", ids))) //
.into(new ArrayList<>());
return ids.stream() //
@@ -239,7 +240,7 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db
private static Stream<Document> documentWithId(Object identifier, Collection<Document> documents) {
return documents.stream() //
.filter(it -> it.get("_id").equals(identifier)) //
.filter(it -> it.get(BasicMongoPersistentProperty.ID_FIELD_NAME).equals(identifier)) //
.limit(1);
}

View File

@@ -15,7 +15,10 @@
*/
package org.springframework.data.mongodb.core.convert;
import org.springframework.data.mongodb.core.mapping.DocumentReference;
import static org.springframework.data.mongodb.core.convert.ReferenceLookupDelegate.*;
import java.util.Collections;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.lang.Nullable;
@@ -37,21 +40,32 @@ public class DefaultReferenceResolver implements ReferenceResolver {
@Nullable
@Override
public Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader,
LookupFunction lookupFunction, ResultConversionFunction resultConversionFunction) {
public Object resolveReference(MongoPersistentProperty property, Object source,
ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader) {
LookupFunction lookupFunction = (filter, ctx) -> {
if (property.isCollectionLike() || property.isMap()) {
return getReferenceLoader().fetchMany(filter, ctx);
}
Object target = getReferenceLoader().fetchOne(filter, ctx);
return target == null ? Collections.emptyList()
: Collections.singleton(getReferenceLoader().fetchOne(filter, ctx));
};
if (isLazyReference(property)) {
return createLazyLoadingProxy(property, source, referenceReader, lookupFunction, resultConversionFunction);
return createLazyLoadingProxy(property, source, referenceLookupDelegate, lookupFunction, entityReader);
}
return referenceReader.readReference(property, source, lookupFunction, resultConversionFunction);
return referenceLookupDelegate.readReference(property, source, lookupFunction, entityReader);
}
private Object createLazyLoadingProxy(MongoPersistentProperty property, Object source,
ReferenceReader referenceReader, LookupFunction lookupFunction,
ResultConversionFunction resultConversionFunction) {
return new LazyLoadingProxyGenerator(referenceReader).createLazyLoadingProxy(property, source, lookupFunction,
resultConversionFunction);
ReferenceLookupDelegate referenceLookupDelegate, LookupFunction lookupFunction,
MongoEntityReader entityReader) {
return new LazyLoadingProxyFactory(referenceLookupDelegate).createLazyLoadingProxy(property, source, lookupFunction,
entityReader);
}
protected boolean isLazyReference(MongoPersistentProperty property) {

View File

@@ -17,12 +17,14 @@ package org.springframework.data.mongodb.core.convert;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bson.Document;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
@@ -37,9 +39,9 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
*/
class DocumentPointerFactory {
private ConversionService conversionService;
private MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private Map<String, LinkageDocument> linkageMap;
private final ConversionService conversionService;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final Map<String, LinkageDocument> linkageMap;
public DocumentPointerFactory(ConversionService conversionService,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
@@ -60,15 +62,24 @@ class DocumentPointerFactory {
} else {
MongoPersistentEntity<?> persistentEntity = mappingContext
.getPersistentEntity(property.getAssociationTargetType());
.getRequiredPersistentEntity(property.getAssociationTargetType());
if (!property.getDocumentReference().lookup().toLowerCase().replaceAll("\\s", "").replaceAll("'", "")
// TODO: Extract method
if (!property.getDocumentReference().lookup().toLowerCase(Locale.ROOT).replaceAll("\\s", "").replaceAll("'", "")
.equals("{_id:?#{#target}}")) {
return () -> linkageMap.computeIfAbsent(property.getDocumentReference().lookup(), key -> {
return new LinkageDocument(key);
}).get(persistentEntity,
BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(property.getOwner(), value));
MongoPersistentEntity<?> valueEntity = mappingContext.getPersistentEntity(value.getClass());
PersistentPropertyAccessor<Object> propertyAccessor;
if (valueEntity == null) {
propertyAccessor = BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(property.getOwner(),
value);
} else {
propertyAccessor = valueEntity.getPropertyAccessor(value);
}
return () -> linkageMap.computeIfAbsent(property.getDocumentReference().lookup(), LinkageDocument::new)
.get(persistentEntity, propertyAccessor);
}
// just take the id as a reference
@@ -78,6 +89,8 @@ class DocumentPointerFactory {
static class LinkageDocument {
static final Pattern pattern = Pattern.compile("\\?#\\{#?[\\w\\d]*\\}");
String lookup;
org.bson.Document fetchDocument;
Map<Integer, String> mapMap;
@@ -87,16 +100,18 @@ class DocumentPointerFactory {
this.lookup = lookup;
String targetLookup = lookup;
Pattern pattern = Pattern.compile("\\?#\\{#?[\\w\\d]*\\}");
Matcher matcher = pattern.matcher(lookup);
int index = 0;
mapMap = new LinkedHashMap<>();
// TODO: Make explicit what's happening here
while (matcher.find()) {
String expr = matcher.group();
mapMap.put(Integer.valueOf(index), expr.substring(0, expr.length() - 1).replace("?#{#", "").replace("?#{", "")
.replace("target.", "").replaceAll("'", ""));
String sanitized = expr.substring(0, expr.length() - 1).replace("?#{#", "").replace("?#{", "")
.replace("target.", "").replaceAll("'", "");
mapMap.put(index, sanitized);
targetLookup = targetLookup.replace(expr, index + "");
index++;
}

View File

@@ -15,47 +15,46 @@
*/
package org.springframework.data.mongodb.core.convert;
import static org.springframework.data.mongodb.core.convert.ReferenceLookupDelegate.*;
import static org.springframework.util.ReflectionUtils.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.Factory;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.data.mongodb.core.convert.ReferenceResolver.LookupFunction;
import org.springframework.data.mongodb.core.convert.ReferenceResolver.ResultConversionFunction;
import org.springframework.data.mongodb.core.convert.ReferenceResolver.MongoEntityReader;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.lang.Nullable;
import org.springframework.objenesis.ObjenesisStd;
import org.springframework.util.ReflectionUtils;
/**
* @author Christoph Strobl
*/
class LazyLoadingProxyGenerator {
class LazyLoadingProxyFactory {
private final ObjenesisStd objenesis;
private final ReferenceReader referenceReader;
private final ReferenceLookupDelegate lookupDelegate;
public LazyLoadingProxyGenerator(ReferenceReader referenceReader) {
public LazyLoadingProxyFactory(ReferenceLookupDelegate lookupDelegate) {
this.referenceReader = referenceReader;
this.lookupDelegate = lookupDelegate;
this.objenesis = new ObjenesisStd(true);
}
public Object createLazyLoadingProxy(MongoPersistentProperty property, Object source, LookupFunction lookupFunction,
ResultConversionFunction resultConversionFunction) {
MongoEntityReader entityReader) {
Class<?> propertyType = property.getType();
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, source, referenceReader, lookupFunction,
resultConversionFunction);
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, source, lookupDelegate, lookupFunction,
entityReader);
if (!propertyType.isInterface()) {
@@ -97,13 +96,13 @@ class LazyLoadingProxyGenerator {
public static class LazyLoadingInterceptor
implements MethodInterceptor, org.springframework.cglib.proxy.MethodInterceptor, Serializable {
private final ReferenceReader referenceReader;
MongoPersistentProperty property;
private final ReferenceLookupDelegate referenceLookupDelegate;
private final MongoPersistentProperty property;
private volatile boolean resolved;
private @org.springframework.lang.Nullable Object result;
private Object source;
private LookupFunction lookupFunction;
private ResultConversionFunction resultConversionFunction;
private @Nullable Object result;
private final Object source;
private final LookupFunction lookupFunction;
private final MongoEntityReader entityReader;
private final Method INITIALIZE_METHOD, TO_DBREF_METHOD, FINALIZE_METHOD, GET_SOURCE_METHOD;
@@ -118,22 +117,23 @@ class LazyLoadingProxyGenerator {
}
}
public LazyLoadingInterceptor(MongoPersistentProperty property, Object source, ReferenceReader reader,
LookupFunction lookupFunction, ResultConversionFunction resultConversionFunction) {
public LazyLoadingInterceptor(MongoPersistentProperty property, Object source, ReferenceLookupDelegate reader,
LookupFunction lookupFunction, MongoEntityReader entityReader) {
this.property = property;
this.source = source;
this.referenceReader = reader;
this.referenceLookupDelegate = reader;
this.lookupFunction = lookupFunction;
this.resultConversionFunction = resultConversionFunction;
this.entityReader = entityReader;
}
@Nullable
@Override
public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
public Object invoke(MethodInvocation invocation) throws Throwable {
return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null);
}
@Nullable
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy proxy) throws Throwable {
@@ -180,6 +180,7 @@ class LazyLoadingProxyGenerator {
return method.invoke(target, args);
}
@Nullable
private Object ensureResolved() {
if (!resolved) {
@@ -190,7 +191,7 @@ class LazyLoadingProxyGenerator {
return this.result;
}
private String proxyToString(Object source) {
private String proxyToString(@Nullable Object source) {
StringBuilder description = new StringBuilder();
if (source != null) {
@@ -203,7 +204,7 @@ class LazyLoadingProxyGenerator {
return description.toString();
}
private boolean proxyEquals(@org.springframework.lang.Nullable Object proxy, Object that) {
private boolean proxyEquals(@Nullable Object proxy, Object that) {
if (!(that instanceof LazyLoadingProxy)) {
return false;
@@ -216,11 +217,11 @@ class LazyLoadingProxyGenerator {
return proxyToString(proxy).equals(that.toString());
}
private int proxyHashCode(@org.springframework.lang.Nullable Object proxy) {
private int proxyHashCode(@Nullable Object proxy) {
return proxyToString(proxy).hashCode();
}
@org.springframework.lang.Nullable
@Nullable
private synchronized Object resolve() {
if (resolved) {
@@ -238,7 +239,7 @@ class LazyLoadingProxyGenerator {
// property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName());
// }
return referenceReader.readReference(property, source, lookupFunction, resultConversionFunction);
return referenceLookupDelegate.readReference(property, source, lookupFunction, entityReader);
} catch (RuntimeException ex) {
throw ex;
@@ -254,4 +255,5 @@ class LazyLoadingProxyGenerator {
}
}
}
}

View File

@@ -38,6 +38,7 @@ import org.bson.json.JsonReader;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -63,6 +64,7 @@ import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
import org.springframework.data.mongodb.CodecRegistryProvider;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.DocumentPointer;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
@@ -114,7 +116,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
protected final QueryMapper idMapper;
protected final DbRefResolver dbRefResolver;
protected final DefaultDbRefProxyHandler dbRefProxyHandler;
protected final ReferenceReader referenceReader;
protected final ReferenceLookupDelegate referenceLookupDelegate;
protected @Nullable ApplicationContext applicationContext;
protected MongoTypeMapper typeMapper;
@@ -123,7 +125,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private SpELContext spELContext;
private @Nullable EntityCallbacks entityCallbacks;
private DocumentPointerFactory documentPointerFactory;
private final DocumentPointerFactory documentPointerFactory;
/**
* Creates a new {@link MappingMongoConverter} given the new {@link DbRefResolver} and {@link MappingContext}.
@@ -154,7 +156,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return MappingMongoConverter.this.getValueInternal(context, prop, bson, evaluator);
});
this.referenceReader = new ReferenceReader(mappingContext, () -> spELContext);
this.referenceLookupDelegate = new ReferenceLookupDelegate(mappingContext, spELContext);
this.documentPointerFactory = new DocumentPointerFactory(conversionService, mappingContext);
}
@@ -361,16 +363,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
parameterProvider);
}
private <S extends Object> S read(ConversionContext context, MongoPersistentEntity<S> entity, Document bson) {
private <S> S read(ConversionContext context, MongoPersistentEntity<S> entity, Document bson) {
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
if (bson.get("_id") != null) {
Object existing = context.getPath().getPathItem(bson.get("_id"), entity.getCollection(), entity.getType());
if (hasIdentifier(bson)) {
S existing = findContextualEntity(context, entity, bson);
if (existing != null) {
return (S) existing;
return existing;
}
}
@@ -391,6 +392,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return instance;
}
private boolean hasIdentifier(Document bson) {
return bson.get(BasicMongoPersistentProperty.ID_FIELD_NAME) != null;
}
@Nullable
private <S> S findContextualEntity(ConversionContext context, MongoPersistentEntity<S> entity, Document bson) {
return context.getPath().getPathItem(bson.get(BasicMongoPersistentProperty.ID_FIELD_NAME), entity.getCollection(),
entity.getType());
}
private <S> S populateProperties(ConversionContext context, MongoPersistentEntity<S> entity,
DocumentAccessor documentAccessor, SpELExpressionEvaluator evaluator, S instance) {
@@ -509,7 +520,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
ConversionContext context, SpELExpressionEvaluator evaluator) {
MongoPersistentProperty property = association.getInverse();
final Object value = documentAccessor.get(property);
Object value = documentAccessor.get(property);
if (value == null) {
return;
@@ -521,18 +532,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (conversionService.canConvert(DocumentPointer.class, property.getActualType())) {
DocumentPointer<?> pointer = new DocumentPointer<Object>() {
@Override
public Object getPointer() {
return value;
}
};
DocumentPointer<?> pointer = () -> value;
// collection like special treatment
accessor.setProperty(property, conversionService.convert(pointer, property.getActualType()));
} else {
accessor.setProperty(property,
dbRefResolver.resolveReference(property, value, referenceReader, context::convert));
dbRefResolver.resolveReference(property, value, referenceLookupDelegate, context::convert));
}
return;
}

View File

@@ -29,13 +29,13 @@ import com.mongodb.client.MongoCollection;
/**
* @author Christoph Strobl
*/
public class DefaultReferenceLoader implements ReferenceLoader {
public class MongoDatabaseFactoryReferenceLoader implements ReferenceLoader {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultReferenceLoader.class);
private static final Logger LOGGER = LoggerFactory.getLogger(MongoDatabaseFactoryReferenceLoader.class);
private final MongoDatabaseFactory mongoDbFactory;
public DefaultReferenceLoader(MongoDatabaseFactory mongoDbFactory) {
public MongoDatabaseFactoryReferenceLoader(MongoDatabaseFactory mongoDbFactory) {
Assert.notNull(mongoDbFactory, "MongoDbFactory translator must not be null!");
@@ -43,7 +43,7 @@ public class DefaultReferenceLoader implements ReferenceLoader {
}
@Override
public Iterable<Document> bulkFetch(DocumentReferenceQuery filter, ReferenceCollection context) {
public Iterable<Document> fetchMany(DocumentReferenceQuery filter, ReferenceCollection context) {
MongoCollection<Document> collection = getCollection(context);

View File

@@ -16,13 +16,10 @@
package org.springframework.data.mongodb.core.convert;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Stream;
import org.bson.Document;
import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import com.mongodb.DBRef;
@@ -76,9 +73,8 @@ public enum NoOpDbRefResolver implements DbRefResolver {
@Nullable
@Override
public Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader,
LookupFunction lookupFunction,
ResultConversionFunction resultConversionFunction) {
public Object resolveReference(MongoPersistentProperty property, Object source,
ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader) {
return null;
}

View File

@@ -31,13 +31,13 @@ import com.mongodb.client.MongoCollection;
public interface ReferenceLoader {
@Nullable
default Document fetch(DocumentReferenceQuery filter, ReferenceCollection context) {
default Document fetchOne(DocumentReferenceQuery filter, ReferenceCollection context) {
Iterator<Document> it = bulkFetch(filter, context).iterator();
Iterator<Document> it = fetchMany(filter, context).iterator();
return it.hasNext() ? it.next() : null;
}
Iterable<Document> bulkFetch(DocumentReferenceQuery filter, ReferenceCollection context);
Iterable<Document> fetchMany(DocumentReferenceQuery filter, ReferenceCollection context);
interface DocumentReferenceQuery {
@@ -52,16 +52,12 @@ public interface ReferenceLoader {
default Iterable<Document> apply(MongoCollection<Document> collection) {
return restoreOrder(collection.find(getFilter()).sort(getSort()));
}
default Iterable<Document> restoreOrder(Iterable<Document> documents) {
return documents;
}
static DocumentReferenceQuery referenceFilter(Bson bson) {
return () -> bson;
}
static DocumentReferenceQuery singleReferenceFilter(Bson bson) {
static DocumentReferenceQuery forSingleDocument(Bson bson) {
return new DocumentReferenceQuery() {
@@ -78,6 +74,22 @@ public interface ReferenceLoader {
}
};
}
static DocumentReferenceQuery forManyDocuments(Bson bson) {
return new DocumentReferenceQuery() {
@Override
public Bson getFilter() {
return bson;
}
@Override
public Iterable<Document> apply(MongoCollection<Document> collection) {
return collection.find(getFilter()).sort(getSort());
}
};
}
}
}

View File

@@ -29,12 +29,12 @@ import java.util.stream.Collectors;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery;
import org.springframework.data.mongodb.core.convert.ReferenceResolver.LookupFunction;
import org.springframework.data.mongodb.core.convert.ReferenceResolver.MongoEntityReader;
import org.springframework.data.mongodb.core.convert.ReferenceResolver.ReferenceCollection;
import org.springframework.data.mongodb.core.convert.ReferenceResolver.ResultConversionFunction;
import org.springframework.data.mongodb.core.mapping.DocumentReference;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
@@ -42,58 +42,59 @@ import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.data.mongodb.util.json.ParameterBindingContext;
import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
import org.springframework.data.mongodb.util.json.ValueProvider;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.Streamable;
import org.springframework.expression.EvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.mongodb.DBRef;
import com.mongodb.client.MongoCollection;
/**
* A common delegate for {@link ReferenceResolver} implementations to resolve a reference to one/many target documents
* that are converted to entities.
*
* @author Christoph Strobl
* @author Mark Paluch
*/
public class ReferenceReader {
public final class ReferenceLookupDelegate {
private final Lazy<MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty>> mappingContext;
private final Supplier<SpELContext> spelContextSupplier;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final SpELContext spELContext;
private final ParameterBindingDocumentCodec codec;
public ReferenceReader(MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
Supplier<SpELContext> spelContextSupplier) {
public ReferenceLookupDelegate(
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
SpELContext spELContext) {
this(() -> mappingContext, spelContextSupplier);
}
Assert.notNull(mappingContext, "MappingContext must not be null");
Assert.notNull(spELContext, "SpELContext must not be null");
public ReferenceReader(
Supplier<MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty>> mappingContextSupplier,
Supplier<SpELContext> spelContextSupplier) {
this.mappingContext = Lazy.of(mappingContextSupplier);
this.spelContextSupplier = spelContextSupplier;
this.mappingContext = mappingContext;
this.spELContext = spELContext;
this.codec = new ParameterBindingDocumentCodec();
}
@Nullable
Object readReference(MongoPersistentProperty property, Object value, LookupFunction lookupFunction,
ResultConversionFunction resultConversionFunction) {
MongoEntityReader entityReader) {
SpELContext spELContext = spelContextSupplier.get();
DocumentReferenceQuery filter = computeFilter(property, value, spELContext);
ReferenceCollection referenceCollection = computeReferenceContext(property, value, spELContext);
Iterable<Document> result = lookupFunction.apply(filter, referenceCollection);
if (property.isCollectionLike()) {
return entityReader.read(result, property.getTypeInformation());
}
if (!result.iterator().hasNext()) {
return null;
}
if (property.isCollectionLike()) {
return resultConversionFunction.apply(result, property.getTypeInformation());
}
return resultConversionFunction.apply(result.iterator().next(), property.getTypeInformation());
return entityReader.read(result.iterator().next(), property.getTypeInformation());
}
private ReferenceCollection computeReferenceContext(MongoPersistentProperty property, Object value,
@@ -107,6 +108,8 @@ public class ReferenceReader {
return ReferenceCollection.fromDBRef((DBRef) value);
}
String collection = mappingContext.getRequiredPersistentEntity(property.getAssociationTargetType()).getCollection();
if (value instanceof Document) {
Document ref = (Document) value;
@@ -120,12 +123,12 @@ public class ReferenceReader {
() -> ref.get("db", String.class));
String targetCollection = parseValueOrGet(documentReference.collection(), bindingContext,
() -> ref.get("collection",
mappingContext.get().getPersistentEntity(property.getAssociationTargetType()).getCollection()));
collection));
return new ReferenceCollection(targetDatabase, targetCollection);
}
return new ReferenceCollection(ref.getString("db"), ref.get("collection",
mappingContext.get().getPersistentEntity(property.getAssociationTargetType()).getCollection()));
collection));
}
if (property.isDocumentReference()) {
@@ -135,16 +138,16 @@ public class ReferenceReader {
String targetDatabase = parseValueOrGet(documentReference.db(), bindingContext, () -> null);
String targetCollection = parseValueOrGet(documentReference.collection(), bindingContext,
() -> mappingContext.get().getPersistentEntity(property.getAssociationTargetType()).getCollection());
() -> collection);
return new ReferenceCollection(targetDatabase, targetCollection);
}
return new ReferenceCollection(null,
mappingContext.get().getPersistentEntity(property.getAssociationTargetType()).getCollection());
collection);
}
@Nullable
@SuppressWarnings("unchecked")
private <T> T parseValueOrGet(String value, ParameterBindingContext bindingContext, Supplier<T> defaultValue) {
if (!StringUtils.hasText(value)) {
@@ -153,7 +156,7 @@ public class ReferenceReader {
if (!BsonUtils.isJsonDocument(value) && value.contains("?#{")) {
String s = "{ 'target-value' : " + value + "}";
T evaluated = (T) codec.decode(s, bindingContext).get("target-value ");
T evaluated = (T) codec.decode(s, bindingContext).get("target-value");
return evaluated != null ? evaluated : defaultValue.get();
}
@@ -186,6 +189,7 @@ public class ReferenceReader {
return ctx;
}
@SuppressWarnings("unchecked")
DocumentReferenceQuery computeFilter(MongoPersistentProperty property, Object value, SpELContext spELContext) {
DocumentReference documentReference = property.getDocumentReference();
@@ -196,7 +200,7 @@ public class ReferenceReader {
if (property.isCollectionLike() && value instanceof Collection) {
List<Document> ors = new ArrayList<>();
for (Object entry : (Collection) value) {
for (Object entry : (Collection<Object>) value) {
Document decoded = codec.decode(lookup, bindingContext(property, entry, spELContext));
ors.add(decoded);
@@ -209,7 +213,7 @@ public class ReferenceReader {
Map<Object, Document> filterMap = new LinkedHashMap<>();
for (Entry entry : ((Map<Object, Object>) value).entrySet()) {
for (Entry<Object, Object> entry : ((Map<Object, Object>) value).entrySet()) {
Document decoded = codec.decode(lookup, bindingContext(property, entry.getValue(), spELContext));
filterMap.put(entry.getKey(), decoded);
@@ -321,9 +325,9 @@ public class ReferenceReader {
int compareAgainstReferenceIndex(List<Document> referenceList, Document document1, Document document2) {
for (int i = 0; i < referenceList.size(); i++) {
for (Document document : referenceList) {
Set<Entry<String, Object>> entries = referenceList.get(i).entrySet();
Set<Entry<String, Object>> entries = document.entrySet();
if (document1.entrySet().containsAll(entries)) {
return -1;
}
@@ -334,4 +338,10 @@ public class ReferenceReader {
return referenceList.size();
}
}
@FunctionalInterface
interface LookupFunction {
Iterable<Document> apply(DocumentReferenceQuery referenceQuery, ReferenceCollection referenceCollection);
}
}

View File

@@ -15,13 +15,10 @@
*/
package org.springframework.data.mongodb.core.convert;
import java.util.Collections;
import org.bson.Document;
import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.mongodb.DBRef;
@@ -31,22 +28,8 @@ import com.mongodb.DBRef;
public interface ReferenceResolver {
@Nullable
Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader,
LookupFunction lookupFunction, ResultConversionFunction resultConversionFunction);
default Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader,
ResultConversionFunction resultConversionFunction) {
return resolveReference(property, source, referenceReader, (filter, ctx) -> {
if (property.isCollectionLike() || property.isMap()) {
return getReferenceLoader().bulkFetch(filter, ctx);
}
Object target = getReferenceLoader().fetch(filter, ctx);
return target == null ? Collections.emptyList() : Collections.singleton(getReferenceLoader().fetch(filter, ctx));
}, resultConversionFunction);
}
Object resolveReference(MongoPersistentProperty property, Object source,
ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader);
ReferenceLoader getReferenceLoader();
@@ -58,6 +41,8 @@ public interface ReferenceResolver {
public ReferenceCollection(@Nullable String database, String collection) {
Assert.hasText(collection, "Collection must not be empty or null");
this.database = database;
this.collection = collection;
}
@@ -76,13 +61,9 @@ public interface ReferenceResolver {
}
}
@FunctionalInterface
interface LookupFunction {
Iterable<Document> apply(DocumentReferenceQuery referenceQuery, ReferenceCollection referenceCollection);
}
@FunctionalInterface
interface ResultConversionFunction {
Object apply(Object source, TypeInformation property);
interface MongoEntityReader {
Object read(Object source, TypeInformation<?> property);
}
}

View File

@@ -47,7 +47,7 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
private static final Logger LOG = LoggerFactory.getLogger(BasicMongoPersistentProperty.class);
private static final String ID_FIELD_NAME = "_id";
public static final String ID_FIELD_NAME = "_id";
private static final String LANGUAGE_FIELD_NAME = "language";
private static final Set<Class<?>> SUPPORTED_ID_TYPES = new HashSet<Class<?>>();
private static final Set<String> SUPPORTED_ID_PROPERTY_NAMES = new HashSet<String>();

View File

@@ -54,7 +54,8 @@ public class LazyLoadingTestUtils {
public static void assertProxy(Object proxy, Consumer<LazyLoadingProxyValueRetriever> verification) {
LazyLoadingProxyGenerator.LazyLoadingInterceptor interceptor = (LazyLoadingProxyGenerator.LazyLoadingInterceptor) (proxy instanceof Advised ? ((Advised) proxy).getAdvisors()[0].getAdvice()
LazyLoadingProxyFactory.LazyLoadingInterceptor interceptor = (LazyLoadingProxyFactory.LazyLoadingInterceptor) (proxy instanceof Advised
? ((Advised) proxy).getAdvisors()[0].getAdvice()
: ((Factory) proxy).getCallback(0));
verification.accept(new LazyLoadingProxyValueRetriever(interceptor));
@@ -67,9 +68,9 @@ public class LazyLoadingTestUtils {
public static class LazyLoadingProxyValueRetriever {
LazyLoadingProxyGenerator.LazyLoadingInterceptor interceptor;
LazyLoadingProxyFactory.LazyLoadingInterceptor interceptor;
public LazyLoadingProxyValueRetriever(LazyLoadingProxyGenerator.LazyLoadingInterceptor interceptor) {
public LazyLoadingProxyValueRetriever(LazyLoadingProxyFactory.LazyLoadingInterceptor interceptor) {
this.interceptor = interceptor;
}

View File

@@ -1501,18 +1501,19 @@ public class QueryMapperUnitTests {
SimpeEntityWithoutId noIdButLookupQuery;
}
// TODO
@Test
void xxx() {
Sample sample = new Sample();
sample.foo = "sample-id";
Query query = query(where("sample").is(sample));
org.bson.Document mappedObject = mapper.getMappedObject(query.getQueryObject(),
context.getPersistentEntity(WithDocumentReferences.class));
System.out.println("mappedObject.toJson(): " + mappedObject.toJson());
}

View File

@@ -30,11 +30,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AliasFor;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
import org.springframework.data.spel.spi.EvaluationContextExtension;
@@ -351,6 +350,9 @@ public class BasicMongoPersistentEntityUnitTests {
@Document("#{myProperty}")
class MappedWithExtension {}
@Document("${value.from.file}")
class MappedWithValue {}
@Document(collation = "#{myCollation}")
class WithCollationFromSpEL {}

View File

@@ -18,20 +18,13 @@ package org.springframework.data.mongodb.performance;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import org.springframework.data.mongodb.core.convert.ReferenceLoader;
import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery;
import org.springframework.data.mongodb.core.convert.ReferenceReader;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.text.DecimalFormat;
import java.util.*;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.bson.Document;
import org.bson.types.ObjectId;
@@ -48,12 +41,15 @@ import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DbRefResolverCallback;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.ReferenceLoader;
import org.springframework.data.mongodb.core.convert.ReferenceLookupDelegate;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.data.mongodb.repository.support.ReactiveMongoRepositoryFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
@@ -106,7 +102,8 @@ public class ReactivePerformanceTests {
@Nullable
@Override
public Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader, LookupFunction lookupFunction, ResultConversionFunction resultConversionFunction) {
public Object resolveReference(MongoPersistentProperty property, Object source,
ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader) {
return null;
}