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 bfe8db8db..ff9ec46d1 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 @@ -23,11 +23,11 @@ import java.util.List; 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; @@ -396,68 +396,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 3ecaf6584..481be82ff 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,10 +21,15 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; +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; import com.mongodb.BasicDBObject; @@ -93,7 +98,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 +110,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 +196,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 +588,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 +598,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 +625,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 +907,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 +917,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 +2451,7 @@ public interface AggregationExpressions { } /** - * Creats new {@link AnyElementTrue}. + * Creates new {@link AnyElementTrue}. * * @param expression must not be {@literal null}. * @return @@ -5878,4 +6055,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#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(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).toDbObject(context)); + } else { + list.add(condition); + } + + list.add(resolve(value, context)); + + return new BasicDBObject("$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).toDbObject(context); + } else if (value instanceof DBObject) { + return value; + } + + return context.getMappedObject(new BasicDBObject("$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 DBObject}, 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 DBObject 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#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + BasicDBObject condObject = new BasicDBObject(); + + condObject.append("if", resolveCriteria(context, condition)); + condObject.append("then", resolveValue(context, thenValue)); + condObject.append("else", resolveValue(context, otherwiseValue)); + + return new BasicDBObject("$cond", condObject); + } + + private Object resolveValue(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject || value instanceof Field) { + return resolve(context, value); + } + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); + } + + private Object resolveCriteria(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject || value instanceof Field) { + return resolve(context, value); + } + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + if (value instanceof CriteriaDefinition) { + + DBObject 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: DBObject, Field references, Criteria, got: %s", value)); + } + + private List getClauses(AggregationOperationContext context, DBObject 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 DBObject) { + args.addAll(getClauses(context, (DBObject) clause)); + } + } + + clauses.add(new BasicDBObject(key, args)); + + } else if (predicate instanceof DBObject) { + + DBObject nested = (DBObject) 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 BasicDBObject(s, args)); + } + + } else if (!isKeyword(key)) { + + List args = new ArrayList(); + args.add("$" + key); + args.add(predicate); + clauses.add(new BasicDBObject("$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 DBObject) { + return context.getMappedObject((DBObject) 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(DBObject 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 DBObject}, 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 DBObject}, 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(com.mongodb.DBObject) + */ + @Override + public ConditionalExpressionBuilder when(DBObject 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 881894a14..000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java +++ /dev/null @@ -1,394 +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.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.mongodb.core.query.CriteriaDefinition; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; - -/** - * 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 DBObject 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 DBObject 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(DBObject 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#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) - */ - @Override - public DBObject toDbObject(AggregationOperationContext context) { - - BasicDBObject condObject = new BasicDBObject(); - - condObject.append("if", resolveCriteria(context, condition)); - condObject.append("then", resolveValue(context, thenValue)); - condObject.append("else", resolveValue(context, otherwiseValue)); - - return new BasicDBObject("$cond", condObject); - } - - private Object resolveValue(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject || value instanceof Field) { - return resolve(context, value); - } - - if (value instanceof ConditionalOperator) { - return ((ConditionalOperator) value).toDbObject(context); - } - - return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); - } - - private Object resolveCriteria(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject || value instanceof Field) { - return resolve(context, value); - } - - if (value instanceof CriteriaDefinition) { - - DBObject 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: DBObject, Field references, Criteria, got: %s", value)); - } - - private List getClauses(AggregationOperationContext context, DBObject 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 DBObject) { - args.addAll(getClauses(context, (DBObject) clause)); - } - } - - clauses.add(new BasicDBObject(key, args)); - - } else if (predicate instanceof DBObject) { - - DBObject nested = (DBObject) 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 BasicDBObject(s, args)); - } - - } else if (!isKeyword(key)) { - - List args = new ArrayList(); - args.add("$" + key); - args.add(predicate); - clauses.add(new BasicDBObject("$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 DBObject) { - return context.getMappedObject((DBObject) 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(DBObject 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 DBObject}, 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 DBObject}, 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(com.mongodb.DBObject) - */ - @Override - public ConditionalExpressionBuilder when(DBObject 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 b51aba01b..000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java +++ /dev/null @@ -1,197 +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.springframework.util.Assert; - -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; - -/** - * 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 - * @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#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) - */ - @Override - public DBObject toDbObject(AggregationOperationContext context) { - - List list = new ArrayList(); - - list.add(context.getReference(field).toString()); - list.add(resolve(value, context)); - - return new BasicDBObject("$ifNull", list); - } - - private Object resolve(Object value, AggregationOperationContext context) { - - if (value instanceof Field) { - return context.getReference((Field) value).toString(); - } else if (value instanceof DBObject) { - return value; - } - - return context.getMappedObject(new BasicDBObject("$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 DBObject}, 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 cc8089622..428abeb5e 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 @@ -20,6 +20,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +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; @@ -243,22 +245,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); } /** @@ -462,10 +464,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)); } /* @@ -473,10 +475,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 0a75c536f..100c59e12 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; @@ -55,6 +57,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.mapping.Document; @@ -73,8 +77,6 @@ import com.mongodb.DBObject; import com.mongodb.MongoException; import com.mongodb.util.JSON; -import lombok.Builder; - /** * Tests for {@link MongoTemplate#aggregate(String, AggregationPipeline, Class)}. * @@ -522,7 +524,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))); @@ -570,7 +572,7 @@ public class AggregationTests { TypedAggregation aggregation = newAggregation(InventoryItem.class, // project("item") // - .and(ifNull("description", "Unspecified")) // + .and(ConditionalOperators.ifNull("description").then("Unspecified")) // .as("description")// ); @@ -597,7 +599,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")); @@ -622,9 +624,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")); @@ -649,9 +651,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())); @@ -678,7 +680,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())); @@ -714,12 +716,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, DBObject.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 84408bbcb..684bda832 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.DBObjectTestUtils.*; 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.*; @@ -31,6 +30,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; import com.mongodb.BasicDBObject; @@ -391,7 +392,9 @@ public class AggregationUnitTests { DBObject agg = newAggregation( // project("a"), // - group("a").first(conditional(Criteria.where("a").gte(42), "answer", "no-answer")).as("foosum") // + group("a") + .first(ConditionalOperators.when(Criteria.where("a").gte(42)).thenValueOf("answer").otherwise("no-answer")) + .as("foosum") // ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); @SuppressWarnings("unchecked") @@ -409,7 +412,7 @@ public class AggregationUnitTests { public void shouldRenderProjectionConditionalExpressionCorrectly() { DBObject agg = Aggregation.newAggregation(// - project().and(ConditionalOperator.newBuilder() // + project().and(Cond.newBuilder() // .when("isYellow") // .then("bright") // .otherwise("dark")).as("color")) @@ -432,7 +435,7 @@ public class AggregationUnitTests { DBObject agg = Aggregation.newAggregation(// project().and("color") - .applyCondition(ConditionalOperator.newBuilder() // + .applyCondition(Cond.newBuilder() // .when("isYellow") // .then("bright") // .otherwise("dark"))) @@ -456,7 +459,8 @@ public class AggregationUnitTests { DBObject 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"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 0, "$project"); @@ -478,7 +482,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"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); @@ -500,7 +507,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"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); @@ -522,7 +532,9 @@ public class AggregationUnitTests { .newAggregation(// project().and("color"), // project().and("luminosity") // - .applyCondition(ifNull(field("chroma"), "unknown"))) // + .applyCondition(ConditionalOperators // + .ifNull("chroma") // + .then("unknown"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); @@ -541,7 +553,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"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject 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 475559a0a..b86fb27d8 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,48 +16,26 @@ 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.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; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** - * 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 @@ -92,12 +70,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"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); DBObject expectedCondition = new BasicDBObject() // @@ -109,12 +87,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"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); @@ -132,11 +110,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"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); @@ -147,20 +124,20 @@ public class ConditionalOperatorUnitTests { DBObject expectedCondition = new BasicDBObject() // .append("if", Arrays. asList(luminosity, new BasicDBObject("$and", Arrays.asList(hue, saturation)))) // .append("then", "bright") // - .append("else", "dark"); + .append("else", "$dark-field"); assertThat(dbObject, 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"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); @@ -176,14 +153,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 a7809644c..000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java +++ /dev/null @@ -1,82 +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.junit.Test; - -import com.mongodb.DBObject; - -/** - * 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"); - - DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); - - assertThat(dbObject, - 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")); - - DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); - - assertThat(dbObject, 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 c659e1463..f3ea25a21 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 @@ -32,6 +32,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; @@ -49,6 +50,7 @@ import com.mongodb.util.JSON; * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public class ProjectionOperationUnitTests { @@ -1707,4 +1709,43 @@ public class ProjectionOperationUnitTests { private static DBObject exctractOperation(String field, DBObject fromProjectClause) { return (DBObject) fromProjectClause.get(field); } + + /** + * @see DATAMONGO-861, DATAMONGO-1542 + */ + @Test + public void shouldRenderIfNullConditionAggregationExpression() { + + DBObject agg = project().and(ConditionalOperators.ifNull(ArrayOperators.arrayOf("array").elementAt(1)).then("a more sophisticated value")) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $ifNull: [ { $arrayElemAt: [\"$array\", 1] }, \"a more sophisticated value\" ] } } }"))); + } + + /** + * @see DATAMONGO-1542 + */ + @Test + public void shouldRenderIfNullValueAggregationExpression() { + + DBObject agg = project() + .and(ConditionalOperators.ifNull("field").then(ArrayOperators.arrayOf("array").elementAt(1))).as("result") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $ifNull: [ \"$field\", { $arrayElemAt: [\"$array\", 1] } ] } } }"))); + } + + /** + * @see DATAMONGO-861, DATAMONGO-1542 + */ + @Test + public void fieldReplacementIfNullShouldRenderCorrectly() { + + DBObject agg = project().and(ConditionalOperators.ifNull("optional").thenValueOf("$never-null")).as("result") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $ifNull: [ \"$optional\", \"$never-null\" ] } } }"))); + } } 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 c65b98fe1..e80bb1781 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 @@ -36,9 +36,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; @@ -49,6 +50,7 @@ import org.springframework.data.mongodb.core.query.Criteria; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import com.mongodb.util.JSON; /** * Unit tests for {@link TypeBasedAggregationOperationContext}. @@ -292,7 +294,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")) // ); DBObject dbo = agg.toDbObject("person", context); @@ -309,7 +312,7 @@ public class TypeBasedAggregationOperationContextUnitTests { } /** - * @see DATAMONGO-861 + * @see DATAMONGO-861, DATAMONGO-1542 */ @Test public void rendersAggregationIfNullInTypedAggregationContextCorrectly() { @@ -318,7 +321,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))) // ); DBObject dbo = agg.toDbObject("person", context); @@ -329,6 +332,9 @@ public class TypeBasedAggregationOperationContextUnitTests { DBObject project = getValue(projection, "$project"); DBObject age = getValue(project, "age"); + assertThat(age, is(JSON.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()));