|
34 | 34 | import org.springframework.data.mongodb.core.MappedDocument.MappedUpdate;
|
35 | 35 | import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
36 | 36 | import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
|
| 37 | +import org.springframework.data.mongodb.core.aggregation.AggregationOptions; |
| 38 | +import org.springframework.data.mongodb.core.aggregation.AggregationPipeline; |
37 | 39 | import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
38 | 40 | import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
|
| 41 | +import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext; |
| 42 | +import org.springframework.data.mongodb.core.aggregation.TypedAggregation; |
39 | 43 | import org.springframework.data.mongodb.core.convert.QueryMapper;
|
40 | 44 | import org.springframework.data.mongodb.core.convert.UpdateMapper;
|
41 | 45 | import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
|
48 | 52 | import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
|
49 | 53 | import org.springframework.data.mongodb.util.BsonUtils;
|
50 | 54 | import org.springframework.data.projection.ProjectionFactory;
|
| 55 | +import org.springframework.data.util.Lazy; |
51 | 56 | import org.springframework.lang.Nullable;
|
52 | 57 | import org.springframework.util.ClassUtils;
|
53 | 58 | import org.springframework.util.ObjectUtils;
|
@@ -194,6 +199,31 @@ DeleteContext deleteSingleContext(Query query) {
|
194 | 199 | return new DeleteContext(query, false);
|
195 | 200 | }
|
196 | 201 |
|
| 202 | + /** |
| 203 | + * Create a new {@link AggregateContext} for the given {@link Aggregation}. |
| 204 | + * |
| 205 | + * @param aggregation must not be {@literal null}. |
| 206 | + * @param inputType fallback mapping type in case of untyped aggregation. Can be {@literal null}. |
| 207 | + * @return new instance of {@link AggregateContext}. |
| 208 | + * @since 3.2 |
| 209 | + */ |
| 210 | + AggregateContext createAggregationContext(Aggregation aggregation, @Nullable Class<?> inputType) { |
| 211 | + return new AggregateContext(aggregation, inputType); |
| 212 | + } |
| 213 | + |
| 214 | + /** |
| 215 | + * Create a new {@link AggregateContext} for the given {@link Aggregation}. |
| 216 | + * |
| 217 | + * @param aggregation must not be {@literal null}. |
| 218 | + * @param aggregationOperationContext the {@link AggregationOperationContext} to use. Can be {@literal null}. |
| 219 | + * @return new instance of {@link AggregateContext}. |
| 220 | + * @since 3.2 |
| 221 | + */ |
| 222 | + AggregateContext createAggregationContext(Aggregation aggregation, |
| 223 | + @Nullable AggregationOperationContext aggregationOperationContext) { |
| 224 | + return new AggregateContext(aggregation, aggregationOperationContext); |
| 225 | + } |
| 226 | + |
197 | 227 | /**
|
198 | 228 | * {@link QueryContext} encapsulates common tasks required to convert a {@link Query} into its MongoDB document
|
199 | 229 | * representation, mapping fieldnames, as well as determinging and applying {@link Collation collations}.
|
@@ -341,7 +371,8 @@ private DistinctQueryContext(@Nullable Object query, String fieldName) {
|
341 | 371 | }
|
342 | 372 |
|
343 | 373 | @Override
|
344 |
| - Document getMappedFields(@Nullable MongoPersistentEntity<?> entity, Class<?> targetType, ProjectionFactory projectionFactory) { |
| 374 | + Document getMappedFields(@Nullable MongoPersistentEntity<?> entity, Class<?> targetType, |
| 375 | + ProjectionFactory projectionFactory) { |
345 | 376 | return getMappedFields(entity);
|
346 | 377 | }
|
347 | 378 |
|
@@ -709,7 +740,8 @@ List<Document> getUpdatePipeline(@Nullable Class<?> domainType) {
|
709 | 740 |
|
710 | 741 | Class<?> type = domainType != null ? domainType : Object.class;
|
711 | 742 |
|
712 |
| - AggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(type, mappingContext, queryMapper); |
| 743 | + AggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(type, mappingContext, |
| 744 | + queryMapper); |
713 | 745 | return aggregationUtil.createPipeline((AggregationUpdate) update, context);
|
714 | 746 | }
|
715 | 747 |
|
@@ -759,4 +791,105 @@ boolean isMulti() {
|
759 | 791 | return multi;
|
760 | 792 | }
|
761 | 793 | }
|
| 794 | + |
| 795 | + /** |
| 796 | + * A context class that encapsulates common tasks required when running {@literal aggregations}. |
| 797 | + * |
| 798 | + * @since 3.2 |
| 799 | + */ |
| 800 | + class AggregateContext { |
| 801 | + |
| 802 | + private Aggregation aggregation; |
| 803 | + private Lazy<AggregationOperationContext> aggregationOperationContext; |
| 804 | + private Lazy<List<Document>> pipeline; |
| 805 | + private @Nullable Class<?> inputType; |
| 806 | + |
| 807 | + /** |
| 808 | + * Creates new instance of {@link AggregateContext} extracting the input type from either the |
| 809 | + * {@link org.springframework.data.mongodb.core.aggregation.Aggregation} in case of a {@link TypedAggregation} or |
| 810 | + * the given {@literal aggregationOperationContext} if present. <br /> |
| 811 | + * Creates a new {@link AggregationOperationContext} if none given, based on the {@link Aggregation} input type and |
| 812 | + * the desired {@link AggregationOptions#getDomainTypeMapping() domain type mapping}. <br /> |
| 813 | + * Pipelines are mapped on first access of {@link #getAggregationPipeline()} and cached for reuse. |
| 814 | + * |
| 815 | + * @param aggregation the source aggregation. |
| 816 | + * @param aggregationOperationContext can be {@literal null}. |
| 817 | + */ |
| 818 | + AggregateContext(Aggregation aggregation, @Nullable AggregationOperationContext aggregationOperationContext) { |
| 819 | + |
| 820 | + this.aggregation = aggregation; |
| 821 | + if (aggregation instanceof TypedAggregation) { |
| 822 | + this.inputType = ((TypedAggregation) aggregation).getInputType(); |
| 823 | + } else if (aggregationOperationContext instanceof TypeBasedAggregationOperationContext) { |
| 824 | + this.inputType = ((TypeBasedAggregationOperationContext) aggregationOperationContext).getType(); |
| 825 | + } |
| 826 | + this.aggregationOperationContext = Lazy.of(() -> aggregationOperationContext != null ? aggregationOperationContext |
| 827 | + : aggregationUtil.createAggregationContext(aggregation, getInputType())); |
| 828 | + this.pipeline = Lazy.of(() -> aggregationUtil.createPipeline(this.aggregation, getAggregationOperationContext())); |
| 829 | + } |
| 830 | + |
| 831 | + /** |
| 832 | + * Creates new instance of {@link AggregateContext} extracting the input type from either the |
| 833 | + * {@link org.springframework.data.mongodb.core.aggregation.Aggregation} in case of a {@link TypedAggregation} or |
| 834 | + * the given {@literal aggregationOperationContext} if present. <br /> |
| 835 | + * Creates a new {@link AggregationOperationContext} based on the {@link Aggregation} input type and the desired |
| 836 | + * {@link AggregationOptions#getDomainTypeMapping() domain type mapping}. <br /> |
| 837 | + * Pipelines are mapped on first access of {@link #getAggregationPipeline()} and cached for reuse. |
| 838 | + * |
| 839 | + * @param aggregation the source aggregation. |
| 840 | + * @param inputType can be {@literal null}. |
| 841 | + */ |
| 842 | + AggregateContext(Aggregation aggregation, @Nullable Class<?> inputType) { |
| 843 | + |
| 844 | + this.aggregation = aggregation; |
| 845 | + |
| 846 | + if (aggregation instanceof TypedAggregation) { |
| 847 | + this.inputType = ((TypedAggregation) aggregation).getInputType(); |
| 848 | + } else { |
| 849 | + this.inputType = inputType; |
| 850 | + } |
| 851 | + |
| 852 | + this.aggregationOperationContext = Lazy |
| 853 | + .of(() -> aggregationUtil.createAggregationContext(aggregation, getInputType())); |
| 854 | + this.pipeline = Lazy.of(() -> aggregationUtil.createPipeline(this.aggregation, getAggregationOperationContext())); |
| 855 | + } |
| 856 | + |
| 857 | + /** |
| 858 | + * Obtain the already mapped pipeline. |
| 859 | + * |
| 860 | + * @return never {@literal null}. |
| 861 | + */ |
| 862 | + List<Document> getAggregationPipeline() { |
| 863 | + return pipeline.get(); |
| 864 | + } |
| 865 | + |
| 866 | + /** |
| 867 | + * @return {@literal true} if the last aggregation stage is either {@literal $out} or {@literal $merge}. |
| 868 | + * @see AggregationPipeline#isOutOrMerge() |
| 869 | + */ |
| 870 | + boolean isOutOrMerge() { |
| 871 | + return aggregation.getPipeline().isOutOrMerge(); |
| 872 | + } |
| 873 | + |
| 874 | + /** |
| 875 | + * Obtain the {@link AggregationOperationContext} used for mapping the pipeline. |
| 876 | + * |
| 877 | + * @return never {@literal null}. |
| 878 | + */ |
| 879 | + AggregationOperationContext getAggregationOperationContext() { |
| 880 | + return aggregationOperationContext.get(); |
| 881 | + } |
| 882 | + |
| 883 | + /** |
| 884 | + * @return the input type to map the pipeline against. Can be {@literal null}. |
| 885 | + */ |
| 886 | + @Nullable |
| 887 | + Class<?> getInputType() { |
| 888 | + return inputType; |
| 889 | + } |
| 890 | + |
| 891 | + Document getAggregationCommand(String collectionName) { |
| 892 | + return aggregationUtil.createCommand(collectionName, aggregation, getAggregationOperationContext()); |
| 893 | + } |
| 894 | + } |
762 | 895 | }
|
0 commit comments