Skip to content

Commit 622be71

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 dc6b986 commit 622be71

File tree

7 files changed

+382
-21
lines changed

7 files changed

+382
-21
lines changed

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

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.beans.BeansException;
3030
import org.springframework.context.ApplicationContext;
3131
import org.springframework.context.ApplicationContextAware;
32+
import org.springframework.context.ApplicationEventPublisher;
3233
import org.springframework.core.CollectionFactory;
3334
import org.springframework.core.convert.ConversionException;
3435
import org.springframework.core.convert.ConversionService;
@@ -53,6 +54,8 @@
5354
import org.springframework.data.mongodb.MongoDbFactory;
5455
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
5556
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
57+
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
58+
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
5659
import org.springframework.data.util.ClassTypeInformation;
5760
import org.springframework.data.util.TypeInformation;
5861
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -74,6 +77,7 @@
7477
* @author Patrik Wasik
7578
* @author Thomas Darimont
7679
* @author Christoph Strobl
80+
* @author Jordi Llach
7781
*/
7882
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware, ValueResolver {
7983

@@ -867,7 +871,7 @@ protected DBRef createDBRef(Object target, MongoPersistentProperty property) {
867871
@Override
868872
public Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator,
869873
ObjectPath path) {
870-
return new MongoDbPropertyValueProvider(dbo, evaluator, path).getPropertyValue(prop);
874+
return new MongoDbPropertyValueProvider(dbo, evaluator, path, false).getPropertyValue(prop);
871875
}
872876

873877
/**
@@ -901,8 +905,8 @@ private Object readCollectionOrArray(TypeInformation<?> targetType, BasicDBList
901905
Object dbObjItem = sourceValue.get(i);
902906

903907
if (dbObjItem instanceof DBRef) {
904-
items.add(
905-
DBRef.class.equals(rawComponentType) ? dbObjItem : read(componentType, readRef((DBRef) dbObjItem), path));
908+
items.add(DBRef.class.equals(rawComponentType) ? dbObjItem
909+
: readAndConvertDBRef((DBRef) dbObjItem, componentType, path, rawComponentType));
906910
} else if (dbObjItem instanceof DBObject) {
907911
items.add(read(componentType, (DBObject) dbObjItem, path));
908912
} else {
@@ -954,7 +958,8 @@ protected Map<Object, Object> readMap(TypeInformation<?> type, DBObject dbObject
954958
if (value instanceof DBObject) {
955959
map.put(key, read(valueType, (DBObject) value, path));
956960
} else if (value instanceof DBRef) {
957-
map.put(key, DBRef.class.equals(rawValueType) ? value : read(valueType, readRef((DBRef) value)));
961+
map.put(key, DBRef.class.equals(rawValueType) ? value
962+
: readAndConvertDBRef((DBRef) value, valueType, ObjectPath.ROOT, rawValueType));
958963
} else {
959964
Class<?> valueClass = valueType == null ? null : valueType.getType();
960965
map.put(key, getPotentiallyConvertedSimpleRead(value, valueClass));
@@ -1099,6 +1104,7 @@ private class MongoDbPropertyValueProvider implements PropertyValueProvider<Mong
10991104
private final DBObjectAccessor source;
11001105
private final SpELExpressionEvaluator evaluator;
11011106
private final ObjectPath path;
1107+
private final boolean ignoreLazyDBRefProperties;
11021108

11031109
/**
11041110
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
@@ -1109,13 +1115,18 @@ private class MongoDbPropertyValueProvider implements PropertyValueProvider<Mong
11091115
* @param path can be {@literal null}.
11101116
*/
11111117
public MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path) {
1118+
this(source, evaluator, path, true); // ignoring by default
1119+
}
11121120

1121+
MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path,
1122+
boolean ignoreLazyDBRefProperties) {
11131123
Assert.notNull(source);
11141124
Assert.notNull(evaluator);
11151125

11161126
this.source = new DBObjectAccessor(source);
11171127
this.evaluator = evaluator;
11181128
this.path = path;
1129+
this.ignoreLazyDBRefProperties = ignoreLazyDBRefProperties;
11191130
}
11201131

11211132
/*
@@ -1130,6 +1141,12 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
11301141
if (value == null) {
11311142
return null;
11321143
}
1144+
if (this.ignoreLazyDBRefProperties && property.isDbReference() && property.getDBRef().lazy()) { // lazy DBRef,
1145+
// BasicDBList are
1146+
// resolved later
1147+
// by default
1148+
return null;
1149+
}
11331150

11341151
return readValue(value, property.getTypeInformation(), path);
11351152
}
@@ -1191,14 +1208,28 @@ private <T> T readValue(Object value, TypeInformation<?> type, ObjectPath path)
11911208

11921209
@SuppressWarnings("unchecked")
11931210
private <T> T potentiallyReadOrResolveDbRef(DBRef dbref, TypeInformation<?> type, ObjectPath path, Class<?> rawType) {
1194-
11951211
if (rawType.equals(DBRef.class)) {
11961212
return (T) dbref;
11971213
}
1198-
11991214
Object object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getCollectionName());
1215+
return (T) (object != null ? object : readAndConvertDBRef(dbref, type, path, rawType));
1216+
}
1217+
1218+
private <T> T readAndConvertDBRef(DBRef dbref, TypeInformation<?> type, ObjectPath path, Class<?> rawType) {
1219+
DBObject readRef = readRef(dbref);
1220+
final String collectionName = dbref.getCollectionName();
1221+
if (canPublishEvent())
1222+
((ApplicationEventPublisher) this.applicationContext)
1223+
.publishEvent(new AfterLoadEvent<T>(readRef, (Class<T>) rawType, collectionName));
1224+
T t = (T) read(type, readRef, path);
1225+
if (canPublishEvent())
1226+
((ApplicationEventPublisher) this.applicationContext)
1227+
.publishEvent(new AfterConvertEvent<T>(readRef, t, collectionName));
1228+
return t;
1229+
}
12001230

1201-
return (T) (object != null ? object : read(type, readRef(dbref), path));
1231+
private boolean canPublishEvent() {
1232+
return this.applicationContext != null;
12021233
}
12031234

12041235
/**

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

Lines changed: 161 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,44 @@
1515
*/
1616
package org.springframework.data.mongodb.core.mapping.event;
1717

18-
import static org.hamcrest.core.Is.*;
19-
import static org.junit.Assert.*;
20-
import static org.springframework.data.mongodb.core.query.Criteria.*;
21-
import static org.springframework.data.mongodb.core.query.Query.*;
22-
18+
import com.mongodb.DB;
19+
import com.mongodb.DBObject;
20+
import com.mongodb.Mongo;
21+
import com.mongodb.MongoClient;
22+
import com.mongodb.WriteConcern;
2323
import java.net.UnknownHostException;
24-
24+
import java.util.Arrays;
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
import static org.hamcrest.core.Is.is;
2528
import org.junit.After;
2629
import org.junit.Assert;
30+
import static org.junit.Assert.assertEquals;
31+
import static org.junit.Assert.assertThat;
2732
import org.junit.Before;
2833
import org.junit.Test;
2934
import org.springframework.context.ApplicationContext;
3035
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3136
import org.springframework.data.mongodb.core.MongoTemplate;
3237
import org.springframework.data.mongodb.core.aggregation.Aggregation;
3338
import org.springframework.data.mongodb.core.mapping.PersonPojoStringId;
34-
35-
import com.mongodb.DB;
36-
import com.mongodb.DBObject;
37-
import com.mongodb.Mongo;
38-
import com.mongodb.MongoClient;
39-
import com.mongodb.WriteConcern;
39+
import static org.springframework.data.mongodb.core.query.Criteria.where;
40+
import static org.springframework.data.mongodb.core.query.Query.query;
4041

4142
/**
4243
* Integration test for Mapping Events.
4344
*
4445
* @author Mark Pollack
4546
* @author Christoph Strobl
47+
* @author Jordi Llach
4648
*/
4749
public class ApplicationContextEventTests {
4850

49-
private static final String COLLECTION_NAME = "personPojoStringId";
51+
private static final String COLLECTION_NAME = "personPojoStringId";
52+
private static final String ROOT_COLLECTION_NAME = "root";
53+
private static final String RELATED_COLLECTION_NAME = "related";
5054

51-
private final String[] collectionsToDrop = new String[] { COLLECTION_NAME };
55+
private final String[] collectionsToDrop = new String[] { COLLECTION_NAME, ROOT_COLLECTION_NAME, RELATED_COLLECTION_NAME };
5256

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

191338
private void comparePersonAndDbo(PersonPojoStringId p, PersonPojoStringId p2, DBObject dbo) {
192339
assertEquals(p.getId(), p2.getId());

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)