Skip to content

Simplify usage of user provided aggregation operations. #4059

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

Closed
wants to merge 2 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.0.0-SNAPSHOT</version>
<version>4.0.0-GH-4038-SNAPSHOT</version>
<packaging>pom</packaging>

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

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.0.0-SNAPSHOT</version>
<version>4.0.0-GH-4038-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 @@ -12,7 +12,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.0-GH-4038-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.List;

import org.bson.Document;
import org.bson.conversions.Bson;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.aggregation.AddFieldsOperation.AddFieldsOperationBuilder;
Expand Down Expand Up @@ -238,6 +239,40 @@ public static AddFieldsOperationBuilder addFields() {
return AddFieldsOperation.builder();
}

/**
* Creates a new {@link AggregationOperation} taking the given {@link Bson bson value} as is. <br />
*
* <pre class="code">
* Aggregation.stage(Aggregates.search(exists(fieldPath("..."))));
* </pre>
*
* Field mapping against a potential domain type or previous aggregation stages will not happen.
*
* @param aggregationOperation the must not be {@literal null}.
* @return new instance of {@link AggregationOperation}.
* @since 4.0
*/
public static AggregationOperation stage(Bson aggregationOperation) {
return new BasicAggregationOperation(aggregationOperation);
}

/**
* Creates a new {@link AggregationOperation} taking the given {@link String json value} as is. <br />
*
* <pre class="code">
* Aggregation.stage("{ $search : { near : { path : 'released' , origin : ... } } }");
* </pre>
*
* Field mapping against a potential domain type or previous aggregation stages will not happen.
*
* @param json the JSON representation of the pipeline stage. Must not be {@literal null}.
* @return new instance of {@link AggregationOperation}.
* @since 4.0
*/
public static AggregationOperation stage(String json) {
return new BasicAggregationOperation(json);
}

/**
* Creates a new {@link ProjectionOperation} including the given fields.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,24 @@
import java.util.Arrays;

import org.bson.Document;
import org.bson.codecs.configuration.CodecRegistry;
import org.springframework.beans.BeanUtils;
import org.springframework.data.mongodb.CodecRegistryProvider;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import com.mongodb.MongoClientSettings;

/**
* The context for an {@link AggregationOperation}.
*
* @author Oliver Gierke
* @author Christoph Strobl
* @since 1.3
*/
public interface AggregationOperationContext {
public interface AggregationOperationContext extends CodecRegistryProvider {

/**
* Returns the mapped {@link Document}, potentially converting the source considering mapping metadata etc.
Expand Down Expand Up @@ -114,4 +118,9 @@ default Fields getFields(Class<?> type) {
default AggregationOperationContext continueOnMissingFieldReference() {
return this;
}

@Override
default CodecRegistry getCodecRegistry() {
return MongoClientSettings.getDefaultCodecRegistry();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice, when (if ever) will this default implementation be used? The underlying driver never uses this method, because it doesn't take into account important user configuration (most notably, the UUID representation).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it’s public API that users may have implemented, defaulting here eases the pain when upgrading to the new version. Internal implementations pick up the driver registry.
Are custom codecs heavily used?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main thing is that even the default registry is changed in the MongoClient constructor in order to apply the uuidRepresentation to the registry. The default registry will throw when trying to encode a UUID value, because the default UuidRepresentation is UNSPECIFIED

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note: MongoClientSettings.getDefaultCodecRegistry() is also used explicitly in the default implementation of the MongoConverter.getCodecRegistry method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true, same reason for those upgrading. As long as the user does not provide a custom implementation of the converter the codec registry (with all the user settings regarding UUID codec,...) will be used.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2022 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.aggregation;

import java.util.Map;

import org.bson.Document;
import org.bson.conversions.Bson;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.util.ObjectUtils;

/**
* {@link AggregationOperation} implementation that c
*
* @author Christoph Strobl
* @since 4.0
*/
class BasicAggregationOperation implements AggregationOperation {

private final Object value;

BasicAggregationOperation(Object value) {
this.value = value;
}

@Override
public Document toDocument(AggregationOperationContext context) {

if (value instanceof Document document) {
return document;
}

if (value instanceof Bson bson) {
return BsonUtils.asDocument(bson, context.getCodecRegistry());
}

if (value instanceof Map map) {
return new Document(map);
}
Comment on lines +42 to +52

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BsonUtil.asMap does not allow itself a liberty of just checking instanceof Map. I think that's because this check does not guarantee that the map keys are of the String type. For this reason, I don't think that

if (value instanceof Map map) {
    return new Document(map);
}

is a type-safe code. Am I missing something?

Given the above, and that BsonUtil.asMap and BsonUtil.asDocument already check for special cases when some work may be skipped, can't the three if blocks be replaced with

if (value instanceof Bson bson) {
    return BsonUtils.asDocument(bson, context.getCodecRegistry());
}

?


if (value instanceof String json && BsonUtils.isJsonDocument(json)) {
return BsonUtils.parse(json, context);
}

throw new IllegalStateException(
String.format("%s cannot be converted to org.bson.Document.", ObjectUtils.nullSafeClassName(value)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core.aggregation;

import org.bson.Document;
import org.bson.codecs.configuration.CodecRegistry;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
Expand Down Expand Up @@ -141,4 +142,9 @@ protected FieldReference resolveExposedField(@Nullable Field field, String name)
AggregationOperationContext getRootContext() {
return rootContext;
}

@Override
public CodecRegistry getCodecRegistry() {
return getRootContext().getCodecRegistry();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import org.bson.Document;

import org.bson.codecs.configuration.CodecRegistry;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExpressionFieldReference;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -92,4 +93,9 @@ public FieldReference getReference(String name) {
public Fields getFields(Class<?> type) {
return delegate.getFields(type);
}

@Override
public CodecRegistry getCodecRegistry() {
return delegate.getCodecRegistry();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Set;

import org.bson.Document;
import org.bson.codecs.configuration.CodecRegistry;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.lang.Nullable;

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

@Override
public CodecRegistry getCodecRegistry() {
return delegate.getCodecRegistry();
}

@SuppressWarnings("unchecked")
private Document doPrefix(Document source) {

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

import org.bson.Document;

import org.bson.codecs.configuration.CodecRegistry;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
Expand Down Expand Up @@ -147,4 +148,9 @@ protected FieldReference getReferenceFor(Field field) {
public Class<?> getType() {
return type;
}

@Override
public CodecRegistry getCodecRegistry() {
return this.mapper.getConverter().getCodecRegistry();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.bson.Document;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.conversions.Bson;
import org.bson.json.JsonReader;
import org.bson.types.ObjectId;
Expand Down Expand Up @@ -1793,6 +1794,11 @@ public Class<?> getWriteTarget(Class<?> source) {
return conversions.getCustomWriteTarget(source).orElse(source);
}

@Override
public CodecRegistry getCodecRegistry() {
return codecRegistryProvider != null ? codecRegistryProvider.getCodecRegistry() : super.getCodecRegistry();
}

/**
* Create a new {@link MappingMongoConverter} using the given {@link MongoDatabaseFactory} when loading {@link DBRef}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@
*/
package org.springframework.data.mongodb.core.convert;

import com.mongodb.MongoClientSettings;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.codecs.configuration.CodecRegistry;
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.mongodb.CodecRegistryProvider;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.util.BsonUtils;
Expand All @@ -48,7 +50,7 @@
*/
public interface MongoConverter
extends EntityConverter<MongoPersistentEntity<?>, MongoPersistentProperty, Object, Bson>, MongoWriter<Object>,
EntityReader<Object, Bson> {
EntityReader<Object, Bson>, CodecRegistryProvider {

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

@Override
default CodecRegistry getCodecRegistry() {
return MongoClientSettings.getDefaultCodecRegistry();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1486,4 +1486,8 @@ public String convert(MongoPersistentProperty source) {
public MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> getMappingContext() {
return mappingContext;
}

public MongoConverter getConverter() {
return converter;
}
}
Loading