Skip to content

Commit de55ada

Browse files
Allow direct usage of (at)Reference from data commons
1 parent 5f260fc commit de55ada

File tree

6 files changed

+166
-26
lines changed

6 files changed

+166
-26
lines changed

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

+36-11
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@
1919

2020
import java.util.Collections;
2121

22+
import org.springframework.data.mongodb.core.mapping.DBRef;
23+
import org.springframework.data.mongodb.core.mapping.DocumentReference;
2224
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
23-
import org.springframework.lang.Nullable;
25+
import org.springframework.util.Assert;
2426

2527
/**
28+
* {@link ReferenceResolver} implementation that uses a given {@link ReferenceLookupDelegate} to load and convert entity
29+
* associations expressed via a {@link MongoPersistentProperty persitent property}. Creates {@link LazyLoadingProxy
30+
* proxies} for associations that should be lazily loaded.
31+
*
2632
* @author Christoph Strobl
2733
*/
2834
public class DefaultReferenceResolver implements ReferenceResolver {
@@ -35,11 +41,17 @@ public class DefaultReferenceResolver implements ReferenceResolver {
3541
return target == null ? Collections.emptyList() : Collections.singleton(getReferenceLoader().fetchOne(filter, ctx));
3642
};
3743

44+
/**
45+
* Create a new instance of {@link DefaultReferenceResolver}.
46+
*
47+
* @param referenceLoader must not be {@literal null}.
48+
*/
3849
public DefaultReferenceResolver(ReferenceLoader referenceLoader) {
50+
51+
Assert.notNull(referenceLoader, "ReferenceLoader must not be null!");
3952
this.referenceLoader = referenceLoader;
4053
}
4154

42-
@Nullable
4355
@Override
4456
public Object resolveReference(MongoPersistentProperty property, Object source,
4557
ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader) {
@@ -54,6 +66,28 @@ public Object resolveReference(MongoPersistentProperty property, Object source,
5466
return referenceLookupDelegate.readReference(property, source, lookupFunction, entityReader);
5567
}
5668

69+
/**
70+
* Check if the association expressed by the given {@link MongoPersistentProperty property} should be resolved lazily.
71+
*
72+
* @param property
73+
* @return return {@literal true} if the defined association is lazy.
74+
* @see DBRef#lazy()
75+
* @see DocumentReference#lazy()
76+
*/
77+
protected boolean isLazyReference(MongoPersistentProperty property) {
78+
79+
if (property.isDocumentReference()) {
80+
return property.getDocumentReference().lazy();
81+
}
82+
83+
return property.getDBRef() != null && property.getDBRef().lazy();
84+
}
85+
86+
/**
87+
* The {@link ReferenceLoader} executing the lookup.
88+
*
89+
* @return never {@literal null}.
90+
*/
5791
protected ReferenceLoader getReferenceLoader() {
5892
return referenceLoader;
5993
}
@@ -63,13 +97,4 @@ private Object createLazyLoadingProxy(MongoPersistentProperty property, Object s
6397
return new LazyLoadingProxyFactory(referenceLookupDelegate).createLazyLoadingProxy(property, source, lookupFunction,
6498
entityReader);
6599
}
66-
67-
protected boolean isLazyReference(MongoPersistentProperty property) {
68-
69-
if (property.isDocumentReference()) {
70-
return property.getDocumentReference().lazy();
71-
}
72-
73-
return property.getDBRef() != null && property.getDBRef().lazy();
74-
}
75100
}

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

+16-5
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
import org.bson.Document;
2626
import org.springframework.core.convert.ConversionService;
2727
import org.springframework.dao.InvalidDataAccessApiUsageException;
28+
import org.springframework.data.annotation.Reference;
2829
import org.springframework.data.mapping.PersistentPropertyAccessor;
2930
import org.springframework.data.mapping.PersistentPropertyPath;
3031
import org.springframework.data.mapping.PropertyPath;
3132
import org.springframework.data.mapping.context.MappingContext;
3233
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
3334
import org.springframework.data.mongodb.core.mapping.DocumentPointer;
34-
import org.springframework.data.mongodb.core.mapping.DocumentReference;
3535
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
3636
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
3737

@@ -82,7 +82,7 @@ DocumentPointer<?> computePointer(
8282
MongoPersistentEntity<?> persistentEntity = mappingContext
8383
.getRequiredPersistentEntity(property.getAssociationTargetType());
8484

85-
if (usesDefaultLookup(property.getDocumentReference())) {
85+
if (usesDefaultLookup(property)) {
8686
return () -> persistentEntity.getIdentifierAccessor(value).getIdentifier();
8787
}
8888

@@ -98,8 +98,18 @@ DocumentPointer<?> computePointer(
9898
.getDocumentPointer(mappingContext, persistentEntity, propertyAccessor);
9999
}
100100

101-
private boolean usesDefaultLookup(DocumentReference documentReference) {
102-
return DEFAULT_LOOKUP_PATTERN.matcher(documentReference.lookup()).matches();
101+
private boolean usesDefaultLookup(MongoPersistentProperty property) {
102+
103+
if (property.isDocumentReference()) {
104+
return DEFAULT_LOOKUP_PATTERN.matcher(property.getDocumentReference().lookup()).matches();
105+
}
106+
107+
Reference atReference = property.findAnnotation(Reference.class);
108+
if (atReference != null) {
109+
return true;
110+
}
111+
112+
throw new IllegalStateException(String.format("%s does not seem to be define Reference", property));
103113
}
104114

105115
/**
@@ -178,7 +188,8 @@ Document updatePlaceholders(org.bson.Document source, org.bson.Document target,
178188

179189
if (entry.getKey().startsWith("$")) {
180190
throw new InvalidDataAccessApiUsageException(String.format(
181-
"Cannot derive document pointer from lookup '%s' using query operator (%s). Please consider registering a custom converter.", lookup, entry.getKey()));
191+
"Cannot derive document pointer from lookup '%s' using query operator (%s). Please consider registering a custom converter.",
192+
lookup, entry.getKey()));
182193
}
183194

184195
if (entry.getValue() instanceof Document) {

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.core.CollectionFactory;
4646
import org.springframework.core.convert.ConversionService;
4747
import org.springframework.core.convert.support.DefaultConversionService;
48+
import org.springframework.data.annotation.Reference;
4849
import org.springframework.data.convert.TypeMapper;
4950
import org.springframework.data.mapping.Association;
5051
import org.springframework.data.mapping.MappingException;
@@ -526,7 +527,7 @@ private void readAssociation(Association<MongoPersistentProperty> association, P
526527
return;
527528
}
528529

529-
if (property.isDocumentReference()) {
530+
if (property.isDocumentReference() || (!property.isDbReference() && property.findAnnotation(Reference.class) != null)) {
530531

531532
// quite unusual but sounds like worth having?
532533

@@ -599,7 +600,8 @@ public DocumentPointer toDocumentPointer(Object source, @Nullable MongoPersisten
599600
if (referringProperty.isDbReference()) {
600601
return () -> toDBRef(source, referringProperty);
601602
}
602-
if (referringProperty.isDocumentReference()) {
603+
604+
if (referringProperty.isDocumentReference() || referringProperty.findAnnotation(Reference.class) != null) {
603605
return createDocumentPointer(source, referringProperty);
604606
}
605607

@@ -623,7 +625,6 @@ DocumentPointer<?> createDocumentPointer(Object source, @Nullable MongoPersisten
623625

624626
if (ClassUtils.isAssignableValue(referringProperty.getAssociationTargetType(), source)) {
625627
return documentPointerFactory.computePointer(mappingContext, referringProperty, source, referringProperty.getActualType());
626-
627628
}
628629

629630
return () -> source;

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.bson.types.ObjectId;
2727
import org.springframework.core.convert.ConversionService;
2828
import org.springframework.core.convert.converter.Converter;
29+
import org.springframework.data.annotation.Reference;
2930
import org.springframework.data.domain.Example;
3031
import org.springframework.data.mapping.Association;
3132
import org.springframework.data.mapping.MappingException;
@@ -672,7 +673,7 @@ private Object createReferenceFor(Object source, MongoPersistentProperty propert
672673
return (DBRef) source;
673674
}
674675

675-
if(property != null && property.isDocumentReference()) {
676+
if(property != null && (property.isDocumentReference() || (!property.isDbReference() && property.findAnnotation(Reference.class) != null))) {
676677
return converter.toDocumentPointer(source, property).getPointer();
677678
}
678679

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

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

18+
import java.lang.annotation.Annotation;
1819
import java.util.ArrayList;
1920
import java.util.Collection;
2021
import java.util.Collections;
@@ -183,7 +184,7 @@ private <T> T parseValueOrGet(String value, ParameterBindingContext bindingConte
183184
return evaluated != null ? evaluated : defaultValue.get();
184185
}
185186

186-
if(BsonUtils.isJsonDocument(value)) {
187+
if (BsonUtils.isJsonDocument(value)) {
187188
return (T) codec.decode(value, bindingContext);
188189
}
189190

@@ -227,7 +228,9 @@ EvaluationContext evaluationContextFor(MongoPersistentProperty property, Object
227228
@SuppressWarnings("unchecked")
228229
DocumentReferenceQuery computeFilter(MongoPersistentProperty property, Object value, SpELContext spELContext) {
229230

230-
DocumentReference documentReference = property.getDocumentReference();
231+
DocumentReference documentReference = property.isDocumentReference() ? property.getDocumentReference()
232+
: ReferenceEmulatingDocumentReference.INSTANCE;
233+
231234
String lookup = documentReference.lookup();
232235

233236
Document sort = parseValueOrGet(documentReference.sort(), bindingContext(property, value, spELContext),
@@ -261,6 +264,41 @@ DocumentReferenceQuery computeFilter(MongoPersistentProperty property, Object va
261264
return new SingleDocumentReferenceQuery(codec.decode(lookup, bindingContext(property, value, spELContext)), sort);
262265
}
263266

267+
enum ReferenceEmulatingDocumentReference implements DocumentReference {
268+
269+
INSTANCE;
270+
271+
@Override
272+
public Class<? extends Annotation> annotationType() {
273+
return DocumentReference.class;
274+
}
275+
276+
@Override
277+
public String db() {
278+
return "";
279+
}
280+
281+
@Override
282+
public String collection() {
283+
return "";
284+
}
285+
286+
@Override
287+
public String lookup() {
288+
return "{ '_id' : ?#{#target} }";
289+
}
290+
291+
@Override
292+
public String sort() {
293+
return "";
294+
}
295+
296+
@Override
297+
public boolean lazy() {
298+
return false;
299+
}
300+
}
301+
264302
/**
265303
* {@link DocumentReferenceQuery} implementation fetching a single {@link Document}.
266304
*/

Diff for: spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateDocumentReferenceTests.java

+68-4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.junit.jupiter.api.extension.ExtendWith;
3939
import org.springframework.core.convert.converter.Converter;
4040
import org.springframework.data.annotation.Id;
41+
import org.springframework.data.annotation.Reference;
4142
import org.springframework.data.convert.WritingConverter;
4243
import org.springframework.data.mongodb.core.convert.LazyLoadingTestUtils;
4344
import org.springframework.data.mongodb.core.mapping.DocumentPointer;
@@ -918,6 +919,56 @@ void queryDerivedMappingFromLookup() {
918919
assertThat(result.publisher).isNotNull();
919920
}
920921

922+
@Test // GH-3602
923+
void allowsDirectUsageOfAtReference() {
924+
925+
Publisher publisher = new Publisher();
926+
publisher.id = "p-1";
927+
publisher.acronym = "TOR";
928+
publisher.name = "Tom Doherty Associates";
929+
930+
template.save(publisher);
931+
932+
UsingAtReference root = new UsingAtReference();
933+
root.id = "book-1";
934+
root.publisher = publisher;
935+
936+
template.save(root);
937+
938+
Document target = template.execute(db -> {
939+
return db.getCollection(template.getCollectionName(UsingAtReference.class)).find(Filters.eq("_id", root.id)).first();
940+
});
941+
942+
assertThat(target).containsEntry("publisher", "p-1");
943+
944+
UsingAtReference result = template.findOne(query(where("id").is(root.id)), UsingAtReference.class);
945+
assertThat(result.publisher).isNotNull();
946+
}
947+
948+
@Test // GH-3602
949+
void updateWhenUsingAtReferenceDirectly() {
950+
951+
Publisher publisher = new Publisher();
952+
publisher.id = "p-1";
953+
publisher.acronym = "TOR";
954+
publisher.name = "Tom Doherty Associates";
955+
956+
template.save(publisher);
957+
958+
UsingAtReference root = new UsingAtReference();
959+
root.id = "book-1";
960+
961+
template.save(root);
962+
template.update(UsingAtReference.class).matching(where("id").is(root.id)).apply(new Update().set("publisher", publisher)).first();
963+
964+
Document target = template.execute(db -> {
965+
return db.getCollection(template.getCollectionName(UsingAtReference.class)).find(Filters.eq("_id", root.id)).first();
966+
});
967+
968+
assertThat(target).containsEntry("publisher", "p-1");
969+
970+
}
971+
921972
@Data
922973
static class SingleRefRoot {
923974

@@ -1082,7 +1133,8 @@ public DocumentPointer<Document> convert(SimpleObjectRefWithReadingConverter sou
10821133
static class WithRefA/* to B */ implements ReferenceAble {
10831134

10841135
@Id String id;
1085-
@DocumentReference WithRefB toB;
1136+
@DocumentReference //
1137+
WithRefB toB;
10861138

10871139
@Override
10881140
public Object toReference() {
@@ -1096,9 +1148,11 @@ public Object toReference() {
10961148
static class WithRefB/* to A */ implements ReferenceAble {
10971149

10981150
@Id String id;
1099-
@DocumentReference(lazy = true) WithRefA lazyToA;
1151+
@DocumentReference(lazy = true) //
1152+
WithRefA lazyToA;
11001153

1101-
@DocumentReference WithRefA eagerToA;
1154+
@DocumentReference //
1155+
WithRefA eagerToA;
11021156

11031157
@Override
11041158
public Object toReference() {
@@ -1122,7 +1176,8 @@ static class Book {
11221176

11231177
String id;
11241178

1125-
@DocumentReference(lookup = "{ 'acronym' : ?#{acc}, 'name' : ?#{n} }") Publisher publisher;
1179+
@DocumentReference(lookup = "{ 'acronym' : ?#{acc}, 'name' : ?#{n} }") //
1180+
Publisher publisher;
11261181

11271182
}
11281183

@@ -1133,4 +1188,13 @@ static class Publisher {
11331188
String name;
11341189
}
11351190

1191+
@Data
1192+
static class UsingAtReference {
1193+
1194+
String id;
1195+
1196+
@Reference //
1197+
Publisher publisher;
1198+
}
1199+
11361200
}

0 commit comments

Comments
 (0)