Browse Source

DATAMONGO-1542 - Refactor CondOperator and IfNullOperator to children of AggregationExpressions.

Renamed CondOperator to Cond and IfNullOperator to IfNull. Both conditional operations are now available from ConditionalOperators.when and ConditionalOperators.ifNull and accept AggregationExpressions for conditions and values.

Original Pull Request: #421
pull/692/head
Mark Paluch 9 years ago committed by Christoph Strobl
parent
commit
b98bc0e2bf
  1. 66
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
  2. 808
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
  3. 392
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java
  4. 195
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java
  5. 26
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java
  6. 36
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java
  7. 36
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java
  8. 62
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java
  9. 81
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java
  10. 41
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java
  11. 15
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java

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

@ -24,11 +24,11 @@ import java.util.List; @@ -24,11 +24,11 @@ import java.util.List;
import org.bson.Document;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
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;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
import org.springframework.data.mongodb.core.aggregation.Fields.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.NearQuery;
@ -394,68 +394,6 @@ public class Aggregation { @@ -394,68 +394,6 @@ public class Aggregation {
return new LookupOperation(from, localField, foreignField, as);
}
/**
* Creates a new {@link IfNullOperator} for the given {@code field} and {@code replacement} value.
*
* @param field must not be {@literal null}.
* @param replacement must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public static IfNullOperator ifNull(String field, Object replacement) {
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
}
/**
* Creates a new {@link IfNullOperator} for the given {@link Field} and {@link Field} to obtain a value from.
*
* @param field must not be {@literal null}.
* @param replacement must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public static IfNullOperator ifNull(Field field, Field replacement) {
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
}
/**
* Creates a new {@link IfNullOperator} for the given {@link Field} and {@code replacement} value.
*
* @param field must not be {@literal null}.
* @param replacement must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public static IfNullOperator ifNull(Field field, Object replacement) {
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
}
/**
* Creates a new {@link ConditionalOperator} for the given {@link Field} that holds a {@literal boolean} value.
*
* @param booleanField must not be {@literal null}.
* @param then must not be {@literal null}.
* @param otherwise must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public static ConditionalOperator conditional(Field booleanField, Object then, Object otherwise) {
return ConditionalOperator.newBuilder().when(booleanField).then(then).otherwise(otherwise);
}
/**
* Creates a new {@link ConditionalOperator} for the given {@link Criteria}.
*
* @param criteria must not be {@literal null}.
* @param then must not be {@literal null}.
* @param otherwise must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public static ConditionalOperator conditional(Criteria criteria, Object then, Object otherwise) {
return ConditionalOperator.newBuilder().when(criteria).then(then).otherwise(otherwise);
}
/**
* Creates a new {@link Fields} instance for the given field names.
*

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

@ -21,13 +21,16 @@ import java.util.Collections; @@ -21,13 +21,16 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.bson.Document;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
@ -93,7 +96,7 @@ public interface AggregationExpressions { @@ -93,7 +96,7 @@ public interface AggregationExpressions {
private final AggregationExpression expression;
/**
* Creates new {@link ComparisonOperatorFactory} for given {@literal fieldReference}.
* Creates new {@link BooleanOperatorFactory} for given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
*/
@ -105,7 +108,7 @@ public interface AggregationExpressions { @@ -105,7 +108,7 @@ public interface AggregationExpressions {
}
/**
* Creats new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}.
* Creates new {@link BooleanOperatorFactory} for given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
*/
@ -191,6 +194,178 @@ public interface AggregationExpressions { @@ -191,6 +194,178 @@ public interface AggregationExpressions {
}
}
/**
* Gateway to {@literal conditional expressions} that evaluate their argument expressions as booleans to a value.
*
* @author Mark Paluch
*/
class ConditionalOperators {
/**
* Take the field referenced by given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static ConditionalOperatorFactory when(String fieldReference) {
return new ConditionalOperatorFactory(fieldReference);
}
/**
* Take the value resulting from the given {@literal expression}.
*
* @param expression must not be {@literal null}.
* @return
*/
public static ConditionalOperatorFactory when(AggregationExpression expression) {
return new ConditionalOperatorFactory(expression);
}
/**
* Take the value resulting from the given {@literal criteriaDefinition}.
*
* @param criteriaDefinition must not be {@literal null}.
* @return
*/
public static ConditionalOperatorFactory when(CriteriaDefinition criteriaDefinition) {
return new ConditionalOperatorFactory(criteriaDefinition);
}
/**
* Creates new {@link AggregationExpressions} that evaluates an expression and returns the value of the expression
* if the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value,
* including instances of undefined values or missing fields, returns the value of the replacement expression.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static IfNull.ThenBuilder ifNull(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return IfNull.ifNull(fieldReference);
}
/**
* Creates new {@link AggregationExpressions} that evaluates an expression and returns the value of the expression
* if the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value,
* including instances of undefined values or missing fields, returns the value of the replacement expression.
*
* @param expression must not be {@literal null}.
* @return
*/
public static IfNull.ThenBuilder ifNull(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return IfNull.ifNull(expression);
}
public static class ConditionalOperatorFactory {
private final String fieldReference;
private final AggregationExpression expression;
private final CriteriaDefinition criteriaDefinition;
/**
* Creates new {@link ConditionalOperatorFactory} for given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
*/
public ConditionalOperatorFactory(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
this.fieldReference = fieldReference;
this.expression = null;
this.criteriaDefinition = null;
}
/**
* Creates new {@link ConditionalOperatorFactory} for given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
*/
public ConditionalOperatorFactory(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
this.fieldReference = null;
this.expression = expression;
this.criteriaDefinition = null;
}
/**
* Creates new {@link ConditionalOperatorFactory} for given {@link CriteriaDefinition}.
*
* @param criteriaDefinition must not be {@literal null}.
*/
public ConditionalOperatorFactory(CriteriaDefinition criteriaDefinition) {
Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!");
this.fieldReference = null;
this.expression = null;
this.criteriaDefinition = criteriaDefinition;
}
/**
* Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two
* specified return expressions.
*
* @param value must not be {@literal null}.
* @return
*/
public OtherwiseBuilder then(Object value) {
Assert.notNull(value, "Value must not be null!");
return createThenBuilder().then(value);
}
/**
* Creates new {@link AggregationExpressions} that evaluates a boolean expression to return one of the two
* specified return expressions.
*
* @param expression must not be {@literal null}.
* @return
*/
public OtherwiseBuilder thenValueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return createThenBuilder().then(expression);
}
/**
* Creates new {@link AggregationExpressions} that evaluates a boolean expression to return one of the two
* specified return expressions.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public OtherwiseBuilder thenValueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return createThenBuilder().then(fieldReference);
}
private ThenBuilder createThenBuilder() {
if (usesFieldRef()) {
return Cond.newBuilder().when(fieldReference);
}
return usesCriteriaDefinition() ? Cond.newBuilder().when(criteriaDefinition)
: Cond.newBuilder().when(expression);
}
private boolean usesFieldRef() {
return this.fieldReference != null;
}
private boolean usesCriteriaDefinition() {
return this.criteriaDefinition != null;
}
}
}
/**
* Gateway to {@literal Set expressions} which perform {@literal set} operation on arrays, treating arrays as sets.
*
@ -411,7 +586,7 @@ public interface AggregationExpressions { @@ -411,7 +586,7 @@ public interface AggregationExpressions {
class ComparisonOperators {
/**
* Take the array referenced by given {@literal fieldReference}.
* Take the field referenced by given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
* @return
@ -421,13 +596,13 @@ public interface AggregationExpressions { @@ -421,13 +596,13 @@ public interface AggregationExpressions {
}
/**
* Take the array referenced by given {@literal fieldReference}.
* Take the value resulting from the given {@literal expression}.
*
* @param fieldReference must not be {@literal null}.
* @param expression must not be {@literal null}.
* @return
*/
public static ComparisonOperatorFactory valueOf(AggregationExpression fieldReference) {
return new ComparisonOperatorFactory(fieldReference);
public static ComparisonOperatorFactory valueOf(AggregationExpression expression) {
return new ComparisonOperatorFactory(expression);
}
public static class ComparisonOperatorFactory {
@ -448,7 +623,7 @@ public interface AggregationExpressions { @@ -448,7 +623,7 @@ public interface AggregationExpressions {
}
/**
* Creats new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}.
* Creates new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
*/
@ -730,7 +905,7 @@ public interface AggregationExpressions { @@ -730,7 +905,7 @@ public interface AggregationExpressions {
class ArithmeticOperators {
/**
* Take the array referenced by given {@literal fieldReference}.
* Take the field referenced by given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
* @return
@ -740,13 +915,13 @@ public interface AggregationExpressions { @@ -740,13 +915,13 @@ public interface AggregationExpressions {
}
/**
* Take the array referenced by given {@literal fieldReference}.
* Take the value resulting from the given {@literal expression}.
*
* @param fieldReference must not be {@literal null}.
* @param expression must not be {@literal null}.
* @return
*/
public static ArithmeticOperatorFactory valueOf(AggregationExpression fieldReference) {
return new ArithmeticOperatorFactory(fieldReference);
public static ArithmeticOperatorFactory valueOf(AggregationExpression expression) {
return new ArithmeticOperatorFactory(expression);
}
public static class ArithmeticOperatorFactory {
@ -2274,7 +2449,7 @@ public interface AggregationExpressions { @@ -2274,7 +2449,7 @@ public interface AggregationExpressions {
}
/**
* Creats new {@link AnyElementTrue}.
* Creates new {@link AnyElementTrue}.
*
* @param expression must not be {@literal null}.
* @return
@ -5878,4 +6053,605 @@ public interface AggregationExpressions { @@ -5878,4 +6053,605 @@ public interface AggregationExpressions {
}
}
/**
* Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field
* field references}, {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be
* converted to a simple MongoDB type.
*
* @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/
* @author Mark Paluch
*/
class IfNull implements AggregationExpression {
private final Object condition;
private final Object value;
private IfNull(Object condition, Object value) {
this.condition = condition;
this.value = value;
}
/**
* Creates new {@link IfNull}.
*
* @param fieldReference the field to check for a {@literal null} value, field reference must not be
* {@literal null}.
* @return
*/
public static ThenBuilder ifNull(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new IfNullOperatorBuilder().ifNull(fieldReference);
}
/**
* Creates new {@link IfNull}.
*
* @param expression the expression to check for a {@literal null} value, field reference must not be
* {@literal null}.
* @return
*/
public static ThenBuilder ifNull(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new IfNullOperatorBuilder().ifNull(expression);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
List<Object> list = new ArrayList<Object>();
if (condition instanceof Field) {
list.add(context.getReference((Field) condition).toString());
} else if (condition instanceof AggregationExpression) {
list.add(((AggregationExpression) condition).toDocument(context));
} else {
list.add(condition);
}
list.add(resolve(value, context));
return new Document("$ifNull", list);
}
private Object resolve(Object value, AggregationOperationContext context) {
if (value instanceof Field) {
return context.getReference((Field) value).toString();
} else if (value instanceof AggregationExpression) {
return ((AggregationExpression) value).toDocument(context);
} else if (value instanceof Document) {
return value;
}
return context.getMappedObject(new Document("$set", value)).get("$set");
}
/**
* @author Mark Paluch
*/
public static interface IfNullBuilder {
/**
* @param fieldReference the field to check for a {@literal null} value, field reference must not be
* {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder ifNull(String fieldReference);
/**
* @param expression the expression to check for a {@literal null} value, field name must not be {@literal null}
* or empty.
* @return the {@link ThenBuilder}
*/
ThenBuilder ifNull(AggregationExpression expression);
}
/**
* @author Mark Paluch
*/
public static interface ThenBuilder {
/**
* @param value the value to be used if the {@code $ifNull} condition evaluates {@literal true}. Can be a
* {@link Document}, a value that is supported by MongoDB or a value that can be converted to a MongoDB
* representation but must not be {@literal null}.
* @return
*/
IfNull then(Object value);
/**
* @param fieldReference the field holding the replacement value, must not be {@literal null}.
* @return
*/
IfNull thenValueOf(String fieldReference);
/**
* @param expression the expression yielding to the replacement value, must not be {@literal null}.
* @return
*/
public IfNull thenValueOf(AggregationExpression expression);
}
/**
* Builder for fluent {@link IfNullOperator} creation.
*
* @author Mark Paluch
*/
static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder {
private Object condition;
private IfNullOperatorBuilder() {}
/**
* Creates a new builder for {@link IfNullOperator}.
*
* @return never {@literal null}.
*/
public static IfNullOperatorBuilder newBuilder() {
return new IfNullOperatorBuilder();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.IfNullBuilder#ifNull(java.lang.String)
*/
public ThenBuilder ifNull(String fieldReference) {
Assert.hasText(fieldReference, "FieldReference name must not be null or empty!");
this.condition = Fields.field(fieldReference);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
@Override
public ThenBuilder ifNull(AggregationExpression expression) {
Assert.notNull(expression, "AggregationExpression name must not be null or empty!");
this.condition = expression;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#then(java.lang.Object)
*/
public IfNull then(Object value) {
return new IfNull(condition, value);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#thenValueOf(java.lang.String)
*/
public IfNull thenValueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new IfNull(condition, Fields.field(fieldReference));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
public IfNull thenValueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new IfNull(condition, expression);
}
}
}
/**
* Encapsulates the aggregation framework {@code $cond} operator. A {@link Cond} allows nested conditions
* {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition}, {@link AggregationExpression}
* or a {@link Document custom} condition. Replacement values can be either {@link Field field references},
* {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be converted to a
* simple MongoDB type.
*
* @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/
* @author Mark Paluch
* @author Christoph Strobl
*/
class Cond implements AggregationExpression {
private final Object condition;
private final Object thenValue;
private final Object otherwiseValue;
/**
* Creates a new {@link Cond} for a given {@link Field} and {@code then}/{@code otherwise} values.
*
* @param condition must not be {@literal null}.
* @param thenValue must not be {@literal null}.
* @param otherwiseValue must not be {@literal null}.
*/
private Cond(Field condition, Object thenValue, Object otherwiseValue) {
this((Object) condition, thenValue, otherwiseValue);
}
/**
* Creates a new {@link Cond} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise} values.
*
* @param condition must not be {@literal null}.
* @param thenValue must not be {@literal null}.
* @param otherwiseValue must not be {@literal null}.
*/
private Cond(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) {
this((Object) condition, thenValue, otherwiseValue);
}
private Cond(Object condition, Object thenValue, Object otherwiseValue) {
Assert.notNull(condition, "Condition must not be null!");
Assert.notNull(thenValue, "Then value must not be null!");
Assert.notNull(otherwiseValue, "Otherwise value must not be null!");
assertNotBuilder(condition, "Condition");
assertNotBuilder(thenValue, "Then value");
assertNotBuilder(otherwiseValue, "Otherwise value");
this.condition = condition;
this.thenValue = thenValue;
this.otherwiseValue = otherwiseValue;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
Document condObject = new Document();
condObject.append("if", resolveCriteria(context, condition));
condObject.append("then", resolveValue(context, thenValue));
condObject.append("else", resolveValue(context, otherwiseValue));
return new Document("$cond", condObject);
}
private Object resolveValue(AggregationOperationContext context, Object value) {
if (value instanceof Document || value instanceof Field) {
return resolve(context, value);
}
if (value instanceof AggregationExpression) {
return ((AggregationExpression) value).toDocument(context);
}
return context.getMappedObject(new Document("$set", value)).get("$set");
}
private Object resolveCriteria(AggregationOperationContext context, Object value) {
if (value instanceof Document || value instanceof Field) {
return resolve(context, value);
}
if (value instanceof AggregationExpression) {
return ((AggregationExpression) value).toDocument(context);
}
if (value instanceof CriteriaDefinition) {
Document mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject());
List<Object> clauses = new ArrayList<Object>();
clauses.addAll(getClauses(context, mappedObject));
return clauses.size() == 1 ? clauses.get(0) : clauses;
}
throw new InvalidDataAccessApiUsageException(
String.format("Invalid value in condition. Supported: Document, Field references, Criteria, got: %s", value));
}
private List<Object> getClauses(AggregationOperationContext context, Document mappedObject) {
List<Object> clauses = new ArrayList<Object>();
for (String key : mappedObject.keySet()) {
Object predicate = mappedObject.get(key);
clauses.addAll(getClauses(context, key, predicate));
}
return clauses;
}
private List<Object> getClauses(AggregationOperationContext context, String key, Object predicate) {
List<Object> clauses = new ArrayList<Object>();
if (predicate instanceof List) {
List<Object> args = new ArrayList<Object>();
for (Object clause : (List<?>) predicate) {
if (clause instanceof Document) {
args.addAll(getClauses(context, (Document) clause));
}
}
clauses.add(new Document(key, args));
} else if (predicate instanceof Document) {
Document nested = (Document) predicate;
for (String s : nested.keySet()) {
if (!isKeyword(s)) {
continue;
}
List<Object> args = new ArrayList<Object>();
args.add("$" + key);
args.add(nested.get(s));
clauses.add(new Document(s, args));
}
} else if (!isKeyword(key)) {
List<Object> args = new ArrayList<Object>();
args.add("$" + key);
args.add(predicate);
clauses.add(new Document("$eq", args));
}
return clauses;
}
/**
* Returns whether the given {@link String} is a MongoDB keyword.
*
* @param candidate
* @return
*/
private boolean isKeyword(String candidate) {
return candidate.startsWith("$");
}
private Object resolve(AggregationOperationContext context, Object value) {
if (value instanceof Document) {
return context.getMappedObject((Document) value);
}
return context.getReference((Field) value).toString();
}
private void assertNotBuilder(Object toCheck, String name) {
Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck),
String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName()));
}
/**
* Get a builder that allows fluent creation of {@link Cond}.
*
* @return a new {@link ConditionalExpressionBuilder}.
*/
public static ConditionalExpressionBuilder newBuilder() {
return ConditionalExpressionBuilder.newBuilder();
}
/**
* @author Mark Paluch
*/
public static interface WhenBuilder {
/**
* @param booleanExpression expression that yields in a boolean result, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(Document booleanExpression);
/**
* @param expression expression that yields in a boolean result, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(AggregationExpression expression);
/**
* @param booleanField name of a field holding a boolean value, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(String booleanField);
/**
* @param criteria criteria to evaluate, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(CriteriaDefinition criteria);
}
/**
* @author Mark Paluch
*/
public static interface ThenBuilder {
/**
* @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link Document}, a
* value that is supported by MongoDB or a value that can be converted to a MongoDB representation but
* must not be {@literal null}.
* @return the {@link OtherwiseBuilder}
*/
OtherwiseBuilder then(Object value);
/**
* @param fieldReference must not be {@literal null}.
* @return the {@link OtherwiseBuilder}
*/
OtherwiseBuilder thenValueOf(String fieldReference);
/**
* @param expression must not be {@literal null}.
* @return the {@link OtherwiseBuilder}
*/
OtherwiseBuilder thenValueOf(AggregationExpression expression);
}
/**
* @author Mark Paluch
*/
public static interface OtherwiseBuilder {
/**
* @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link Document}, a
* value that is supported by MongoDB or a value that can be converted to a MongoDB representation but
* must not be {@literal null}.
* @return the {@link Cond}
*/
Cond otherwise(Object value);
/**
* @param fieldReference must not be {@literal null}.
* @return the {@link Cond}
*/
Cond otherwiseValueOf(String fieldReference);
/**
* @param expression must not be {@literal null}.
* @return the {@link Cond}
*/
Cond otherwiseValueOf(AggregationExpression expression);
}
/**
* Builder for fluent {@link Cond} creation.
*
* @author Mark Paluch
*/
static class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder {
private Object condition;
private Object thenValue;
private ConditionalExpressionBuilder() {}
/**
* Creates a new builder for {@link Cond}.
*
* @return never {@literal null}.
*/
public static ConditionalExpressionBuilder newBuilder() {
return new ConditionalExpressionBuilder();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.bson.Document)
*/
@Override
public ConditionalExpressionBuilder when(Document booleanExpression) {
Assert.notNull(booleanExpression, "'Boolean expression' must not be null!");
this.condition = booleanExpression;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition)
*/
@Override
public ThenBuilder when(CriteriaDefinition criteria) {
Assert.notNull(criteria, "Criteria must not be null!");
this.condition = criteria;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
@Override
public ThenBuilder when(AggregationExpression expression) {
Assert.notNull(expression, "AggregationExpression field must not be null!");
this.condition = expression;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(java.lang.String)
*/
@Override
public ThenBuilder when(String booleanField) {
Assert.hasText(booleanField, "Boolean field name must not be null or empty!");
this.condition = Fields.field(booleanField);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#then(java.lang.Object)
*/
@Override
public OtherwiseBuilder then(Object thenValue) {
Assert.notNull(thenValue, "Then-value must not be null!");
this.thenValue = thenValue;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#thenValueOf(java.lang.String)
*/
@Override
public OtherwiseBuilder thenValueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
this.thenValue = Fields.field(fieldReference);
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
@Override
public OtherwiseBuilder thenValueOf(AggregationExpression expression) {
Assert.notNull(expression, "AggregationExpression must not be null!");
this.thenValue = expression;
return this;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwise(java.lang.Object)
*/
@Override
public Cond otherwise(Object otherwiseValue) {
Assert.notNull(otherwiseValue, "Value must not be null!");
return new Cond(condition, thenValue, otherwiseValue);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwiseValueOf(java.lang.String)
*/
@Override
public Cond otherwiseValueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Cond(condition, thenValue, Fields.field(fieldReference));
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwiseValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
*/
@Override
public Cond otherwiseValueOf(AggregationExpression expression) {
Assert.notNull(expression, "AggregationExpression must not be null!");
return new Cond(condition, thenValue, expression);
}
}
}
}

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

@ -1,392 +0,0 @@ @@ -1,392 +0,0 @@
/*
* Copyright 2016 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
*
* http://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.ArrayList;
import java.util.List;
import org.bson.Document;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Encapsulates the aggregation framework {@code $cond} operator. A {@link ConditionalOperator} allows nested conditions
* {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition} or a {@link Document custom}
* condition. Replacement values can be either {@link Field field references}, values of simple MongoDB types or values
* that can be converted to a simple MongoDB type.
*
* @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
*/
public class ConditionalOperator implements AggregationExpression {
private final Object condition;
private final Object thenValue;
private final Object otherwiseValue;
/**
* Creates a new {@link ConditionalOperator} for a given {@link Field} and {@code then}/{@code otherwise} values.
*
* @param condition must not be {@literal null}.
* @param thenValue must not be {@literal null}.
* @param otherwiseValue must not be {@literal null}.
*/
public ConditionalOperator(Field condition, Object thenValue, Object otherwiseValue) {
this((Object) condition, thenValue, otherwiseValue);
}
/**
* Creates a new {@link ConditionalOperator} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise}
* values.
*
* @param condition must not be {@literal null}.
* @param thenValue must not be {@literal null}.
* @param otherwiseValue must not be {@literal null}.
*/
public ConditionalOperator(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) {
this((Object) condition, thenValue, otherwiseValue);
}
/**
* Creates a new {@link ConditionalOperator} for a given {@link Document criteria} and {@code then}/{@code otherwise}
* values.
*
* @param condition must not be {@literal null}.
* @param thenValue must not be {@literal null}.
* @param otherwiseValue must not be {@literal null}.
*/
public ConditionalOperator(Document condition, Object thenValue, Object otherwiseValue) {
this((Object) condition, thenValue, otherwiseValue);
}
private ConditionalOperator(Object condition, Object thenValue, Object otherwiseValue) {
Assert.notNull(condition, "Condition must not be null!");
Assert.notNull(thenValue, "'Then value' must not be null!");
Assert.notNull(otherwiseValue, "'Otherwise value' must not be null!");
assertNotBuilder(condition, "Condition");
assertNotBuilder(thenValue, "'Then value'");
assertNotBuilder(otherwiseValue, "'Otherwise value'");
this.condition = condition;
this.thenValue = thenValue;
this.otherwiseValue = otherwiseValue;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
Document condObject = new Document();
condObject.append("if", resolveCriteria(context, condition));
condObject.append("then", resolveValue(context, thenValue));
condObject.append("else", resolveValue(context, otherwiseValue));
return new Document("$cond", condObject);
}
private Object resolveValue(AggregationOperationContext context, Object value) {
if (value instanceof Document || value instanceof Field) {
return resolve(context, value);
}
if (value instanceof ConditionalOperator) {
return ((ConditionalOperator) value).toDocument(context);
}
return context.getMappedObject(new Document("$set", value)).get("$set");
}
private Object resolveCriteria(AggregationOperationContext context, Object value) {
if (value instanceof Document || value instanceof Field) {
return resolve(context, value);
}
if (value instanceof CriteriaDefinition) {
Document mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject());
List<Object> clauses = new ArrayList<Object>();
clauses.addAll(getClauses(context, mappedObject));
if (clauses.size() == 1) {
return clauses.get(0);
}
return clauses;
}
throw new InvalidDataAccessApiUsageException(
String.format("Invalid value in condition. Supported: Document, Field references, Criteria, got: %s", value));
}
private List<Object> getClauses(AggregationOperationContext context, Document mappedObject) {
List<Object> clauses = new ArrayList<Object>();
for (String key : mappedObject.keySet()) {
Object predicate = mappedObject.get(key);
clauses.addAll(getClauses(context, key, predicate));
}
return clauses;
}
private List<Object> getClauses(AggregationOperationContext context, String key, Object predicate) {
List<Object> clauses = new ArrayList<Object>();
if (predicate instanceof List) {
List<Object> args = new ArrayList<Object>();
for (Object clause : (List<?>) predicate) {
if (clause instanceof Document) {
args.addAll(getClauses(context, (Document) clause));
}
}
clauses.add(new Document(key, args));
} else if (predicate instanceof Document) {
Document nested = (Document) predicate;
for (String s : nested.keySet()) {
if (!isKeyword(s)) {
continue;
}
List<Object> args = new ArrayList<Object>();
args.add("$" + key);
args.add(nested.get(s));
clauses.add(new Document(s, args));
}
} else if (!isKeyword(key)) {
List<Object> args = new ArrayList<Object>();
args.add("$" + key);
args.add(predicate);
clauses.add(new Document("$eq", args));
}
return clauses;
}
/**
* Returns whether the given {@link String} is a MongoDB keyword.
*
* @param candidate
* @return
*/
private boolean isKeyword(String candidate) {
return candidate.startsWith("$");
}
private Object resolve(AggregationOperationContext context, Object value) {
if (value instanceof Document) {
return context.getMappedObject((Document) value);
}
return context.getReference((Field) value).toString();
}
private void assertNotBuilder(Object toCheck, String name) {
Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck),
String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName()));
}
/**
* Get a builder that allows fluent creation of {@link ConditionalOperator}.
*
* @return a new {@link ConditionalExpressionBuilder}.
*/
public static ConditionalExpressionBuilder newBuilder() {
return ConditionalExpressionBuilder.newBuilder();
}
/**
* @since 1.10
*/
public static interface WhenBuilder {
/**
* @param booleanExpression expression that yields in a boolean result, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(Document booleanExpression);
/**
* @param booleanField reference to a field holding a boolean value, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(Field booleanField);
/**
* @param booleanField name of a field holding a boolean value, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(String booleanField);
/**
* @param criteria criteria to evaluate, must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder when(CriteriaDefinition criteria);
}
/**
* @since 1.10
*/
public static interface ThenBuilder {
/**
* @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link Document}, a value
* that is supported by MongoDB or a value that can be converted to a MongoDB representation but must not
* be {@literal null}.
* @return the {@link OtherwiseBuilder}
*/
OtherwiseBuilder then(Object value);
}
/**
* @since 1.10
*/
public static interface OtherwiseBuilder {
/**
* @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link Document}, a value
* that is supported by MongoDB or a value that can be converted to a MongoDB representation but must not
* be {@literal null}.
* @return the {@link ConditionalOperator}
*/
ConditionalOperator otherwise(Object value);
}
/**
* Builder for fluent {@link ConditionalOperator} creation.
*
* @author Mark Paluch
* @since 1.10
*/
public static final class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder {
private Object condition;
private Object thenValue;
private ConditionalExpressionBuilder() {}
/**
* Creates a new builder for {@link ConditionalOperator}.
*
* @return never {@literal null}.
*/
public static ConditionalExpressionBuilder newBuilder() {
return new ConditionalExpressionBuilder();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.bson.Document)
*/
@Override
public ConditionalExpressionBuilder when(Document booleanExpression) {
Assert.notNull(booleanExpression, "'Boolean expression' must not be null!");
this.condition = booleanExpression;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition)
*/
@Override
public ThenBuilder when(CriteriaDefinition criteria) {
Assert.notNull(criteria, "Criteria must not be null!");
this.condition = criteria;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.Field)
*/
@Override
public ThenBuilder when(Field booleanField) {
Assert.notNull(booleanField, "Boolean field must not be null!");
this.condition = booleanField;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(java.lang.String)
*/
@Override
public ThenBuilder when(String booleanField) {
Assert.hasText(booleanField, "Boolean field name must not be null or empty!");
this.condition = Fields.field(booleanField);
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.ThenBuilder#then(java.lang.Object)
*/
@Override
public OtherwiseBuilder then(Object thenValue) {
Assert.notNull(thenValue, "'Then-value' must not be null!");
this.thenValue = thenValue;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.OtherwiseBuilder#otherwise(java.lang.Object)
*/
@Override
public ConditionalOperator otherwise(Object otherwiseValue) {
Assert.notNull(otherwiseValue, "'Otherwise-value' must not be null!");
return new ConditionalOperator(condition, thenValue, otherwiseValue);
}
}
}

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

@ -1,195 +0,0 @@ @@ -1,195 +0,0 @@
/*
* Copyright 2016 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
*
* http://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.ArrayList;
import java.util.List;
import org.bson.Document;
import org.springframework.util.Assert;
/**
* Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field field
* references}, values of simple MongoDB types or values that can be converted to a simple MongoDB type.
*
* @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
*/
public class IfNullOperator implements AggregationExpression {
private final Field field;
private final Object value;
/**
* Creates a new {@link IfNullOperator} for the given {@link Field} and replacement {@code value}.
*
* @param field must not be {@literal null}.
* @param value must not be {@literal null}.
*/
public IfNullOperator(Field field, Object value) {
Assert.notNull(field, "Field must not be null!");
Assert.notNull(value, "'Replacement-value' must not be null!");
this.field = field;
this.value = value;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
public Document toDocument(AggregationOperationContext context) {
List<Object> list = new ArrayList<Object>();
list.add(context.getReference(field).toString());
list.add(resolve(value, context));
return new Document("$ifNull", list);
}
private Object resolve(Object value, AggregationOperationContext context) {
if (value instanceof Field) {
return context.getReference((Field) value).toString();
} else if (value instanceof Document) {
return value;
}
return context.getMappedObject(new Document("$set", value)).get("$set");
}
/**
* Get a builder that allows fluent creation of {@link IfNullOperator}.
*
* @return a new {@link IfNullBuilder}.
*/
public static IfNullBuilder newBuilder() {
return IfNullOperatorBuilder.newBuilder();
}
/**
* @since 1.10
*/
public static interface IfNullBuilder {
/**
* @param field the field to check for a {@literal null} value, field reference must not be {@literal null}.
* @return the {@link ThenBuilder}
*/
ThenBuilder ifNull(Field field);
/**
* @param field the field to check for a {@literal null} value, field name must not be {@literal null} or empty.
* @return the {@link ThenBuilder}
*/
ThenBuilder ifNull(String field);
}
/**
* @since 1.10
*/
public static interface ThenBuilder {
/**
* @param field the field holding the replacement value, must not be {@literal null}.
* @return the {@link IfNullOperator}
*/
IfNullOperator thenReplaceWith(Field field);
/**
* @param value the value to be used if the {@code $ifNull }condition evaluates {@literal true}. Can be a
* {@link Document}, a value that is supported by MongoDB or a value that can be converted to a MongoDB
* representation but must not be {@literal null}.
* @return the {@link IfNullOperator}
*/
IfNullOperator thenReplaceWith(Object value);
}
/**
* Builder for fluent {@link IfNullOperator} creation.
*
* @author Mark Paluch
* @since 1.10
*/
public static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder {
private Field field;
private IfNullOperatorBuilder() {}
/**
* Creates a new builder for {@link IfNullOperator}.
*
* @return never {@literal null}.
*/
public static IfNullOperatorBuilder newBuilder() {
return new IfNullOperatorBuilder();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.Field)
*/
public ThenBuilder ifNull(Field field) {
Assert.notNull(field, "Field must not be null!");
this.field = field;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.IfNullBuilder#ifNull(java.lang.String)
*/
public ThenBuilder ifNull(String name) {
Assert.hasText(name, "Field name must not be null or empty!");
this.field = Fields.field(name);
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.ThenReplaceBuilder#thenReplaceWith(org.springframework.data.mongodb.core.aggregation.Field)
*/
@Override
public IfNullOperator thenReplaceWith(Field replacementField) {
Assert.notNull(replacementField, "Replacement field must not be null!");
return new IfNullOperator(this.field, replacementField);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.ThenReplaceBuilder#thenReplaceWith(java.lang.Object)
*/
@Override
public IfNullOperator thenReplaceWith(Object value) {
Assert.notNull(value, "'Replacement-value' must not be null!");
return new IfNullOperator(this.field, value);
}
}
}

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

@ -21,6 +21,8 @@ import java.util.Collections; @@ -21,6 +21,8 @@ import java.util.Collections;
import java.util.List;
import org.bson.Document;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection;
@ -241,22 +243,22 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -241,22 +243,22 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
public abstract ProjectionOperation as(String alias);
/**
* Apply a conditional projection using {@link ConditionalOperator}.
* Apply a conditional projection using {@link Cond}.
*
* @param conditionalOperator must not be {@literal null}.
* @param cond must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public abstract ProjectionOperation applyCondition(ConditionalOperator conditionalOperator);
public abstract ProjectionOperation applyCondition(Cond cond);
/**
* Apply a conditional value replacement for {@literal null} values using {@link IfNullOperator}.
* Apply a conditional value replacement for {@literal null} values using {@link IfNull}.
*
* @param ifNullOperator must not be {@literal null}.
* @param ifNull must not be {@literal null}.
* @return never {@literal null}.
* @since 1.10
*/
public abstract ProjectionOperation applyCondition(IfNullOperator ifNullOperator);
public abstract ProjectionOperation applyCondition(IfNull ifNull);
}
/**
@ -460,10 +462,10 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -460,10 +462,10 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#transform(org.springframework.data.mongodb.core.aggregation.ConditionalOperator)
*/
@Override
public ProjectionOperation applyCondition(ConditionalOperator conditionalOperator) {
public ProjectionOperation applyCondition(Cond cond) {
Assert.notNull(conditionalOperator, "ConditionalOperator must not be null!");
return this.operation.and(new ExpressionProjection(Fields.field(name), conditionalOperator));
Assert.notNull(cond, "ConditionalOperator must not be null!");
return this.operation.and(new ExpressionProjection(Fields.field(name), cond));
}
/*
@ -471,10 +473,10 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -471,10 +473,10 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#transform(org.springframework.data.mongodb.core.aggregation.IfNullOperator)
*/
@Override
public ProjectionOperation applyCondition(IfNullOperator ifNullOperator) {
public ProjectionOperation applyCondition(IfNull ifNull) {
Assert.notNull(ifNullOperator, "IfNullOperator must not be null!");
return this.operation.and(new ExpressionProjection(Fields.field(name), ifNullOperator));
Assert.notNull(ifNull, "IfNullOperator must not be null!");
return this.operation.and(new ExpressionProjection(Fields.field(name), ifNull));
}
/**

36
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java

@ -24,6 +24,8 @@ import static org.springframework.data.mongodb.core.aggregation.Fields.*; @@ -24,6 +24,8 @@ import static org.springframework.data.mongodb.core.aggregation.Fields.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
import lombok.Builder;
import java.io.BufferedInputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@ -56,6 +58,8 @@ import org.springframework.data.mapping.model.MappingException; @@ -56,6 +58,8 @@ import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.CollectionCallback;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.Venue;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators;
import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry;
import org.springframework.data.mongodb.core.index.GeospatialIndex;
import org.springframework.data.mongodb.core.query.Criteria;
@ -69,8 +73,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -69,8 +73,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.mongodb.MongoException;
import com.mongodb.client.MongoCollection;
import lombok.Builder;
/**
* Tests for {@link MongoTemplate#aggregate(String, AggregationPipeline, Class)}.
*
@ -518,7 +520,7 @@ public class AggregationTests { @@ -518,7 +520,7 @@ public class AggregationTests {
TypedAggregation<InventoryItem> aggregation = newAggregation(InventoryItem.class, //
project("item") //
.and("discount")//
.applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("qty").gte(250)) //
.applyCondition(Cond.newBuilder().when(Criteria.where("qty").gte(250)) //
.then(30) //
.otherwise(20)));
@ -566,7 +568,7 @@ public class AggregationTests { @@ -566,7 +568,7 @@ public class AggregationTests {
TypedAggregation<InventoryItem> aggregation = newAggregation(InventoryItem.class, //
project("item") //
.and(ifNull("description", "Unspecified")) //
.and(ConditionalOperators.ifNull("description").then("Unspecified")) //
.as("description")//
);
@ -593,7 +595,7 @@ public class AggregationTests { @@ -593,7 +595,7 @@ public class AggregationTests {
TypedAggregation<ZipInfo> aggregation = newAggregation(ZipInfo.class, //
project() //
.and("largePopulation")//
.applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(20000)) //
.applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) //
.then(true) //
.otherwise(false)) //
.and("population").as("population"));
@ -618,9 +620,9 @@ public class AggregationTests { @@ -618,9 +620,9 @@ public class AggregationTests {
TypedAggregation<ZipInfo> aggregation = newAggregation(ZipInfo.class, //
project() //
.and("size")//
.applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(20000)) //
.then(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(200000)).then("huge")
.otherwise("small")) //
.applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) //
.then(
ConditionalOperators.when(Criteria.where("population").gte(200000)).then("huge").otherwise("small")) //
.otherwise("small")) //
.and("population").as("population"));
@ -645,9 +647,9 @@ public class AggregationTests { @@ -645,9 +647,9 @@ public class AggregationTests {
mongoTemplate.insert(new LineItem("idonly", null, 0));
TypedAggregation<LineItem> aggregation = newAggregation(LineItem.class, //
project("id") //
.and("caption")//
.applyCondition(ifNull(field("caption"), "unknown")),
project("id") //
.and("caption")//
.applyCondition(ConditionalOperators.ifNull("caption").then("unknown")),
sort(ASC, "id"));
assertThat(aggregation.toString(), is(notNullValue()));
@ -674,7 +676,7 @@ public class AggregationTests { @@ -674,7 +676,7 @@ public class AggregationTests {
TypedAggregation<LineItem> aggregation = newAggregation(LineItem.class, //
project("id") //
.and("caption")//
.applyCondition(ifNull(field("caption"), field("id"))),
.applyCondition(ConditionalOperators.ifNull("caption").thenValueOf("id")),
sort(ASC, "id"));
assertThat(aggregation.toString(), is(notNullValue()));
@ -710,12 +712,16 @@ public class AggregationTests { @@ -710,12 +712,16 @@ public class AggregationTests {
TypedAggregation<CarPerson> agg = Aggregation.newAggregation(CarPerson.class,
unwind("descriptors.carDescriptor.entries"), //
project() //
.and(new ConditionalOperator(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1"), "good",
"meh"))
.and(ConditionalOperators //
.when(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1")).then("good")
.otherwise("meh"))
.as("make") //
.and("descriptors.carDescriptor.entries.model").as("model") //
.and("descriptors.carDescriptor.entries.year").as("year"), //
group("make").avg(new ConditionalOperator(Criteria.where("year").gte(2012), 1, 9000)).as("score"),
group("make").avg(ConditionalOperators //
.when(Criteria.where("year").gte(2012)) //
.then(1) //
.otherwise(9000)).as("score"),
sort(ASC, "make"));
AggregationResults<Document> result = mongoTemplate.aggregate(agg, Document.class);

36
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java

@ -19,7 +19,6 @@ import static org.hamcrest.CoreMatchers.*; @@ -19,7 +19,6 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.DocumentTestUtils.*;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
@ -32,6 +31,8 @@ import org.junit.Rule; @@ -32,6 +31,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators;
import org.springframework.data.mongodb.core.query.Criteria;
/**
@ -384,15 +385,18 @@ public class AggregationUnitTests { @@ -384,15 +385,18 @@ public class AggregationUnitTests {
public void conditionExpressionBasedFieldsShouldBeReferencableInFollowingOperations() {
Document agg = newAggregation( //
project("a"), //
group("a").first(conditional(Criteria.where("a").gte(42), "answer", "no-answer")).as("foosum") //
project("a", "answer"), //
group("a")
.first(Cond.newBuilder().when(Criteria.where("a").gte(42)).thenValueOf("answer").otherwise("no-answer"))
.as("foosum") //
).toDocument("foo", Aggregation.DEFAULT_CONTEXT);
System.out.println("agg: " + agg);
@SuppressWarnings("unchecked")
Document secondProjection = ((List<Document>) agg.get("pipeline")).get(1);
Document fields = getAsDocument(secondProjection, "$group");
assertThat(getAsDocument(fields, "foosum"), isBsonObject().containing("$first"));
assertThat(getAsDocument(fields, "foosum"), isBsonObject().containing("$first.$cond.then", "answer"));
assertThat(getAsDocument(fields, "foosum"), isBsonObject().containing("$first.$cond.then", "$answer"));
assertThat(getAsDocument(fields, "foosum"), isBsonObject().containing("$first.$cond.else", "no-answer"));
}
@ -403,7 +407,7 @@ public class AggregationUnitTests { @@ -403,7 +407,7 @@ public class AggregationUnitTests {
public void shouldRenderProjectionConditionalExpressionCorrectly() {
Document agg = Aggregation.newAggregation(//
project().and(ConditionalOperator.newBuilder() //
project().and(Cond.newBuilder() //
.when("isYellow") //
.then("bright") //
.otherwise("dark")).as("color"))
@ -426,7 +430,7 @@ public class AggregationUnitTests { @@ -426,7 +430,7 @@ public class AggregationUnitTests {
Document agg = Aggregation.newAggregation(//
project().and("color")
.applyCondition(ConditionalOperator.newBuilder() //
.applyCondition(Cond.newBuilder() //
.when("isYellow") //
.then("bright") //
.otherwise("dark")))
@ -450,7 +454,8 @@ public class AggregationUnitTests { @@ -450,7 +454,8 @@ public class AggregationUnitTests {
Document agg = Aggregation
.newAggregation(project()//
.and("color")//
.applyCondition(conditional(Criteria.where("key").gt(5), "bright", "dark"))) //
.applyCondition(Cond.newBuilder().when(Criteria.where("key").gt(5)) //
.then("bright").otherwise("dark"))) //
.toDocument("foo", Aggregation.DEFAULT_CONTEXT);
Document project = extractPipelineElement(agg, 0, "$project");
@ -472,7 +477,10 @@ public class AggregationUnitTests { @@ -472,7 +477,10 @@ public class AggregationUnitTests {
.newAggregation(//
project().and("color").as("chroma"),
project().and("luminosity") //
.applyCondition(conditional(field("chroma"), "bright", "dark"))) //
.applyCondition(ConditionalOperators //
.when("chroma") //
.thenValueOf("bright") //
.otherwise("dark"))) //
.toDocument("foo", Aggregation.DEFAULT_CONTEXT);
Document project = extractPipelineElement(agg, 1, "$project");
@ -494,7 +502,10 @@ public class AggregationUnitTests { @@ -494,7 +502,10 @@ public class AggregationUnitTests {
.newAggregation(//
project().and("color").as("chroma"),
project().and("luminosity") //
.applyCondition(conditional(Criteria.where("chroma").is(100), "bright", "dark"))) //
.applyCondition(Cond.newBuilder()
.when(Criteria.where("chroma") //
.is(100)) //
.then("bright").otherwise("dark"))) //
.toDocument("foo", Aggregation.DEFAULT_CONTEXT);
Document project = extractPipelineElement(agg, 1, "$project");
@ -516,7 +527,9 @@ public class AggregationUnitTests { @@ -516,7 +527,9 @@ public class AggregationUnitTests {
.newAggregation(//
project().and("color"), //
project().and("luminosity") //
.applyCondition(ifNull(field("chroma"), "unknown"))) //
.applyCondition(ConditionalOperators //
.ifNull("chroma") //
.then("unknown"))) //
.toDocument("foo", Aggregation.DEFAULT_CONTEXT);
Document project = extractPipelineElement(agg, 1, "$project");
@ -535,7 +548,8 @@ public class AggregationUnitTests { @@ -535,7 +548,8 @@ public class AggregationUnitTests {
.newAggregation(//
project("fallback").and("color").as("chroma"),
project().and("luminosity") //
.applyCondition(ifNull(field("chroma"), field("fallback")))) //
.applyCondition(ConditionalOperators.ifNull("chroma") //
.thenValueOf("fallback"))) //
.toDocument("foo", Aggregation.DEFAULT_CONTEXT);
Document project = extractPipelineElement(agg, 1, "$project");

62
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperatorUnitTests.java → spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java

@ -16,46 +16,24 @@ @@ -16,46 +16,24 @@
package org.springframework.data.mongodb.core.aggregation;
import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.aggregation.ConditionalOperator.*;
import static org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
import java.util.Arrays;
import org.bson.Document;
import org.junit.Test;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators;
import org.springframework.data.mongodb.core.query.Criteria;
/**
* Unit tests for {@link ConditionalOperator}.
* Unit tests for {@link Cond}.
*
* @author Mark Paluch
* @author Christoph Strobl
*/
public class ConditionalOperatorUnitTests {
/**
* @see DATAMONGO-861
*/
@Test(expected = IllegalArgumentException.class)
public void shouldRejectNullCondition() {
new ConditionalOperator((Field) null, "", "");
}
/**
* @see DATAMONGO-861
*/
@Test(expected = IllegalArgumentException.class)
public void shouldRejectThenValue() {
new ConditionalOperator(Fields.field("field"), null, "");
}
/**
* @see DATAMONGO-861
*/
@Test(expected = IllegalArgumentException.class)
public void shouldRejectOtherwiseValue() {
new ConditionalOperator(Fields.field("field"), "", null);
}
public class CondExpressionUnitTests {
/**
* @see DATAMONGO-861
@ -90,12 +68,12 @@ public class ConditionalOperatorUnitTests { @@ -90,12 +68,12 @@ public class ConditionalOperatorUnitTests {
}
/**
* @see DATAMONGO-861
* @see DATAMONGO-861, DATAMONGO-1542
*/
@Test
public void simpleBuilderShouldRenderCorrectly() {
ConditionalOperator operator = newBuilder().when("isYellow").then("bright").otherwise("dark");
Cond operator = ConditionalOperators.when("isYellow").thenValueOf("bright").otherwise("dark");
Document document = operator.toDocument(Aggregation.DEFAULT_CONTEXT);
Document expectedCondition = new Document() //
@ -107,12 +85,12 @@ public class ConditionalOperatorUnitTests { @@ -107,12 +85,12 @@ public class ConditionalOperatorUnitTests {
}
/**
* @see DATAMONGO-861
* @see DATAMONGO-861, DATAMONGO-1542
*/
@Test
public void simpleCriteriaShouldRenderCorrectly() {
ConditionalOperator operator = newBuilder().when(Criteria.where("luminosity").gte(100)).then("bright")
Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100)).thenValueOf("bright")
.otherwise("dark");
Document document = operator.toDocument(Aggregation.DEFAULT_CONTEXT);
@ -130,11 +108,10 @@ public class ConditionalOperatorUnitTests { @@ -130,11 +108,10 @@ public class ConditionalOperatorUnitTests {
@Test
public void andCriteriaShouldRenderCorrectly() {
ConditionalOperator operator = newBuilder() //
.when(Criteria.where("luminosity").gte(100) //
.andOperator(Criteria.where("hue").is(50), //
Criteria.where("saturation").lt(11)))
.then("bright").otherwise("dark");
Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100) //
.andOperator(Criteria.where("hue").is(50), //
Criteria.where("saturation").lt(11)))
.thenValueOf("bright").otherwiseValueOf("dark-field");
Document document = operator.toDocument(Aggregation.DEFAULT_CONTEXT);
@ -145,20 +122,20 @@ public class ConditionalOperatorUnitTests { @@ -145,20 +122,20 @@ public class ConditionalOperatorUnitTests {
Document expectedCondition = new Document() //
.append("if", Arrays.<Object> asList(luminosity, new Document("$and", Arrays.asList(hue, saturation)))) //
.append("then", "bright") //
.append("else", "dark");
.append("else", "$dark-field");
assertThat(document, isBsonObject().containing("$cond", expectedCondition));
}
/**
* @see DATAMONGO-861
* @see DATAMONGO-861, DATAMONGO-1542
*/
@Test
public void twoArgsCriteriaShouldRenderCorrectly() {
Criteria criteria = Criteria.where("luminosity").gte(100) //
.and("saturation").and("chroma").is(200);
ConditionalOperator operator = newBuilder().when(criteria).then("bright").otherwise("dark");
Cond operator = ConditionalOperators.when(criteria).thenValueOf("bright").otherwise("dark");
Document document = operator.toDocument(Aggregation.DEFAULT_CONTEXT);
@ -174,14 +151,13 @@ public class ConditionalOperatorUnitTests { @@ -174,14 +151,13 @@ public class ConditionalOperatorUnitTests {
}
/**
* @see DATAMONGO-861
* @see DATAMONGO-861, DATAMONGO-1542
*/
@Test
public void nestedCriteriaShouldRenderCorrectly() {
ConditionalOperator operator = newBuilder() //
.when(Criteria.where("luminosity").gte(100)) //
.then(newBuilder() //
Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100)) //
.thenValueOf(newBuilder() //
.when(Criteria.where("luminosity").gte(200)) //
.then("verybright") //
.otherwise("not-so-bright")) //

81
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java

@ -1,81 +0,0 @@ @@ -1,81 +0,0 @@
/*
* Copyright 2016 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
*
* http://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.junit.Assert.*;
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
import java.util.Arrays;
import org.bson.Document;
import org.junit.Test;
/**
* Unit tests for {@link IfNullOperator}.
*
* @author Mark Paluch
* @author Christoph Strobl
*/
public class IfNullOperatorUnitTests {
/**
* @see DATAMONGO-861
*/
@Test(expected = IllegalArgumentException.class)
public void shouldRejectNullCondition() {
new IfNullOperator(null, "");
}
/**
* @see DATAMONGO-861
*/
@Test(expected = IllegalArgumentException.class)
public void shouldRejectThenValue() {
new IfNullOperator(Fields.field("aa"), null);
}
/**
* @see DATAMONGO-861
*/
@Test
public void simpleIfNullShouldRenderCorrectly() {
IfNullOperator operator = IfNullOperator.newBuilder() //
.ifNull("optional") //
.thenReplaceWith("a more sophisticated value");
Document document = operator.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(document,
isBsonObject().containing("$ifNull", Arrays.<Object> asList("$optional", "a more sophisticated value")));
}
/**
* @see DATAMONGO-861
*/
@Test
public void fieldReplacementIfNullShouldRenderCorrectly() {
IfNullOperator operator = IfNullOperator.newBuilder() //
.ifNull(Fields.field("optional")) //
.thenReplaceWith(Fields.field("never-null"));
Document document = operator.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(document, isBsonObject().containing("$ifNull", Arrays.<Object> asList("$optional", "$never-null")));
}
}

41
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java

@ -34,6 +34,7 @@ import org.springframework.data.mongodb.core.aggregation.AggregationExpressions. @@ -34,6 +34,7 @@ import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArrayOperators;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.BooleanOperators;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ComparisonOperators;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.DateOperators;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.LiteralOperators;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.SetOperators;
@ -47,6 +48,7 @@ import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Pro @@ -47,6 +48,7 @@ import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Pro
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
public class ProjectionOperationUnitTests {
@ -1703,6 +1705,45 @@ public class ProjectionOperationUnitTests { @@ -1703,6 +1705,45 @@ public class ProjectionOperationUnitTests {
"{ $project:{ adjustedGrades:{ $map: { input: { $size : [\"foo\"]}, as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}")));
}
/**
* @see DATAMONGO-861, DATAMONGO-1542
*/
@Test
public void shouldRenderIfNullConditionAggregationExpression() {
Document agg = project().and(ConditionalOperators.ifNull(ArrayOperators.arrayOf("array").elementAt(1)).then("a more sophisticated value"))
.as("result").toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(agg,
is(Document.parse("{ $project: { result: { $ifNull: [ { $arrayElemAt: [\"$array\", 1] }, \"a more sophisticated value\" ] } } }")));
}
/**
* @see DATAMONGO-1542
*/
@Test
public void shouldRenderIfNullValueAggregationExpression() {
Document agg = project()
.and(ConditionalOperators.ifNull("field").then(ArrayOperators.arrayOf("array").elementAt(1))).as("result")
.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(agg,
is(Document.parse("{ $project: { result: { $ifNull: [ \"$field\", { $arrayElemAt: [\"$array\", 1] } ] } } }")));
}
/**
* @see DATAMONGO-861, DATAMONGO-1542
*/
@Test
public void fieldReplacementIfNullShouldRenderCorrectly() {
Document agg = project().and(ConditionalOperators.ifNull("optional").thenValueOf("$never-null")).as("result")
.toDocument(Aggregation.DEFAULT_CONTEXT);
assertThat(agg, is(Document.parse("{ $project: { result: { $ifNull: [ \"$optional\", \"$never-null\" ] } } }")));
}
private static Document exctractOperation(String field, Document fromProjectClause) {
return (Document) fromProjectClause.get(field);
}

15
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java

@ -37,9 +37,10 @@ import org.springframework.data.annotation.Id; @@ -37,9 +37,10 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators;
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;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
@ -291,7 +292,8 @@ public class TypeBasedAggregationOperationContextUnitTests { @@ -291,7 +292,8 @@ public class TypeBasedAggregationOperationContextUnitTests {
TypedAggregation<FooPerson> agg = newAggregation(FooPerson.class,
project("name") //
.and("age") //
.applyCondition(conditional(Criteria.where("age.value").lt(10), new Age(0), field("age"))) //
.applyCondition(
ConditionalOperators.when(Criteria.where("age.value").lt(10)).then(new Age(0)).otherwiseValueOf("age")) //
);
Document document = agg.toDocument("person", context);
@ -307,8 +309,8 @@ public class TypeBasedAggregationOperationContextUnitTests { @@ -307,8 +309,8 @@ public class TypeBasedAggregationOperationContextUnitTests {
assertThat(getValue(age, "$cond"), isBsonObject().containing("else", "$age"));
}
/**
* @see DATAMONGO-861
/**.AggregationUnitTests
* @see DATAMONGO-861, DATAMONGO-1542
*/
@Test
public void rendersAggregationIfNullInTypedAggregationContextCorrectly() {
@ -317,7 +319,7 @@ public class TypeBasedAggregationOperationContextUnitTests { @@ -317,7 +319,7 @@ public class TypeBasedAggregationOperationContextUnitTests {
TypedAggregation<FooPerson> agg = newAggregation(FooPerson.class,
project("name") //
.and("age") //
.applyCondition(ifNull("age", new Age(0))) //
.applyCondition(ConditionalOperators.ifNull("age").then(new Age(0))) //
);
Document document = agg.toDocument("person", context);
@ -328,6 +330,9 @@ public class TypeBasedAggregationOperationContextUnitTests { @@ -328,6 +330,9 @@ public class TypeBasedAggregationOperationContextUnitTests {
Document project = getValue(projection, "$project");
Document age = getValue(project, "age");
assertThat(age, is(Document.parse(
"{ $ifNull: [ \"$age\", { \"_class\":\"org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContextUnitTests$Age\", \"value\": 0} ] }")));
assertThat(age, isBsonObject().containing("$ifNull.[0]", "$age"));
assertThat(age, isBsonObject().containing("$ifNull.[1].value", 0));
assertThat(age, isBsonObject().containing("$ifNull.[1]._class", Age.class.getName()));

Loading…
Cancel
Save