Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix interface projections for repository level string based aggregations. #4841

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.5.0-SNAPSHOT</version>
<version>4.5.x-GH-4839-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.5.0-SNAPSHOT</version>
<version>4.5.x-GH-4839-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.5.0-SNAPSHOT</version>
<version>4.5.x-GH-4839-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -61,7 +62,6 @@
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
Expand All @@ -70,7 +70,6 @@
import org.springframework.util.StringUtils;

import com.mongodb.MongoClientSettings;
import reactor.util.function.Tuple2;

/**
* Base class for reactive {@link RepositoryQuery} implementations for MongoDB.
Expand Down Expand Up @@ -112,16 +111,20 @@ public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongo
this.method = method;
this.operations = operations;
this.instantiators = new EntityInstantiators();
this.valueExpressionDelegate = new ValueExpressionDelegate(new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(), evaluationContextProvider.getEvaluationContextProvider()), ValueExpressionParser.create(() -> expressionParser));
this.valueExpressionDelegate = new ValueExpressionDelegate(
new QueryMethodValueEvaluationContextAccessor(new StandardEnvironment(),
evaluationContextProvider.getEvaluationContextProvider()),
ValueExpressionParser.create(() -> expressionParser));

MongoEntityMetadata<?> metadata = method.getEntityInformation();
Class<?> type = metadata.getCollectionEntity().getType();

this.findOperationWithProjection = operations.query(type);
this.updateOps = operations.update(type);
ValueEvaluationContextProvider valueContextProvider = valueExpressionDelegate.createValueContextProvider(
method.getParameters());
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider, "ValueEvaluationContextProvider must be reactive");
ValueEvaluationContextProvider valueContextProvider = valueExpressionDelegate
.createValueContextProvider(method.getParameters());
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider,
"ValueEvaluationContextProvider must be reactive");
this.valueEvaluationContextProvider = (ReactiveValueEvaluationContextProvider) valueContextProvider;
}

Expand Down Expand Up @@ -151,9 +154,10 @@ public AbstractReactiveMongoQuery(ReactiveMongoQueryMethod method, ReactiveMongo

this.findOperationWithProjection = operations.query(type);
this.updateOps = operations.update(type);
ValueEvaluationContextProvider valueContextProvider = valueExpressionDelegate.createValueContextProvider(
method.getParameters());
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider, "ValueEvaluationContextProvider must be reactive");
ValueEvaluationContextProvider valueContextProvider = valueExpressionDelegate
.createValueContextProvider(method.getParameters());
Assert.isInstanceOf(ReactiveValueEvaluationContextProvider.class, valueContextProvider,
"ValueEvaluationContextProvider must be reactive");
this.valueEvaluationContextProvider = (ReactiveValueEvaluationContextProvider) valueContextProvider;
}

Expand Down Expand Up @@ -182,14 +186,9 @@ private Publisher<Object> execute(MongoParameterAccessor parameterAccessor) {
ConvertingParameterAccessor accessor = new ConvertingParameterAccessor(operations.getConverter(),
parameterAccessor);

TypeInformation<?> returnType = method.getReturnType();
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
Class<?> typeToRead = processor.getReturnedType().getTypeToRead();

if (typeToRead == null && returnType.getComponentType() != null) {
typeToRead = returnType.getComponentType().getType();
}

return doExecute(method, processor, accessor, typeToRead);
}

Expand Down Expand Up @@ -221,11 +220,15 @@ protected Publisher<Object> doExecute(ReactiveMongoQueryMethod method, ResultPro
String collection = method.getEntityInformation().getCollectionName();

ReactiveMongoQueryExecution execution = getExecution(accessor,
new ResultProcessingConverter(processor, operations, instantiators), find);
getResultProcessing(processor), find);
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
});
}

ResultProcessingConverter getResultProcessing(ResultProcessor processor) {
return new ResultProcessingConverter(processor, operations, instantiators);
}

/**
* Returns the execution instance to use.
*
Expand Down Expand Up @@ -439,8 +442,8 @@ private Mono<Tuple2<ValueExpressionEvaluator, ParameterBindingDocumentCodec>> ex
return getValueExpressionEvaluatorLater(dependencies, accessor).zipWith(Mono.just(codec));
}

private Document decode(Tuple2<ValueExpressionEvaluator, ParameterBindingDocumentCodec> expressionEvaluator, String source, MongoParameterAccessor accessor,
ParameterBindingDocumentCodec codec) {
private Document decode(Tuple2<ValueExpressionEvaluator, ParameterBindingDocumentCodec> expressionEvaluator,
String source, MongoParameterAccessor accessor, ParameterBindingDocumentCodec codec) {

ParameterBindingContext bindingContext = new ParameterBindingContext(accessor::getBindableValue,
expressionEvaluator.getT1());
Expand Down Expand Up @@ -490,8 +493,8 @@ ValueExpressionEvaluator getValueExpressionEvaluator(MongoParameterAccessor acce
@Override
public <T> T evaluate(String expressionString) {
ValueExpression expression = valueExpressionDelegate.parse(expressionString);
ValueEvaluationContext evaluationContext = valueEvaluationContextProvider.getEvaluationContext(accessor.getValues(),
expression.getExpressionDependencies());
ValueEvaluationContext evaluationContext = valueEvaluationContextProvider
.getEvaluationContext(accessor.getValues(), expression.getExpressionDependencies());
return (T) expression.evaluate(evaluationContext);
}
};
Expand All @@ -509,8 +512,9 @@ public <T> T evaluate(String expressionString) {
protected Mono<ValueExpressionEvaluator> getValueExpressionEvaluatorLater(ExpressionDependencies dependencies,
MongoParameterAccessor accessor) {

return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
.map(evaluationContext -> new ValueExpressionDelegateValueExpressionEvaluator(valueExpressionDelegate, valueExpression -> evaluationContext));
return valueEvaluationContextProvider.getEvaluationContextLater(accessor.getValues(), dependencies)
.map(evaluationContext -> new ValueExpressionDelegateValueExpressionEvaluator(valueExpressionDelegate,
valueExpression -> evaluationContext));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.time.Duration;
import java.util.Map;
import java.util.function.Function;
import java.util.function.IntUnaryOperator;
import java.util.function.LongUnaryOperator;

Expand All @@ -28,11 +29,18 @@
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapping.FieldName;
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Meta;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
Expand Down Expand Up @@ -116,13 +124,15 @@ static AggregationOptions.Builder applyHint(AggregationOptions.Builder builder,
}

/**
* If present apply the preference from the {@link org.springframework.data.mongodb.repository.ReadPreference} annotation.
* If present apply the preference from the {@link org.springframework.data.mongodb.repository.ReadPreference}
* annotation.
*
* @param builder must not be {@literal null}.
* @return never {@literal null}.
* @since 4.2
*/
static AggregationOptions.Builder applyReadPreference(AggregationOptions.Builder builder, MongoQueryMethod queryMethod) {
static AggregationOptions.Builder applyReadPreference(AggregationOptions.Builder builder,
MongoQueryMethod queryMethod) {

if (!queryMethod.hasAnnotatedReadPreference()) {
return builder;
Expand All @@ -131,6 +141,90 @@ static AggregationOptions.Builder applyReadPreference(AggregationOptions.Builder
return builder.readPreference(ReadPreference.valueOf(queryMethod.getAnnotatedReadPreference()));
}

static AggregationOptions computeOptions(MongoQueryMethod method, ConvertingParameterAccessor accessor,
AggregationPipeline pipeline, ValueExpressionEvaluator evaluator) {

AggregationOptions.Builder builder = Aggregation.newAggregationOptions();

AggregationUtils.applyCollation(builder, method.getAnnotatedCollation(), accessor, evaluator);
AggregationUtils.applyMeta(builder, method);
AggregationUtils.applyHint(builder, method);
AggregationUtils.applyReadPreference(builder, method);

TypeInformation<?> returnType = method.getReturnType();
if (returnType.getComponentType() != null) {
returnType = returnType.getRequiredComponentType();
}
if (ReflectionUtils.isVoid(returnType.getType()) && pipeline.isOutOrMerge()) {
builder.skipOutput();
}

return builder.build();
}

/**
* Prepares the AggregationPipeline including type discovery and calling {@link AggregationCallback} to run the
* aggregation.
*/
@Nullable
static <T> T doAggregate(AggregationPipeline pipeline, MongoQueryMethod method, ResultProcessor processor,
ConvertingParameterAccessor accessor,
Function<MongoParameterAccessor, ValueExpressionEvaluator> evaluatorFunction, AggregationCallback<T> callback) {

Class<?> sourceType = method.getDomainClass();
ReturnedType returnedType = processor.getReturnedType();
// 🙈
TypeInformation<?> returnType = method.getReturnType();
Class<?> returnElementType = (returnType.getComponentType() != null ? returnType.getRequiredComponentType()
: returnType).getType();
Class<?> entityType;

boolean isRawAggregationResult = ClassUtils.isAssignable(AggregationResults.class, method.getReturnedObjectType());

if (returnElementType.equals(Document.class)) {
entityType = sourceType;
} else {
entityType = returnElementType;
}

AggregationUtils.appendSortIfPresent(pipeline, accessor, entityType);

if (method.isSliceQuery()) {
AggregationUtils.appendLimitAndOffsetIfPresent(pipeline, accessor, LongUnaryOperator.identity(),
limit -> limit + 1);
} else {
AggregationUtils.appendLimitAndOffsetIfPresent(pipeline, accessor);
}

AggregationOptions options = AggregationUtils.computeOptions(method, accessor, pipeline,
evaluatorFunction.apply(accessor));
TypedAggregation<?> aggregation = new TypedAggregation<>(sourceType, pipeline.getOperations(), options);

boolean isSimpleReturnType = MongoSimpleTypes.HOLDER.isSimpleType(returnElementType);
Class<?> typeToRead;

if (isSimpleReturnType) {
typeToRead = Document.class;
} else if (isRawAggregationResult) {
typeToRead = returnElementType;
} else {

if (returnedType.isProjecting()) {
typeToRead = returnedType.getReturnedType().isInterface() ? Document.class : returnedType.getReturnedType();
} else {
typeToRead = entityType;
}
}

return callback.doAggregate(aggregation, sourceType, typeToRead, returnElementType, isSimpleReturnType,
isRawAggregationResult);
}

static AggregationPipeline computePipeline(AbstractMongoQuery mongoQuery, MongoQueryMethod method,
ConvertingParameterAccessor accessor) {
return new AggregationPipeline(mongoQuery.parseAggregationPipeline(method.getAnnotatedAggregation(), accessor));
}

/**
* Append {@code $sort} aggregation stage if {@link ConvertingParameterAccessor#getSort()} is present.
*
Expand All @@ -139,7 +233,7 @@ static AggregationOptions.Builder applyReadPreference(AggregationOptions.Builder
* @param targetType
*/
static void appendSortIfPresent(AggregationPipeline aggregationPipeline, ConvertingParameterAccessor accessor,
Class<?> targetType) {
@Nullable Class<?> targetType) {

if (accessor.getSort().isUnsorted()) {
return;
Expand Down Expand Up @@ -254,4 +348,26 @@ private static <T> T getPotentiallyConvertedSimpleTypeValue(MongoConverter conve

return converter.getConversionService().convert(value, targetType);
}

/**
* Interface to invoke an aggregation along with source, intermediate, and target types.
*
* @param <T>
*/
interface AggregationCallback<T> {

/**
* @param aggregation
* @param domainType
* @param typeToRead
* @param elementType
* @param simpleType whether the aggregation returns {@link Document} or a
* {@link org.springframework.data.mapping.model.SimpleTypeHolder simple type}.
* @param rawResult whether the aggregation returns {@link AggregationResults}.
* @return
*/
@Nullable
T doAggregate(TypedAggregation<?> aggregation, Class<?> domainType, Class<?> typeToRead, Class<?> elementType,
boolean simpleType, boolean rawResult);
}
}
Loading