Skip to content

Commit 6ed274b

Browse files
christophstroblmp911de
authored andcommitted
Update entity linking support to derive document pointer from lookup query.
Simplify usage by computing the pointer from the lookup. Update the reference documentation, add JavaDoc and refine API. Original pull request: #3647. Closes #3602.
1 parent 48ac7e7 commit 6ed274b

27 files changed

+1468
-247
lines changed

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java

+8-7
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
import org.springframework.data.mongodb.LazyLoadingException;
4747
import org.springframework.data.mongodb.MongoDatabaseFactory;
4848
import org.springframework.data.mongodb.MongoDatabaseUtils;
49-
import org.springframework.data.mongodb.core.convert.ReferenceLoader.ReferenceFilter;
49+
import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery;
5050
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
5151
import org.springframework.lang.Nullable;
5252
import org.springframework.objenesis.ObjenesisStd;
@@ -117,7 +117,8 @@ public Object resolveDbRef(MongoPersistentProperty property, @Nullable DBRef dbr
117117
*/
118118
@Override
119119
public Document fetch(DBRef dbRef) {
120-
return getReferenceLoader().fetch(ReferenceFilter.singleReferenceFilter(Filters.eq("_id", dbRef.getId())), ReferenceContext.fromDBRef(dbRef));
120+
return getReferenceLoader().fetch(DocumentReferenceQuery.singleReferenceFilter(Filters.eq("_id", dbRef.getId())),
121+
ReferenceCollection.fromDBRef(dbRef));
121122
}
122123

123124
/*
@@ -157,9 +158,9 @@ public List<Document> bulkFetch(List<DBRef> refs) {
157158
databaseSource.getCollectionName());
158159
}
159160

160-
List<Document> result = getReferenceLoader()
161-
.bulkFetch(ReferenceFilter.referenceFilter(new Document("_id", new Document("$in", ids))), ReferenceContext.fromDBRef(refs.iterator().next()))
162-
.collect(Collectors.toList());
161+
List<Document> result = mongoCollection //
162+
.find(new Document("_id", new Document("$in", ids))) //
163+
.into(new ArrayList<>());
163164

164165
return ids.stream() //
165166
.flatMap(id -> documentWithId(id, result)) //
@@ -498,9 +499,9 @@ protected MongoCollection<Document> getCollection(DBRef dbref) {
498499
.getCollection(dbref.getCollectionName(), Document.class);
499500
}
500501

501-
protected MongoCollection<Document> getCollection(ReferenceContext context) {
502+
protected MongoCollection<Document> getCollection(ReferenceCollection context) {
502503

503-
return MongoDatabaseUtils.getDatabase(context.database, mongoDbFactory).getCollection(context.collection,
504+
return MongoDatabaseUtils.getDatabase(context.getDatabase(), mongoDbFactory).getCollection(context.getCollection(),
504505
Document.class);
505506
}
506507
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceLoader.java

+4-10
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,15 @@
1515
*/
1616
package org.springframework.data.mongodb.core.convert;
1717

18-
import java.util.stream.Stream;
19-
import java.util.stream.StreamSupport;
20-
2118
import org.bson.Document;
22-
import org.bson.conversions.Bson;
2319
import org.slf4j.Logger;
2420
import org.slf4j.LoggerFactory;
2521
import org.springframework.data.mongodb.MongoDatabaseFactory;
2622
import org.springframework.data.mongodb.MongoDatabaseUtils;
27-
import org.springframework.data.mongodb.core.convert.ReferenceResolver.ReferenceContext;
28-
import org.springframework.lang.Nullable;
23+
import org.springframework.data.mongodb.core.convert.ReferenceResolver.ReferenceCollection;
2924
import org.springframework.util.Assert;
3025
import org.springframework.util.StringUtils;
3126

32-
import com.mongodb.client.FindIterable;
3327
import com.mongodb.client.MongoCollection;
3428

3529
/**
@@ -49,7 +43,7 @@ public DefaultReferenceLoader(MongoDatabaseFactory mongoDbFactory) {
4943
}
5044

5145
@Override
52-
public Stream<Document> bulkFetch(ReferenceFilter filter, ReferenceContext context) {
46+
public Iterable<Document> bulkFetch(DocumentReferenceQuery filter, ReferenceCollection context) {
5347

5448
MongoCollection<Document> collection = getCollection(context);
5549

@@ -63,9 +57,9 @@ public Stream<Document> bulkFetch(ReferenceFilter filter, ReferenceContext conte
6357
return filter.apply(collection);
6458
}
6559

66-
protected MongoCollection<Document> getCollection(ReferenceContext context) {
60+
protected MongoCollection<Document> getCollection(ReferenceCollection context) {
6761

68-
return MongoDatabaseUtils.getDatabase(context.database, mongoDbFactory).getCollection(context.collection,
62+
return MongoDatabaseUtils.getDatabase(context.getDatabase(), mongoDbFactory).getCollection(context.getCollection(),
6963
Document.class);
7064
}
7165
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceResolver.java

+9-13
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,6 @@
1515
*/
1616
package org.springframework.data.mongodb.core.convert;
1717

18-
import java.util.function.BiFunction;
19-
import java.util.stream.Stream;
20-
21-
import org.bson.Document;
22-
import org.bson.conversions.Bson;
23-
import org.springframework.data.mongodb.core.convert.ReferenceLoader.ReferenceFilter;
2418
import org.springframework.data.mongodb.core.mapping.DocumentReference;
2519
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
2620
import org.springframework.lang.Nullable;
@@ -44,24 +38,26 @@ public ReferenceLoader getReferenceLoader() {
4438
@Nullable
4539
@Override
4640
public Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader,
47-
BiFunction<ReferenceContext, ReferenceFilter, Stream<Document>> lookupFunction) {
41+
LookupFunction lookupFunction, ResultConversionFunction resultConversionFunction) {
4842

4943
if (isLazyReference(property)) {
50-
return createLazyLoadingProxy(property, source, referenceReader, lookupFunction);
44+
return createLazyLoadingProxy(property, source, referenceReader, lookupFunction, resultConversionFunction);
5145
}
5246

53-
return referenceReader.readReference(property, source, lookupFunction);
47+
return referenceReader.readReference(property, source, lookupFunction, resultConversionFunction);
5448
}
5549

5650
private Object createLazyLoadingProxy(MongoPersistentProperty property, Object source,
57-
ReferenceReader referenceReader, BiFunction<ReferenceContext, ReferenceFilter, Stream<Document>> lookupFunction) {
58-
return new LazyLoadingProxyGenerator(referenceReader).createLazyLoadingProxy(property, source, lookupFunction);
51+
ReferenceReader referenceReader, LookupFunction lookupFunction,
52+
ResultConversionFunction resultConversionFunction) {
53+
return new LazyLoadingProxyGenerator(referenceReader).createLazyLoadingProxy(property, source, lookupFunction,
54+
resultConversionFunction);
5955
}
6056

6157
protected boolean isLazyReference(MongoPersistentProperty property) {
6258

63-
if (property.findAnnotation(DocumentReference.class) != null) {
64-
return property.findAnnotation(DocumentReference.class).lazy();
59+
if (property.isDocumentReference()) {
60+
return property.getDocumentReference().lazy();
6561
}
6662

6763
return property.getDBRef() != null && property.getDBRef().lazy();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.convert;
17+
18+
import java.util.HashMap;
19+
import java.util.LinkedHashMap;
20+
import java.util.Map;
21+
import java.util.Map.Entry;
22+
import java.util.regex.Matcher;
23+
import java.util.regex.Pattern;
24+
25+
import org.bson.Document;
26+
import org.springframework.core.convert.ConversionService;
27+
import org.springframework.data.mapping.PersistentPropertyAccessor;
28+
import org.springframework.data.mapping.context.MappingContext;
29+
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
30+
import org.springframework.data.mongodb.core.mapping.DocumentPointer;
31+
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
32+
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
33+
34+
/**
35+
* @author Christoph Strobl
36+
* @since 3.3
37+
*/
38+
class DocumentPointerFactory {
39+
40+
private ConversionService conversionService;
41+
private MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
42+
private Map<String, LinkageDocument> linkageMap;
43+
44+
public DocumentPointerFactory(ConversionService conversionService,
45+
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
46+
47+
this.conversionService = conversionService;
48+
this.mappingContext = mappingContext;
49+
this.linkageMap = new HashMap<>();
50+
}
51+
52+
public DocumentPointer<?> computePointer(MongoPersistentProperty property, Object value, Class<?> typeHint) {
53+
54+
if (value instanceof LazyLoadingProxy) {
55+
return () -> ((LazyLoadingProxy) value).getSource();
56+
}
57+
58+
if (conversionService.canConvert(typeHint, DocumentPointer.class)) {
59+
return conversionService.convert(value, DocumentPointer.class);
60+
} else {
61+
62+
MongoPersistentEntity<?> persistentEntity = mappingContext
63+
.getPersistentEntity(property.getAssociationTargetType());
64+
65+
if (!property.getDocumentReference().lookup().toLowerCase().replaceAll("\\s", "").replaceAll("'", "")
66+
.equals("{_id:?#{#target}}")) {
67+
68+
return () -> linkageMap.computeIfAbsent(property.getDocumentReference().lookup(), key -> {
69+
return new LinkageDocument(key);
70+
}).get(persistentEntity,
71+
BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(property.getOwner(), value));
72+
}
73+
74+
// just take the id as a reference
75+
return () -> persistentEntity.getIdentifierAccessor(value).getIdentifier();
76+
}
77+
}
78+
79+
static class LinkageDocument {
80+
81+
String lookup;
82+
org.bson.Document fetchDocument;
83+
Map<Integer, String> mapMap;
84+
85+
public LinkageDocument(String lookup) {
86+
87+
this.lookup = lookup;
88+
String targetLookup = lookup;
89+
90+
Pattern pattern = Pattern.compile("\\?#\\{#?[\\w\\d]*\\}");
91+
92+
Matcher matcher = pattern.matcher(lookup);
93+
int index = 0;
94+
mapMap = new LinkedHashMap<>();
95+
while (matcher.find()) {
96+
97+
String expr = matcher.group();
98+
mapMap.put(Integer.valueOf(index), expr.substring(0, expr.length() - 1).replace("?#{#", "").replace("?#{", "")
99+
.replace("target.", "").replaceAll("'", ""));
100+
targetLookup = targetLookup.replace(expr, index + "");
101+
index++;
102+
}
103+
104+
fetchDocument = org.bson.Document.parse(targetLookup);
105+
}
106+
107+
org.bson.Document get(MongoPersistentEntity<?> persistentEntity, PersistentPropertyAccessor<?> propertyAccessor) {
108+
109+
org.bson.Document targetDocument = new Document();
110+
111+
// TODO: recursive matching over nested Documents or would the parameter binding json parser be a thing?
112+
// like we have it ordered by index values and could provide the parameter array from it.
113+
114+
for (Entry<String, Object> entry : fetchDocument.entrySet()) {
115+
116+
if (entry.getKey().equals("target")) {
117+
118+
String refKey = mapMap.get(entry.getValue());
119+
120+
if (persistentEntity.hasIdProperty()) {
121+
targetDocument.put(refKey, propertyAccessor.getProperty(persistentEntity.getIdProperty()));
122+
} else {
123+
targetDocument.put(refKey, propertyAccessor.getBean());
124+
}
125+
continue;
126+
}
127+
128+
Object target = propertyAccessor.getProperty(persistentEntity.getPersistentProperty(entry.getKey()));
129+
String refKey = mapMap.get(entry.getValue());
130+
targetDocument.put(refKey, target);
131+
}
132+
return targetDocument;
133+
}
134+
}
135+
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxy.java

+11
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,15 @@ public interface LazyLoadingProxy {
4646
*/
4747
@Nullable
4848
DBRef toDBRef();
49+
50+
/**
51+
* Returns the raw {@literal source} object that defines the reference.
52+
*
53+
* @return can be {@literal null}.
54+
* @since 3.3
55+
*/
56+
@Nullable
57+
default Object getSource() {
58+
return toDBRef();
59+
}
4960
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyGenerator.java

+17-13
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,19 @@
1919

2020
import java.io.Serializable;
2121
import java.lang.reflect.Method;
22-
import java.util.function.BiFunction;
23-
import java.util.stream.Stream;
2422

2523
import javax.annotation.Nonnull;
2624
import javax.annotation.Nullable;
2725

2826
import org.aopalliance.intercept.MethodInterceptor;
2927
import org.aopalliance.intercept.MethodInvocation;
30-
import org.bson.Document;
31-
import org.bson.conversions.Bson;
3228
import org.springframework.aop.framework.ProxyFactory;
3329
import org.springframework.cglib.proxy.Callback;
3430
import org.springframework.cglib.proxy.Enhancer;
3531
import org.springframework.cglib.proxy.Factory;
3632
import org.springframework.cglib.proxy.MethodProxy;
37-
import org.springframework.data.mongodb.core.convert.ReferenceLoader.ReferenceFilter;
38-
import org.springframework.data.mongodb.core.convert.ReferenceResolver.ReferenceContext;
33+
import org.springframework.data.mongodb.core.convert.ReferenceResolver.LookupFunction;
34+
import org.springframework.data.mongodb.core.convert.ReferenceResolver.ResultConversionFunction;
3935
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
4036
import org.springframework.objenesis.ObjenesisStd;
4137
import org.springframework.util.ReflectionUtils;
@@ -54,11 +50,12 @@ public LazyLoadingProxyGenerator(ReferenceReader referenceReader) {
5450
this.objenesis = new ObjenesisStd(true);
5551
}
5652

57-
public Object createLazyLoadingProxy(MongoPersistentProperty property, Object source,
58-
BiFunction<ReferenceContext, ReferenceFilter, Stream<Document>> lookupFunction) {
53+
public Object createLazyLoadingProxy(MongoPersistentProperty property, Object source, LookupFunction lookupFunction,
54+
ResultConversionFunction resultConversionFunction) {
5955

6056
Class<?> propertyType = property.getType();
61-
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, source, referenceReader, lookupFunction);
57+
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, source, referenceReader, lookupFunction,
58+
resultConversionFunction);
6259

6360
if (!propertyType.isInterface()) {
6461

@@ -105,27 +102,30 @@ public static class LazyLoadingInterceptor
105102
private volatile boolean resolved;
106103
private @org.springframework.lang.Nullable Object result;
107104
private Object source;
108-
private BiFunction<ReferenceContext, ReferenceFilter, Stream<Document>> lookupFunction;
105+
private LookupFunction lookupFunction;
106+
private ResultConversionFunction resultConversionFunction;
109107

110-
private final Method INITIALIZE_METHOD, TO_DBREF_METHOD, FINALIZE_METHOD;
108+
private final Method INITIALIZE_METHOD, TO_DBREF_METHOD, FINALIZE_METHOD, GET_SOURCE_METHOD;
111109

112110
{
113111
try {
114112
INITIALIZE_METHOD = LazyLoadingProxy.class.getMethod("getTarget");
115113
TO_DBREF_METHOD = LazyLoadingProxy.class.getMethod("toDBRef");
116114
FINALIZE_METHOD = Object.class.getDeclaredMethod("finalize");
115+
GET_SOURCE_METHOD = LazyLoadingProxy.class.getMethod("getSource");
117116
} catch (Exception e) {
118117
throw new RuntimeException(e);
119118
}
120119
}
121120

122121
public LazyLoadingInterceptor(MongoPersistentProperty property, Object source, ReferenceReader reader,
123-
BiFunction<ReferenceContext, ReferenceFilter, Stream<Document>> lookupFunction) {
122+
LookupFunction lookupFunction, ResultConversionFunction resultConversionFunction) {
124123

125124
this.property = property;
126125
this.source = source;
127126
this.referenceReader = reader;
128127
this.lookupFunction = lookupFunction;
128+
this.resultConversionFunction = resultConversionFunction;
129129
}
130130

131131
@Nullable
@@ -145,6 +145,10 @@ public Object intercept(Object o, Method method, Object[] args, MethodProxy prox
145145
return null;
146146
}
147147

148+
if (GET_SOURCE_METHOD.equals(method)) {
149+
return source;
150+
}
151+
148152
if (isObjectMethod(method) && Object.class.equals(method.getDeclaringClass())) {
149153

150154
if (ReflectionUtils.isToStringMethod(method)) {
@@ -234,7 +238,7 @@ private synchronized Object resolve() {
234238
// property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName());
235239
// }
236240

237-
return referenceReader.readReference(property, source, lookupFunction);
241+
return referenceReader.readReference(property, source, lookupFunction, resultConversionFunction);
238242

239243
} catch (RuntimeException ex) {
240244
throw ex;

0 commit comments

Comments
 (0)