DATAMONGO-931 - Add support for $redact aggregation.
We now support $redact via Aggregation.redact.
Aggregation.redact(ConditionalOperators.when(Criteria.where("level").is(5))
.then(RedactOperation.PRUNE)
.otherwise(RedactOperation.DESCEND));
Original pull request: #844.
This commit is contained in:
committed by
Mark Paluch
parent
0e0d726457
commit
581961e79a
@@ -657,6 +657,26 @@ public class Aggregation {
|
||||
return new CountOperationBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link RedactOperation} that can restrict the content of a document based on information stored
|
||||
* within the document itself.
|
||||
*
|
||||
* <pre class="code">
|
||||
*
|
||||
* Aggregation.redact(ConditionalOperators.when(Criteria.where("level").is(5)) //
|
||||
* .then(RedactOperation.PRUNE) //
|
||||
* .otherwise(RedactOperation.DESCEND));
|
||||
* </pre>
|
||||
*
|
||||
* @param condition Any {@link AggregationExpression} that resolves to {@literal $$DESCEND}, {@literal $$PRUNE}, or
|
||||
* {@literal $$KEEP}. Must not be {@literal null}.
|
||||
* @return new instance of {@link RedactOperation}. Never {@literal null}.
|
||||
* @since 3.0
|
||||
*/
|
||||
public static RedactOperation redact(AggregationExpression condition) {
|
||||
return new RedactOperation(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Fields} instance for the given field names.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2020 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 org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder;
|
||||
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link RedactOperation} allows to restrict the content of a {@link Document} based on information stored within
|
||||
* itself.
|
||||
*
|
||||
* <pre class="code">
|
||||
* RedactOperation.builder() //
|
||||
* .when(Criteria.where("level").is(5)) //
|
||||
* .thenPrune() //
|
||||
* .otherwiseDescend() //
|
||||
* .build();
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/operator/aggregation/redact/">https://docs.mongodb.com/manual/reference/operator/aggregation/redact/</a>
|
||||
* @since 3.0
|
||||
*/
|
||||
public class RedactOperation implements AggregationOperation {
|
||||
|
||||
/**
|
||||
* Return fields at the current document level. Exclude embedded ones.
|
||||
*/
|
||||
public static final String DESCEND = "$$DESCEND";
|
||||
|
||||
/**
|
||||
* Return/Keep all fields at the current document/embedded level.
|
||||
*/
|
||||
public static final String KEEP = "$$KEEP";
|
||||
|
||||
/**
|
||||
* Exclude all fields at this current document/embedded level.
|
||||
*/
|
||||
public static final String PRUNE = "$$PRUNE";
|
||||
|
||||
private final AggregationExpression condition;
|
||||
|
||||
/**
|
||||
* Create new {@link RedactOperation}.
|
||||
*
|
||||
* @param condition Any {@link AggregationExpression} that resolves to {@literal $$DESCEND}, {@literal $$PRUNE}, or
|
||||
* {@literal $$KEEP}. Must not be {@literal null}.
|
||||
*/
|
||||
public RedactOperation(AggregationExpression condition) {
|
||||
|
||||
Assert.notNull(condition, "Condition must not be null!");
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
return new Document("$redact", condition.toDocument(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a new instance of {@link RedactOperationBuilder} to specify condition and outcome of the {@literal $redact}
|
||||
* operation.
|
||||
*
|
||||
* @return new instance of {@link RedactOperationBuilder}.
|
||||
*/
|
||||
public static RedactOperationBuilder builder() {
|
||||
return new RedactOperationBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder to create new instance of {@link RedactOperation}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class RedactOperationBuilder {
|
||||
|
||||
private Object when;
|
||||
private Object then;
|
||||
private Object otherwise;
|
||||
|
||||
/**
|
||||
* Specify the evaluation condition.
|
||||
*
|
||||
* @param criteria must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder when(CriteriaDefinition criteria) {
|
||||
|
||||
this.when = criteria;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the evaluation condition.
|
||||
*
|
||||
* @param condition must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder when(AggregationExpression condition) {
|
||||
|
||||
this.when = condition;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the evaluation condition.
|
||||
*
|
||||
* @param condition must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder when(Document condition) {
|
||||
|
||||
this.when = condition;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fields at the current document level and exclude embedded ones if the condition is met.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder thenDescend() {
|
||||
return then(DESCEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return/Keep all fields at the current document/embedded level if the condition is met.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder thenKeep() {
|
||||
return then(KEEP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude all fields at this current document/embedded level if the condition is met.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder thenPrune() {
|
||||
return then(PRUNE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the outcome (anything that resolves to {@literal $$DESCEND}, {@literal $$PRUNE}, or {@literal $$KEEP})
|
||||
* when the condition is met.
|
||||
*
|
||||
* @param then must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder then(Object then) {
|
||||
|
||||
this.then = then;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fields at the current document level and exclude embedded ones if the condition is not met.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder otherwiseDescend() {
|
||||
return otherwise(DESCEND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return/Keep all fields at the current document/embedded level if the condition is not met.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder otherwiseKeep() {
|
||||
return otherwise(KEEP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude all fields at this current document/embedded level if the condition is not met.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder otherwisePrune() {
|
||||
return otherwise(PRUNE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the outcome (anything that resolves to {@literal $$DESCEND}, {@literal $$PRUNE}, or {@literal $$KEEP})
|
||||
* when the condition is not met.
|
||||
*
|
||||
* @param otherwise must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public RedactOperationBuilder otherwise(Object otherwise) {
|
||||
this.otherwise = otherwise;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return new instance of {@link RedactOperation}.
|
||||
*/
|
||||
public RedactOperation build() {
|
||||
return new RedactOperation(when().then(then).otherwise(otherwise));
|
||||
}
|
||||
|
||||
private ThenBuilder when() {
|
||||
|
||||
if (when instanceof CriteriaDefinition) {
|
||||
return ConditionalOperators.Cond.when((CriteriaDefinition) when);
|
||||
}
|
||||
if (when instanceof AggregationExpression) {
|
||||
return ConditionalOperators.Cond.when((AggregationExpression) when);
|
||||
}
|
||||
if (when instanceof Document) {
|
||||
return ConditionalOperators.Cond.when((Document) when);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Invalid Condition. Expected CriteriaDefinition, AggregationExpression or Document but was %s.", when));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2020 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 static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
class RedactOperationUnitTests {
|
||||
|
||||
Document expected = new Document("$redact",
|
||||
new Document("$cond", new Document("if", new Document("$eq", Arrays.asList("$level", 5)))
|
||||
.append("then", "$$PRUNE").append("else", "$$DESCEND")));
|
||||
Document expectedMapped = new Document("$redact",
|
||||
new Document("$cond", new Document("if", new Document("$eq", Arrays.asList("$le_v_el", 5)))
|
||||
.append("then", "$$PRUNE").append("else", "$$DESCEND")));
|
||||
|
||||
@Test // DATAMONGO-931
|
||||
void errorsOnNullExpression() {
|
||||
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> new RedactOperation(null));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-931
|
||||
void mapsAggregationExpressionCorrectly() {
|
||||
|
||||
assertThat(new RedactOperation(ConditionalOperators.when(Criteria.where("level").is(5)) //
|
||||
.then(RedactOperation.PRUNE) //
|
||||
.otherwise(RedactOperation.DESCEND)).toDocument(contextFor(null))).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-931
|
||||
void mapsAggregationExpressionViaBuilderCorrectly() {
|
||||
|
||||
assertThat(RedactOperation.builder().when(Criteria.where("level").is(5)) //
|
||||
.thenPrune() //
|
||||
.otherwiseDescend().build().toDocument(contextFor(null))).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-931
|
||||
void mapsTypedAggregationExpressionCorrectly() {
|
||||
|
||||
assertThat(new RedactOperation(ConditionalOperators.when(Criteria.where("level").is(5)) //
|
||||
.then(RedactOperation.PRUNE) //
|
||||
.otherwise(RedactOperation.DESCEND)).toDocument(contextFor(DomainType.class))).isEqualTo(expectedMapped);
|
||||
}
|
||||
|
||||
@Data
|
||||
static class DomainType {
|
||||
|
||||
@Field("le_v_el") String level;
|
||||
}
|
||||
|
||||
private static AggregationOperationContext contextFor(@Nullable Class<?> type) {
|
||||
|
||||
if (type == null) {
|
||||
return Aggregation.DEFAULT_CONTEXT;
|
||||
}
|
||||
|
||||
MappingMongoConverter mongoConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE,
|
||||
new MongoMappingContext());
|
||||
mongoConverter.afterPropertiesSet();
|
||||
|
||||
return new TypeBasedAggregationOperationContext(type, mongoConverter.getMappingContext(),
|
||||
new QueryMapper(mongoConverter)).continueOnMissingFieldReference();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user