Skip to content

Commit e4699d0

Browse files
yacotachristophstrobl
authored andcommitted
DATAMONGO-1271 - Provide lifecycle events for DBRefs.
We now publish livecycle events when loading DBRefs. Original Pull Request: #322 CLA: 121620150519031801 (Jordi Llach Fernandez)
1 parent 9b04dd8 commit e4699d0

File tree

7 files changed

+379
-9
lines changed

7 files changed

+379
-9
lines changed

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

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.beans.BeansException;
3333
import org.springframework.context.ApplicationContext;
3434
import org.springframework.context.ApplicationContextAware;
35+
import org.springframework.context.ApplicationEventPublisher;
3536
import org.springframework.core.CollectionFactory;
3637
import org.springframework.core.convert.ConversionException;
3738
import org.springframework.core.convert.ConversionService;
@@ -56,6 +57,8 @@
5657
import org.springframework.data.mongodb.MongoDbFactory;
5758
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
5859
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
60+
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
61+
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
5962
import org.springframework.data.util.ClassTypeInformation;
6063
import org.springframework.data.util.TypeInformation;
6164
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -78,6 +81,7 @@
7881
* @author Patrik Wasik
7982
* @author Thomas Darimont
8083
* @author Christoph Strobl
84+
* @author Jordi Llach
8185
*/
8286
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware, ValueResolver {
8387

@@ -888,7 +892,7 @@ protected DBRef createDBRef(Object target, MongoPersistentProperty property) {
888892
@Override
889893
public Object getValueInternal(MongoPersistentProperty prop, Bson dbo, SpELExpressionEvaluator evaluator,
890894
ObjectPath path) {
891-
return new MongoDbPropertyValueProvider(dbo, evaluator, path).getPropertyValue(prop);
895+
return new MongoDbPropertyValueProvider(dbo, evaluator, path, false).getPropertyValue(prop);
892896
}
893897

894898
/**
@@ -922,8 +926,9 @@ private Object readCollectionOrArray(TypeInformation<?> targetType, List sourceV
922926
Object dbObjItem = sourceValue.get(i);
923927

924928
if (dbObjItem instanceof DBRef) {
925-
items.add(
926-
DBRef.class.equals(rawComponentType) ? dbObjItem : read(componentType, readRef((DBRef) dbObjItem), path));
929+
items.add(DBRef.class.equals(rawComponentType) ? dbObjItem
930+
: readAndConvertDBRef((DBRef) dbObjItem, componentType, path, rawComponentType));
931+
927932
} else if (dbObjItem instanceof Document) {
928933
items.add(read(componentType, (Document) dbObjItem, path));
929934
} else if (dbObjItem instanceof BasicDBObject) {
@@ -991,7 +996,8 @@ protected Map<Object, Object> readMap(TypeInformation<?> type, Bson dbObject, Ob
991996
} else if (value instanceof BasicDBObject) {
992997
map.put(key, read(valueType, (BasicDBObject) value, path));
993998
} else if (value instanceof DBRef) {
994-
map.put(key, DBRef.class.equals(rawValueType) ? value : read(valueType, readRef((DBRef) value)));
999+
map.put(key, DBRef.class.equals(rawValueType) ? value
1000+
: readAndConvertDBRef((DBRef) value, valueType, ObjectPath.ROOT, rawValueType));
9951001
} else if (value instanceof List) {
9961002
map.put(key, readCollectionOrArray(valueType, (List) value, path));
9971003
} else {
@@ -1212,6 +1218,7 @@ private class MongoDbPropertyValueProvider implements PropertyValueProvider<Mong
12121218
private final DocumentAccessor source;
12131219
private final SpELExpressionEvaluator evaluator;
12141220
private final ObjectPath path;
1221+
private final boolean ignoreLazyDBRefProperties;
12151222

12161223
/**
12171224
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
@@ -1222,13 +1229,18 @@ private class MongoDbPropertyValueProvider implements PropertyValueProvider<Mong
12221229
* @param path can be {@literal null}.
12231230
*/
12241231
public MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path) {
1232+
this(source, evaluator, path, true); // ignoring by default
1233+
}
12251234

1235+
MongoDbPropertyValueProvider(Bson source, SpELExpressionEvaluator evaluator, ObjectPath path,
1236+
boolean ignoreLazyDBRefProperties) {
12261237
Assert.notNull(source);
12271238
Assert.notNull(evaluator);
12281239

12291240
this.source = new DocumentAccessor(source);
12301241
this.evaluator = evaluator;
12311242
this.path = path;
1243+
this.ignoreLazyDBRefProperties = ignoreLazyDBRefProperties;
12321244
}
12331245

12341246
/*
@@ -1243,6 +1255,12 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
12431255
if (value == null) {
12441256
return null;
12451257
}
1258+
if (this.ignoreLazyDBRefProperties && property.isDbReference() && property.getDBRef().lazy()) { // lazy DBRef,
1259+
// BasicDBList are
1260+
// resolved later
1261+
// by default
1262+
return null;
1263+
}
12461264

12471265
return readValue(value, property.getTypeInformation(), path);
12481266
}
@@ -1306,14 +1324,28 @@ private <T> T readValue(Object value, TypeInformation<?> type, ObjectPath path)
13061324

13071325
@SuppressWarnings("unchecked")
13081326
private <T> T potentiallyReadOrResolveDbRef(DBRef dbref, TypeInformation<?> type, ObjectPath path, Class<?> rawType) {
1309-
13101327
if (rawType.equals(DBRef.class)) {
13111328
return (T) dbref;
13121329
}
1313-
13141330
Object object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getCollectionName());
1331+
return (T) (object != null ? object : readAndConvertDBRef(dbref, type, path, rawType));
1332+
}
1333+
1334+
private <T> T readAndConvertDBRef(DBRef dbref, TypeInformation<?> type, ObjectPath path, Class<?> rawType) {
1335+
Document readRef = readRef(dbref);
1336+
final String collectionName = dbref.getCollectionName();
1337+
if (canPublishEvent())
1338+
((ApplicationEventPublisher) this.applicationContext)
1339+
.publishEvent(new AfterLoadEvent<T>(readRef, (Class<T>) rawType, collectionName));
1340+
T t = (T) read(type, readRef, path);
1341+
if (canPublishEvent())
1342+
((ApplicationEventPublisher) this.applicationContext)
1343+
.publishEvent(new AfterConvertEvent<T>(readRef, t, collectionName));
1344+
return t;
1345+
}
13151346

1316-
return (T) (object != null ? object : read(type, readRef(dbref), path));
1347+
private boolean canPublishEvent() {
1348+
return this.applicationContext != null;
13171349
}
13181350

13191351
/**

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
import static org.springframework.data.mongodb.core.query.Query.*;
2222

2323
import java.net.UnknownHostException;
24+
import java.util.Arrays;
25+
import java.util.HashMap;
26+
import java.util.Map;
2427

28+
import org.bson.Document;
2529
import org.junit.After;
2630
import org.junit.Assert;
2731
import org.junit.Before;
@@ -33,7 +37,6 @@
3337
import org.springframework.data.mongodb.core.mapping.PersonPojoStringId;
3438

3539
import com.mongodb.DB;
36-
import org.bson.Document;
3740
import com.mongodb.Mongo;
3841
import com.mongodb.MongoClient;
3942
import com.mongodb.WriteConcern;
@@ -43,12 +46,16 @@
4346
*
4447
* @author Mark Pollack
4548
* @author Christoph Strobl
49+
* @author Jordi Llach
4650
*/
4751
public class ApplicationContextEventTests {
4852

4953
private static final String COLLECTION_NAME = "personPojoStringId";
54+
private static final String ROOT_COLLECTION_NAME = "root";
55+
private static final String RELATED_COLLECTION_NAME = "related";
5056

51-
private final String[] collectionsToDrop = new String[] { COLLECTION_NAME };
57+
private final String[] collectionsToDrop = new String[] { COLLECTION_NAME, ROOT_COLLECTION_NAME,
58+
RELATED_COLLECTION_NAME };
5259

5360
private ApplicationContext applicationContext;
5461
private MongoTemplate template;
@@ -188,6 +195,154 @@ public void deleteEvents() {
188195
assertThat(simpleMappingEventListener.onAfterDeleteEvents.get(0).getCollectionName(), is(COLLECTION_NAME));
189196
}
190197

198+
/**
199+
* DATAMONGO-1271 DATAMONGO-1287
200+
*/
201+
@Test
202+
public void loadAndConvertEventsInInnerSimpleDBRef() throws Exception {
203+
ParentMappingEventListener simpleMappingEventListener = applicationContext
204+
.getBean(ParentMappingEventListener.class);
205+
Related embed = new Related(1L, "embed desc");
206+
Related ref1 = new Related(2L, "related desc1");
207+
Related ref2 = new Related(3L, "related desc2");
208+
template.insert(embed);
209+
template.insert(ref1);
210+
template.insert(ref2);
211+
212+
Root root = new Root(1L, embed, ref1, ref2, null, null, null, null);
213+
template.insert(root);
214+
215+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(0));
216+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(0));
217+
218+
// initially fetching ROOT document and also eagerly fetching 1 DBRef
219+
Root rootR = template.findOne(query(where("id").is(root.getId())), Root.class);
220+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(2));
221+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(2));
222+
223+
// checking that no event is fired because those documents were previously eagerly fetched
224+
rootR.getRef().getDescription();
225+
rootR.getEmbed().getDescription();
226+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(2));
227+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(2));
228+
// checking that accessing lazy DBRef fires 1 more event of each type
229+
rootR.getLazyRef().getDescription();
230+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3));
231+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3));
232+
233+
// checking collectionNames fired
234+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(0).getCollectionName(), is(ROOT_COLLECTION_NAME));
235+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(RELATED_COLLECTION_NAME));
236+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME));
237+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(1).getCollectionName(), is(ROOT_COLLECTION_NAME));
238+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME));
239+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME));
240+
}
241+
242+
/**
243+
* DATAMONGO-1271 DATAMONGO-1287
244+
*/
245+
@Test
246+
public void loadAndConvertEventsInInnerListDBRef() throws Exception {
247+
ParentMappingEventListener simpleMappingEventListener = applicationContext
248+
.getBean(ParentMappingEventListener.class);
249+
Related embed = new Related(1L, "embed desc");
250+
Related ref1 = new Related(2L, "related desc1");
251+
Related ref2 = new Related(3L, "related desc2");
252+
template.insert(embed);
253+
template.insert(ref1);
254+
template.insert(ref2);
255+
256+
Root root = new Root(1L, embed, null, null, Arrays.asList(ref1, ref2), Arrays.asList(ref1, ref2), null, null);
257+
template.insert(root);
258+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(0));
259+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(0));
260+
261+
// initially fetching ROOT document and also eagerly fetching 2 DBRef
262+
Root rootR = template.findOne(query(where("id").is(root.getId())), Root.class);
263+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3));
264+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3));
265+
266+
// checking that no event is fired because those documents were previously eagerly fetched
267+
rootR.getListRef().get(0).getDescription();
268+
rootR.getListRef().get(1).getDescription();
269+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3));
270+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3));
271+
272+
// fetching lazily dbref
273+
rootR.getListLazy().get(0).getDescription();
274+
rootR.getListLazy().get(1).getDescription();
275+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(5));
276+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(5));
277+
278+
// checking collectionNames fired
279+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(0).getCollectionName(), is(ROOT_COLLECTION_NAME));
280+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(RELATED_COLLECTION_NAME));
281+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME));
282+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME));
283+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME));
284+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(2).getCollectionName(), is(ROOT_COLLECTION_NAME));
285+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME));
286+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME));
287+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME));
288+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME));
289+
}
290+
291+
/**
292+
* DATAMONGO-1271 DATAMONGO-1287
293+
*/
294+
@Test
295+
public void loadAndConvertEventsInInnerMapDBRef() throws Exception {
296+
ParentMappingEventListener simpleMappingEventListener = applicationContext
297+
.getBean(ParentMappingEventListener.class);
298+
Related embed = new Related(1L, "embed desc");
299+
Related ref1 = new Related(2L, "related desc1");
300+
Related ref2 = new Related(3L, "related desc2");
301+
template.insert(embed);
302+
template.insert(ref1);
303+
template.insert(ref2);
304+
305+
Map<String, Related> mapRef = new HashMap();
306+
mapRef.put("1", ref1);
307+
mapRef.put("2", ref2);
308+
Map<String, Related> mapLazy = new HashMap();
309+
mapLazy.put("1", ref1);
310+
mapLazy.put("2", ref2);
311+
312+
Root root = new Root(1L, embed, null, null, null, null, mapRef, mapLazy);
313+
template.insert(root);
314+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(0));
315+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(0));
316+
317+
// initially fetching ROOT document and also eagerly fetching 2 DBRef (eager map)
318+
Root rootR = template.findOne(query(where("id").is(root.getId())), Root.class);
319+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3));
320+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3));
321+
322+
// checking that accessing eagerly fetched map does not fire any new event
323+
Assert.assertEquals(0,
324+
rootR.getMapRef().keySet().stream().filter(key -> rootR.getMapRef().get(key).getDescription() == null).count());
325+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3));
326+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3));
327+
// accessing lazy map of dbref
328+
Assert.assertEquals(0, rootR.getMapLazy().keySet().stream()
329+
.filter(key -> rootR.getMapLazy().get(key).getDescription() == null).count());
330+
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(5));
331+
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(5));
332+
333+
// checking collectionNames fired
334+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(0).getCollectionName(), is(ROOT_COLLECTION_NAME));
335+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(RELATED_COLLECTION_NAME));
336+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME));
337+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME));
338+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME));
339+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(2).getCollectionName(), is(ROOT_COLLECTION_NAME));
340+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME));
341+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME));
342+
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME));
343+
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME));
344+
}
345+
191346
private void comparePersonAndDbo(PersonPojoStringId p, PersonPojoStringId p2, Document dbo) {
192347
assertEquals(p.getId(), p2.getId());
193348
assertEquals(p.getText(), p2.getText());

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTestsAppConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,8 @@ public SimpleMappingEventListener simpleMappingEventListener() {
5151
return new SimpleMappingEventListener();
5252
}
5353

54+
@Bean
55+
public ParentMappingEventListener parentMappingEventListener() {
56+
return new ParentMappingEventListener();
57+
}
5458
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* To change this license header, choose License Headers in Project Properties.
3+
* To change this template file, choose Tools | Templates
4+
* and open the template in the editor.
5+
*/
6+
package org.springframework.data.mongodb.core.mapping.event;
7+
8+
/**
9+
*
10+
* @author Jordi Llach
11+
*/
12+
public class Parent {
13+
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* To change this license header, choose License Headers in Project Properties.
3+
* To change this template file, choose Tools | Templates
4+
* and open the template in the editor.
5+
*/
6+
package org.springframework.data.mongodb.core.mapping.event;
7+
8+
import java.util.ArrayList;
9+
10+
/**
11+
*
12+
* @author Jordi Llach
13+
*/
14+
public class ParentMappingEventListener extends AbstractMongoEventListener<Parent> {
15+
16+
public final ArrayList<AfterLoadEvent<Parent>> onAfterLoadEvents = new ArrayList<AfterLoadEvent<Parent>>();
17+
public final ArrayList<AfterConvertEvent<Parent>> onAfterConvertEvents = new ArrayList<AfterConvertEvent<Parent>>();
18+
19+
@Override
20+
public void onAfterLoad(AfterLoadEvent<Parent> event) {
21+
onAfterLoadEvents.add(event);
22+
}
23+
24+
@Override
25+
public void onAfterConvert(AfterConvertEvent<Parent> event) {
26+
onAfterConvertEvents.add(event);
27+
}
28+
}

0 commit comments

Comments
 (0)