Skip to content

Commit ee076ec

Browse files
christophstroblmp911de
authored andcommitted
Simplify usage of user provided aggregation operations.
Introduce Aggregation.stage which allows to use a plain JSON String or any valid Bson representation to be used within an aggregation pipeline stage, without having to implement AggregationOperation directly. The change allows to make use of driver native builder API for aggregates. Original pull request: #4059. Closes #4038
1 parent 1184d6e commit ee076ec

15 files changed

+340
-22
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java

+35
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.List;
2222

2323
import org.bson.Document;
24+
import org.bson.conversions.Bson;
2425
import org.springframework.data.domain.Sort;
2526
import org.springframework.data.domain.Sort.Direction;
2627
import org.springframework.data.mongodb.core.aggregation.AddFieldsOperation.AddFieldsOperationBuilder;
@@ -238,6 +239,40 @@ public static AddFieldsOperationBuilder addFields() {
238239
return AddFieldsOperation.builder();
239240
}
240241

242+
/**
243+
* Creates a new {@link AggregationOperation} taking the given {@link Bson bson value} as is. <br />
244+
*
245+
* <pre class="code">
246+
* Aggregation.stage(Aggregates.search(exists(fieldPath("..."))));
247+
* </pre>
248+
*
249+
* Field mapping against a potential domain type or previous aggregation stages will not happen.
250+
*
251+
* @param aggregationOperation the must not be {@literal null}.
252+
* @return new instance of {@link AggregationOperation}.
253+
* @since 4.0
254+
*/
255+
public static AggregationOperation stage(Bson aggregationOperation) {
256+
return new BasicAggregationOperation(aggregationOperation);
257+
}
258+
259+
/**
260+
* Creates a new {@link AggregationOperation} taking the given {@link String json value} as is. <br />
261+
*
262+
* <pre class="code">
263+
* Aggregation.stage("{ $search : { near : { path : 'released' , origin : ... } } }");
264+
* </pre>
265+
*
266+
* Field mapping against a potential domain type or previous aggregation stages will not happen.
267+
*
268+
* @param json the JSON representation of the pipeline stage. Must not be {@literal null}.
269+
* @return new instance of {@link AggregationOperation}.
270+
* @since 4.0
271+
*/
272+
public static AggregationOperation stage(String json) {
273+
return new BasicAggregationOperation(json);
274+
}
275+
241276
/**
242277
* Creates a new {@link ProjectionOperation} including the given fields.
243278
*

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationContext.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,24 @@
2020
import java.util.Arrays;
2121

2222
import org.bson.Document;
23+
import org.bson.codecs.configuration.CodecRegistry;
2324
import org.springframework.beans.BeanUtils;
25+
import org.springframework.data.mongodb.CodecRegistryProvider;
2426
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
2527
import org.springframework.lang.Nullable;
2628
import org.springframework.util.Assert;
2729
import org.springframework.util.ReflectionUtils;
2830

31+
import com.mongodb.MongoClientSettings;
32+
2933
/**
3034
* The context for an {@link AggregationOperation}.
3135
*
3236
* @author Oliver Gierke
3337
* @author Christoph Strobl
3438
* @since 1.3
3539
*/
36-
public interface AggregationOperationContext {
40+
public interface AggregationOperationContext extends CodecRegistryProvider {
3741

3842
/**
3943
* Returns the mapped {@link Document}, potentially converting the source considering mapping metadata etc.
@@ -114,4 +118,9 @@ default Fields getFields(Class<?> type) {
114118
default AggregationOperationContext continueOnMissingFieldReference() {
115119
return this;
116120
}
121+
122+
@Override
123+
default CodecRegistry getCodecRegistry() {
124+
return MongoClientSettings.getDefaultCodecRegistry();
125+
}
117126
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.aggregation;
17+
18+
import java.util.Map;
19+
20+
import org.bson.Document;
21+
import org.bson.conversions.Bson;
22+
import org.springframework.data.mongodb.util.BsonUtils;
23+
import org.springframework.util.ObjectUtils;
24+
25+
/**
26+
* {@link AggregationOperation} implementation that c
27+
*
28+
* @author Christoph Strobl
29+
* @since 4.0
30+
*/
31+
class BasicAggregationOperation implements AggregationOperation {
32+
33+
private final Object value;
34+
35+
BasicAggregationOperation(Object value) {
36+
this.value = value;
37+
}
38+
39+
@Override
40+
public Document toDocument(AggregationOperationContext context) {
41+
42+
if (value instanceof Document document) {
43+
return document;
44+
}
45+
46+
if (value instanceof Bson bson) {
47+
return BsonUtils.asDocument(bson, context.getCodecRegistry());
48+
}
49+
50+
if (value instanceof Map map) {
51+
return new Document(map);
52+
}
53+
54+
if (value instanceof String json && BsonUtils.isJsonDocument(json)) {
55+
return BsonUtils.parse(json, context);
56+
}
57+
58+
throw new IllegalStateException(
59+
String.format("%s cannot be converted to org.bson.Document.", ObjectUtils.nullSafeClassName(value)));
60+
}
61+
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.mongodb.core.aggregation;
1717

1818
import org.bson.Document;
19+
import org.bson.codecs.configuration.CodecRegistry;
1920
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
2021
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
2122
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
@@ -141,4 +142,9 @@ protected FieldReference resolveExposedField(@Nullable Field field, String name)
141142
AggregationOperationContext getRootContext() {
142143
return rootContext;
143144
}
145+
146+
@Override
147+
public CodecRegistry getCodecRegistry() {
148+
return getRootContext().getCodecRegistry();
149+
}
144150
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.bson.Document;
2121

22+
import org.bson.codecs.configuration.CodecRegistry;
2223
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExpressionFieldReference;
2324
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
2425
import org.springframework.util.Assert;
@@ -92,4 +93,9 @@ public FieldReference getReference(String name) {
9293
public Fields getFields(Class<?> type) {
9394
return delegate.getFields(type);
9495
}
96+
97+
@Override
98+
public CodecRegistry getCodecRegistry() {
99+
return delegate.getCodecRegistry();
100+
}
95101
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/PrefixingDelegatingAggregationOperationContext.java

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Set;
2525

2626
import org.bson.Document;
27+
import org.bson.codecs.configuration.CodecRegistry;
2728
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
2829
import org.springframework.lang.Nullable;
2930

@@ -80,6 +81,11 @@ public Fields getFields(Class<?> type) {
8081
return delegate.getFields(type);
8182
}
8283

84+
@Override
85+
public CodecRegistry getCodecRegistry() {
86+
return delegate.getCodecRegistry();
87+
}
88+
8389
@SuppressWarnings("unchecked")
8490
private Document doPrefix(Document source) {
8591

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.bson.Document;
2424

25+
import org.bson.codecs.configuration.CodecRegistry;
2526
import org.springframework.data.mapping.PersistentPropertyPath;
2627
import org.springframework.data.mapping.context.MappingContext;
2728
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
@@ -147,4 +148,9 @@ protected FieldReference getReferenceFor(Field field) {
147148
public Class<?> getType() {
148149
return type;
149150
}
151+
152+
@Override
153+
public CodecRegistry getCodecRegistry() {
154+
return this.mapper.getConverter().getCodecRegistry();
155+
}
150156
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.bson.Document;
2727
import org.bson.codecs.Codec;
2828
import org.bson.codecs.DecoderContext;
29+
import org.bson.codecs.configuration.CodecRegistry;
2930
import org.bson.conversions.Bson;
3031
import org.bson.json.JsonReader;
3132
import org.bson.types.ObjectId;
@@ -1793,6 +1794,11 @@ public Class<?> getWriteTarget(Class<?> source) {
17931794
return conversions.getCustomWriteTarget(source).orElse(source);
17941795
}
17951796

1797+
@Override
1798+
public CodecRegistry getCodecRegistry() {
1799+
return codecRegistryProvider != null ? codecRegistryProvider.getCodecRegistry() : super.getCodecRegistry();
1800+
}
1801+
17961802
/**
17971803
* Create a new {@link MappingMongoConverter} using the given {@link MongoDatabaseFactory} when loading {@link DBRef}.
17981804
*

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java

+9-2
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@
1515
*/
1616
package org.springframework.data.mongodb.core.convert;
1717

18+
import com.mongodb.MongoClientSettings;
1819
import org.bson.BsonValue;
1920
import org.bson.Document;
21+
import org.bson.codecs.configuration.CodecRegistry;
2022
import org.bson.conversions.Bson;
2123
import org.bson.types.ObjectId;
22-
2324
import org.springframework.core.convert.ConversionException;
2425
import org.springframework.data.convert.CustomConversions;
2526
import org.springframework.data.convert.EntityConverter;
2627
import org.springframework.data.convert.EntityReader;
2728
import org.springframework.data.convert.TypeMapper;
29+
import org.springframework.data.mongodb.CodecRegistryProvider;
2830
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
2931
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
3032
import org.springframework.data.mongodb.util.BsonUtils;
@@ -48,7 +50,7 @@
4850
*/
4951
public interface MongoConverter
5052
extends EntityConverter<MongoPersistentEntity<?>, MongoPersistentProperty, Object, Bson>, MongoWriter<Object>,
51-
EntityReader<Object, Bson> {
53+
EntityReader<Object, Bson>, CodecRegistryProvider {
5254

5355
/**
5456
* Returns the {@link TypeMapper} being used to write type information into {@link Document}s created with that
@@ -188,4 +190,9 @@ default Object convertId(@Nullable Object id, Class<?> targetType) {
188190
}
189191
}
190192

193+
@Override
194+
default CodecRegistry getCodecRegistry() {
195+
return MongoClientSettings.getDefaultCodecRegistry();
196+
}
197+
191198
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

+4
Original file line numberDiff line numberDiff line change
@@ -1486,4 +1486,8 @@ public String convert(MongoPersistentProperty source) {
14861486
public MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> getMappingContext() {
14871487
return mappingContext;
14881488
}
1489+
1490+
public MongoConverter getConverter() {
1491+
return converter;
1492+
}
14891493
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/BsonUtils.java

+44-11
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.bson.BsonValue;
3636
import org.bson.Document;
3737
import org.bson.codecs.DocumentCodec;
38+
import org.bson.codecs.configuration.CodecRegistry;
3839
import org.bson.conversions.Bson;
3940
import org.bson.json.JsonParseException;
4041
import org.bson.types.ObjectId;
@@ -81,18 +82,36 @@ public static <T> T get(Bson bson, String key) {
8182
* @return
8283
*/
8384
public static Map<String, Object> asMap(Bson bson) {
85+
return asMap(bson, MongoClientSettings.getDefaultCodecRegistry());
86+
}
8487

85-
if (bson instanceof Document) {
86-
return (Document) bson;
88+
/**
89+
* Return the {@link Bson} object as {@link Map}. Depending on the input type, the return value can be either a casted
90+
* version of {@code bson} or a converted (detached from the original value) using the given {@link CodecRegistry} to
91+
* obtain {@link org.bson.codecs.Codec codecs} that might be required for conversion.
92+
*
93+
* @param bson can be {@literal null}.
94+
* @param codecRegistry must not be {@literal null}.
95+
* @return never {@literal null}. Returns an empty {@link Map} if input {@link Bson} is {@literal null}.
96+
* @since 4.0
97+
*/
98+
public static Map<String, Object> asMap(@Nullable Bson bson, CodecRegistry codecRegistry) {
99+
100+
if (bson == null) {
101+
return Collections.emptyMap();
87102
}
88-
if (bson instanceof BasicDBObject) {
89-
return ((BasicDBObject) bson);
103+
104+
if (bson instanceof Document document) {
105+
return document;
90106
}
91-
if (bson instanceof DBObject) {
92-
return ((DBObject) bson).toMap();
107+
if (bson instanceof BasicDBObject dbo) {
108+
return dbo;
109+
}
110+
if (bson instanceof DBObject dbo) {
111+
return dbo.toMap();
93112
}
94113

95-
return (Map) bson.toBsonDocument(Document.class, MongoClientSettings.getDefaultCodecRegistry());
114+
return new Document((Map) bson.toBsonDocument(Document.class, codecRegistry));
96115
}
97116

98117
/**
@@ -104,12 +123,26 @@ public static Map<String, Object> asMap(Bson bson) {
104123
* @since 3.2.5
105124
*/
106125
public static Document asDocument(Bson bson) {
126+
return asDocument(bson, MongoClientSettings.getDefaultCodecRegistry());
127+
}
107128

108-
if (bson instanceof Document) {
109-
return (Document) bson;
129+
/**
130+
* Return the {@link Bson} object as {@link Document}. Depending on the input type, the return value can be either a
131+
* casted version of {@code bson} or a converted (detached from the original value) using the given
132+
* {@link CodecRegistry} to obtain {@link org.bson.codecs.Codec codecs} that might be required for conversion.
133+
*
134+
* @param bson
135+
* @param codecRegistry must not be {@literal null}.
136+
* @return never {@literal null}.
137+
* @since 4.0
138+
*/
139+
public static Document asDocument(Bson bson, CodecRegistry codecRegistry) {
140+
141+
if (bson instanceof Document document) {
142+
return document;
110143
}
111144

112-
Map<String, Object> map = asMap(bson);
145+
Map<String, Object> map = asMap(bson, codecRegistry);
113146

114147
if (map instanceof Document) {
115148
return (Document) map;
@@ -413,7 +446,7 @@ public static String toJson(@Nullable Document source) {
413446
*/
414447
public static boolean isJsonDocument(@Nullable String value) {
415448

416-
if(!StringUtils.hasText(value)) {
449+
if (!StringUtils.hasText(value)) {
417450
return false;
418451
}
419452

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/aggregation/TestAggregationContext.java

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.mongodb.util.aggregation;
1717

1818
import org.bson.Document;
19+
import org.bson.codecs.configuration.CodecRegistry;
1920
import org.springframework.data.mongodb.core.aggregation.Aggregation;
2021
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
2122
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
@@ -72,4 +73,9 @@ public FieldReference getReference(Field field) {
7273
public FieldReference getReference(String name) {
7374
return delegate.getReference(name);
7475
}
76+
77+
@Override
78+
public CodecRegistry getCodecRegistry() {
79+
return delegate.getCodecRegistry();
80+
}
7581
}

0 commit comments

Comments
 (0)