From 82af678caba783997f920d2fe00e0309f5667e9d Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 18 May 2021 10:43:13 +0200 Subject: [PATCH] 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. --- .../core/convert/DefaultDbRefResolver.java | 9 ++- .../convert/DefaultReferenceResolver.java | 32 +++++--- .../core/convert/DocumentPointerFactory.java | 39 +++++++--- ...ator.java => LazyLoadingProxyFactory.java} | 58 +++++++------- .../core/convert/MappingMongoConverter.java | 38 ++++++---- ... MongoDatabaseFactoryReferenceLoader.java} | 8 +- .../core/convert/NoOpDbRefResolver.java | 10 +-- .../mongodb/core/convert/ReferenceLoader.java | 30 +++++--- ...ader.java => ReferenceLookupDelegate.java} | 76 +++++++++++-------- .../core/convert/ReferenceResolver.java | 33 ++------ .../mapping/BasicMongoPersistentProperty.java | 2 +- .../core/convert/LazyLoadingTestUtils.java | 7 +- .../core/convert/QueryMapperUnitTests.java | 9 ++- .../BasicMongoPersistentEntityUnitTests.java | 6 +- .../performance/ReactivePerformanceTests.java | 13 ++-- 15 files changed, 204 insertions(+), 166 deletions(-) rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/{LazyLoadingProxyGenerator.java => LazyLoadingProxyFactory.java} (81%) rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/{DefaultReferenceLoader.java => MongoDatabaseFactoryReferenceLoader.java} (85%) rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/{ReferenceReader.java => ReferenceLookupDelegate.java} (84%) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java index 5277fbc0b..f64c7f0f0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java @@ -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 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 documentWithId(Object identifier, Collection documents) { return documents.stream() // - .filter(it -> it.get("_id").equals(identifier)) // + .filter(it -> it.get(BasicMongoPersistentProperty.ID_FIELD_NAME).equals(identifier)) // .limit(1); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceResolver.java index 0692f719b..7e38b6995 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceResolver.java @@ -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) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentPointerFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentPointerFactory.java index a91a48d92..8e9554b6b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentPointerFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentPointerFactory.java @@ -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, MongoPersistentProperty> mappingContext; - private Map linkageMap; + private final ConversionService conversionService; + private final MappingContext, MongoPersistentProperty> mappingContext; + private final Map linkageMap; public DocumentPointerFactory(ConversionService conversionService, MappingContext, 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 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 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++; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyGenerator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java similarity index 81% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyGenerator.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java index 570a516d9..8c2156df2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyGenerator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java @@ -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 { } } } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 2ad4d7523..8a77b51e2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -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 read(ConversionContext context, MongoPersistentEntity entity, Document bson) { + private S read(ConversionContext context, MongoPersistentEntity 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 findContextualEntity(ConversionContext context, MongoPersistentEntity entity, Document bson) { + return context.getPath().getPathItem(bson.get(BasicMongoPersistentProperty.ID_FIELD_NAME), entity.getCollection(), + entity.getType()); + } + private S populateProperties(ConversionContext context, MongoPersistentEntity 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() { - @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; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceLoader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoDatabaseFactoryReferenceLoader.java similarity index 85% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceLoader.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoDatabaseFactoryReferenceLoader.java index 66b698077..2483f5754 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceLoader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoDatabaseFactoryReferenceLoader.java @@ -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 bulkFetch(DocumentReferenceQuery filter, ReferenceCollection context) { + public Iterable fetchMany(DocumentReferenceQuery filter, ReferenceCollection context) { MongoCollection collection = getCollection(context); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/NoOpDbRefResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/NoOpDbRefResolver.java index 8b6c96943..41d7ab3c1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/NoOpDbRefResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/NoOpDbRefResolver.java @@ -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; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java index d5c72afad..7cfd5e315 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java @@ -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 it = bulkFetch(filter, context).iterator(); + Iterator it = fetchMany(filter, context).iterator(); return it.hasNext() ? it.next() : null; } - Iterable bulkFetch(DocumentReferenceQuery filter, ReferenceCollection context); + Iterable fetchMany(DocumentReferenceQuery filter, ReferenceCollection context); interface DocumentReferenceQuery { @@ -52,16 +52,12 @@ public interface ReferenceLoader { default Iterable apply(MongoCollection collection) { return restoreOrder(collection.find(getFilter()).sort(getSort())); } - + default Iterable restoreOrder(Iterable 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 apply(MongoCollection collection) { + return collection.find(getFilter()).sort(getSort()); + } + }; + } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceReader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java similarity index 84% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceReader.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java index fb37367b1..3c441c138 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceReader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java @@ -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, MongoPersistentProperty>> mappingContext; - private final Supplier spelContextSupplier; + private final MappingContext, MongoPersistentProperty> mappingContext; + private final SpELContext spELContext; private final ParameterBindingDocumentCodec codec; - public ReferenceReader(MappingContext, MongoPersistentProperty> mappingContext, - Supplier spelContextSupplier) { + public ReferenceLookupDelegate( + MappingContext, 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, MongoPersistentProperty>> mappingContextSupplier, - Supplier 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 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 parseValueOrGet(String value, ParameterBindingContext bindingContext, Supplier 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 ors = new ArrayList<>(); - for (Object entry : (Collection) value) { + for (Object entry : (Collection) value) { Document decoded = codec.decode(lookup, bindingContext(property, entry, spELContext)); ors.add(decoded); @@ -209,7 +213,7 @@ public class ReferenceReader { Map filterMap = new LinkedHashMap<>(); - for (Entry entry : ((Map) value).entrySet()) { + for (Entry entry : ((Map) 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 referenceList, Document document1, Document document2) { - for (int i = 0; i < referenceList.size(); i++) { + for (Document document : referenceList) { - Set> entries = referenceList.get(i).entrySet(); + Set> entries = document.entrySet(); if (document1.entrySet().containsAll(entries)) { return -1; } @@ -334,4 +338,10 @@ public class ReferenceReader { return referenceList.size(); } } + + @FunctionalInterface + interface LookupFunction { + + Iterable apply(DocumentReferenceQuery referenceQuery, ReferenceCollection referenceCollection); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceResolver.java index f29dc16a7..dae2043b4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceResolver.java @@ -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 apply(DocumentReferenceQuery referenceQuery, ReferenceCollection referenceCollection); - } @FunctionalInterface - interface ResultConversionFunction { - Object apply(Object source, TypeInformation property); + interface MongoEntityReader { + Object read(Object source, TypeInformation property); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java index b7b71a7fe..53af00fc5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java @@ -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> SUPPORTED_ID_TYPES = new HashSet>(); private static final Set SUPPORTED_ID_PROPERTY_NAMES = new HashSet(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingTestUtils.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingTestUtils.java index f5d43c8ef..91afb8c6e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingTestUtils.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingTestUtils.java @@ -54,7 +54,8 @@ public class LazyLoadingTestUtils { public static void assertProxy(Object proxy, Consumer 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; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index 9c157db75..d371b32c1 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -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()); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java index 28d512350..9c898d28c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java @@ -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 {} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/ReactivePerformanceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/ReactivePerformanceTests.java index b70930dae..8a462a937 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/ReactivePerformanceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/ReactivePerformanceTests.java @@ -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; }