Skip to content

Commit 026f98f

Browse files
use the derived document pointer.
1 parent 8c250c4 commit 026f98f

File tree

4 files changed

+256
-93
lines changed

4 files changed

+256
-93
lines changed
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/MappingMongoConverter.java

+16-72
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@
1717

1818
import java.lang.reflect.Constructor;
1919
import java.lang.reflect.Method;
20-
import java.util.*;
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.HashSet;
25+
import java.util.LinkedHashMap;
26+
import java.util.List;
27+
import java.util.Map;
2128
import java.util.Map.Entry;
22-
import java.util.regex.Matcher;
23-
import java.util.regex.Pattern;
29+
import java.util.Optional;
30+
import java.util.Set;
2431
import java.util.stream.Collectors;
2532

2633
import org.bson.Document;
@@ -45,7 +52,6 @@
4552
import org.springframework.data.mapping.PreferredConstructor.Parameter;
4653
import org.springframework.data.mapping.callback.EntityCallbacks;
4754
import org.springframework.data.mapping.context.MappingContext;
48-
import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
4955
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
5056
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
5157
import org.springframework.data.mapping.model.EntityInstantiator;
@@ -117,6 +123,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
117123

118124
private SpELContext spELContext;
119125
private @Nullable EntityCallbacks entityCallbacks;
126+
private DocumentPointerFactory documentPointerFactory;
120127

121128
/**
122129
* Creates a new {@link MappingMongoConverter} given the new {@link DbRefResolver} and {@link MappingContext}.
@@ -148,6 +155,7 @@ public MappingMongoConverter(DbRefResolver dbRefResolver,
148155
});
149156

150157
this.referenceReader = new ReferenceReader(mappingContext, () -> spELContext);
158+
this.documentPointerFactory = new DocumentPointerFactory(conversionService, mappingContext);
151159
}
152160

153161
/**
@@ -604,8 +612,8 @@ Object createDocumentPointer(Object source, @Nullable MongoPersistentProperty re
604612
}
605613

606614
if (ClassUtils.isAssignableValue(referringProperty.getAssociationTargetType(), source)) {
607-
return mappingContext.getPersistentEntity(referringProperty.getAssociationTargetType())
608-
.getIdentifierAccessor(source).getIdentifier();
615+
return documentPointerFactory.computePointer(referringProperty, source, referringProperty.getActualType())
616+
.getPointer();
609617

610618
}
611619

@@ -798,72 +806,8 @@ protected void writePropertyInternal(@Nullable Object obj, DocumentAccessor acce
798806

799807
if (prop.isAssociation()) {
800808

801-
if (obj instanceof LazyLoadingProxy) {
802-
803-
Object refSource = ((LazyLoadingProxy) obj).getSource();
804-
accessor.put(prop, refSource);
805-
return;
806-
}
807-
808-
if (conversionService.canConvert(valueType.getType(), DocumentPointer.class)) {
809-
accessor.put(prop, conversionService.convert(obj, DocumentPointer.class).getPointer());
810-
} else {
811-
812-
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(prop.getAssociationTargetType());
813-
// TODO: Move this to helper for queries
814-
815-
816-
if(!prop.getDocumentReference().lookup().toLowerCase().replaceAll("\\s", "").replaceAll("'","").equals("{_id:?#{#target}}")) {
817-
818-
String lookup = prop.getDocumentReference().lookup();
819-
String targetLookup = lookup;
820-
821-
Pattern pattern = Pattern.compile("\\?#\\{#?[\\w\\d]*\\}");
822-
823-
Matcher matcher = pattern.matcher(lookup);
824-
int index = 0;
825-
Map<Integer, String> mapMap = new LinkedHashMap<>();
826-
while(matcher.find()) {
827-
828-
String expr = matcher.group();
829-
mapMap.put(Integer.valueOf(index), expr.substring(0, expr.length()-1).replace("?#{#", "").replace("?#{", "").replace("target.","").replaceAll("'",""));
830-
targetLookup = targetLookup.replace(expr, index + "");
831-
index++;
832-
}
833-
834-
Document fetchDocument = Document.parse(targetLookup);
835-
836-
PersistentPropertyAccessor<?> propertyAccessor = BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(prop.getOwner(), obj);
837-
838-
839-
Document targetDocument = new Document();
840-
for(Entry<String, Object> entry : fetchDocument.entrySet()) {
841-
842-
if(entry.getKey().equals("target")) {
843-
844-
String refKey = mapMap.get(entry.getValue());
845-
846-
if(persistentEntity.hasIdProperty()) {
847-
targetDocument.put(refKey, persistentEntity.getIdentifierAccessor(obj).getIdentifier());
848-
} else {
849-
targetDocument.put(refKey, obj);
850-
}
851-
continue;
852-
}
853-
854-
Object value = propertyAccessor.getProperty(persistentEntity.getPersistentProperty(entry.getKey()));
855-
String refKey = mapMap.get(entry.getValue());
856-
targetDocument.put(refKey, value);
857-
}
858-
859-
accessor.put(prop, targetDocument);
860-
return;
861-
}
862-
863-
// just take the id as a reference
864-
accessor.put(prop, persistentEntity
865-
.getIdentifierAccessor(obj).getIdentifier());
866-
}
809+
accessor.put(prop, new DocumentPointerFactory(conversionService, mappingContext)
810+
.computePointer(prop, obj, valueType.getType()).getPointer());
867811
return;
868812
}
869813

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

+48
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,54 @@ void deriveMappingFromLookup() {
842842
assertThat(result.publisher).isNotNull();
843843
}
844844

845+
@Test // GH-3602
846+
void updateDerivedMappingFromLookup() {
847+
848+
Publisher publisher = new Publisher();
849+
publisher.id = "p-1";
850+
publisher.acronym = "TOR";
851+
publisher.name = "Tom Doherty Associates";
852+
853+
template.save(publisher);
854+
855+
Book book = new Book();
856+
book.id = "book-1";
857+
858+
template.save(book);
859+
860+
template.update(Book.class).matching(where("id").is(book.id)).apply(new Update().set("publisher", publisher)).first();
861+
862+
Document target = template.execute(db -> {
863+
return db.getCollection(template.getCollectionName(Book.class)).find(Filters.eq("_id", book.id)).first();
864+
});
865+
866+
assertThat(target).containsEntry("publisher", new Document("acc", publisher.acronym).append("n", publisher.name));
867+
868+
Book result = template.findOne(query(where("id").is(book.id)), Book.class);
869+
assertThat(result.publisher).isNotNull();
870+
}
871+
872+
@Test // GH-3602
873+
void queryDerivedMappingFromLookup() {
874+
875+
Publisher publisher = new Publisher();
876+
publisher.id = "p-1";
877+
publisher.acronym = "TOR";
878+
publisher.name = "Tom Doherty Associates";
879+
880+
template.save(publisher);
881+
882+
Book book = new Book();
883+
book.id = "book-1";
884+
book.publisher = publisher;
885+
886+
template.save(book);
887+
book.publisher = publisher;
888+
889+
Book result = template.findOne(query(where("publisher").is(publisher)), Book.class);
890+
assertThat(result.publisher).isNotNull();
891+
}
892+
845893
@Data
846894
static class SingleRefRoot {
847895

0 commit comments

Comments
 (0)