Skip to content

Commit d1251c4

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 4140dd5 commit d1251c4

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
@@ -30,6 +30,7 @@
3030
import org.springframework.beans.BeansException;
3131
import org.springframework.context.ApplicationContext;
3232
import org.springframework.context.ApplicationContextAware;
33+
import org.springframework.context.ApplicationEventPublisher;
3334
import org.springframework.core.CollectionFactory;
3435
import org.springframework.core.convert.ConversionException;
3536
import org.springframework.core.convert.ConversionService;
@@ -54,6 +55,8 @@
5455
import org.springframework.data.mongodb.MongoDbFactory;
5556
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
5657
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
58+
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
59+
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
5760
import org.springframework.data.util.ClassTypeInformation;
5861
import org.springframework.data.util.TypeInformation;
5962
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -75,6 +78,7 @@
7578
* @author Patrik Wasik
7679
* @author Thomas Darimont
7780
* @author Christoph Strobl
81+
* @author Jordi Llach
7882
*/
7983
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware, ValueResolver {
8084

@@ -868,7 +872,7 @@ protected DBRef createDBRef(Object target, MongoPersistentProperty property) {
868872
@Override
869873
public Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator,
870874
ObjectPath path) {
871-
return new MongoDbPropertyValueProvider(dbo, evaluator, path).getPropertyValue(prop);
875+
return new MongoDbPropertyValueProvider(dbo, evaluator, path, false).getPropertyValue(prop);
872876
}
873877

874878
/**
@@ -902,8 +906,8 @@ private Object readCollectionOrArray(TypeInformation<?> targetType, BasicDBList
902906
Object dbObjItem = sourceValue.get(i);
903907

904908
if (dbObjItem instanceof DBRef) {
905-
items.add(
906-
DBRef.class.equals(rawComponentType) ? dbObjItem : read(componentType, readRef((DBRef) dbObjItem), path));
909+
items.add(DBRef.class.equals(rawComponentType) ? dbObjItem
910+
: readAndConvertDBRef((DBRef) dbObjItem, componentType, path, rawComponentType));
907911
} else if (dbObjItem instanceof DBObject) {
908912
items.add(read(componentType, (DBObject) dbObjItem, path));
909913
} else {
@@ -955,7 +959,8 @@ protected Map<Object, Object> readMap(TypeInformation<?> type, DBObject dbObject
955959
if (value instanceof DBObject) {
956960
map.put(key, read(valueType, (DBObject) value, path));
957961
} else if (value instanceof DBRef) {
958-
map.put(key, DBRef.class.equals(rawValueType) ? value : read(valueType, readRef((DBRef) value)));
962+
map.put(key, DBRef.class.equals(rawValueType) ? value
963+
: readAndConvertDBRef((DBRef) value, valueType, ObjectPath.ROOT, rawValueType));
959964
} else {
960965
Class<?> valueClass = valueType == null ? null : valueType.getType();
961966
map.put(key, getPotentiallyConvertedSimpleRead(value, valueClass));
@@ -1111,6 +1116,7 @@ private class MongoDbPropertyValueProvider implements PropertyValueProvider<Mong
11111116
private final DBObjectAccessor source;
11121117
private final SpELExpressionEvaluator evaluator;
11131118
private final ObjectPath path;
1119+
private final boolean ignoreLazyDBRefProperties;
11141120

11151121
/**
11161122
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
@@ -1121,13 +1127,18 @@ private class MongoDbPropertyValueProvider implements PropertyValueProvider<Mong
11211127
* @param path can be {@literal null}.
11221128
*/
11231129
public MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path) {
1130+
this(source, evaluator, path, true); // ignoring by default
1131+
}
11241132

1133+
MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path,
1134+
boolean ignoreLazyDBRefProperties) {
11251135
Assert.notNull(source);
11261136
Assert.notNull(evaluator);
11271137

11281138
this.source = new DBObjectAccessor(source);
11291139
this.evaluator = evaluator;
11301140
this.path = path;
1141+
this.ignoreLazyDBRefProperties = ignoreLazyDBRefProperties;
11311142
}
11321143

11331144
/*
@@ -1142,6 +1153,12 @@ public <T> T getPropertyValue(MongoPersistentProperty property) {
11421153
if (value == null) {
11431154
return null;
11441155
}
1156+
if (this.ignoreLazyDBRefProperties && property.isDbReference() && property.getDBRef().lazy()) { // lazy DBRef,
1157+
// BasicDBList are
1158+
// resolved later
1159+
// by default
1160+
return null;
1161+
}
11451162

11461163
return readValue(value, property.getTypeInformation(), path);
11471164
}
@@ -1203,14 +1220,28 @@ private <T> T readValue(Object value, TypeInformation<?> type, ObjectPath path)
12031220

12041221
@SuppressWarnings("unchecked")
12051222
private <T> T potentiallyReadOrResolveDbRef(DBRef dbref, TypeInformation<?> type, ObjectPath path, Class<?> rawType) {
1206-
12071223
if (rawType.equals(DBRef.class)) {
12081224
return (T) dbref;
12091225
}
1210-
12111226
Object object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getCollectionName());
1227+
return (T) (object != null ? object : readAndConvertDBRef(dbref, type, path, rawType));
1228+
}
1229+
1230+
private <T> T readAndConvertDBRef(DBRef dbref, TypeInformation<?> type, ObjectPath path, Class<?> rawType) {
1231+
DBObject readRef = readRef(dbref);
1232+
final String collectionName = dbref.getCollectionName();
1233+
if (canPublishEvent())
1234+
((ApplicationEventPublisher) this.applicationContext)
1235+
.publishEvent(new AfterLoadEvent<T>(readRef, (Class<T>) rawType, collectionName));
1236+
T t = (T) read(type, readRef, path);
1237+
if (canPublishEvent())
1238+
((ApplicationEventPublisher) this.applicationContext)
1239+
.publishEvent(new AfterConvertEvent<T>(readRef, t, collectionName));
1240+
return t;
1241+
}
12121242

1213-
return (T) (object != null ? object : read(type, readRef(dbref), path));
1243+
private boolean canPublishEvent() {
1244+
return this.applicationContext != null;
12141245
}
12151246

12161247
/**

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)