DATAMONGO-1271 - Provide lifecycle events for DBRefs.

We now publish livecycle events when loading DBRefs.

Original Pull Request: #322
CLA: 121620150519031801 (Jordi Llach Fernandez)
This commit is contained in:
Jordi Llach Fernandez
2015-09-17 01:25:49 +02:00
committed by Christoph Strobl
parent 4140dd573f
commit d1251c42ca
7 changed files with 382 additions and 21 deletions

View File

@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
@@ -54,6 +55,8 @@ import org.springframework.data.mapping.model.SpELExpressionParameterValueProvid
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -75,6 +78,7 @@ import com.mongodb.DBRef;
* @author Patrik Wasik
* @author Thomas Darimont
* @author Christoph Strobl
* @author Jordi Llach
*/
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware, ValueResolver {
@@ -868,7 +872,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@Override
public Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator,
ObjectPath path) {
return new MongoDbPropertyValueProvider(dbo, evaluator, path).getPropertyValue(prop);
return new MongoDbPropertyValueProvider(dbo, evaluator, path, false).getPropertyValue(prop);
}
/**
@@ -902,8 +906,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Object dbObjItem = sourceValue.get(i);
if (dbObjItem instanceof DBRef) {
items.add(
DBRef.class.equals(rawComponentType) ? dbObjItem : read(componentType, readRef((DBRef) dbObjItem), path));
items.add(DBRef.class.equals(rawComponentType) ? dbObjItem
: readAndConvertDBRef((DBRef) dbObjItem, componentType, path, rawComponentType));
} else if (dbObjItem instanceof DBObject) {
items.add(read(componentType, (DBObject) dbObjItem, path));
} else {
@@ -955,7 +959,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (value instanceof DBObject) {
map.put(key, read(valueType, (DBObject) value, path));
} else if (value instanceof DBRef) {
map.put(key, DBRef.class.equals(rawValueType) ? value : read(valueType, readRef((DBRef) value)));
map.put(key, DBRef.class.equals(rawValueType) ? value
: readAndConvertDBRef((DBRef) value, valueType, ObjectPath.ROOT, rawValueType));
} else {
Class<?> valueClass = valueType == null ? null : valueType.getType();
map.put(key, getPotentiallyConvertedSimpleRead(value, valueClass));
@@ -1111,6 +1116,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private final DBObjectAccessor source;
private final SpELExpressionEvaluator evaluator;
private final ObjectPath path;
private final boolean ignoreLazyDBRefProperties;
/**
* Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and
@@ -1121,13 +1127,18 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @param path can be {@literal null}.
*/
public MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path) {
this(source, evaluator, path, true); // ignoring by default
}
MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path,
boolean ignoreLazyDBRefProperties) {
Assert.notNull(source);
Assert.notNull(evaluator);
this.source = new DBObjectAccessor(source);
this.evaluator = evaluator;
this.path = path;
this.ignoreLazyDBRefProperties = ignoreLazyDBRefProperties;
}
/*
@@ -1142,6 +1153,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (value == null) {
return null;
}
if (this.ignoreLazyDBRefProperties && property.isDbReference() && property.getDBRef().lazy()) { // lazy DBRef,
// BasicDBList are
// resolved later
// by default
return null;
}
return readValue(value, property.getTypeInformation(), path);
}
@@ -1203,14 +1220,28 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@SuppressWarnings("unchecked")
private <T> T potentiallyReadOrResolveDbRef(DBRef dbref, TypeInformation<?> type, ObjectPath path, Class<?> rawType) {
if (rawType.equals(DBRef.class)) {
return (T) dbref;
}
Object object = dbref == null ? null : path.getPathItem(dbref.getId(), dbref.getCollectionName());
return (T) (object != null ? object : readAndConvertDBRef(dbref, type, path, rawType));
}
return (T) (object != null ? object : read(type, readRef(dbref), path));
private <T> T readAndConvertDBRef(DBRef dbref, TypeInformation<?> type, ObjectPath path, Class<?> rawType) {
DBObject readRef = readRef(dbref);
final String collectionName = dbref.getCollectionName();
if (canPublishEvent())
((ApplicationEventPublisher) this.applicationContext)
.publishEvent(new AfterLoadEvent<T>(readRef, (Class<T>) rawType, collectionName));
T t = (T) read(type, readRef, path);
if (canPublishEvent())
((ApplicationEventPublisher) this.applicationContext)
.publishEvent(new AfterConvertEvent<T>(readRef, t, collectionName));
return t;
}
private boolean canPublishEvent() {
return this.applicationContext != null;
}
/**

View File

@@ -15,15 +15,20 @@
*/
package org.springframework.data.mongodb.core.mapping.event;
import static org.hamcrest.core.Is.*;
import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import com.mongodb.DB;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.WriteConcern;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.core.Is.is;
import org.junit.After;
import org.junit.Assert;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
@@ -31,24 +36,23 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.mapping.PersonPojoStringId;
import com.mongodb.DB;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.WriteConcern;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;
/**
* Integration test for Mapping Events.
*
* @author Mark Pollack
* @author Christoph Strobl
* @author Jordi Llach
*/
public class ApplicationContextEventTests {
private static final String COLLECTION_NAME = "personPojoStringId";
private static final String COLLECTION_NAME = "personPojoStringId";
private static final String ROOT_COLLECTION_NAME = "root";
private static final String RELATED_COLLECTION_NAME = "related";
private final String[] collectionsToDrop = new String[] { COLLECTION_NAME };
private final String[] collectionsToDrop = new String[] { COLLECTION_NAME, ROOT_COLLECTION_NAME, RELATED_COLLECTION_NAME };
private ApplicationContext applicationContext;
private MongoTemplate template;
@@ -187,6 +191,149 @@ public class ApplicationContextEventTests {
assertThat(simpleMappingEventListener.onAfterDeleteEvents.size(), is(1));
assertThat(simpleMappingEventListener.onAfterDeleteEvents.get(0).getCollectionName(), is(COLLECTION_NAME));
}
/**
* DATAMONGO-1271 DATAMONGO-1287
*/
@Test
public void loadAndConvertEventsInInnerSimpleDBRef () throws Exception {
ParentMappingEventListener simpleMappingEventListener = applicationContext.getBean(ParentMappingEventListener.class);
Related embed = new Related(1L, "embed desc");
Related ref1 = new Related(2L, "related desc1");
Related ref2 = new Related(3L, "related desc2");
template.insert(embed);
template.insert(ref1);
template.insert(ref2);
Root root = new Root(1L, embed, ref1, ref2, null, null, null, null);
template.insert(root);
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(0));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(0));
// initially fetching ROOT document and also eagerly fetching 1 DBRef
Root rootR = template.findOne(query(where("id").is(root.getId())), Root.class);
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(2));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(2));
// checking that no event is fired because those documents were previously eagerly fetched
rootR.getRef().getDescription();
rootR.getEmbed().getDescription();
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(2));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(2));
// checking that accessing lazy DBRef fires 1 more event of each type
rootR.getLazyRef().getDescription();
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3));
// checking collectionNames fired
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(0).getCollectionName(), is(ROOT_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(1).getCollectionName(), is(ROOT_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME));
}
/**
* DATAMONGO-1271 DATAMONGO-1287
*/
@Test
public void loadAndConvertEventsInInnerListDBRef() throws Exception {
ParentMappingEventListener simpleMappingEventListener = applicationContext.getBean(ParentMappingEventListener.class);
Related embed = new Related(1L, "embed desc");
Related ref1 = new Related(2L, "related desc1");
Related ref2 = new Related(3L, "related desc2");
template.insert(embed);
template.insert(ref1);
template.insert(ref2);
Root root = new Root(1L, embed, null, null, Arrays.asList(ref1, ref2), Arrays.asList(ref1, ref2), null, null);
template.insert(root);
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(0));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(0));
// initially fetching ROOT document and also eagerly fetching 2 DBRef
Root rootR = template.findOne(query(where("id").is(root.getId())), Root.class);
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3));
// checking that no event is fired because those documents were previously eagerly fetched
rootR.getListRef().get(0).getDescription();
rootR.getListRef().get(1).getDescription();
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3));
// fetching lazily dbref
rootR.getListLazy().get(0).getDescription();
rootR.getListLazy().get(1).getDescription();
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(5));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(5));
// checking collectionNames fired
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(0).getCollectionName(), is(ROOT_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(2).getCollectionName(), is(ROOT_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME));
}
/**
* DATAMONGO-1271 DATAMONGO-1287
*/
@Test
public void loadAndConvertEventsInInnerMapDBRef() throws Exception {
ParentMappingEventListener simpleMappingEventListener = applicationContext.getBean(ParentMappingEventListener.class);
Related embed = new Related(1L, "embed desc");
Related ref1 = new Related(2L, "related desc1");
Related ref2 = new Related(3L, "related desc2");
template.insert(embed);
template.insert(ref1);
template.insert(ref2);
Map<String,Related> mapRef = new HashMap();
mapRef.put("1", ref1);
mapRef.put("2", ref2);
Map<String,Related> mapLazy = new HashMap();
mapLazy.put("1", ref1);
mapLazy.put("2", ref2);
Root root = new Root(1L, embed, null, null, null, null, mapRef, mapLazy);
template.insert(root);
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(0));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(0));
// initially fetching ROOT document and also eagerly fetching 2 DBRef (eager map)
Root rootR = template.findOne(query(where("id").is(root.getId())), Root.class);
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3));
// checking that accessing eagerly fetched map does not fire any new event
Assert.assertEquals(0, rootR.getMapRef().keySet().stream().filter(key -> rootR.getMapRef().get(key).getDescription() == null).count());
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(3));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(3));
// accessing lazy map of dbref
Assert.assertEquals(0, rootR.getMapLazy().keySet().stream().filter(key -> rootR.getMapLazy().get(key).getDescription() == null).count());
assertThat(simpleMappingEventListener.onAfterLoadEvents.size(), is(5));
assertThat(simpleMappingEventListener.onAfterConvertEvents.size(), is(5));
// checking collectionNames fired
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(0).getCollectionName(), is(ROOT_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(1).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(2).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(2).getCollectionName(), is(ROOT_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(3).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterLoadEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME));
assertThat(simpleMappingEventListener.onAfterConvertEvents.get(4).getCollectionName(), is(RELATED_COLLECTION_NAME));
}
private void comparePersonAndDbo(PersonPojoStringId p, PersonPojoStringId p2, DBObject dbo) {
assertEquals(p.getId(), p2.getId());

View File

@@ -51,4 +51,8 @@ public class ApplicationContextEventTestsAppConfig extends AbstractMongoConfigur
return new SimpleMappingEventListener();
}
@Bean
public ParentMappingEventListener parentMappingEventListener() {
return new ParentMappingEventListener();
}
}

View File

@@ -0,0 +1,14 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.springframework.data.mongodb.core.mapping.event;
/**
*
* @author Jordi Llach
*/
public class Parent {
}

View File

@@ -0,0 +1,28 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.springframework.data.mongodb.core.mapping.event;
import java.util.ArrayList;
/**
*
* @author Jordi Llach
*/
public class ParentMappingEventListener extends AbstractMongoEventListener<Parent> {
public final ArrayList<AfterLoadEvent<Parent>> onAfterLoadEvents = new ArrayList<AfterLoadEvent<Parent>>();
public final ArrayList<AfterConvertEvent<Parent>> onAfterConvertEvents = new ArrayList<AfterConvertEvent<Parent>>();
@Override
public void onAfterLoad(AfterLoadEvent<Parent> event) {
onAfterLoadEvents.add(event);
}
@Override
public void onAfterConvert(AfterConvertEvent<Parent> event) {
onAfterConvertEvents.add(event);
}
}

View File

@@ -0,0 +1,42 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.springframework.data.mongodb.core.mapping.event;
import java.io.Serializable;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.mongodb.core.mapping.Document;
/**
*
* @author Jordi Llach
*/
@Document
public class Related
extends Parent
implements Serializable {
private static final long serialVersionUID = -5719343113953216434L;
@Id
private Long id;
private String description;
@PersistenceConstructor
public Related(Long id, String description) {
this.id = id;
this.description = description;
}
public Long getId() {
return id;
}
public String getDescription() {
return description;
}
}

View File

@@ -0,0 +1,95 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.springframework.data.mongodb.core.mapping.event;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
/**
*
* @author Jordi Llach
*/
@Document
public class Root
extends Parent
implements Serializable {
private static final long serialVersionUID = -3211692873265644541L;
@Id
private Long id;
// simple
private Related embed;
// dbref simple
@DBRef
private Related ref;
@DBRef(lazy = true)
private Related lazyRef;
// collection support
@DBRef
private List<Related> listRef;
@DBRef(lazy = true)
private List<Related> listLazy;
// map support
@DBRef
private Map<String, Related> mapRef;
@DBRef(lazy = true)
private Map<String, Related> mapLazy;
@PersistenceConstructor
public Root(Long id, Related embed, Related ref, Related lazyRef, List<Related> listRef, List<Related> listLazy,
Map<String, Related> mapRef, Map<String, Related> mapLazy) {
this.id = id;
this.embed = embed;
this.ref = ref;
this.lazyRef = lazyRef;
this.listRef = listRef;
this.listLazy = listLazy;
this.mapRef = mapRef;
this.mapLazy = mapLazy;
}
public Long getId() {
return id;
}
public Related getEmbed() {
return embed;
}
public Related getRef() {
return ref;
}
public Related getLazyRef() {
return lazyRef;
}
public List<Related> getListRef() {
return listRef;
}
public List<Related> getListLazy() {
return listLazy;
}
public Map<String, Related> getMapRef() {
return mapRef;
}
public Map<String, Related> getMapLazy() {
return mapLazy;
}
}