diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java index edd51d08b..76a02d588 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java @@ -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 { 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. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java index e1a3d2990..7196de930 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java @@ -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 { 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 { } /** - * 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 { } } + /** + * 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 { 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 { } /** - * 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 { } /** - * 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 { 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 { } /** - * 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 { } /** - * Creats new {@link AnyElementTrue}. + * Creates new {@link AnyElementTrue}. * * @param expression must not be {@literal null}. * @return @@ -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 list = new ArrayList(); + + 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 clauses = new ArrayList(); + + 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 getClauses(AggregationOperationContext context, Document mappedObject) { + + List clauses = new ArrayList(); + + for (String key : mappedObject.keySet()) { + + Object predicate = mappedObject.get(key); + clauses.addAll(getClauses(context, key, predicate)); + } + + return clauses; + } + + private List getClauses(AggregationOperationContext context, String key, Object predicate) { + + List clauses = new ArrayList(); + + if (predicate instanceof List) { + + List args = new ArrayList(); + 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 args = new ArrayList(); + args.add("$" + key); + args.add(nested.get(s)); + clauses.add(new Document(s, args)); + } + + } else if (!isKeyword(key)) { + + List args = new ArrayList(); + 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); + } + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java deleted file mode 100644 index 393530880..000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java +++ /dev/null @@ -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 clauses = new ArrayList(); - - 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 getClauses(AggregationOperationContext context, Document mappedObject) { - - List clauses = new ArrayList(); - - for (String key : mappedObject.keySet()) { - - Object predicate = mappedObject.get(key); - clauses.addAll(getClauses(context, key, predicate)); - } - - return clauses; - } - - private List getClauses(AggregationOperationContext context, String key, Object predicate) { - - List clauses = new ArrayList(); - - if (predicate instanceof List) { - - List args = new ArrayList(); - 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 args = new ArrayList(); - args.add("$" + key); - args.add(nested.get(s)); - clauses.add(new Document(s, args)); - } - - } else if (!isKeyword(key)) { - - List args = new ArrayList(); - 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); - } - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java deleted file mode 100644 index e7a7572a4..000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java +++ /dev/null @@ -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 list = new ArrayList(); - - 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); - } - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java index 580051bae..1bf6d9967 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java @@ -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 { 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 { * @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 { * @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)); } /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 4242d0114..81128b808 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/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.*; 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; 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; 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 { TypedAggregation 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 { TypedAggregation 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 { TypedAggregation 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 { TypedAggregation 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 { mongoTemplate.insert(new LineItem("idonly", null, 0)); TypedAggregation 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 { TypedAggregation 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 { TypedAggregation 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 result = mongoTemplate.aggregate(agg, Document.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index 2a9f3263d..75cd09f61 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -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; 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 { 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) 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 { 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 { 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 { 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 { .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 { .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 { .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 { .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"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java similarity index 77% rename from spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperatorUnitTests.java rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java index f7a7c3098..8e8e16911 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java @@ -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 { } /** - * @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 { } /** - * @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 { @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 { Document expectedCondition = new Document() // .append("if", Arrays. 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 { } /** - * @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")) // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java deleted file mode 100644 index 5a72d977c..000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java +++ /dev/null @@ -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. 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. asList("$optional", "$never-null"))); - } -} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java index a7fa43e86..2f0432e8e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java +++ b/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. 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 * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ 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); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java index d5e48d249..2c217889d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java @@ -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 { TypedAggregation 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 { 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 { TypedAggregation 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 { 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()));