committed by
Christoph Strobl
parent
bafc2bebf2
commit
0070b12f95
@@ -23,13 +23,17 @@ import java.util.Optional;
|
||||
import org.bson.Document;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.EntityProjection;
|
||||
import org.springframework.data.mapping.context.EntityProjectionIntrospector;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mongodb.core.CollectionOptions.TimeSeriesOptions;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
@@ -39,6 +43,7 @@ import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.timeseries.Granularity;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -63,8 +68,19 @@ class EntityOperations {
|
||||
|
||||
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context;
|
||||
|
||||
EntityOperations(MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context) {
|
||||
private final EntityProjectionIntrospector introspector;
|
||||
|
||||
EntityOperations(MongoConverter converter) {
|
||||
this(converter.getMappingContext(), converter.getCustomConversions(), converter.getProjectionFactory());
|
||||
}
|
||||
|
||||
EntityOperations(MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context,
|
||||
CustomConversions conversions, ProjectionFactory projectionFactory) {
|
||||
this.context = context;
|
||||
this.introspector = EntityProjectionIntrospector.create(projectionFactory,
|
||||
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
|
||||
.and(((target, underlyingType) -> !conversions.isSimpleType(target))),
|
||||
context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,6 +245,11 @@ class EntityOperations {
|
||||
return UntypedOperations.instance();
|
||||
}
|
||||
|
||||
public <M, D> EntityProjection<M, D> introspectProjection(Class<M> resultType,
|
||||
Class<D> entityType) {
|
||||
return introspector.introspect(resultType, entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* A representation of information about an entity.
|
||||
*
|
||||
|
||||
@@ -49,6 +49,7 @@ import org.springframework.data.geo.GeoResults;
|
||||
import org.springframework.data.geo.Metric;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mapping.context.EntityProjection;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.MongoDatabaseUtils;
|
||||
@@ -102,7 +103,6 @@ import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
|
||||
import org.springframework.data.mongodb.core.timeseries.Granularity;
|
||||
import org.springframework.data.mongodb.core.validation.Validator;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -173,7 +173,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private final QueryMapper queryMapper;
|
||||
private final UpdateMapper updateMapper;
|
||||
private final JsonSchemaMapper schemaMapper;
|
||||
private final SpelAwareProxyProjectionFactory projectionFactory;
|
||||
private final EntityOperations operations;
|
||||
private final PropertyOperations propertyOperations;
|
||||
private final QueryOperations queryOperations;
|
||||
@@ -225,8 +224,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
this.queryMapper = new QueryMapper(this.mongoConverter);
|
||||
this.updateMapper = new UpdateMapper(this.mongoConverter);
|
||||
this.schemaMapper = new MongoJsonSchemaMapper(this.mongoConverter);
|
||||
this.projectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
this.operations = new EntityOperations(this.mongoConverter.getMappingContext());
|
||||
this.operations = new EntityOperations(this.mongoConverter);
|
||||
this.propertyOperations = new PropertyOperations(this.mongoConverter.getMappingContext());
|
||||
this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, propertyOperations,
|
||||
mongoDbFactory);
|
||||
@@ -264,7 +262,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
this.queryMapper = that.queryMapper;
|
||||
this.updateMapper = that.updateMapper;
|
||||
this.schemaMapper = that.schemaMapper;
|
||||
this.projectionFactory = that.projectionFactory;
|
||||
this.mappingContext = that.mappingContext;
|
||||
this.operations = that.operations;
|
||||
this.propertyOperations = that.propertyOperations;
|
||||
@@ -330,9 +327,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
resourceLoader = applicationContext;
|
||||
|
||||
projectionFactory.setBeanFactory(applicationContext);
|
||||
projectionFactory.setBeanClassLoader(applicationContext.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,15 +410,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityType);
|
||||
|
||||
QueryContext queryContext = queryOperations.createQueryContext(query);
|
||||
EntityProjection<T, ?> projection = operations.introspectProjection(returnType,
|
||||
entityType);
|
||||
|
||||
Document mappedQuery = queryContext.getMappedQuery(persistentEntity);
|
||||
Document mappedFields = queryContext.getMappedFields(persistentEntity, returnType, projectionFactory);
|
||||
Document mappedFields = queryContext.getMappedFields(persistentEntity, projection);
|
||||
|
||||
FindIterable<Document> cursor = new QueryCursorPreparer(query, entityType).initiateFind(collection,
|
||||
col -> col.find(mappedQuery, Document.class).projection(mappedFields));
|
||||
|
||||
return new CloseableIterableCursorAdapter<>(cursor, exceptionTranslator,
|
||||
new ProjectingReadCallback<>(mongoConverter, entityType, returnType, collectionName));
|
||||
new ProjectingReadCallback<>(mongoConverter, projection, collectionName));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -964,9 +960,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
.withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
|
||||
|
||||
AggregationResults<Document> results = aggregate($geoNear, collection, Document.class);
|
||||
EntityProjection<T, ?> projection = operations.introspectProjection(returnType,
|
||||
domainType);
|
||||
|
||||
DocumentCallback<GeoResult<T>> callback = new GeoNearResultDocumentCallback<>(distanceField,
|
||||
new ProjectingReadCallback<>(mongoConverter, domainType, returnType, collection), near.getMetric());
|
||||
new ProjectingReadCallback<>(mongoConverter, projection, collection), near.getMetric());
|
||||
|
||||
List<GeoResult<T>> result = new ArrayList<>();
|
||||
|
||||
@@ -1050,8 +1048,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityType);
|
||||
QueryContext queryContext = queryOperations.createQueryContext(query);
|
||||
|
||||
EntityProjection<T, S> projection = operations.introspectProjection(resultType,
|
||||
entityType);
|
||||
Document mappedQuery = queryContext.getMappedQuery(entity);
|
||||
Document mappedFields = queryContext.getMappedFields(entity, resultType, projectionFactory);
|
||||
Document mappedFields = queryContext.getMappedFields(entity, projection);
|
||||
Document mappedSort = queryContext.getMappedSort(entity);
|
||||
|
||||
replacement = maybeCallBeforeConvert(replacement, collectionName);
|
||||
@@ -1061,7 +1061,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
maybeCallBeforeSave(replacement, mappedReplacement, collectionName);
|
||||
|
||||
T saved = doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
|
||||
queryContext.getCollation(entityType).orElse(null), entityType, mappedReplacement, options, resultType);
|
||||
queryContext.getCollation(entityType).orElse(null), entityType, mappedReplacement, options,
|
||||
projection);
|
||||
|
||||
if (saved != null) {
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, mappedReplacement, collectionName));
|
||||
@@ -2499,7 +2500,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
|
||||
QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
|
||||
Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory);
|
||||
Document mappedFields = queryContext.getMappedFields(entity,
|
||||
EntityProjection.nonProjecting(entityClass));
|
||||
Document mappedQuery = queryContext.getMappedQuery(entity);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -2551,7 +2553,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
|
||||
QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
|
||||
Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory);
|
||||
Document mappedFields = queryContext.getMappedFields(entity,
|
||||
EntityProjection.nonProjecting(entityClass));
|
||||
Document mappedQuery = queryContext.getMappedQuery(entity);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -2573,9 +2576,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Class<T> targetClass, CursorPreparer preparer) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(sourceClass);
|
||||
EntityProjection<T, S> projection = operations.introspectProjection(targetClass,
|
||||
sourceClass);
|
||||
|
||||
QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
|
||||
Document mappedFields = queryContext.getMappedFields(entity, targetClass, projectionFactory);
|
||||
Document mappedFields = queryContext.getMappedFields(entity, projection);
|
||||
Document mappedQuery = queryContext.getMappedQuery(entity);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -2584,9 +2589,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null), preparer,
|
||||
new ProjectingReadCallback<>(mongoConverter, sourceClass, targetClass, collectionName), collectionName);
|
||||
new ProjectingReadCallback<>(mongoConverter, projection, collectionName), collectionName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert given {@link CollectionOptions} to a document and take the domain type information into account when
|
||||
* creating a mapped schema for validation. <br />
|
||||
@@ -2745,6 +2751,35 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Document mappedSort, @Nullable com.mongodb.client.model.Collation collation, Class<?> entityType,
|
||||
Document replacement, FindAndReplaceOptions options, Class<T> resultType) {
|
||||
|
||||
EntityProjection<T, ?> projection = operations.introspectProjection(resultType,
|
||||
entityType);
|
||||
|
||||
return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort, collation, entityType, replacement,
|
||||
options, projection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize this part for findAndReplace.
|
||||
*
|
||||
* @param collectionName The name of the collection to perform the operation in.
|
||||
* @param mappedQuery the query to look up documents.
|
||||
* @param mappedFields the fields to project the result to.
|
||||
* @param mappedSort the sort to be applied when executing the query.
|
||||
* @param collation collation settings for the query. Can be {@literal null}.
|
||||
* @param entityType the source domain type.
|
||||
* @param replacement the replacement {@link Document}.
|
||||
* @param options applicable options.
|
||||
* @param projection the projection descriptor.
|
||||
* @return {@literal null} if object does not exist, {@link FindAndReplaceOptions#isReturnNew() return new} is
|
||||
* {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}.
|
||||
* @since 3.4
|
||||
*/
|
||||
@Nullable
|
||||
private <T> T doFindAndReplace(String collectionName, Document mappedQuery, Document mappedFields,
|
||||
Document mappedSort, @Nullable com.mongodb.client.model.Collation collation, Class<?> entityType,
|
||||
Document replacement, FindAndReplaceOptions options,
|
||||
EntityProjection<T, ?> projection) {
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format(
|
||||
"findAndReplace using query: %s fields: %s sort: %s for class: %s and replacement: %s " + "in collection: %s",
|
||||
@@ -2754,7 +2789,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
return executeFindOneInternal(
|
||||
new FindAndReplaceCallback(mappedQuery, mappedFields, mappedSort, replacement, collation, options),
|
||||
new ProjectingReadCallback<>(mongoConverter, entityType, resultType, collectionName), collectionName);
|
||||
new ProjectingReadCallback<>(mongoConverter, projection, collectionName), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3205,17 +3240,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
*/
|
||||
private class ProjectingReadCallback<S, T> implements DocumentCallback<T> {
|
||||
|
||||
private final EntityReader<Object, Bson> reader;
|
||||
private final Class<S> entityType;
|
||||
private final Class<T> targetType;
|
||||
private final MongoConverter reader;
|
||||
private final EntityProjection<T, S> projection;
|
||||
private final String collectionName;
|
||||
|
||||
ProjectingReadCallback(EntityReader<Object, Bson> reader, Class<S> entityType, Class<T> targetType,
|
||||
ProjectingReadCallback(MongoConverter reader, EntityProjection<T, S> projection,
|
||||
String collectionName) {
|
||||
|
||||
this.reader = reader;
|
||||
this.entityType = entityType;
|
||||
this.targetType = targetType;
|
||||
this.projection = projection;
|
||||
this.collectionName = collectionName;
|
||||
}
|
||||
|
||||
@@ -3230,21 +3263,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType
|
||||
: targetType;
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, projection.getMappedType().getType(), collectionName));
|
||||
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, targetType, collectionName));
|
||||
|
||||
Object entity = reader.read(typeToRead, document);
|
||||
Object entity = reader.project(projection, document);
|
||||
|
||||
if (entity == null) {
|
||||
throw new MappingException(String.format("EntityReader %s returned null", reader));
|
||||
}
|
||||
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity;
|
||||
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName));
|
||||
return (T) maybeCallAfterConvert(result, document, collectionName);
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName));
|
||||
return (T) maybeCallAfterConvert(entity, document, collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,18 +16,19 @@
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
|
||||
import org.springframework.data.mapping.context.EntityProjection;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.data.mongodb.core.mapping.PersistentPropertyTranslator;
|
||||
import org.springframework.data.util.Predicates;
|
||||
|
||||
/**
|
||||
* Common operations performed on properties of an entity like extracting fields information for projection creation.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
class PropertyOperations {
|
||||
@@ -40,37 +41,37 @@ class PropertyOperations {
|
||||
|
||||
/**
|
||||
* For cases where {@code fields} is {@link Document#isEmpty() empty} include only fields that are required for
|
||||
* creating the projection (target) type if the {@code targetType} is a {@literal DTO projection} or a
|
||||
* creating the projection (target) type if the {@code EntityProjection} is a {@literal DTO projection} or a
|
||||
* {@literal closed interface projection}.
|
||||
*
|
||||
* @param projectionFactory must not be {@literal null}.
|
||||
* @param projection must not be {@literal null}.
|
||||
* @param fields must not be {@literal null}.
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @return {@link Document} with fields to be included.
|
||||
*/
|
||||
Document computeFieldsForProjection(ProjectionFactory projectionFactory, Document fields, Class<?> domainType,
|
||||
Class<?> targetType) {
|
||||
Document computeMappedFieldsForProjection(EntityProjection<?, ?> projection,
|
||||
Document fields) {
|
||||
|
||||
if (!fields.isEmpty() || ClassUtils.isAssignable(domainType, targetType)) {
|
||||
if (!projection.isProjection() || !projection.isClosedProjection()) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
Document projectedFields = new Document();
|
||||
|
||||
if (targetType.isInterface()) {
|
||||
|
||||
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(targetType);
|
||||
|
||||
if (projectionInformation.isClosed()) {
|
||||
projectionInformation.getInputProperties().forEach(it -> projectedFields.append(it.getName(), 1));
|
||||
}
|
||||
if (projection.getMappedType().getType().isInterface()) {
|
||||
projection.forEach(it -> {
|
||||
projectedFields.put(it.getPropertyPath().getSegment(), 1);
|
||||
});
|
||||
} else {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(targetType);
|
||||
if (entity != null) {
|
||||
entity.doWithProperties(
|
||||
(SimplePropertyHandler) persistentProperty -> projectedFields.append(persistentProperty.getName(), 1));
|
||||
// DTO projections use merged metadata between domain type and result type
|
||||
PersistentPropertyTranslator translator = PersistentPropertyTranslator.create(
|
||||
mappingContext.getRequiredPersistentEntity(projection.getDomainType()),
|
||||
Predicates.negate(MongoPersistentProperty::hasExplicitFieldName));
|
||||
|
||||
MongoPersistentEntity<?> persistentEntity = mappingContext
|
||||
.getRequiredPersistentEntity(projection.getMappedType());
|
||||
for (MongoPersistentProperty property : persistentEntity) {
|
||||
projectedFields.put(translator.translate(property).getFieldName(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,10 @@ import java.util.stream.Collectors;
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.bson.codecs.Codec;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.data.mapping.context.EntityProjection;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.CodecRegistryProvider;
|
||||
import org.springframework.data.mongodb.MongoExpression;
|
||||
@@ -54,11 +56,9 @@ import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.client.model.CountOptions;
|
||||
@@ -288,45 +288,58 @@ class QueryOperations {
|
||||
return queryMapper.getMappedObject(getQueryObject(), entity);
|
||||
}
|
||||
|
||||
Document getMappedFields(@Nullable MongoPersistentEntity<?> entity, Class<?> targetType,
|
||||
ProjectionFactory projectionFactory) {
|
||||
Document getMappedFields(@Nullable MongoPersistentEntity<?> entity,
|
||||
EntityProjection<?, ?> projection) {
|
||||
|
||||
Document fields = new Document();
|
||||
Document fields = evaluateFields(entity);
|
||||
|
||||
for (Entry<String, Object> entry : query.getFieldsObject().entrySet()) {
|
||||
if (entity == null) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
Document mappedFields;
|
||||
if (!fields.isEmpty()) {
|
||||
mappedFields = queryMapper.getMappedFields(fields, entity);
|
||||
} else {
|
||||
mappedFields = propertyOperations.computeMappedFieldsForProjection(projection, fields);
|
||||
}
|
||||
|
||||
if (entity.hasTextScoreProperty() && mappedFields.containsKey(entity.getTextScoreProperty().getFieldName())
|
||||
&& !query.getQueryObject().containsKey("$text")) {
|
||||
mappedFields.remove(entity.getTextScoreProperty().getFieldName());
|
||||
}
|
||||
|
||||
if (mappedFields.isEmpty()) {
|
||||
return BsonUtils.EMPTY_DOCUMENT;
|
||||
}
|
||||
|
||||
return mappedFields;
|
||||
}
|
||||
|
||||
private Document evaluateFields(@Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
Document fields = query.getFieldsObject();
|
||||
|
||||
if (fields.isEmpty()) {
|
||||
return BsonUtils.EMPTY_DOCUMENT;
|
||||
}
|
||||
|
||||
Document evaluated = new Document();
|
||||
|
||||
for (Entry<String, Object> entry : fields.entrySet()) {
|
||||
|
||||
if (entry.getValue() instanceof MongoExpression) {
|
||||
|
||||
AggregationOperationContext ctx = entity == null ? Aggregation.DEFAULT_CONTEXT
|
||||
: new RelaxedTypeBasedAggregationOperationContext(entity.getType(), mappingContext, queryMapper);
|
||||
|
||||
fields.put(entry.getKey(), AggregationExpression.from((MongoExpression) entry.getValue()).toDocument(ctx));
|
||||
evaluated.put(entry.getKey(), AggregationExpression.from((MongoExpression) entry.getValue()).toDocument(ctx));
|
||||
} else {
|
||||
fields.put(entry.getKey(), entry.getValue());
|
||||
evaluated.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
Document mappedFields = fields;
|
||||
|
||||
if (entity == null) {
|
||||
return mappedFields;
|
||||
}
|
||||
|
||||
Document projectedFields = propertyOperations.computeFieldsForProjection(projectionFactory, fields,
|
||||
entity.getType(), targetType);
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(fields, projectedFields)) {
|
||||
mappedFields = queryMapper.getMappedFields(projectedFields, entity);
|
||||
} else {
|
||||
mappedFields = queryMapper.getMappedFields(projectedFields,
|
||||
mappingContext.getRequiredPersistentEntity(targetType));
|
||||
}
|
||||
|
||||
if (entity.hasTextScoreProperty() && !query.getQueryObject().containsKey("$text")) {
|
||||
mappedFields.remove(entity.getTextScoreProperty().getFieldName());
|
||||
}
|
||||
|
||||
return mappedFields;
|
||||
return evaluated;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,8 +401,8 @@ class QueryOperations {
|
||||
}
|
||||
|
||||
@Override
|
||||
Document getMappedFields(@Nullable MongoPersistentEntity<?> entity, Class<?> targetType,
|
||||
ProjectionFactory projectionFactory) {
|
||||
Document getMappedFields(@Nullable MongoPersistentEntity<?> entity,
|
||||
EntityProjection<?, ?> projection) {
|
||||
return getMappedFields(entity);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ import org.springframework.data.geo.Metric;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
import org.springframework.data.mapping.context.EntityProjection;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.MappingContextEvent;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
@@ -113,7 +114,6 @@ import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
|
||||
import org.springframework.data.mongodb.core.timeseries.Granularity;
|
||||
import org.springframework.data.mongodb.core.validation.Validator;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -175,7 +175,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final QueryMapper queryMapper;
|
||||
private final UpdateMapper updateMapper;
|
||||
private final JsonSchemaMapper schemaMapper;
|
||||
private final SpelAwareProxyProjectionFactory projectionFactory;
|
||||
private final ApplicationListener<MappingContextEvent<?, ?>> indexCreatorListener;
|
||||
private final EntityOperations operations;
|
||||
private final PropertyOperations propertyOperations;
|
||||
@@ -242,13 +241,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.queryMapper = new QueryMapper(this.mongoConverter);
|
||||
this.updateMapper = new UpdateMapper(this.mongoConverter);
|
||||
this.schemaMapper = new MongoJsonSchemaMapper(this.mongoConverter);
|
||||
this.projectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
this.indexCreatorListener = new IndexCreatorEventListener(subscriptionExceptionHandler);
|
||||
|
||||
// We always have a mapping context in the converter, whether it's a simple one or not
|
||||
this.mappingContext = this.mongoConverter.getMappingContext();
|
||||
this.operations = new EntityOperations(this.mappingContext);
|
||||
this.propertyOperations = new PropertyOperations(this.mappingContext);
|
||||
this.operations = new EntityOperations(this.mongoConverter);
|
||||
this.propertyOperations = new PropertyOperations(this.mongoConverter.getMappingContext());
|
||||
this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, propertyOperations,
|
||||
mongoDatabaseFactory);
|
||||
|
||||
@@ -276,7 +274,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
this.queryMapper = that.queryMapper;
|
||||
this.updateMapper = that.updateMapper;
|
||||
this.schemaMapper = that.schemaMapper;
|
||||
this.projectionFactory = that.projectionFactory;
|
||||
this.indexCreator = that.indexCreator;
|
||||
this.indexCreatorListener = that.indexCreatorListener;
|
||||
this.mappingContext = that.mappingContext;
|
||||
@@ -353,9 +350,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
if (mappingContext instanceof ApplicationEventPublisherAware) {
|
||||
((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher);
|
||||
}
|
||||
|
||||
projectionFactory.setBeanFactory(applicationContext);
|
||||
projectionFactory.setBeanClassLoader(applicationContext.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1058,9 +1052,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
String collection = StringUtils.hasText(collectionName) ? collectionName : getCollectionName(entityClass);
|
||||
String distanceField = operations.nearQueryDistanceFieldName(entityClass);
|
||||
EntityProjection<T, ?> projection = operations.introspectProjection(returnType,
|
||||
entityClass);
|
||||
|
||||
GeoNearResultDocumentCallback<T> callback = new GeoNearResultDocumentCallback<>(distanceField,
|
||||
new ProjectingReadCallback<>(mongoConverter, entityClass, returnType, collection), near.getMetric());
|
||||
new ProjectingReadCallback<>(mongoConverter, projection, collection), near.getMetric());
|
||||
|
||||
Aggregation $geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, distanceField))
|
||||
.withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
|
||||
@@ -1139,9 +1135,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityType);
|
||||
QueryContext queryContext = queryOperations.createQueryContext(query);
|
||||
EntityProjection<T, S> projection = operations.introspectProjection(resultType,
|
||||
entityType);
|
||||
|
||||
Document mappedQuery = queryContext.getMappedQuery(entity);
|
||||
Document mappedFields = queryContext.getMappedFields(entity, resultType, projectionFactory);
|
||||
Document mappedFields = queryContext.getMappedFields(entity, projection);
|
||||
Document mappedSort = queryContext.getMappedSort(entity);
|
||||
|
||||
return Mono.defer(() -> {
|
||||
@@ -1161,7 +1159,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}).flatMap(it -> {
|
||||
|
||||
Mono<T> afterFindAndReplace = doFindAndReplace(it.getCollection(), mappedQuery, mappedFields, mappedSort,
|
||||
queryContext.getCollation(entityType).orElse(null), entityType, it.getTarget(), options, resultType);
|
||||
queryContext.getCollation(entityType).orElse(null), entityType, it.getTarget(), options,
|
||||
projection);
|
||||
return afterFindAndReplace.flatMap(saved -> {
|
||||
maybeEmitEvent(new AfterSaveEvent<>(saved, it.getTarget(), it.getCollection()));
|
||||
return maybeCallAfterSave(saved, it.getTarget(), it.getCollection());
|
||||
@@ -2373,7 +2372,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
QueryContext queryContext = queryOperations
|
||||
.createQueryContext(new BasicQuery(query, fields != null ? fields : new Document()));
|
||||
Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory);
|
||||
Document mappedFields = queryContext.getMappedFields(entity,
|
||||
EntityProjection.nonProjecting(entityClass));
|
||||
Document mappedQuery = queryContext.getMappedQuery(entity);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -2425,7 +2425,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
|
||||
QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
|
||||
Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory);
|
||||
Document mappedFields = queryContext.getMappedFields(entity,
|
||||
EntityProjection.nonProjecting(entityClass));
|
||||
Document mappedQuery = queryContext.getMappedQuery(entity);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -2447,9 +2448,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Class<T> targetClass, FindPublisherPreparer preparer) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(sourceClass);
|
||||
EntityProjection<T, S> projection = operations.introspectProjection(targetClass,
|
||||
sourceClass);
|
||||
|
||||
QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
|
||||
Document mappedFields = queryContext.getMappedFields(entity, targetClass, projectionFactory);
|
||||
Document mappedFields = queryContext.getMappedFields(entity, projection);
|
||||
Document mappedQuery = queryContext.getMappedQuery(entity);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -2458,24 +2461,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer,
|
||||
new ProjectingReadCallback<>(mongoConverter, sourceClass, targetClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
private Document getMappedFieldsObject(Document fields, @Nullable MongoPersistentEntity<?> entity,
|
||||
Class<?> targetType) {
|
||||
|
||||
if (entity == null) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
Document projectedFields = propertyOperations.computeFieldsForProjection(projectionFactory, fields,
|
||||
entity.getType(), targetType);
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(fields, projectedFields)) {
|
||||
return queryMapper.getMappedFields(projectedFields, entity);
|
||||
}
|
||||
|
||||
return queryMapper.getMappedFields(projectedFields, mappingContext.getRequiredPersistentEntity(targetType));
|
||||
new ProjectingReadCallback<>(mongoConverter, projection, collectionName), collectionName);
|
||||
}
|
||||
|
||||
protected CreateCollectionOptions convertToCreateCollectionOptions(@Nullable CollectionOptions collectionOptions) {
|
||||
@@ -2610,6 +2596,34 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Document mappedSort, com.mongodb.client.model.Collation collation, Class<?> entityType, Document replacement,
|
||||
FindAndReplaceOptions options, Class<T> resultType) {
|
||||
|
||||
EntityProjection<T, ?> projection = operations.introspectProjection(resultType,
|
||||
entityType);
|
||||
|
||||
return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort, collation, entityType, replacement,
|
||||
options, projection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize this part for findAndReplace.
|
||||
*
|
||||
* @param collectionName The name of the collection to perform the operation in.
|
||||
* @param mappedQuery the query to look up documents.
|
||||
* @param mappedFields the fields to project the result to.
|
||||
* @param mappedSort the sort to be applied when executing the query.
|
||||
* @param collation collation settings for the query. Can be {@literal null}.
|
||||
* @param entityType the source domain type.
|
||||
* @param replacement the replacement {@link Document}.
|
||||
* @param options applicable options.
|
||||
* @param projection the projection descriptor.
|
||||
* @return {@link Mono#empty()} if object does not exist, {@link FindAndReplaceOptions#isReturnNew() return new} is
|
||||
* {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}.
|
||||
* @since 3.4
|
||||
*/
|
||||
private <T> Mono<T> doFindAndReplace(String collectionName, Document mappedQuery, Document mappedFields,
|
||||
Document mappedSort, com.mongodb.client.model.Collation collation, Class<?> entityType, Document replacement,
|
||||
FindAndReplaceOptions options,
|
||||
EntityProjection<T, ?> projection) {
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
@@ -2622,7 +2636,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
return executeFindOneInternal(
|
||||
new FindAndReplaceCallback(mappedQuery, mappedFields, mappedSort, replacement, collation, options),
|
||||
new ProjectingReadCallback<>(this.mongoConverter, entityType, resultType, collectionName), collectionName);
|
||||
new ProjectingReadCallback<>(this.mongoConverter, projection, collectionName), collectionName);
|
||||
|
||||
});
|
||||
}
|
||||
@@ -3203,37 +3217,30 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
private class ProjectingReadCallback<S, T> implements DocumentCallback<T> {
|
||||
|
||||
private final EntityReader<Object, Bson> reader;
|
||||
private final Class<S> entityType;
|
||||
private final Class<T> targetType;
|
||||
private final MongoConverter reader;
|
||||
private final EntityProjection<T, S> projection;
|
||||
private final String collectionName;
|
||||
|
||||
ProjectingReadCallback(EntityReader<Object, Bson> reader, Class<S> entityType, Class<T> targetType,
|
||||
ProjectingReadCallback(MongoConverter reader, EntityProjection<T, S> projection,
|
||||
String collectionName) {
|
||||
this.reader = reader;
|
||||
this.entityType = entityType;
|
||||
this.targetType = targetType;
|
||||
this.projection = projection;
|
||||
this.collectionName = collectionName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Mono<T> doWith(Document document) {
|
||||
|
||||
Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) //
|
||||
? entityType //
|
||||
: targetType;
|
||||
Class<T> returnType = projection.getMappedType().getType();
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, returnType, collectionName));
|
||||
|
||||
maybeEmitEvent(new AfterLoadEvent<>(document, typeToRead, collectionName));
|
||||
|
||||
Object entity = reader.read(typeToRead, document);
|
||||
Object entity = reader.project(projection, document);
|
||||
|
||||
if (entity == null) {
|
||||
throw new MappingException(String.format("EntityReader %s returned null", reader));
|
||||
}
|
||||
|
||||
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity;
|
||||
|
||||
T castEntity = (T) result;
|
||||
T castEntity = (T) entity;
|
||||
maybeEmitEvent(new AfterConvertEvent<>(document, castEntity, collectionName));
|
||||
return maybeCallAfterConvert(castEntity, document, collectionName);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ class DocumentAccessor {
|
||||
public void put(MongoPersistentProperty prop, @Nullable Object value) {
|
||||
|
||||
Assert.notNull(prop, "MongoPersistentProperty must not be null!");
|
||||
String fieldName = prop.getFieldName();
|
||||
String fieldName = getFieldName(prop);
|
||||
|
||||
if (!fieldName.contains(".")) {
|
||||
BsonUtils.addToMap(document, fieldName, value);
|
||||
@@ -123,7 +123,7 @@ class DocumentAccessor {
|
||||
*/
|
||||
@Nullable
|
||||
public Object get(MongoPersistentProperty property) {
|
||||
return BsonUtils.resolveValue(document, property.getFieldName());
|
||||
return BsonUtils.resolveValue(document, getFieldName(property));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,7 +150,11 @@ class DocumentAccessor {
|
||||
|
||||
Assert.notNull(property, "Property must not be null!");
|
||||
|
||||
return BsonUtils.hasValue(document, property.getFieldName());
|
||||
return BsonUtils.hasValue(document, getFieldName(property));
|
||||
}
|
||||
|
||||
String getFieldName(MongoPersistentProperty prop) {
|
||||
return prop.getFieldName();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -46,13 +47,20 @@ import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.annotation.Reference;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.TypeMapper;
|
||||
import org.springframework.data.mapping.AccessOptions;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
|
||||
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.EntityProjection;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
|
||||
@@ -69,6 +77,7 @@ import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentPropert
|
||||
import org.springframework.data.mongodb.core.mapping.DocumentPointer;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.mapping.PersistentPropertyTranslator;
|
||||
import org.springframework.data.mongodb.core.mapping.Unwrapped;
|
||||
import org.springframework.data.mongodb.core.mapping.Unwrapped.OnEmpty;
|
||||
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
|
||||
@@ -76,7 +85,10 @@ 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;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.Predicates;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -129,6 +141,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
private SpELContext spELContext;
|
||||
private @Nullable EntityCallbacks entityCallbacks;
|
||||
private final DocumentPointerFactory documentPointerFactory;
|
||||
private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
|
||||
/**
|
||||
* Creates a new {@link MappingMongoConverter} given the new {@link DbRefResolver} and {@link MappingContext}.
|
||||
@@ -212,6 +225,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return this.typeMapper == null ? this.defaultTypeMapper : this.typeMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectionFactory getProjectionFactory() {
|
||||
return projectionFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomConversions getCustomConversions() {
|
||||
return conversions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the characters dots potentially contained in a {@link Map} shall be replaced with. By default we don't do
|
||||
* any translation but rather reject a {@link Map} with keys containing dots causing the conversion for the entire
|
||||
@@ -254,6 +277,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
|
||||
this.applicationContext = applicationContext;
|
||||
this.spELContext = new SpELContext(this.spELContext, applicationContext);
|
||||
this.projectionFactory.setBeanFactory(applicationContext);
|
||||
this.projectionFactory.setBeanClassLoader(applicationContext.getClassLoader());
|
||||
|
||||
if (entityCallbacks == null) {
|
||||
setEntityCallbacks(EntityCallbacks.create(applicationContext));
|
||||
@@ -281,6 +306,150 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R project(EntityProjection<R, ?> projection, Bson bson) {
|
||||
|
||||
if (!projection.isProjection()) { // backed by real object
|
||||
|
||||
TypeInformation<?> typeToRead = projection.getMappedType().getType().isInterface() ? projection.getDomainType()
|
||||
: projection.getMappedType();
|
||||
return (R) read(typeToRead, bson);
|
||||
}
|
||||
|
||||
ProjectingConversionContext context = new ProjectingConversionContext(conversions, ObjectPath.ROOT,
|
||||
this::readCollectionOrArray, this::readMap, this::readDBRef, this::getPotentiallyConvertedSimpleRead,
|
||||
projection);
|
||||
|
||||
return doReadProjection(context, bson, projection);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <R> R doReadProjection(ConversionContext context, Bson bson,
|
||||
EntityProjection<R, ?> projection) {
|
||||
|
||||
MongoPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(projection.getActualDomainType());
|
||||
TypeInformation<?> mappedType = projection.getActualMappedType();
|
||||
MongoPersistentEntity<R> mappedEntity = (MongoPersistentEntity<R>) getMappingContext()
|
||||
.getPersistentEntity(mappedType);
|
||||
SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext);
|
||||
|
||||
boolean isInterfaceProjection = mappedType.getType().isInterface();
|
||||
if (isInterfaceProjection) {
|
||||
|
||||
PersistentPropertyTranslator propertyTranslator = PersistentPropertyTranslator.create(mappedEntity);
|
||||
DocumentAccessor documentAccessor = new DocumentAccessor(bson);
|
||||
PersistentPropertyAccessor<?> accessor = new MapPersistentPropertyAccessor();
|
||||
|
||||
PersistentPropertyAccessor<?> convertingAccessor = PropertyTranslatingPropertyAccessor
|
||||
.create(new ConvertingPropertyAccessor<>(accessor, conversionService), propertyTranslator);
|
||||
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(context, documentAccessor,
|
||||
evaluator);
|
||||
|
||||
readProperties(context, entity, convertingAccessor, documentAccessor, valueProvider, evaluator,
|
||||
Predicates.isTrue());
|
||||
return (R) projectionFactory.createProjection(mappedType.getType(), accessor.getBean());
|
||||
}
|
||||
|
||||
// DTO projection
|
||||
if (mappedEntity == null) {
|
||||
throw new MappingException(String.format("No mapping metadata found for %s", mappedType.getType().getName()));
|
||||
}
|
||||
|
||||
// create target instance, merge metadata from underlying DTO type
|
||||
PersistentPropertyTranslator propertyTranslator = PersistentPropertyTranslator.create(entity,
|
||||
Predicates.negate(MongoPersistentProperty::hasExplicitFieldName));
|
||||
DocumentAccessor documentAccessor = new DocumentAccessor(bson) {
|
||||
@Override
|
||||
String getFieldName(MongoPersistentProperty prop) {
|
||||
return propertyTranslator.translate(prop).getFieldName();
|
||||
}
|
||||
};
|
||||
|
||||
PreferredConstructor<?, MongoPersistentProperty> persistenceConstructor = mappedEntity.getPersistenceConstructor();
|
||||
ParameterValueProvider<MongoPersistentProperty> provider = persistenceConstructor != null
|
||||
&& persistenceConstructor.hasParameters()
|
||||
? getParameterProvider(context, mappedEntity, documentAccessor, evaluator)
|
||||
: NoOpParameterValueProvider.INSTANCE;
|
||||
|
||||
EntityInstantiator instantiator = instantiators.getInstantiatorFor(mappedEntity);
|
||||
R instance = instantiator.createInstance(mappedEntity, provider);
|
||||
PersistentPropertyAccessor<R> accessor = mappedEntity.getPropertyAccessor(instance);
|
||||
|
||||
populateProperties(context, mappedEntity, documentAccessor, evaluator, instance);
|
||||
|
||||
PersistentPropertyAccessor<?> convertingAccessor = new ConvertingPropertyAccessor<>(accessor, conversionService);
|
||||
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(context, documentAccessor, evaluator);
|
||||
|
||||
readProperties(context, mappedEntity, convertingAccessor, documentAccessor, valueProvider, evaluator,
|
||||
Predicates.isTrue());
|
||||
|
||||
return accessor.getBean();
|
||||
}
|
||||
|
||||
private Object doReadOrProject(ConversionContext context, Bson source, TypeInformation<?> typeHint,
|
||||
EntityProjection<?, ?> typeDescriptor) {
|
||||
|
||||
if (typeDescriptor.isProjection()) {
|
||||
return doReadProjection(context, BsonUtils.asDocument(source), typeDescriptor);
|
||||
}
|
||||
|
||||
return readDocument(context, source, typeHint);
|
||||
}
|
||||
|
||||
class ProjectingConversionContext extends ConversionContext {
|
||||
|
||||
private final EntityProjection<?, ?> returnedTypeDescriptor;
|
||||
|
||||
ProjectingConversionContext(CustomConversions customConversions, ObjectPath path,
|
||||
ContainerValueConverter<Collection<?>> collectionConverter, ContainerValueConverter<Bson> mapConverter,
|
||||
ContainerValueConverter<DBRef> dbRefConverter, ValueConverter<Object> elementConverter,
|
||||
EntityProjection<?, ?> projection) {
|
||||
super(customConversions, path,
|
||||
(context, source, typeHint) -> doReadOrProject(context, source, typeHint, projection),
|
||||
|
||||
collectionConverter, mapConverter, dbRefConverter, elementConverter);
|
||||
this.returnedTypeDescriptor = projection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConversionContext forProperty(String name) {
|
||||
|
||||
EntityProjection<?, ?> property = returnedTypeDescriptor.findProperty(name);
|
||||
if (property == null) {
|
||||
return super.forProperty(name);
|
||||
}
|
||||
|
||||
return new ProjectingConversionContext(conversions, path, collectionConverter, mapConverter, dbRefConverter,
|
||||
elementConverter, property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConversionContext withPath(ObjectPath currentPath) {
|
||||
return new ProjectingConversionContext(conversions, currentPath, collectionConverter, mapConverter,
|
||||
dbRefConverter, elementConverter, returnedTypeDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
static class MapPersistentPropertyAccessor implements PersistentPropertyAccessor<Map<String, Object>> {
|
||||
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public void setProperty(PersistentProperty<?> persistentProperty, Object o) {
|
||||
map.put(persistentProperty.getName(), o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(PersistentProperty<?> persistentProperty) {
|
||||
return map.get(persistentProperty.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getBean() {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.core.MongoReader#read(java.lang.Class, com.mongodb.Document)
|
||||
@@ -423,7 +592,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
MongoDbPropertyValueProvider valueProvider = new MongoDbPropertyValueProvider(contextToUse, documentAccessor,
|
||||
evaluator);
|
||||
|
||||
readProperties(contextToUse, entity, accessor, documentAccessor, valueProvider, evaluator);
|
||||
Predicate<MongoPersistentProperty> propertyFilter = isIdentifier(entity).or(isConstructorArgument(entity)).negate();
|
||||
readProperties(contextToUse, entity, accessor, documentAccessor, valueProvider, evaluator, propertyFilter);
|
||||
|
||||
return accessor.getBean();
|
||||
}
|
||||
@@ -465,19 +635,28 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
|
||||
private void readProperties(ConversionContext context, MongoPersistentEntity<?> entity,
|
||||
PersistentPropertyAccessor<?> accessor, DocumentAccessor documentAccessor,
|
||||
MongoDbPropertyValueProvider valueProvider, SpELExpressionEvaluator evaluator) {
|
||||
MongoDbPropertyValueProvider valueProvider, SpELExpressionEvaluator evaluator,
|
||||
Predicate<MongoPersistentProperty> propertyFilter) {
|
||||
|
||||
DbRefResolverCallback callback = null;
|
||||
|
||||
for (MongoPersistentProperty prop : entity) {
|
||||
|
||||
if (!propertyFilter.test(prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ConversionContext propertyContext = context.forProperty(prop.getName());
|
||||
MongoDbPropertyValueProvider valueProviderToUse = valueProvider.withContext(propertyContext);
|
||||
|
||||
if (prop.isAssociation() && !entity.isConstructorArgument(prop)) {
|
||||
|
||||
if (callback == null) {
|
||||
callback = getDbRefResolverCallback(context, documentAccessor, evaluator);
|
||||
callback = getDbRefResolverCallback(propertyContext, documentAccessor, evaluator);
|
||||
}
|
||||
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback, context,
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback,
|
||||
propertyContext,
|
||||
evaluator);
|
||||
continue;
|
||||
}
|
||||
@@ -485,32 +664,27 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
if (prop.isUnwrapped()) {
|
||||
|
||||
accessor.setProperty(prop,
|
||||
readUnwrapped(context, documentAccessor, prop, mappingContext.getRequiredPersistentEntity(prop)));
|
||||
readUnwrapped(propertyContext, documentAccessor, prop, mappingContext.getRequiredPersistentEntity(prop)));
|
||||
continue;
|
||||
}
|
||||
|
||||
// We skip the id property since it was already set
|
||||
|
||||
if (entity.isIdProperty(prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity.isConstructorArgument(prop) || !documentAccessor.hasValue(prop)) {
|
||||
if (!documentAccessor.hasValue(prop)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prop.isAssociation()) {
|
||||
|
||||
if (callback == null) {
|
||||
callback = getDbRefResolverCallback(context, documentAccessor, evaluator);
|
||||
callback = getDbRefResolverCallback(propertyContext, documentAccessor, evaluator);
|
||||
}
|
||||
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback, context,
|
||||
readAssociation(prop.getRequiredAssociation(), accessor, documentAccessor, dbRefProxyHandler, callback,
|
||||
propertyContext,
|
||||
evaluator);
|
||||
continue;
|
||||
}
|
||||
|
||||
accessor.setProperty(prop, valueProvider.getPropertyValue(prop));
|
||||
accessor.setProperty(prop, valueProviderToUse.getPropertyValue(prop));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1716,6 +1890,14 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return true;
|
||||
}
|
||||
|
||||
static Predicate<MongoPersistentProperty> isIdentifier(PersistentEntity<?, ?> entity) {
|
||||
return entity::isIdProperty;
|
||||
}
|
||||
|
||||
static Predicate<MongoPersistentProperty> isConstructorArgument(PersistentEntity<?, ?> entity) {
|
||||
return entity::isConstructorArgument;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PropertyValueProvider} to evaluate a SpEL expression if present on the property or simply accesses the field
|
||||
* of the configured source {@link Document}.
|
||||
@@ -1779,6 +1961,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
|
||||
return (T) context.convert(value, property.getTypeInformation());
|
||||
}
|
||||
|
||||
public MongoDbPropertyValueProvider withContext(ConversionContext context) {
|
||||
if (context == this.context) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return new MongoDbPropertyValueProvider(context, accessor, evaluator);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1997,13 +2188,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
*/
|
||||
protected static class ConversionContext {
|
||||
|
||||
private final org.springframework.data.convert.CustomConversions conversions;
|
||||
private final ObjectPath path;
|
||||
private final ContainerValueConverter<Bson> documentConverter;
|
||||
private final ContainerValueConverter<Collection<?>> collectionConverter;
|
||||
private final ContainerValueConverter<Bson> mapConverter;
|
||||
private final ContainerValueConverter<DBRef> dbRefConverter;
|
||||
private final ValueConverter<Object> elementConverter;
|
||||
final org.springframework.data.convert.CustomConversions conversions;
|
||||
final ObjectPath path;
|
||||
final ContainerValueConverter<Bson> documentConverter;
|
||||
final ContainerValueConverter<Collection<?>> collectionConverter;
|
||||
final ContainerValueConverter<Bson> mapConverter;
|
||||
final ContainerValueConverter<DBRef> dbRefConverter;
|
||||
final ValueConverter<Object> elementConverter;
|
||||
|
||||
ConversionContext(org.springframework.data.convert.CustomConversions customConversions, ObjectPath path,
|
||||
ContainerValueConverter<Bson> documentConverter, ContainerValueConverter<Collection<?>> collectionConverter,
|
||||
@@ -2099,6 +2290,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
return path;
|
||||
}
|
||||
|
||||
public ConversionContext forProperty(String name) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a simple {@code source} value into {@link TypeInformation the target type}.
|
||||
*
|
||||
@@ -2123,4 +2318,58 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class PropertyTranslatingPropertyAccessor<T> implements PersistentPropertyPathAccessor<T> {
|
||||
|
||||
private final PersistentPropertyAccessor<T> delegate;
|
||||
private final PersistentPropertyTranslator propertyTranslator;
|
||||
|
||||
private PropertyTranslatingPropertyAccessor(PersistentPropertyAccessor<T> delegate,
|
||||
PersistentPropertyTranslator propertyTranslator) {
|
||||
this.delegate = delegate;
|
||||
this.propertyTranslator = propertyTranslator;
|
||||
}
|
||||
|
||||
static <T> PersistentPropertyAccessor<T> create(PersistentPropertyAccessor<T> delegate,
|
||||
PersistentPropertyTranslator propertyTranslator) {
|
||||
return new PropertyTranslatingPropertyAccessor<>(delegate, propertyTranslator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(PersistentProperty property, @Nullable Object value) {
|
||||
delegate.setProperty(translate(property), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(PersistentProperty<?> property) {
|
||||
return delegate.getProperty(translate(property));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getBean() {
|
||||
return delegate.getBean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(PersistentPropertyPath<? extends PersistentProperty<?>> path, Object value,
|
||||
AccessOptions.SetOptions options) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(PersistentPropertyPath<? extends PersistentProperty<?>> path,
|
||||
AccessOptions.GetOptions context) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(PersistentPropertyPath<? extends PersistentProperty<?>> path, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private MongoPersistentProperty translate(PersistentProperty<?> property) {
|
||||
return propertyTranslator.translate((MongoPersistentProperty) property);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,13 +19,17 @@ import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
import org.springframework.core.convert.ConversionException;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.EntityConverter;
|
||||
import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.convert.TypeMapper;
|
||||
import org.springframework.data.mapping.context.EntityProjection;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -54,6 +58,35 @@ public interface MongoConverter
|
||||
*/
|
||||
MongoTypeMapper getTypeMapper();
|
||||
|
||||
/**
|
||||
* Returns the {@link ProjectionFactory} for this converter.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
* @since 3.4
|
||||
*/
|
||||
ProjectionFactory getProjectionFactory();
|
||||
|
||||
/**
|
||||
* Returns the {@link CustomConversions} for this converter.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
* @since 3.4
|
||||
*/
|
||||
CustomConversions getCustomConversions();
|
||||
|
||||
/**
|
||||
* Apply a projection to {@link Bson} and return the projection return type {@code R}.
|
||||
* {@link EntityProjection#isProjection() Non-projecting} descriptors fall back to {@link #read(Class, Object) regular
|
||||
* object materialization}.
|
||||
*
|
||||
* @param descriptor the projection descriptor, must not be {@literal null}.
|
||||
* @param bson must not be {@literal null}.
|
||||
* @param <R>
|
||||
* @return a new instance of the projection return type {@code R}.
|
||||
* @since 3.4
|
||||
*/
|
||||
<R> R project(EntityProjection<R, ?> descriptor, Bson bson);
|
||||
|
||||
/**
|
||||
* Mapping function capable of converting values into a desired target type by eg. extracting the actual java type
|
||||
* from a given {@link BsonValue}.
|
||||
@@ -154,4 +187,5 @@ public interface MongoConverter
|
||||
return convertToMongoType(id,(TypeInformation<?>) null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -223,8 +223,8 @@ public class QueryMapper {
|
||||
|
||||
if (fields.isEmpty()) {
|
||||
return BsonUtils.EMPTY_DOCUMENT;
|
||||
|
||||
}
|
||||
|
||||
Document target = new Document();
|
||||
|
||||
BsonUtils.asMap(filterUnwrappedObjects(fields, entity)).forEach((k, v) -> {
|
||||
|
||||
@@ -201,7 +201,7 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
||||
* {@link org.springframework.data.mongodb.core.mapping.Field#value()} present.
|
||||
* @since 1.7
|
||||
*/
|
||||
protected boolean hasExplicitFieldName() {
|
||||
public boolean hasExplicitFieldName() {
|
||||
return StringUtils.hasText(getAnnotatedFieldName());
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,13 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
|
||||
*/
|
||||
String getFieldName();
|
||||
|
||||
/**
|
||||
* Returns whether the property uses an annotated field name through {@link Field}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean hasExplicitFieldName();
|
||||
|
||||
/**
|
||||
* Returns the {@link Class Java FieldType} of the field a property is persisted to.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.data.util.Predicates;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Utility to translate a {@link MongoPersistentProperty} into a corresponding property from a different
|
||||
* {@link MongoPersistentEntity} by looking it up by name.
|
||||
* <p>
|
||||
* Mainly used within the framework.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.4
|
||||
*/
|
||||
public class PersistentPropertyTranslator {
|
||||
|
||||
/**
|
||||
* Translate a {@link MongoPersistentProperty} into a corresponding property from a different
|
||||
* {@link MongoPersistentEntity}.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @return the translated property. Can be the original {@code property}.
|
||||
*/
|
||||
public MongoPersistentProperty translate(MongoPersistentProperty property) {
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link PersistentPropertyTranslator}.
|
||||
*
|
||||
* @param targetEntity must not be {@literal null}.
|
||||
* @return the property translator to use.
|
||||
*/
|
||||
public static PersistentPropertyTranslator create(@Nullable MongoPersistentEntity<?> targetEntity) {
|
||||
return create(targetEntity, Predicates.isTrue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link PersistentPropertyTranslator} accepting a {@link Predicate filter predicate} whether the
|
||||
* translation should happen at all.
|
||||
*
|
||||
* @param targetEntity must not be {@literal null}.
|
||||
* @param translationFilter must not be {@literal null}.
|
||||
* @return the property translator to use.
|
||||
*/
|
||||
public static PersistentPropertyTranslator create(@Nullable MongoPersistentEntity<?> targetEntity,
|
||||
Predicate<MongoPersistentProperty> translationFilter) {
|
||||
return targetEntity != null ? new EntityPropertyTranslator(targetEntity, translationFilter)
|
||||
: new PersistentPropertyTranslator();
|
||||
}
|
||||
|
||||
private static class EntityPropertyTranslator extends PersistentPropertyTranslator {
|
||||
|
||||
private final MongoPersistentEntity<?> targetEntity;
|
||||
private final Predicate<MongoPersistentProperty> translationFilter;
|
||||
|
||||
EntityPropertyTranslator(MongoPersistentEntity<?> targetEntity,
|
||||
Predicate<MongoPersistentProperty> translationFilter) {
|
||||
this.targetEntity = targetEntity;
|
||||
this.translationFilter = translationFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MongoPersistentProperty translate(MongoPersistentProperty property) {
|
||||
|
||||
if (!translationFilter.test(property)) {
|
||||
return property;
|
||||
}
|
||||
|
||||
MongoPersistentProperty targetProperty = targetEntity.getPersistentProperty(property.getName());
|
||||
return targetProperty != null ? targetProperty : property;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -56,6 +56,12 @@ class UnwrappedMongoPersistentProperty implements MongoPersistentProperty {
|
||||
return context.getProperty().findAnnotation(Unwrapped.class).prefix() + delegate.getFieldName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasExplicitFieldName() {
|
||||
return delegate.hasExplicitFieldName()
|
||||
|| !ObjectUtils.isEmpty(context.getProperty().findAnnotation(Unwrapped.class).prefix());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getFieldType() {
|
||||
return delegate.getFieldType();
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
@@ -76,6 +77,11 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
|
||||
this.mappingContext = mongoOperations.getConverter().getMappingContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFactory beanFactory) {
|
||||
return this.operations.getConverter().getProjectionFactory();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
@@ -78,6 +79,11 @@ public class ReactiveMongoRepositoryFactory extends ReactiveRepositoryFactorySup
|
||||
setEvaluationContextProvider(ReactiveQueryMethodEvaluationContextProvider.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFactory beanFactory) {
|
||||
return this.operations.getConverter().getProjectionFactory();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
|
||||
|
||||
@@ -24,6 +24,8 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
|
||||
/**
|
||||
@@ -37,7 +39,7 @@ public class EntityOperationUnitTests {
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ops = new EntityOperations(mappingContext);
|
||||
ops = new EntityOperations(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2293
|
||||
|
||||
@@ -22,7 +22,8 @@ import java.time.Instant;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.mapping.TimeSeries;
|
||||
import org.springframework.data.mongodb.test.util.MongoTestMappingContext;
|
||||
|
||||
@@ -33,7 +34,8 @@ import org.springframework.data.mongodb.test.util.MongoTestMappingContext;
|
||||
*/
|
||||
class EntityOperationsUnitTests {
|
||||
|
||||
EntityOperations operations = new EntityOperations(MongoTestMappingContext.newTestContext());
|
||||
EntityOperations operations = new EntityOperations(
|
||||
new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, MongoTestMappingContext.newTestContext()));
|
||||
|
||||
@Test // GH-3731
|
||||
void shouldReportInvalidTimeField() {
|
||||
|
||||
@@ -21,7 +21,9 @@ import static org.springframework.data.mongodb.core.query.Query.*;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.stream.Stream;
|
||||
@@ -32,6 +34,7 @@ import org.bson.Document;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
@@ -569,6 +572,8 @@ class ExecutableFindOperationSupportTests {
|
||||
String getName();
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter // TODO: Without getters/setters, not identified as projection/properties
|
||||
static class PersonDtoProjection {
|
||||
|
||||
@Field("firstname") String name;
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.bson.BsonString;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
@@ -278,7 +279,6 @@ class ExecutableUpdateOperationSupportTests {
|
||||
|
||||
@Data
|
||||
static class Jedi {
|
||||
|
||||
@Field("firstname") String name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,12 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.context.EntityProjection;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.convert.AbstractMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
@@ -37,6 +40,7 @@ import org.springframework.data.mongodb.core.convert.MongoTypeMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
@@ -92,6 +96,21 @@ public abstract class MongoOperationsUnitTests {
|
||||
public MongoTypeMapper getTypeMapper() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectionFactory getProjectionFactory() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomConversions getCustomConversions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R project(EntityProjection<R, ?> descriptor, Bson bson) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.core.timeseries.Granularity;
|
||||
import org.springframework.data.mongodb.util.BsonUtils;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -410,6 +411,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
when(cursor.next()).thenReturn(new org.bson.Document("_id", Integer.valueOf(0)));
|
||||
MappingMongoConverter converter = mock(MappingMongoConverter.class);
|
||||
when(converter.getMappingContext()).thenReturn((MappingContext) mappingContext);
|
||||
when(converter.getProjectionFactory()).thenReturn(new SpelAwareProxyProjectionFactory());
|
||||
template = new MongoTemplate(factory, converter);
|
||||
|
||||
assertThatExceptionOfType(MappingException.class).isThrownBy(() -> template.findAll(Person.class))
|
||||
|
||||
@@ -90,6 +90,7 @@ import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.core.timeseries.Granularity;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -1151,6 +1152,7 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
|
||||
MappingMongoConverter converter = mock(MappingMongoConverter.class);
|
||||
when(converter.getMappingContext()).thenReturn((MappingContext) mappingContext);
|
||||
when(converter.getProjectionFactory()).thenReturn(new SpelAwareProxyProjectionFactory());
|
||||
template = new ReactiveMongoTemplate(factory, converter);
|
||||
|
||||
when(collection.find(Document.class)).thenReturn(findPublisher);
|
||||
@@ -1480,7 +1482,6 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
|
||||
AutogenerateableId foo;
|
||||
}
|
||||
|
||||
static class PersonExtended extends Person {
|
||||
|
||||
String lastname;
|
||||
|
||||
@@ -48,8 +48,8 @@ class UpdateOperationsUnitTests {
|
||||
MongoConverter mongoConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext);
|
||||
QueryMapper queryMapper = new QueryMapper(mongoConverter);
|
||||
UpdateMapper updateMapper = new UpdateMapper(mongoConverter);
|
||||
EntityOperations entityOperations = new EntityOperations(mappingContext);
|
||||
PropertyOperations propertyOperations = new PropertyOperations(mappingContext);
|
||||
EntityOperations entityOperations = new EntityOperations(mongoConverter);
|
||||
PropertyOperations propertyOperations = new PropertyOperations(mongoConverter.getMappingContext());
|
||||
|
||||
ExtendedQueryOperations queryOperations = new ExtendedQueryOperations(queryMapper, updateMapper, entityOperations, propertyOperations,
|
||||
MongoClientSettings::getDefaultCodecRegistry);
|
||||
|
||||
@@ -19,13 +19,17 @@ import static org.mockito.Mockito.*;
|
||||
|
||||
import org.bson.conversions.Bson;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.mapping.context.EntityProjection;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverters.ObjectIdToStringConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverters.StringToObjectIdConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
import com.mongodb.DBRef;
|
||||
@@ -59,6 +63,21 @@ public class AbstractMongoConverterUnitTests {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectionFactory getProjectionFactory() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomConversions getCustomConversions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R project(EntityProjection<R, ?> descriptor, Bson bson) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> getMappingContext() {
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
@@ -21,6 +21,7 @@ import static org.mockito.Mockito.*;
|
||||
import static org.springframework.data.mongodb.core.DocumentTestUtils.*;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -66,6 +67,8 @@ 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.context.EntityProjection;
|
||||
import org.springframework.data.mapping.context.EntityProjectionIntrospector;
|
||||
import org.springframework.data.mapping.model.MappingInstantiationException;
|
||||
import org.springframework.data.mongodb.core.DocumentTestUtils;
|
||||
import org.springframework.data.mongodb.core.convert.DocumentAccessorUnitTests.NestedType;
|
||||
@@ -138,7 +141,7 @@ class MappingMongoConverterUnitTests {
|
||||
converter.write(address, document);
|
||||
|
||||
assertThat(document.get("city").toString()).isEqualTo("New York");
|
||||
assertThat(document.get("street").toString()).isEqualTo("Broadway");
|
||||
assertThat(document.get("s").toString()).isEqualTo("Broadway");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -2191,7 +2194,8 @@ class MappingMongoConverterUnitTests {
|
||||
@Test // GH-3546
|
||||
void readFlattensNestedDocumentToStringIfNecessary() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("street", new org.bson.Document("json", "string").append("_id", UUID.randomUUID()));
|
||||
org.bson.Document source = new org.bson.Document("s",
|
||||
new org.bson.Document("json", "string").append("_id", UUID.randomUUID()));
|
||||
|
||||
Address target = converter.read(Address.class, source);
|
||||
assertThat(target.street).isNotNull();
|
||||
@@ -2355,7 +2359,7 @@ class MappingMongoConverterUnitTests {
|
||||
void readUnwrappedTypeWithComplexValue() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("_id", "id-1").append("address",
|
||||
new org.bson.Document("street", "1007 Mountain Drive").append("city", "Gotham"));
|
||||
new org.bson.Document("s", "1007 Mountain Drive").append("city", "Gotham"));
|
||||
|
||||
WithNullableUnwrapped target = converter.read(WithNullableUnwrapped.class, source);
|
||||
|
||||
@@ -2381,9 +2385,9 @@ class MappingMongoConverterUnitTests {
|
||||
converter.write(source, target);
|
||||
|
||||
assertThat(target) //
|
||||
.containsEntry("address", new org.bson.Document("street", "1007 Mountain Drive").append("city", "Gotham")) //
|
||||
.containsEntry("address", new org.bson.Document("s", "1007 Mountain Drive").append("city", "Gotham")) //
|
||||
.doesNotContainKey("street") //
|
||||
.doesNotContainKey("address.street") //
|
||||
.doesNotContainKey("address.s") //
|
||||
.doesNotContainKey("city") //
|
||||
.doesNotContainKey("address.city");
|
||||
}
|
||||
@@ -2636,6 +2640,80 @@ class MappingMongoConverterUnitTests {
|
||||
assertThat(accessor.getDocument()).isEqualTo(new org.bson.Document("pName", new org.bson.Document("_id", id.toString())));
|
||||
}
|
||||
|
||||
@Test // GH-2860
|
||||
void projectShouldReadSimpleInterfaceProjection() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("birthDate", new LocalDate(1999, 12, 1).toDate()).append("foo",
|
||||
"Walter");
|
||||
|
||||
EntityProjectionIntrospector discoverer = EntityProjectionIntrospector.create(converter.getProjectionFactory(),
|
||||
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
|
||||
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
|
||||
mappingContext);
|
||||
|
||||
EntityProjection<PersonProjection, Person> projection = discoverer
|
||||
.introspect(PersonProjection.class, Person.class);
|
||||
PersonProjection person = converter.project(projection, source);
|
||||
|
||||
assertThat(person.getBirthDate()).isEqualTo(new LocalDate(1999, 12, 1));
|
||||
assertThat(person.getFirstname()).isEqualTo("Walter");
|
||||
}
|
||||
|
||||
@Test // GH-2860
|
||||
void projectShouldReadSimpleDtoProjection() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("birthDate", new LocalDate(1999, 12, 1).toDate()).append("foo",
|
||||
"Walter");
|
||||
|
||||
EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(converter.getProjectionFactory(),
|
||||
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
|
||||
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
|
||||
mappingContext);
|
||||
|
||||
EntityProjection<PersonDto, Person> projection = introspector
|
||||
.introspect(PersonDto.class, Person.class);
|
||||
PersonDto person = converter.project(projection, source);
|
||||
|
||||
assertThat(person.getBirthDate()).isEqualTo(new LocalDate(1999, 12, 1));
|
||||
assertThat(person.getFirstname()).isEqualTo("Walter");
|
||||
}
|
||||
|
||||
@Test // GH-2860
|
||||
void projectShouldReadNestedProjection() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("addresses",
|
||||
Collections.singletonList(new org.bson.Document("s", "hwy")));
|
||||
|
||||
EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(converter.getProjectionFactory(),
|
||||
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
|
||||
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
|
||||
mappingContext);
|
||||
|
||||
EntityProjection<WithNestedProjection, Person> projection = introspector
|
||||
.introspect(WithNestedProjection.class, Person.class);
|
||||
WithNestedProjection person = converter.project(projection, source);
|
||||
|
||||
assertThat(person.getAddresses()).extracting(AddressProjection::getStreet).hasSize(1).containsOnly("hwy");
|
||||
}
|
||||
|
||||
@Test // GH-2860
|
||||
void projectShouldReadProjectionWithNestedEntity() {
|
||||
|
||||
org.bson.Document source = new org.bson.Document("addresses",
|
||||
Collections.singletonList(new org.bson.Document("s", "hwy")));
|
||||
|
||||
EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(converter.getProjectionFactory(),
|
||||
EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy()
|
||||
.and((target, underlyingType) -> !converter.conversions.isSimpleType(target)),
|
||||
mappingContext);
|
||||
|
||||
EntityProjection<ProjectionWithNestedEntity, Person> projection = introspector
|
||||
.introspect(ProjectionWithNestedEntity.class, Person.class);
|
||||
ProjectionWithNestedEntity person = converter.project(projection, source);
|
||||
|
||||
assertThat(person.getAddresses()).extracting(Address::getStreet).hasSize(1).containsOnly("hwy");
|
||||
}
|
||||
|
||||
static class GenericType<T> {
|
||||
T content;
|
||||
}
|
||||
@@ -2666,7 +2744,9 @@ class MappingMongoConverterUnitTests {
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
static class Address implements InterfaceType {
|
||||
@Field("s")
|
||||
String street;
|
||||
String city;
|
||||
}
|
||||
@@ -2696,6 +2776,54 @@ class MappingMongoConverterUnitTests {
|
||||
}
|
||||
}
|
||||
|
||||
interface PersonProjection {
|
||||
|
||||
LocalDate getBirthDate();
|
||||
|
||||
String getFirstname();
|
||||
}
|
||||
|
||||
interface WithNestedProjection {
|
||||
|
||||
Set<AddressProjection> getAddresses();
|
||||
}
|
||||
|
||||
interface ProjectionWithNestedEntity {
|
||||
|
||||
Set<Address> getAddresses();
|
||||
}
|
||||
|
||||
interface AddressProjection {
|
||||
|
||||
String getStreet();
|
||||
}
|
||||
|
||||
static class PersonDto {
|
||||
|
||||
LocalDate birthDate;
|
||||
|
||||
@Field("foo") String firstname;
|
||||
String lastname;
|
||||
|
||||
public PersonDto(LocalDate birthDate, String firstname, String lastname) {
|
||||
this.birthDate = birthDate;
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
|
||||
public LocalDate getBirthDate() {
|
||||
return birthDate;
|
||||
}
|
||||
|
||||
public String getFirstname() {
|
||||
return firstname;
|
||||
}
|
||||
|
||||
public String getLastname() {
|
||||
return lastname;
|
||||
}
|
||||
}
|
||||
|
||||
static class ClassWithSortedMap {
|
||||
SortedMap<String, String> map;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
@@ -32,6 +34,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.repository.Person;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
/**
|
||||
@@ -40,6 +43,7 @@ import org.springframework.data.repository.Repository;
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class MongoRepositoryFactoryUnitTests {
|
||||
|
||||
@Mock MongoTemplate template;
|
||||
@@ -55,6 +59,7 @@ public class MongoRepositoryFactoryUnitTests {
|
||||
public void setUp() {
|
||||
when(template.getConverter()).thenReturn(converter);
|
||||
when(converter.getMappingContext()).thenReturn(mappingContext);
|
||||
when(converter.getProjectionFactory()).thenReturn(new SpelAwareProxyProjectionFactory());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user