DATAMONGO-2479 - Add AfterConvertCallback and AfterSaveCallback (and their reactive versions).
Previously, only BeforeConvertCallback and BeforeSaveCallback were supported (and their reactive counterparts). This commit adds support for 'after-convert' and 'after-save' events using entity callbacks feature. Original pull request: #839.
This commit is contained in:
committed by
Mark Paluch
parent
b0b905ddb7
commit
ee59c6b774
@@ -32,6 +32,7 @@ import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.convert.UpdateMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
@@ -62,6 +63,7 @@ import com.mongodb.client.model.*;
|
||||
* @author Minsu Kim
|
||||
* @author Jens Schauder
|
||||
* @author Michail Nikolaev
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 1.9
|
||||
*/
|
||||
class DefaultBulkOperations implements BulkOperations {
|
||||
@@ -300,6 +302,7 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
Assert.state(result != null, "Result must not be null.");
|
||||
|
||||
models.forEach(this::maybeEmitAfterSaveEvent);
|
||||
models.forEach(this::maybeInvokeAfterSaveCallback);
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
@@ -447,6 +450,19 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeInvokeAfterSaveCallback(SourceAwareWriteModelHolder it) {
|
||||
|
||||
if (it.getModel() instanceof InsertOneModel) {
|
||||
|
||||
Document target = ((InsertOneModel<Document>) it.getModel()).getDocument();
|
||||
maybeInvokeAfterSaveCallback(it.getSource(), target);
|
||||
} else if (it.getModel() instanceof ReplaceOneModel) {
|
||||
|
||||
Document target = ((ReplaceOneModel<Document>) it.getModel()).getReplacement();
|
||||
maybeInvokeAfterSaveCallback(it.getSource(), target);
|
||||
}
|
||||
}
|
||||
|
||||
private <E extends MongoMappingEvent<T>, T> E maybeEmitEvent(E event) {
|
||||
|
||||
if (null != bulkOperationContext.getEventPublisher()) {
|
||||
@@ -475,6 +491,16 @@ class DefaultBulkOperations implements BulkOperations {
|
||||
collectionName);
|
||||
}
|
||||
|
||||
private Object maybeInvokeAfterSaveCallback(Object value, Document mappedDocument) {
|
||||
|
||||
if (bulkOperationContext.getEntityCallbacks() == null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return bulkOperationContext.getEntityCallbacks().callback(AfterSaveCallback.class, value, mappedDocument,
|
||||
collectionName);
|
||||
}
|
||||
|
||||
private static BulkWriteOptions getBulkWriteOptions(BulkMode bulkMode) {
|
||||
|
||||
BulkWriteOptions options = new BulkWriteOptions();
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -70,16 +71,7 @@ import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.JsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.convert.UpdateMapper;
|
||||
import org.springframework.data.mongodb.core.convert.*;
|
||||
import org.springframework.data.mongodb.core.index.IndexOperations;
|
||||
import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
|
||||
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
|
||||
@@ -87,16 +79,7 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCre
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
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.AfterDeleteEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeDeleteEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.*;
|
||||
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
|
||||
import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
@@ -128,16 +111,7 @@ import com.mongodb.ClientSessionOptions;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.AggregateIterable;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.DistinctIterable;
|
||||
import com.mongodb.client.FindIterable;
|
||||
import com.mongodb.client.MapReduceIterable;
|
||||
import com.mongodb.client.MongoClient;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoCursor;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.MongoIterable;
|
||||
import com.mongodb.client.*;
|
||||
import com.mongodb.client.model.*;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
@@ -166,6 +140,7 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Andreas Zink
|
||||
* @author Cimon Lucas
|
||||
* @author Michael J. Simons
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
public class MongoTemplate implements MongoOperations, ApplicationContextAware, IndexOperationsProvider {
|
||||
|
||||
@@ -1070,8 +1045,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(replacement, mappedReplacement, collectionName));
|
||||
maybeCallBeforeSave(replacement, mappedReplacement, collectionName);
|
||||
|
||||
return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
|
||||
T saved = doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
|
||||
queryContext.getCollation(entityType).orElse(null), entityType, mappedReplacement, options, resultType);
|
||||
if (saved != null) {
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, mappedReplacement, collectionName));
|
||||
return maybeCallAfterSave(saved, mappedReplacement, collectionName);
|
||||
}
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
// Find methods that take a Query to express the query and that return a single object that is also removed from the
|
||||
@@ -1233,8 +1214,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
T saved = populateIdIfNecessary(initialized, id);
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName));
|
||||
|
||||
return saved;
|
||||
return maybeCallAfterSave(saved, dbDoc, collectionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1327,8 +1307,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
if (i < ids.size()) {
|
||||
T saved = populateIdIfNecessary(obj, ids.get(i));
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, documentList.get(i), collectionName));
|
||||
savedObjects.add(saved);
|
||||
Document doc = documentList.get(i);
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, doc, collectionName));
|
||||
savedObjects.add(maybeCallAfterSave(saved, doc, collectionName));
|
||||
} else {
|
||||
savedObjects.add(obj);
|
||||
}
|
||||
@@ -1398,7 +1379,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
maybeEmitEvent(new AfterSaveEvent<>(toSave, mapped.getDocument(), collectionName));
|
||||
|
||||
return toSave;
|
||||
return maybeCallAfterSave(toSave, mapped.getDocument(), collectionName);
|
||||
}
|
||||
|
||||
protected <T> T doSave(String collectionName, T objectToSave, MongoWriter<T> writer) {
|
||||
@@ -1419,7 +1400,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
T saved = populateIdIfNecessary(objectToSave, id);
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName));
|
||||
|
||||
return saved;
|
||||
return maybeCallAfterSave(saved, dbDoc, collectionName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@@ -2312,7 +2293,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
return event;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T maybeCallBeforeConvert(T object, String collection) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
@@ -2322,7 +2302,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
return object;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T maybeCallBeforeSave(T object, Document document, String collection) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
@@ -2332,6 +2311,24 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
return object;
|
||||
}
|
||||
|
||||
protected <T> T maybeCallAfterSave(T object, Document document, String collection) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
return entityCallbacks.callback(AfterSaveCallback.class, object, document, collection);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
protected <T> T maybeCallAfterConvert(T object, Document document, String collection) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
return entityCallbacks.callback(AfterConvertCallback.class, object, document, collection);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the specified collection using the provided options
|
||||
*
|
||||
@@ -3101,6 +3098,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private class ReadDocumentCallback<T> implements DocumentCallback<T> {
|
||||
@@ -3110,16 +3108,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private final String collectionName;
|
||||
|
||||
@Nullable
|
||||
public T doWith(@Nullable Document object) {
|
||||
public T doWith(@Nullable Document document) {
|
||||
|
||||
if (null != object) {
|
||||
maybeEmitEvent(new AfterLoadEvent<>(object, type, collectionName));
|
||||
if (null != document) {
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
|
||||
}
|
||||
|
||||
T source = reader.read(type, object);
|
||||
T source = reader.read(type, document);
|
||||
|
||||
if (null != source) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(object, source, collectionName));
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, source, collectionName));
|
||||
source = maybeCallAfterConvert(source, document, collectionName);
|
||||
}
|
||||
|
||||
return source;
|
||||
@@ -3148,24 +3147,25 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public T doWith(@Nullable Document object) {
|
||||
public T doWith(@Nullable Document document) {
|
||||
|
||||
if (object == null) {
|
||||
if (document == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType
|
||||
: targetType;
|
||||
|
||||
if (null != object) {
|
||||
maybeEmitEvent(new AfterLoadEvent<>(object, targetType, collectionName));
|
||||
if (null != document) {
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, targetType, collectionName));
|
||||
}
|
||||
|
||||
Object source = reader.read(typeToRead, object);
|
||||
Object source = reader.read(typeToRead, document);
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;
|
||||
|
||||
if (null != result) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(object, result, collectionName));
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName));
|
||||
result = maybeCallAfterConvert(result, document, collectionName);
|
||||
}
|
||||
|
||||
return (T) result;
|
||||
|
||||
@@ -74,16 +74,7 @@ import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.PrefixingDelegatingAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.JsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.convert.UpdateMapper;
|
||||
import org.springframework.data.mongodb.core.convert.*;
|
||||
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
|
||||
import org.springframework.data.mongodb.core.index.ReactiveIndexOperations;
|
||||
import org.springframework.data.mongodb.core.index.ReactiveMongoPersistentEntityIndexCreator;
|
||||
@@ -91,16 +82,7 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterDeleteEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeDeleteEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeConvertCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeSaveCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.*;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.query.Meta;
|
||||
@@ -127,16 +109,7 @@ import com.mongodb.CursorType;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.model.CountOptions;
|
||||
import com.mongodb.client.model.CreateCollectionOptions;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndDeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndReplaceOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
import com.mongodb.client.model.ReplaceOptions;
|
||||
import com.mongodb.client.model.ReturnDocument;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.client.model.ValidationOptions;
|
||||
import com.mongodb.client.model.*;
|
||||
import com.mongodb.client.model.changestream.FullDocument;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.InsertOneResult;
|
||||
@@ -163,6 +136,7 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ReactiveMongoTemplate implements ReactiveMongoOperations, ApplicationContextAware {
|
||||
@@ -1050,7 +1024,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
cursor = cursor.maxTime(options.getMaxTime().toMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
return Flux.from(cursor).map(readCallback::doWith);
|
||||
return Flux.from(cursor).concatMap(readCallback::doWith);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1093,7 +1067,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
.withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
|
||||
|
||||
return aggregate($geoNear, collection, Document.class) //
|
||||
.map(callback::doWith);
|
||||
.concatMap(callback::doWith);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1186,9 +1160,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}).flatMap(it -> {
|
||||
|
||||
PersistableEntityModel<S> flowObject = (PersistableEntityModel<S>) it;
|
||||
return doFindAndReplace(flowObject.getCollection(), mappedQuery, mappedFields, mappedSort,
|
||||
queryContext.getCollation(entityType).orElse(null), entityType, flowObject.getTarget(), options,
|
||||
resultType);
|
||||
Mono<T> afterFindAndReplace = doFindAndReplace(flowObject.getCollection(), mappedQuery,
|
||||
mappedFields, mappedSort, queryContext.getCollation(entityType).orElse(null),
|
||||
entityType, flowObject.getTarget(), options, resultType);
|
||||
return afterFindAndReplace.flatMap(saved -> {
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, flowObject.getTarget(), flowObject.getCollection()));
|
||||
return maybeCallAfterSave(saved, flowObject.getTarget(), flowObject.getCollection());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1345,12 +1323,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
}).flatMap(it -> {
|
||||
|
||||
return insertDocument(it.getCollection(), it.getTarget(), it.getSource().getClass()).map(id -> {
|
||||
return insertDocument(it.getCollection(), it.getTarget(), it.getSource().getClass()).flatMap(id -> {
|
||||
|
||||
T saved = operations.forEntity(it.getSource(), mongoConverter.getConversionService())
|
||||
.populateIdIfNecessary(id);
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, it.getTarget(), collectionName));
|
||||
return saved;
|
||||
return maybeCallAfterSave(saved, it.getTarget(), collectionName);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1436,13 +1414,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return insertDocumentList(collectionName, documents).thenMany(Flux.fromIterable(tuples));
|
||||
});
|
||||
|
||||
return insertDocuments.map(tuple -> {
|
||||
return insertDocuments.flatMap(tuple -> {
|
||||
|
||||
Object id = MappedDocument.of(tuple.getT2()).getId();
|
||||
Document document = tuple.getT2();
|
||||
Object id = MappedDocument.of(document).getId();
|
||||
|
||||
T saved = tuple.getT1().populateIdIfNecessary(id);
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, tuple.getT2(), collectionName));
|
||||
return saved;
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, document, collectionName));
|
||||
return maybeCallAfterSave(saved, document, collectionName);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1522,9 +1501,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(toConvert, document, collectionName));
|
||||
return maybeCallBeforeSave(toConvert, document, collectionName).flatMap(it -> {
|
||||
|
||||
return doUpdate(collectionName, query, mapped.updateWithoutId(), it.getClass(), false, false).map(result -> {
|
||||
return maybeEmitEvent(new AfterSaveEvent<T>(it, document, collectionName)).getSource();
|
||||
});
|
||||
return doUpdate(collectionName, query, mapped.updateWithoutId(), it.getClass(), false, false)
|
||||
.flatMap(result -> {
|
||||
maybeEmitEvent(new AfterSaveEvent<T>(it, document, collectionName));
|
||||
return maybeCallAfterSave(it, document, collectionName);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1546,10 +1527,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
return maybeCallBeforeSave(toConvert, dbDoc, collectionName).flatMap(it -> {
|
||||
|
||||
return saveDocument(collectionName, dbDoc, it.getClass()).map(id -> {
|
||||
return saveDocument(collectionName, dbDoc, it.getClass()).flatMap(id -> {
|
||||
|
||||
T saved = entity.populateIdIfNecessary(id);
|
||||
return maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName)).getSource();
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName));
|
||||
return maybeCallAfterSave(saved, dbDoc, collectionName);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2213,7 +2195,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
publisher = collation.map(Collation::toMongoCollation).map(publisher::collation).orElse(publisher);
|
||||
|
||||
return Flux.from(publisher)
|
||||
.map(new ReadDocumentCallback<>(mongoConverter, resultType, inputCollectionName)::doWith);
|
||||
.concatMap(new ReadDocumentCallback<>(mongoConverter, resultType, inputCollectionName)::doWith);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2613,7 +2595,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return event;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> Mono<T> maybeCallBeforeConvert(T object, String collection) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
@@ -2623,7 +2604,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> Mono<T> maybeCallBeforeSave(T object, Document document, String collection) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
@@ -2633,6 +2613,24 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
protected <T> Mono<T> maybeCallAfterSave(T object, Document document, String collection) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
return entityCallbacks.callback(ReactiveAfterSaveCallback.class, object, document, collection);
|
||||
}
|
||||
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
protected <T> Mono<T> maybeCallAfterConvert(T object, Document document, String collection) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
return entityCallbacks.callback(ReactiveAfterConvertCallback.class, object, document, collection);
|
||||
}
|
||||
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
private MongoCollection<Document> getAndPrepareCollection(MongoDatabase db, String collectionName) {
|
||||
|
||||
try {
|
||||
@@ -2720,7 +2718,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
DocumentCallback<T> objectCallback, String collectionName) {
|
||||
|
||||
return createMono(collectionName,
|
||||
collection -> Mono.from(collectionCallback.doInCollection(collection)).map(objectCallback::doWith));
|
||||
collection -> Mono.from(collectionCallback.doInCollection(collection)).flatMap(objectCallback::doWith));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2746,7 +2744,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
return createFlux(collectionName, collection -> {
|
||||
return Flux.from(preparer.initiateFind(collection, collectionCallback::doInCollection))
|
||||
.map(objectCallback::doWith);
|
||||
.concatMap(objectCallback::doWith);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3042,7 +3040,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
interface DocumentCallback<T> {
|
||||
|
||||
T doWith(Document object);
|
||||
Mono<T> doWith(Document object);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3071,6 +3069,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* {@link EntityReader}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
class ReadDocumentCallback<T> implements DocumentCallback<T> {
|
||||
|
||||
@@ -3088,27 +3087,33 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.collectionName = collectionName;
|
||||
}
|
||||
|
||||
public T doWith(@Nullable Document object) {
|
||||
public Mono<T> doWith(Document document) {
|
||||
|
||||
if (null != object) {
|
||||
maybeEmitEvent(new AfterLoadEvent<>(object, type, collectionName));
|
||||
}
|
||||
T source = reader.read(type, object);
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
|
||||
|
||||
T source = reader.read(type, document);
|
||||
if (null != source) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(object, source, collectionName));
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, source, collectionName));
|
||||
}
|
||||
return source;
|
||||
return Mono.defer(() -> {
|
||||
if (null != source) {
|
||||
return maybeCallAfterConvert(source, document, collectionName);
|
||||
} else {
|
||||
return Mono.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MongoTemplate.DocumentCallback} transforming {@link Document} into the given {@code targetType} or
|
||||
* {@link DocumentCallback} transforming {@link Document} into the given {@code targetType} or
|
||||
* decorating the {@code sourceType} with a {@literal projection} in case the {@code targetType} is an
|
||||
* {@litera interface}.
|
||||
* {@literal interface}.
|
||||
*
|
||||
* @param <S>
|
||||
* @param <T>
|
||||
* @author Christoph Strobl
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@@ -3119,29 +3124,30 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final @NonNull Class<T> targetType;
|
||||
private final @NonNull String collectionName;
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public T doWith(@Nullable Document object) {
|
||||
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
public Mono<T> doWith(Document document) {
|
||||
|
||||
Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) //
|
||||
? entityType //
|
||||
: targetType;
|
||||
|
||||
if (null != object) {
|
||||
maybeEmitEvent(new AfterLoadEvent<>(object, typeToRead, collectionName));
|
||||
}
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, typeToRead, collectionName));
|
||||
|
||||
Object source = reader.read(typeToRead, object);
|
||||
Object source = reader.read(typeToRead, document);
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;
|
||||
|
||||
if (null != source) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(object, result, collectionName));
|
||||
T castEntity = (T) result;
|
||||
if (null != castEntity) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, castEntity, collectionName));
|
||||
}
|
||||
return (T) result;
|
||||
|
||||
return Mono.defer(() -> {
|
||||
if (null != castEntity) {
|
||||
return maybeCallAfterConvert(castEntity, document, collectionName);
|
||||
} else {
|
||||
return Mono.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3151,6 +3157,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Chrstoph Strobl
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
static class GeoNearResultDocumentCallback<T> implements DocumentCallback<GeoResult<T>> {
|
||||
|
||||
@@ -3175,16 +3182,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.metric = metric;
|
||||
}
|
||||
|
||||
public GeoResult<T> doWith(Document object) {
|
||||
public Mono<GeoResult<T>> doWith(Document object) {
|
||||
|
||||
double distance = Double.NaN;
|
||||
final double distance;
|
||||
if (object.containsKey(distanceField)) {
|
||||
distance = NumberUtils.convertNumberToTargetClass(object.get(distanceField, Number.class), Double.class);
|
||||
} else {
|
||||
distance = Double.NaN;
|
||||
}
|
||||
|
||||
T doWith = delegate.doWith(object);
|
||||
|
||||
return new GeoResult<>(doWith, new Distance(distance, metric));
|
||||
|
||||
return delegate.doWith(object)
|
||||
.map(doWith -> new GeoResult<>(doWith, new Distance(distance, metric)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3202,7 +3210,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public FindPublisher<Document> prepare(FindPublisher<Document> findPublisher) {
|
||||
|
||||
FindPublisher<Document> findPublisherToUse = operations.forType(type) //
|
||||
|
||||
@@ -50,6 +50,7 @@ import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
|
||||
@@ -64,6 +65,7 @@ import org.springframework.data.mongodb.CodecRegistryProvider;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent;
|
||||
@@ -91,6 +93,7 @@ import com.mongodb.DBRef;
|
||||
* @author Christoph Strobl
|
||||
* @author Jordi Llach
|
||||
* @author Mark Paluch
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware {
|
||||
|
||||
@@ -110,6 +113,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
protected @Nullable CodecRegistryProvider codecRegistryProvider;
|
||||
|
||||
private SpELContext spELContext;
|
||||
private @Nullable EntityCallbacks entityCallbacks;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MappingMongoConverter} given the new {@link DbRefResolver} and {@link MappingContext}.
|
||||
@@ -212,6 +216,26 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
|
||||
this.applicationContext = applicationContext;
|
||||
this.spELContext = new SpELContext(this.spELContext, applicationContext);
|
||||
|
||||
if (entityCallbacks == null) {
|
||||
setEntityCallbacks(EntityCallbacks.create(applicationContext));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link EntityCallbacks} instance to use when invoking
|
||||
* {@link org.springframework.data.mapping.callback.EntityCallback callbacks} like the {@link AfterConvertCallback}.
|
||||
* <p />
|
||||
* Overrides potentially existing {@link EntityCallbacks}.
|
||||
*
|
||||
* @param entityCallbacks must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if the given instance is {@literal null}.
|
||||
* @since 3.0
|
||||
*/
|
||||
public void setEntityCallbacks(EntityCallbacks entityCallbacks) {
|
||||
|
||||
Assert.notNull(entityCallbacks, "EntityCallbacks must not be null!");
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1605,7 +1629,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
: bulkReadRefs(dbrefs);
|
||||
String collectionName = dbrefs.iterator().next().getCollectionName();
|
||||
|
||||
List<T> targeList = new ArrayList<>(dbrefs.size());
|
||||
List<T> targetList = new ArrayList<>(dbrefs.size());
|
||||
|
||||
for (Document document : referencedRawDocuments) {
|
||||
|
||||
@@ -1613,15 +1637,17 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, (Class<T>) rawType, collectionName));
|
||||
}
|
||||
|
||||
final T target = (T) read(type, document, path);
|
||||
targeList.add(target);
|
||||
T target = (T) read(type, document, path);
|
||||
|
||||
if (target != null) {
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, target, collectionName));
|
||||
target = maybeCallAfterConvert(target, document, collectionName);
|
||||
}
|
||||
|
||||
targetList.add(target);
|
||||
}
|
||||
|
||||
return targeList;
|
||||
return targetList;
|
||||
}
|
||||
|
||||
private void maybeEmitEvent(MongoMappingEvent<?> event) {
|
||||
@@ -1635,6 +1661,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return this.applicationContext != null;
|
||||
}
|
||||
|
||||
protected <T> T maybeCallAfterConvert(T object, Document document, String collection) {
|
||||
|
||||
if (null != entityCallbacks) {
|
||||
return entityCallbacks.callback(AfterConvertCallback.class, object, document, collection);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the fetch operation for the given {@link DBRef}.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping.event;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
|
||||
/**
|
||||
* Callback being invoked after a domain object is converted from a Document (when reading from the DB).
|
||||
*
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AfterConvertCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked after a domain object is converted from a Document. Can return either the same
|
||||
* or a modified instance of the domain object.
|
||||
*
|
||||
* @param entity the domain object (the result of the conversion).
|
||||
* @param document must not be {@literal null}.
|
||||
* @param collection name of the collection.
|
||||
* @return the domain object that is the result of the conversion from the Document.
|
||||
*/
|
||||
T onAfterConvert(T entity, Document document, String collection);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping.event;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
|
||||
/**
|
||||
* Entity callback triggered after save of a document.
|
||||
*
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 3.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AfterSaveCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked after a domain object is saved. Can return either the same or a modified instance
|
||||
* of the domain object.
|
||||
*
|
||||
* @param entity the domain object that was saved.
|
||||
* @param document {@link Document} representing the {@code entity}.
|
||||
* @param collection name of the collection.
|
||||
* @return the domain object that was persisted.
|
||||
*/
|
||||
T onAfterSave(T entity, Document document, String collection);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping.event;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
|
||||
/**
|
||||
* Callback being invoked after a domain object is converted from a Document (when reading from the DB).
|
||||
*
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 3.0
|
||||
* @see ReactiveEntityCallbacks
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveAfterConvertCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked after a domain object is converted from a Document. Can return either the same
|
||||
* or a modified instance of the domain object.
|
||||
*
|
||||
* @param entity the domain object (the result of the conversion).
|
||||
* @param document must not be {@literal null}.
|
||||
* @param collection name of the collection.
|
||||
* @return a {@link Publisher} emitting the domain object that is the result of the conversion from the Document.
|
||||
*/
|
||||
Publisher<T> onAfterConvert(T entity, Document document, String collection);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mongodb.core.mapping.event;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
|
||||
/**
|
||||
* Entity callback triggered after save of a document.
|
||||
*
|
||||
* @author Roman Puchkovskiy
|
||||
* @since 3.0
|
||||
* @see ReactiveEntityCallbacks
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ReactiveAfterSaveCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked after a domain object is saved. Can return either the same or a modified instance
|
||||
* of the domain object.
|
||||
*
|
||||
* @param entity the domain object that was saved.
|
||||
* @param document {@link Document} representing the {@code entity}.
|
||||
* @param collection name of the collection.
|
||||
* @return a {@link Publisher} emitting the domain object to be returned to the caller.
|
||||
*/
|
||||
Publisher<T> onAfterSave(T entity, Document document, String collection);
|
||||
}
|
||||
@@ -54,6 +54,7 @@ import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.convert.UpdateMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
@@ -84,6 +85,7 @@ import com.mongodb.client.model.WriteModel;
|
||||
* @author Mark Paluch
|
||||
* @author Minsu Kim
|
||||
* @author Jens Schauder
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DefaultBulkOperationsUnitTests {
|
||||
@@ -208,16 +210,17 @@ class DefaultBulkOperationsUnitTests {
|
||||
assertThat(updateModel.getReplacement().getString("lastName")).isEqualTo("Kim");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2261
|
||||
@Test // DATAMONGO-2261, DATAMONGO-2479
|
||||
void bulkInsertInvokesEntityCallbacks() {
|
||||
|
||||
BeforeConvertPersonCallback beforeConvertCallback = spy(new BeforeConvertPersonCallback());
|
||||
BeforeSavePersonCallback beforeSaveCallback = spy(new BeforeSavePersonCallback());
|
||||
AfterSavePersonCallback afterSaveCallback = spy(new AfterSavePersonCallback());
|
||||
|
||||
ops = new DefaultBulkOperations(template, "collection-1",
|
||||
new BulkOperationContext(BulkMode.ORDERED, Optional.of(mappingContext.getPersistentEntity(Person.class)),
|
||||
new QueryMapper(converter), new UpdateMapper(converter), null,
|
||||
EntityCallbacks.create(beforeConvertCallback, beforeSaveCallback)));
|
||||
EntityCallbacks.create(beforeConvertCallback, beforeSaveCallback, afterSaveCallback)));
|
||||
|
||||
Person entity = new Person("init");
|
||||
ops.insert(entity);
|
||||
@@ -229,11 +232,13 @@ class DefaultBulkOperationsUnitTests {
|
||||
ops.execute();
|
||||
|
||||
verify(beforeSaveCallback).onBeforeSave(personArgumentCaptor.capture(), any(), eq("collection-1"));
|
||||
assertThat(personArgumentCaptor.getAllValues()).extracting("firstName").containsExactly("init", "before-convert");
|
||||
verify(afterSaveCallback).onAfterSave(personArgumentCaptor.capture(), any(), eq("collection-1"));
|
||||
assertThat(personArgumentCaptor.getAllValues()).extracting("firstName")
|
||||
.containsExactly("init", "before-convert", "before-convert");
|
||||
verify(collection).bulkWrite(captor.capture(), any());
|
||||
|
||||
InsertOneModel<Document> updateModel = (InsertOneModel<Document>) captor.getValue().get(0);
|
||||
assertThat(updateModel.getDocument()).containsEntry("firstName", "before-save");
|
||||
assertThat(updateModel.getDocument()).containsEntry("firstName", "after-save");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2290
|
||||
@@ -365,6 +370,16 @@ class DefaultBulkOperationsUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
static class AfterSavePersonCallback implements AfterSaveCallback<Person> {
|
||||
|
||||
@Override
|
||||
public Person onAfterSave(Person entity, Document document, String collection) {
|
||||
|
||||
document.put("firstName", "after-save");
|
||||
return new Person("after-save");
|
||||
}
|
||||
}
|
||||
|
||||
static class NullExceptionTranslator implements PersistenceExceptionTranslator {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,7 +25,9 @@ import java.math.BigInteger;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -37,6 +39,7 @@ import org.assertj.core.api.Assertions;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -81,6 +84,9 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCre
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveCallback;
|
||||
@@ -97,10 +103,13 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.mongodb.MongoClientSettings;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.MongoNamespace;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.ServerAddress;
|
||||
import com.mongodb.ServerCursor;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.AggregateIterable;
|
||||
import com.mongodb.client.DistinctIterable;
|
||||
@@ -129,6 +138,7 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Michael J. Simons
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@@ -1921,6 +1931,241 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
verify(findIterable).projection(new Document("country", 1).append("userid", 1));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findShouldInvokeAfterConvertCallback() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(findIterable.iterator()).thenReturn(new OneElementCursor<>(document));
|
||||
|
||||
template.find(new Query(), Person.class);
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
}
|
||||
|
||||
private Document initialLukeDocument() {
|
||||
return new Document(ImmutableMap.of(
|
||||
"_id", "init",
|
||||
"firstname", "luke"
|
||||
));
|
||||
}
|
||||
|
||||
private Person initialLuke() {
|
||||
Person expectedEnitty = new Person();
|
||||
expectedEnitty.id = "init";
|
||||
expectedEnitty.firstname = "luke";
|
||||
return expectedEnitty;
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findByIdShouldInvokeAfterConvertCallback() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(findIterable.first()).thenReturn(document);
|
||||
|
||||
template.findById("init", Person.class);
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findOneShouldInvokeAfterConvertCallback() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(findIterable.first()).thenReturn(document);
|
||||
|
||||
template.findOne(new Query(), Person.class);
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAllShouldInvokeAfterConvertCallback() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(findIterable.iterator()).thenReturn(new OneElementCursor<>(document));
|
||||
|
||||
template.findAll(Person.class);
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAndModifyShouldInvokeAfterConvertCallback() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.findOneAndUpdate(any(Bson.class), any(Bson.class), any())).thenReturn(document);
|
||||
|
||||
template.findAndModify(new Query(), new Update(), Person.class);
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAndRemoveShouldInvokeAfterConvertCallback() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.findOneAndDelete(any(Bson.class), any())).thenReturn(document);
|
||||
|
||||
template.findAndRemove(new Query(), Person.class);
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAllAndRemoveShouldInvokeAfterConvertCallback() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(findIterable.iterator()).thenReturn(new OneElementCursor<>(document));
|
||||
|
||||
template.findAllAndRemove(new Query(), Person.class);
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAndReplaceShouldInvokeAfterConvertCallbacks() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Person entity = initialLuke();
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.findOneAndReplace(any(Bson.class), any(Document.class), any())).thenReturn(document);
|
||||
|
||||
Person saved = template.findAndReplace(new Query(), entity);
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
assertThat(saved.id).isEqualTo("after-convert");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void saveShouldInvokeAfterSaveCallbacks() {
|
||||
|
||||
ValueCapturingAfterSaveCallback afterSaveCallback = spy(new ValueCapturingAfterSaveCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
|
||||
|
||||
Person entity = initialLuke();
|
||||
|
||||
Person saved = template.save(entity);
|
||||
|
||||
verify(afterSaveCallback).onAfterSave(eq(entity), any(), anyString());
|
||||
assertThat(saved.id).isEqualTo("after-save");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void insertShouldInvokeAfterSaveCallbacks() {
|
||||
|
||||
ValueCapturingAfterSaveCallback afterSaveCallback = spy(new ValueCapturingAfterSaveCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
|
||||
|
||||
Person entity = initialLuke();
|
||||
|
||||
Person saved = template.insert(entity);
|
||||
|
||||
verify(afterSaveCallback).onAfterSave(eq(entity), any(), anyString());
|
||||
assertThat(saved.id).isEqualTo("after-save");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void insertAllShouldInvokeAfterSaveCallbacks() {
|
||||
|
||||
ValueCapturingAfterSaveCallback afterSaveCallback = spy(new ValueCapturingAfterSaveCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
|
||||
|
||||
Person entity1 = new Person();
|
||||
entity1.id = "1";
|
||||
entity1.firstname = "luke";
|
||||
|
||||
Person entity2 = new Person();
|
||||
entity1.id = "2";
|
||||
entity1.firstname = "luke";
|
||||
|
||||
Collection<Person> saved = template.insertAll(Arrays.asList(entity1, entity2));
|
||||
|
||||
verify(afterSaveCallback, times(2)).onAfterSave(any(), any(), anyString());
|
||||
assertThat(saved.iterator().next().getId()).isEqualTo("after-save");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAndReplaceShouldInvokeAfterSaveCallbacks() {
|
||||
|
||||
ValueCapturingAfterSaveCallback afterSaveCallback = spy(new ValueCapturingAfterSaveCallback());
|
||||
|
||||
template.setEntityCallbacks(EntityCallbacks.create(afterSaveCallback));
|
||||
|
||||
Person entity = initialLuke();
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.findOneAndReplace(any(Bson.class), any(Document.class), any())).thenReturn(document);
|
||||
|
||||
Person saved = template.findAndReplace(new Query(), entity);
|
||||
|
||||
verify(afterSaveCallback).onAfterSave(eq(initialLuke()), any(), anyString());
|
||||
assertThat(saved.id).isEqualTo("after-save");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAndReplaceShouldEmitAfterSaveEvent() {
|
||||
|
||||
AbstractMongoEventListener<Person> eventListener = new AbstractMongoEventListener<Person>() {
|
||||
|
||||
@Override
|
||||
public void onAfterSave(AfterSaveEvent<Person> event) {
|
||||
|
||||
assertThat(event.getSource().id).isEqualTo("init");
|
||||
event.getSource().id = "after-save-event";
|
||||
}
|
||||
};
|
||||
|
||||
StaticApplicationContext ctx = new StaticApplicationContext();
|
||||
ctx.registerBean(ApplicationListener.class, () -> eventListener);
|
||||
ctx.refresh();
|
||||
|
||||
template.setApplicationContext(ctx);
|
||||
|
||||
Person entity = initialLuke();
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.findOneAndReplace(any(Bson.class), any(Document.class), any())).thenReturn(document);
|
||||
|
||||
Person saved = template.findAndReplace(new Query(), entity);
|
||||
|
||||
assertThat(saved.id).isEqualTo("after-save-event");
|
||||
}
|
||||
|
||||
class AutogenerateableId {
|
||||
|
||||
@Id BigInteger id;
|
||||
@@ -2097,4 +2342,74 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
static class ValueCapturingAfterSaveCallback extends ValueCapturingEntityCallback<Person>
|
||||
implements AfterSaveCallback<Person> {
|
||||
|
||||
@Override
|
||||
public Person onAfterSave(Person entity, Document document, String collection) {
|
||||
|
||||
capture(entity);
|
||||
return new Person() {{
|
||||
id = "after-save";
|
||||
firstname = entity.firstname;
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
static class ValueCapturingAfterConvertCallback extends ValueCapturingEntityCallback<Person>
|
||||
implements AfterConvertCallback<Person> {
|
||||
|
||||
@Override
|
||||
public Person onAfterConvert(Person entity, Document document, String collection) {
|
||||
|
||||
capture(entity);
|
||||
return new Person() {{
|
||||
id = "after-convert";
|
||||
firstname = entity.firstname;
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
static class OneElementCursor<T> implements MongoCursor<T> {
|
||||
private final Iterator<T> iterator;
|
||||
|
||||
OneElementCursor(T element) {
|
||||
iterator = Collections.singletonList(element).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// nothing to close
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return iterator.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T tryNext() {
|
||||
if (iterator.hasNext()) {
|
||||
return iterator.next();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCursor getServerCursor() {
|
||||
throw new IllegalStateException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerAddress getServerAddress() {
|
||||
throw new IllegalStateException("Not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
import static org.springframework.data.mongodb.test.util.Assertions.assertThat;
|
||||
|
||||
import lombok.Data;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
@@ -32,6 +33,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.bson.Document;
|
||||
@@ -46,9 +48,11 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscriber;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
@@ -65,6 +69,10 @@ import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterConvertCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ReactiveAfterSaveCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeConvertCallback;
|
||||
import org.springframework.data.mongodb.core.mapping.event.ReactiveBeforeSaveCallback;
|
||||
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
|
||||
@@ -78,6 +86,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.mongodb.MongoClientSettings;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.client.model.CountOptions;
|
||||
@@ -88,6 +97,9 @@ import com.mongodb.client.model.FindOneAndReplaceOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
import com.mongodb.client.model.ReplaceOptions;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.InsertManyResult;
|
||||
import com.mongodb.client.result.InsertOneResult;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import com.mongodb.reactivestreams.client.AggregatePublisher;
|
||||
import com.mongodb.reactivestreams.client.DistinctPublisher;
|
||||
@@ -102,6 +114,7 @@ import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@@ -1096,6 +1109,286 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
verify(findPublisher).projection(new Document("country", 1).append("userid", 1));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findShouldInvokeAfterConvertCallbacks() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.find(Document.class)).thenReturn(findPublisher);
|
||||
makeFindPublisherPublishJust(document);
|
||||
|
||||
List<Person> results = template.find(new Query(), Person.class).timeout(Duration.ofSeconds(1))
|
||||
.toStream().collect(Collectors.toList());
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
assertThat(results.get(0).id).isEqualTo("after-convert");
|
||||
}
|
||||
|
||||
private Document initialLukeDocument() {
|
||||
return new Document(ImmutableMap.of(
|
||||
"_id", "init",
|
||||
"firstname", "luke"
|
||||
));
|
||||
}
|
||||
|
||||
private Person initialLuke() {
|
||||
Person expectedEnitty = new Person();
|
||||
expectedEnitty.id = "init";
|
||||
expectedEnitty.firstname = "luke";
|
||||
return expectedEnitty;
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findByIdShouldInvokeAfterConvertCallbacks() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.find(any(Bson.class), eq(Document.class))).thenReturn(findPublisher);
|
||||
makeFindPublisherPublishJust(document);
|
||||
|
||||
Person result = template.findById("init", Person.class).block(Duration.ofSeconds(1));
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
assertThat(result.id).isEqualTo("after-convert");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findOneShouldInvokeAfterConvertCallbacks() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.find(any(Bson.class), eq(Document.class))).thenReturn(findPublisher);
|
||||
makeFindPublisherPublishJust(document);
|
||||
|
||||
Person result = template.findOne(new Query(), Person.class).block(Duration.ofSeconds(1));
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
assertThat(result.id).isEqualTo("after-convert");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAllShouldInvokeAfterConvertCallbacks() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.find(Document.class)).thenReturn(findPublisher);
|
||||
makeFindPublisherPublishJust(document);
|
||||
|
||||
List<Person> results = template.findAll(Person.class).timeout(Duration.ofSeconds(1))
|
||||
.toStream().collect(Collectors.toList());
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
assertThat(results.get(0).id).isEqualTo("after-convert");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAndModifyShouldInvokeAfterConvertCallbacks() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.findOneAndUpdate(any(Bson.class), any(Bson.class), any())).thenReturn(findPublisher);
|
||||
makeFindPublisherPublishJust(document);
|
||||
|
||||
Person result = template.findAndModify(new Query(), new Update(), Person.class).block(Duration.ofSeconds(1));
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
assertThat(result.id).isEqualTo("after-convert");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAndRemoveShouldInvokeAfterConvertCallbacks() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.findOneAndDelete(any(Bson.class), any())).thenReturn(findPublisher);
|
||||
makeFindPublisherPublishJust(document);
|
||||
|
||||
Person result = template.findAndRemove(new Query(), Person.class).block(Duration.ofSeconds(1));
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
assertThat(result.id).isEqualTo("after-convert");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAllAndRemoveShouldInvokeAfterConvertCallbacks() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.find(Document.class)).thenReturn(findPublisher);
|
||||
makeFindPublisherPublishJust(document);
|
||||
when(collection.deleteMany(any(Bson.class), any(DeleteOptions.class)))
|
||||
.thenReturn(Mono.just(spy(DeleteResult.class)));
|
||||
|
||||
List<Person> results = template.findAllAndRemove(new Query(), Person.class).timeout(Duration.ofSeconds(1))
|
||||
.toStream().collect(Collectors.toList());
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(initialLuke()), eq(document), anyString());
|
||||
assertThat(results.get(0).id).isEqualTo("after-convert");
|
||||
}
|
||||
|
||||
private void makeFindPublisherPublishJust(Document document) {
|
||||
|
||||
Publisher<Document> realPublisher = Flux.just(document);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
Subscriber<Document> subscriber = invocation.getArgument(0);
|
||||
realPublisher.subscribe(subscriber);
|
||||
return null;
|
||||
}).when(findPublisher).subscribe(any());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAndReplaceShouldInvokeAfterConvertCallbacks() {
|
||||
|
||||
ValueCapturingAfterConvertCallback afterConvertCallback = spy(new ValueCapturingAfterConvertCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
when(collection.findOneAndReplace(any(Bson.class), any(Document.class), any())).thenReturn(findPublisher);
|
||||
makeFindPublisherPublishJust(initialLukeDocument());
|
||||
|
||||
Person entity = new Person();
|
||||
entity.id = "init";
|
||||
entity.firstname = "luke";
|
||||
|
||||
Person saved = template.findAndReplace(new Query(), entity).block(Duration.ofSeconds(1));
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(entity), any(), anyString());
|
||||
assertThat(saved.id).isEqualTo("after-convert");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void saveShouldInvokeAfterSaveCallbacks() {
|
||||
|
||||
ValueCapturingAfterSaveCallback afterSaveCallback = spy(new ValueCapturingAfterSaveCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterSaveCallback));
|
||||
|
||||
when(collection.replaceOne(any(Bson.class), any(Document.class), any(ReplaceOptions.class)))
|
||||
.thenReturn(Mono.just(mock(UpdateResult.class)));
|
||||
|
||||
Person entity = initialLuke();
|
||||
|
||||
Person saved = template.save(entity).block(Duration.ofSeconds(1));
|
||||
|
||||
verify(afterSaveCallback).onAfterSave(eq(entity), any(), anyString());
|
||||
assertThat(saved.id).isEqualTo("after-save");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void insertShouldInvokeAfterSaveCallbacks() {
|
||||
|
||||
ValueCapturingAfterSaveCallback afterSaveCallback = spy(new ValueCapturingAfterSaveCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterSaveCallback));
|
||||
|
||||
when(collection.insertOne(any())).thenReturn(Mono.just(mock(InsertOneResult.class)));
|
||||
|
||||
Person entity = initialLuke();
|
||||
|
||||
Person saved = template.insert(entity).block(Duration.ofSeconds(1));
|
||||
|
||||
verify(afterSaveCallback).onAfterSave(eq(entity), any(), anyString());
|
||||
assertThat(saved.id).isEqualTo("after-save");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void insertAllShouldInvokeAfterSaveCallbacks() {
|
||||
|
||||
ValueCapturingAfterSaveCallback afterSaveCallback = spy(new ValueCapturingAfterSaveCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterSaveCallback));
|
||||
|
||||
Person entity1 = new Person();
|
||||
entity1.id = "1";
|
||||
entity1.firstname = "luke";
|
||||
|
||||
Person entity2 = new Person();
|
||||
entity1.id = "2";
|
||||
entity1.firstname = "luke";
|
||||
|
||||
when(collection.insertMany(anyList())).then(invocation -> {
|
||||
List<?> list = invocation.getArgument(0);
|
||||
return Flux.fromIterable(list).map(i -> mock(InsertManyResult.class));
|
||||
});
|
||||
|
||||
List<Person> saved = template.insertAll(Arrays.asList(entity1, entity2))
|
||||
.timeout(Duration.ofSeconds(1))
|
||||
.toStream().collect(Collectors.toList());
|
||||
|
||||
verify(afterSaveCallback, times(2)).onAfterSave(any(), any(), anyString());
|
||||
assertThat(saved.get(0).id).isEqualTo("after-save");
|
||||
assertThat(saved.get(1).id).isEqualTo("after-save");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAndReplaceShouldInvokeAfterSaveCallbacks() {
|
||||
|
||||
ValueCapturingAfterSaveCallback afterSaveCallback = spy(new ValueCapturingAfterSaveCallback());
|
||||
|
||||
template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterSaveCallback));
|
||||
|
||||
when(collection.findOneAndReplace(any(Bson.class), any(Document.class), any())).thenReturn(findPublisher);
|
||||
makeFindPublisherPublishJust(initialLukeDocument());
|
||||
|
||||
Person entity = initialLuke();
|
||||
|
||||
Person saved = template.findAndReplace(new Query(), entity).block(Duration.ofSeconds(1));
|
||||
|
||||
verify(afterSaveCallback).onAfterSave(eq(entity), any(), anyString());
|
||||
assertThat(saved.id).isEqualTo("after-save");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void findAndReplaceShouldEmitAfterSaveEvent() {
|
||||
|
||||
AbstractMongoEventListener<Person> eventListener = new AbstractMongoEventListener<Person>() {
|
||||
|
||||
@Override
|
||||
public void onAfterSave(AfterSaveEvent<Person> event) {
|
||||
|
||||
assertThat(event.getSource().id).isEqualTo("init");
|
||||
event.getSource().id = "after-save-event";
|
||||
}
|
||||
};
|
||||
|
||||
StaticApplicationContext ctx = new StaticApplicationContext();
|
||||
ctx.registerBean(ApplicationListener.class, () -> eventListener);
|
||||
ctx.refresh();
|
||||
|
||||
template.setApplicationContext(ctx);
|
||||
|
||||
Person entity = initialLuke();
|
||||
|
||||
Document document = initialLukeDocument();
|
||||
when(collection.findOneAndReplace(any(Bson.class), any(Document.class), any())).thenReturn(Mono.just(document));
|
||||
|
||||
Person saved = template.findAndReplace(new Query(), entity).block(Duration.ofSeconds(1));
|
||||
|
||||
assertThat(saved.id).isEqualTo("after-save-event");
|
||||
}
|
||||
|
||||
@Data
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collection = "star-wars")
|
||||
static class Person {
|
||||
@@ -1179,4 +1472,33 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
return Mono.just(entity);
|
||||
}
|
||||
}
|
||||
|
||||
static class ValueCapturingAfterConvertCallback extends ValueCapturingEntityCallback<Person>
|
||||
implements ReactiveAfterConvertCallback<Person> {
|
||||
|
||||
@Override
|
||||
public Mono<Person> onAfterConvert(Person entity, Document document, String collection) {
|
||||
|
||||
capture(entity);
|
||||
return Mono.just(new Person() {{
|
||||
id = "after-convert";
|
||||
firstname = entity.firstname;
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
static class ValueCapturingAfterSaveCallback extends ValueCapturingEntityCallback<Person>
|
||||
implements ReactiveAfterSaveCallback<Person> {
|
||||
|
||||
@Override
|
||||
public Mono<Person> onAfterSave(Person entity, Document document, String collection) {
|
||||
|
||||
capture(entity);
|
||||
return Mono.just(new Person() {{
|
||||
id = "after-save";
|
||||
firstname = entity.firstname;
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.bson.types.Code;
|
||||
import org.bson.types.Decimal128;
|
||||
import org.bson.types.ObjectId;
|
||||
@@ -46,6 +47,7 @@ import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.ConversionNotSupportedException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
@@ -62,6 +64,7 @@ import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.geo.Polygon;
|
||||
import org.springframework.data.geo.Shape;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mapping.model.MappingInstantiationException;
|
||||
import org.springframework.data.mongodb.core.DocumentTestUtils;
|
||||
import org.springframework.data.mongodb.core.convert.DocumentAccessorUnitTests.NestedType;
|
||||
@@ -75,6 +78,7 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.PersonPojoStringId;
|
||||
import org.springframework.data.mongodb.core.mapping.TextScore;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
@@ -90,6 +94,7 @@ import com.mongodb.DBRef;
|
||||
* @author Patrik Wasik
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Roman Puchkovskiy
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class MappingMongoConverterUnitTests {
|
||||
@@ -2103,6 +2108,62 @@ public class MappingMongoConverterUnitTests {
|
||||
.isEqualTo(new BasicDBObject("property", "value"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void entityCallbacksAreNotSetByDefault() {
|
||||
Assertions.assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void entityCallbacksShouldBeInitiatedOnSettingApplicationContext() {
|
||||
|
||||
ApplicationContext ctx = new StaticApplicationContext();
|
||||
converter.setApplicationContext(ctx);
|
||||
|
||||
Assertions.assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void setterForEntityCallbackOverridesContextInitializedOnes() {
|
||||
|
||||
ApplicationContext ctx = new StaticApplicationContext();
|
||||
converter.setApplicationContext(ctx);
|
||||
|
||||
EntityCallbacks callbacks = EntityCallbacks.create();
|
||||
converter.setEntityCallbacks(callbacks);
|
||||
|
||||
Assertions.assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isSameAs(callbacks);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void setterForApplicationContextShouldNotOverrideAlreadySetEntityCallbacks() {
|
||||
|
||||
EntityCallbacks callbacks = EntityCallbacks.create();
|
||||
ApplicationContext ctx = new StaticApplicationContext();
|
||||
|
||||
converter.setEntityCallbacks(callbacks);
|
||||
converter.setApplicationContext(ctx);
|
||||
|
||||
Assertions.assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isSameAs(callbacks);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2479
|
||||
public void resolveDBRefMapValueShouldInvokeCallbacks() {
|
||||
|
||||
AfterConvertCallback<Person> afterConvertCallback = spy(new ReturningAfterConvertCallback());
|
||||
converter.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
|
||||
|
||||
when(resolver.fetch(Mockito.any(DBRef.class))).thenReturn(new org.bson.Document());
|
||||
DBRef dbRef = mock(DBRef.class);
|
||||
|
||||
org.bson.Document refMap = new org.bson.Document("foo", dbRef);
|
||||
org.bson.Document document = new org.bson.Document("personMap", refMap);
|
||||
|
||||
DBRefWrapper result = converter.read(DBRefWrapper.class, document);
|
||||
|
||||
verify(afterConvertCallback).onAfterConvert(eq(result.personMap.get("foo")),
|
||||
eq(new org.bson.Document()), any());
|
||||
}
|
||||
|
||||
static class GenericType<T> {
|
||||
T content;
|
||||
}
|
||||
@@ -2565,4 +2626,12 @@ public class MappingMongoConverterUnitTests {
|
||||
Date dateAsObjectId;
|
||||
}
|
||||
|
||||
static class ReturningAfterConvertCallback implements AfterConvertCallback<Person> {
|
||||
|
||||
@Override
|
||||
public Person onAfterConvert(Person entity, org.bson.Document document, String collection) {
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user