From f5da4688bb6634552b452d666b93d388357ee2c8 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 6 Dec 2016 14:32:48 +0100 Subject: [PATCH 1/3] DATAMONGO-1548 - Add support for MongoDB 3.4 aggregation operators. Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-cross-store/pom.xml | 4 ++-- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb-log4j/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index ea80a3cb74..19b54876d4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1548-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index ae0a5d6c8f..08d1e76ce9 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1548-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1548-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 2d02722262..5ab3647b24 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1548-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index ee5e3336db..0142c1b99a 100644 --- a/spring-data-mongodb-log4j/pom.xml +++ b/spring-data-mongodb-log4j/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1548-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 8072d3f665..8bdc1b020d 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1548-SNAPSHOT ../pom.xml From 80f5c54243a4824c85c3dcfd899c5c1c832ca8af Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 6 Dec 2016 20:15:45 +0100 Subject: [PATCH 2/3] DATAMONGO-1548 - Add support for MongoDB 3.4 aggregation operators. We now support the following MongoDB 3.4 aggregation operators: $indexOfBytes, $indexOfCP, $split, $strLenBytes, $strLenCP, $substrCP, $indexOfArray, $range, $reverseArray, $reduce, $zip, $in, $isoDayOfWeek, $isoWeek, $isoWeekYear, $switch and $type. --- .../aggregation/AggregationExpressions.java | 1641 ++++++++++++++++- .../core/aggregation/ExposedFields.java | 6 + .../data/mongodb/core/aggregation/Fields.java | 25 +- .../SpelExpressionTransformer.java | 6 +- .../core/spel/MethodReferenceNode.java | 18 + .../ProjectionOperationUnitTests.java | 311 ++++ .../SpelExpressionTransformerUnitTests.java | 127 +- src/main/asciidoc/reference/mongodb.adoc | 12 +- 8 files changed, 2054 insertions(+), 92 deletions(-) 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 2b5e873747..76812cbb31 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 @@ -23,11 +23,15 @@ import java.util.List; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Range; 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.AggregationExpressions.Let.ExpressionVariable; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Reduce.PropertyExpression; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Switch.CaseOperator; 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; @@ -262,6 +266,30 @@ public static IfNull.ThenBuilder ifNull(AggregationExpression expression) { return IfNull.ifNull(expression); } + /** + * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it + * finds an expression which evaluates to true, {@code $switch} executes a specified expression and breaks out of + * the control flow. + * + * @param conditions must not be {@literal null}. + * @return + */ + public static Switch switchCases(CaseOperator... conditions) { + return Switch.switchCases(conditions); + } + + /** + * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it + * finds an expression which evaluates to true, {@code $switch} executes a specified expression and breaks out of + * the control flow. + * + * @param conditions must not be {@literal null}. + * @return + */ + public static Switch switchCases(List conditions) { + return Switch.switchCases(conditions); + } + public static class ConditionalOperatorFactory { private final String fieldReference; @@ -1564,6 +1592,184 @@ public StrCaseCmp strCaseCmpValueOf(AggregationExpression expression) { private StrCaseCmp createStrCaseCmp() { return fieldReference != null ? StrCaseCmp.valueOf(fieldReference) : StrCaseCmp.valueOf(expression); } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a + * string for an occurence of a given {@literal substring} and returns the UTF-8 byte index (zero-based) of the + * first occurence. + * + * @param substring must not be {@literal null}. + * @return + */ + public IndexOfBytes indexOf(String substring) { + + Assert.notNull(substring, "Substring must not be null!"); + return createIndexOfBytesSubstringBuilder().indexOf(substring); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a + * string for an occurence of a substring contained in the given {@literal field reference} and returns the UTF-8 + * byte index (zero-based) of the first occurence. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public IndexOfBytes indexOf(Field fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createIndexOfBytesSubstringBuilder().indexOf(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a + * string for an occurence of a substring resulting from the given {@link AggregationExpression} and returns the + * UTF-8 byte index (zero-based) of the first occurence. + * + * @param expression must not be {@literal null}. + * @return + */ + public IndexOfBytes indexOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createIndexOfBytesSubstringBuilder().indexOf(expression); + } + + private IndexOfBytes.SubstringBuilder createIndexOfBytesSubstringBuilder() { + return fieldReference != null ? IndexOfBytes.valueOf(fieldReference) : IndexOfBytes.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a + * string for an occurence of a given {@literal substring} and returns the UTF-8 code point index (zero-based) of + * the first occurence. + * + * @param substring must not be {@literal null}. + * @return + */ + public IndexOfCP indexOfCP(String substring) { + + Assert.notNull(substring, "Substring must not be null!"); + return createIndexOfCPSubstringBuilder().indexOf(substring); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a + * string for an occurence of a substring contained in the given {@literal field reference} and returns the UTF-8 + * code point index (zero-based) of the first occurence. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public IndexOfCP indexOfCP(Field fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createIndexOfCPSubstringBuilder().indexOf(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a + * string for an occurence of a substring resulting from the given {@link AggregationExpression} and returns the + * UTF-8 code point index (zero-based) of the first occurence. + * + * @param expression must not be {@literal null}. + * @return + */ + public IndexOfCP indexOfCP(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createIndexOfCPSubstringBuilder().indexOf(expression); + } + + private IndexOfCP.SubstringBuilder createIndexOfCPSubstringBuilder() { + return fieldReference != null ? IndexOfCP.valueOf(fieldReference) : IndexOfCP.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated string representation into an array of + * substrings based on the given delimiter. + * + * @param delimiter must not be {@literal null}. + * @return + */ + public Split split(String delimiter) { + return createSplit().split(delimiter); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated string representation into an array of + * substrings based on the delimiter resulting from the referenced field.. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Split split(Field fieldReference) { + return createSplit().split(fieldReference); + } + + /** + * Creates new {@link AggregationExpression} that divides the associated string representation into an array of + * substrings based on a delimiter resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Split split(AggregationExpression expression) { + return createSplit().split(expression); + } + + private Split createSplit() { + return fieldReference != null ? Split.valueOf(fieldReference) : Split.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the number of UTF-8 bytes in the associated string + * representation. + * + * @return + */ + public StrLenBytes length() { + return fieldReference != null ? StrLenBytes.stringLengthOf(fieldReference) + : StrLenBytes.stringLengthOf(expression); + } + + /** + * Creates new {@link AggregationExpression} that returns the number of UTF-8 code points in the associated string + * representation. + * + * @return + */ + public StrLenCP lengthCP() { + return fieldReference != null ? StrLenCP.stringLengthOfCP(fieldReference) + : StrLenCP.stringLengthOfCP(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a + * substring starting at a specified code point index position. + * + * @param codePointStart + * @return + */ + public SubstrCP substringCP(int codePointStart) { + return substringCP(codePointStart, -1); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a + * substring starting at a specified code point index position including the specified number of code points. + * + * @param codePointStart + * @param nrOfCodePoints + * @return + */ + public SubstrCP substringCP(int codePointStart, int nrOfCodePoints) { + return createSubstrCP().substringCP(codePointStart, nrOfCodePoints); + } + + private SubstrCP createSubstrCP() { + return fieldReference != null ? SubstrCP.valueOf(fieldReference) : SubstrCP.valueOf(expression); + } } } @@ -1731,6 +1937,89 @@ public Slice slice() { return usesFieldRef() ? Slice.sliceArrayOf(fieldReference) : Slice.sliceArrayOf(expression); } + /** + * Creates new {@link AggregationExpressions} that searches the associated array for an occurence of a specified + * value and returns the array index (zero-based) of the first occurence. + * + * @param value must not be {@literal null}. + * @return + */ + public IndexOfArray indexOf(Object value) { + return usesFieldRef() ? IndexOfArray.arrayOf(fieldReference).indexOf(value) + : IndexOfArray.arrayOf(expression).indexOf(value); + } + + /** + * Creates new {@link AggregationExpressions} that returns an array with the elements in reverse order. + * + * @return + */ + public ReverseArray reverse() { + return usesFieldRef() ? ReverseArray.reverseArrayOf(fieldReference) : ReverseArray.reverseArrayOf(expression); + } + + /** + * Start creating new {@link AggregationExpressions} that applies an {@link AggregationExpression} to each element + * in an array and combines them into a single value. + * + * @param expression must not be {@literal null}. + * @return + */ + public ReduceInitialValueBuilder reduce(final AggregationExpression expression) { + return new ReduceInitialValueBuilder() { + @Override + public Reduce startingWith(Object initialValue) { + return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression)) + .withInitialValue(initialValue).reduce(expression); + } + }; + } + + /** + * Start creating new {@link AggregationExpressions} that applies an {@link AggregationExpression} to each element + * in an array and combines them into a single value. + * + * @param expressions + * @return + */ + public ReduceInitialValueBuilder reduce(final PropertyExpression... expressions) { + + return new ReduceInitialValueBuilder() { + @Override + public Reduce startingWith(Object initialValue) { + return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression)) + .withInitialValue(initialValue).reduce(expressions); + } + }; + } + + /** + * Creates new {@link AggregationExpressions} that transposes an array of input arrays so that the first element + * of the output array would be an array containing, the first element of the first input array, the first element + * of the second input array, etc + * + * @param arrays must not be {@literal null}. + * @return + */ + public Zip zipWith(Object... arrays) { + return (usesFieldRef() ? Zip.arrayOf(fieldReference) : Zip.arrayOf(expression)).zip(arrays); + } + + /** + * Creates new {@link AggregationExpressions} that returns a boolean indicating whether a specified value is in + * the associcated array. + * + * @param value must not be {@literal null}. + * @return + */ + public In containsValue(Object value) { + return (usesFieldRef() ? In.arrayOf(fieldReference) : In.arrayOf(expression)).containsValue(value); + } + + public interface ReduceInitialValueBuilder { + Reduce startingWith(Object initialValue); + } + private boolean usesFieldRef() { return fieldReference != null; } @@ -1952,6 +2241,35 @@ public DateToString toString(String format) { .toString(format); } + /** + * Creates new {@link AggregationExpressions} that returns the weekday number in ISO 8601 format, ranging from 1 + * (for Monday) to 7 (for Sunday). + * + * @return + */ + public IsoDayOfWeek isoDayOfWeek() { + return usesFieldRef() ? IsoDayOfWeek.isoDayOfWeek(fieldReference) : IsoDayOfWeek.isoDayOfWeek(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the week number in ISO 8601 format, ranging from 1 to + * 53. + * + * @return + */ + public IsoWeek isoWeek() { + return usesFieldRef() ? IsoWeek.isoWeekOf(fieldReference) : IsoWeek.isoWeekOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the year number in ISO 8601 format. + * + * @return + */ + public IsoWeekYear isoWeekYear() { + return usesFieldRef() ? IsoWeekYear.isoWeekYearOf(fieldReference) : IsoWeekYear.isoWeekYearOf(expression); + } + private boolean usesFieldRef() { return fieldReference != null; } @@ -2072,6 +2390,17 @@ private Object unpack(Object value, AggregationOperationContext context) { return context.getReference((Field) value).toString(); } + if (value instanceof List) { + + List sourceList = (List) value; + List mappedList = new ArrayList(sourceList.size()); + + for (Object item : sourceList) { + mappedList.add(unpack(item, context)); + } + return mappedList; + } + return value; } @@ -2094,17 +2423,29 @@ protected List append(Object value) { return Arrays.asList(this.value, value); } - protected Object append(String key, Object value) { + protected java.util.Map append(String key, Object value) { - if (!(value instanceof java.util.Map)) { + if (!(this.value instanceof java.util.Map)) { throw new IllegalArgumentException("o_O"); } - java.util.Map clone = new LinkedHashMap((java.util.Map) value); + java.util.Map clone = new LinkedHashMap( + (java.util.Map) this.value); clone.put(key, value); return clone; } + protected List values() { + + if (value instanceof List) { + return new ArrayList((List) value); + } + if (value instanceof java.util.Map) { + return new ArrayList(((java.util.Map) value).values()); + } + return new ArrayList(Arrays.asList(value)); + } + protected abstract String getMongoMethod(); } @@ -3442,6 +3783,10 @@ public static Trunc truncValueOf(Number value) { } } + // ######################################### + // STRING OPERATORS + // ######################################### + /** * {@link AggregationExpression} for {@code $concat}. * @@ -3736,117 +4081,453 @@ public StrCaseCmp strcasecmpValueOf(AggregationExpression expression) { } /** - * {@link AggregationExpression} for {@code $arrayElementAt}. + * {@link AggregationExpression} for {@code $indexOfBytes}. * * @author Christoph Strobl */ - class ArrayElemAt extends AbstractAggregationExpression { + class IndexOfBytes extends AbstractAggregationExpression { - private ArrayElemAt(List value) { + private IndexOfBytes(List value) { super(value); } @Override protected String getMongoMethod() { - return "$arrayElemAt"; + return "$indexOfBytes"; } /** - * Creates new {@link ArrayElemAt}. + * Start creating a new {@link IndexOfBytes}. * * @param fieldReference must not be {@literal null}. * @return */ - public static ArrayElemAt arrayOf(String fieldReference) { + public static SubstringBuilder valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new ArrayElemAt(asFields(fieldReference)); + return new SubstringBuilder(Fields.field(fieldReference)); } /** - * Creates new {@link ArrayElemAt}. + * Start creating a new {@link IndexOfBytes}. * * @param expression must not be {@literal null}. * @return */ - public static ArrayElemAt arrayOf(AggregationExpression expression) { + public static SubstringBuilder valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new ArrayElemAt(Collections.singletonList(expression)); + return new SubstringBuilder(expression); } - public ArrayElemAt elementAt(int index) { - return new ArrayElemAt(append(index)); - } + /** + * Optionally define the substring search start and end position. + * + * @param range must not be {@literal null}. + * @return + */ + public IndexOfBytes within(Range range) { - public ArrayElemAt elementAt(AggregationExpression expression) { + Assert.notNull(range, "Range must not be null!"); - Assert.notNull(expression, "Expression must not be null!"); - return new ArrayElemAt(append(expression)); + List rangeValues = new ArrayList(2); + rangeValues.add(range.getLowerBound()); + if (range.getUpperBound() != null) { + rangeValues.add(range.getUpperBound()); + } + + return new IndexOfBytes(append(rangeValues)); } - public ArrayElemAt elementAt(String arrayFieldReference) { + public static class SubstringBuilder { - Assert.notNull(arrayFieldReference, "ArrayReference must not be null!"); - return new ArrayElemAt(append(Fields.field(arrayFieldReference))); + private final Object stringExpression; + + private SubstringBuilder(Object stringExpression) { + this.stringExpression = stringExpression; + } + + public IndexOfBytes indexOf(String substring) { + return new IndexOfBytes(Arrays.asList(stringExpression, substring)); + } + + public IndexOfBytes indexOf(AggregationExpression expression) { + return new IndexOfBytes(Arrays.asList(stringExpression, expression)); + } + + public IndexOfBytes indexOf(Field fieldReference) { + return new IndexOfBytes(Arrays.asList(stringExpression, fieldReference)); + } } } /** - * {@link AggregationExpression} for {@code $concatArrays}. + * {@link AggregationExpression} for {@code $indexOfCP}. * * @author Christoph Strobl */ - class ConcatArrays extends AbstractAggregationExpression { + class IndexOfCP extends AbstractAggregationExpression { - private ConcatArrays(List value) { + private IndexOfCP(List value) { super(value); } @Override protected String getMongoMethod() { - return "$concatArrays"; + return "$indexOfCP"; } /** - * Creates new {@link ConcatArrays}. + * Start creating a new {@link IndexOfCP}. * * @param fieldReference must not be {@literal null}. * @return */ - public static ConcatArrays arrayOf(String fieldReference) { + public static SubstringBuilder valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new ConcatArrays(asFields(fieldReference)); + return new SubstringBuilder(Fields.field(fieldReference)); } /** - * Creates new {@link ConcatArrays}. + * Start creating a new {@link IndexOfCP}. * * @param expression must not be {@literal null}. * @return */ - public static ConcatArrays arrayOf(AggregationExpression expression) { + public static SubstringBuilder valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new ConcatArrays(Collections.singletonList(expression)); + return new SubstringBuilder(expression); } - public ConcatArrays concat(String arrayFieldReference) { + /** + * Optionally define the substring search start and end position. + * + * @param range must not be {@literal null}. + * @return + */ + public IndexOfCP within(Range range) { - Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!"); - return new ConcatArrays(append(Fields.field(arrayFieldReference))); + Assert.notNull(range, "Range must not be null!"); + + List rangeValues = new ArrayList(2); + rangeValues.add(range.getLowerBound()); + if (range.getUpperBound() != null) { + rangeValues.add(range.getUpperBound()); + } + + return new IndexOfCP(append(rangeValues)); } - public ConcatArrays concat(AggregationExpression expression) { + public static class SubstringBuilder { - Assert.notNull(expression, "Expression must not be null!"); - return new ConcatArrays(append(expression)); + private final Object stringExpression; + + private SubstringBuilder(Object stringExpression) { + this.stringExpression = stringExpression; + } + + public IndexOfCP indexOf(String substring) { + return new IndexOfCP(Arrays.asList(stringExpression, substring)); + } + + public IndexOfCP indexOf(AggregationExpression expression) { + return new IndexOfCP(Arrays.asList(stringExpression, expression)); + } + + public IndexOfCP indexOf(Field fieldReference) { + return new IndexOfCP(Arrays.asList(stringExpression, fieldReference)); + } } } /** - * {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the + * {@link AggregationExpression} for {@code $split}. + */ + class Split extends AbstractAggregationExpression { + + private Split(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$split"; + } + + /** + * Start creating a new {@link Split}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Split valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Split(asFields(fieldReference)); + } + + /** + * Start creating a new {@link Split}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Split valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Split(Collections.singletonList(expression)); + } + + /** + * Use given {@link String} as deliminator + * + * @param deliminator must not be {@literal null}. + * @return + */ + public Split split(String deliminator) { + + Assert.notNull(deliminator, "Deliminator must not be null!"); + return new Split(append(deliminator)); + } + + /** + * Usge value of referenced field as deliminator. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Split split(Field fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Split(append(fieldReference)); + } + + /** + * Use value resulting from {@link AggregationExpression} as deliminator. + * + * @param expression must not be {@literal null}. + * @return + */ + public Split split(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Split(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $strLenBytes}. + */ + class StrLenBytes extends AbstractAggregationExpression { + + private StrLenBytes(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$strLenBytes"; + } + + public static StrLenBytes stringLengthOf(String fieldReference) { + return new StrLenBytes(Fields.field(fieldReference)); + } + + public static StrLenBytes stringLengthOf(AggregationExpression expression) { + return new StrLenBytes(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $strLenCP}. + */ + class StrLenCP extends AbstractAggregationExpression { + + private StrLenCP(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$strLenCP"; + } + + public static StrLenCP stringLengthOfCP(String fieldReference) { + return new StrLenCP(Fields.field(fieldReference)); + } + + public static StrLenCP stringLengthOfCP(AggregationExpression expression) { + return new StrLenCP(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $substrCP}. + * + * @author Christoph Strobl + */ + class SubstrCP extends AbstractAggregationExpression { + + private SubstrCP(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$substrCP"; + } + + /** + * Creates new {@link SubstrCP}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static SubstrCP valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new SubstrCP(asFields(fieldReference)); + } + + /** + * Creates new {@link SubstrCP}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SubstrCP valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SubstrCP(Collections.singletonList(expression)); + } + + public SubstrCP substringCP(int start) { + return substringCP(start, -1); + } + + public SubstrCP substringCP(int start, int nrOfChars) { + return new SubstrCP(append(Arrays.asList(start, nrOfChars))); + } + } + + // ######################################### + // ARRAY OPERATORS + // ######################################### + + /** + * {@link AggregationExpression} for {@code $arrayElementAt}. + * + * @author Christoph Strobl + */ + class ArrayElemAt extends AbstractAggregationExpression { + + private ArrayElemAt(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$arrayElemAt"; + } + + /** + * Creates new {@link ArrayElemAt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArrayElemAt arrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ArrayElemAt(asFields(fieldReference)); + } + + /** + * Creates new {@link ArrayElemAt}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ArrayElemAt arrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ArrayElemAt(Collections.singletonList(expression)); + } + + public ArrayElemAt elementAt(int index) { + return new ArrayElemAt(append(index)); + } + + public ArrayElemAt elementAt(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ArrayElemAt(append(expression)); + } + + public ArrayElemAt elementAt(String arrayFieldReference) { + + Assert.notNull(arrayFieldReference, "ArrayReference must not be null!"); + return new ArrayElemAt(append(Fields.field(arrayFieldReference))); + } + } + + /** + * {@link AggregationExpression} for {@code $concatArrays}. + * + * @author Christoph Strobl + */ + class ConcatArrays extends AbstractAggregationExpression { + + private ConcatArrays(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$concatArrays"; + } + + /** + * Creates new {@link ConcatArrays}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ConcatArrays arrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ConcatArrays(asFields(fieldReference)); + } + + /** + * Creates new {@link ConcatArrays}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ConcatArrays arrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ConcatArrays(Collections.singletonList(expression)); + } + + public ConcatArrays concat(String arrayFieldReference) { + + Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!"); + return new ConcatArrays(append(Fields.field(arrayFieldReference))); + } + + public ConcatArrays concat(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ConcatArrays(append(expression)); + } + } + + /** + * {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the * specified condition. * * @author Christoph Strobl @@ -4127,112 +4808,678 @@ public static IsArray isArray(String fieldReference) { */ public static IsArray isArray(AggregationExpression expression) { - Assert.notNull(expression, "Expression must not be null!"); - return new IsArray(expression); + Assert.notNull(expression, "Expression must not be null!"); + return new IsArray(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $size}. + * + * @author Christoph Strobl + */ + class Size extends AbstractAggregationExpression { + + private Size(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$size"; + } + + /** + * Creates new {@link Size}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Size lengthOfArray(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Size(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Size}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Size lengthOfArray(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Size(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $slice}. + * + * @author Christoph Strobl + */ + class Slice extends AbstractAggregationExpression { + + private Slice(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$slice"; + } + + /** + * Creates new {@link Slice}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Slice sliceArrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Slice(asFields(fieldReference)); + } + + /** + * Creates new {@link Slice}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Slice sliceArrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Slice(Collections.singletonList(expression)); + } + + public Slice itemCount(int nrElements) { + return new Slice(append(nrElements)); + } + + public SliceElementsBuilder offset(final int position) { + + return new SliceElementsBuilder() { + + @Override + public Slice itemCount(int nrElements) { + return new Slice(append(position)).itemCount(nrElements); + } + }; + } + + public interface SliceElementsBuilder { + Slice itemCount(int nrElements); + } + } + + /** + * {@link AggregationExpression} for {@code $indexOfArray}. + * + * @author Christoph Strobl + */ + class IndexOfArray extends AbstractAggregationExpression { + + private IndexOfArray(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$indexOfArray"; + } + + /** + * Start creating new {@link IndexOfArray}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IndexOfArrayBuilder arrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IndexOfArrayBuilder(Fields.field(fieldReference)); + } + + /** + * Start creating new {@link IndexOfArray}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IndexOfArrayBuilder arrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IndexOfArrayBuilder(expression); + } + + public IndexOfArray within(Range range) { + + Assert.notNull(range, "Range must not be null!"); + + List rangeValues = new ArrayList(2); + rangeValues.add(range.getLowerBound()); + if (range.getUpperBound() != null) { + rangeValues.add(range.getUpperBound()); + } + + return new IndexOfArray(append(rangeValues)); + } + + public static class IndexOfArrayBuilder { + + private final Object targetArray; + + private IndexOfArrayBuilder(Object targetArray) { + this.targetArray = targetArray; + } + + public IndexOfArray indexOf(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new IndexOfArray(Arrays.asList(targetArray, value)); + } + } + } + + /** + * {@link AggregationExpression} for {@code $range}. + * + * @author Christoph Strobl + */ + class RangeOperator extends AbstractAggregationExpression { + + private RangeOperator(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$range"; + } + + public static RangeOperatorBuilder rangeStartingAt(String fieldReference) { + return new RangeOperatorBuilder(Fields.field(fieldReference)); + } + + public static RangeOperatorBuilder rangeStartingAt(AggregationExpression expression) { + return new RangeOperatorBuilder(expression); + } + + public static RangeOperatorBuilder rangeStartingAt(Long value) { + return new RangeOperatorBuilder(value); + } + + public RangeOperator withStepSize(Long stepSize) { + return new RangeOperator(append(stepSize)); + } + + public static class RangeOperatorBuilder { + + private final Object startPoint; + + private RangeOperatorBuilder(Object startPoint) { + this.startPoint = startPoint; + } + + public RangeOperator to(Long index) { + return new RangeOperator(Arrays.asList(startPoint, index)); + } + + public RangeOperator to(AggregationExpression expression) { + return new RangeOperator(Arrays.asList(startPoint, expression)); + } + + public RangeOperator to(String fieldReference) { + return new RangeOperator(Arrays.asList(startPoint, Fields.field(fieldReference))); + } + } + + } + + /** + * {@link AggregationExpression} for {@code $reverseArray}. + */ + class ReverseArray extends AbstractAggregationExpression { + + private ReverseArray(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$reverseArray"; + } + + public static ReverseArray reverseArrayOf(String fieldReference) { + return new ReverseArray(Fields.field(fieldReference)); + } + + public static ReverseArray reverseArrayOf(AggregationExpression expression) { + return new ReverseArray(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $reduce}. + */ + class Reduce implements AggregationExpression { + + private final Object input; + private final Object initialValue; + private final List reduceExpressions; + + private Reduce(Object input, Object initialValue, List reduceExpressions) { + this.input = input; + this.initialValue = initialValue; + this.reduceExpressions = reduceExpressions; + } + + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + DBObject dbo = new BasicDBObject(); + + dbo.put("input", getMappedValue(input, context)); + dbo.put("initialValue", getMappedValue(initialValue, context)); + + if (reduceExpressions.iterator().next() instanceof PropertyExpression) { + + DBObject properties = new BasicDBObject(); + for (AggregationExpression e : reduceExpressions) { + properties.putAll(e.toDbObject(context)); + } + dbo.put("in", properties); + } else { + dbo.put("in", (reduceExpressions.iterator().next()).toDbObject(context)); + } + + return new BasicDBObject("$reduce", dbo); + } + + private Object getMappedValue(Object value, AggregationOperationContext context) { + + if (value instanceof DBObject) { + return value; + } + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } else if (value instanceof Field) { + return context.getReference(((Field) value)).toString(); + } else { + return context.getMappedObject(new BasicDBObject("###val###", value)).get("###val###"); + } + } + + public static InitialValueBuilder arrayOf(final String fieldReference) { + return new InitialValueBuilder() { + + @Override + public ReduceBuilder withInitialValue(final Object initialValue) { + return new ReduceBuilder() { + @Override + public Reduce reduce(AggregationExpression expression) { + return new Reduce(Fields.field(fieldReference), initialValue, Collections.singletonList(expression)); + } + + @Override + public Reduce reduce(PropertyExpression... expressions) { + return new Reduce(Fields.field(fieldReference), initialValue, + Arrays. asList(expressions)); + } + }; + } + }; + } + + public static InitialValueBuilder arrayOf(final AggregationExpression expression) { + return new InitialValueBuilder() { + + @Override + public ReduceBuilder withInitialValue(final Object initialValue) { + return new ReduceBuilder() { + @Override + public Reduce reduce(AggregationExpression expression) { + return new Reduce(expression, initialValue, Collections.singletonList(expression)); + } + + @Override + public Reduce reduce(PropertyExpression... expressions) { + return new Reduce(expression, initialValue, Arrays. asList(expressions)); + } + }; + } + }; + } + + public interface InitialValueBuilder { + + /** + * Define the initial cumulative value set before in is applied to the first element of the input array. + * + * @param intialValue must not be {@literal null}. + * @return + */ + ReduceBuilder withInitialValue(Object intialValue); + } + + public interface ReduceBuilder { + + /** + * Define the {@link AggregationExpression} to apply to each element in the input array in left-to-right order. + *
+ * NOTE: During evaulation of the in expression the variable references {@link Variable#THIS} and + * {@link Variable#VALUE} are availble. + * + * @param expression must not be {@literal null}. + * @return + */ + Reduce reduce(AggregationExpression expression); + + /** + * Define the {@link PropertyExpression}s to apply to each element in the input array in left-to-right order. + *
+ * NOTE: During evaulation of the in expression the variable references {@link Variable#THIS} and + * {@link Variable#VALUE} are availble. + * + * @param expression must not be {@literal null}. + * @return + */ + Reduce reduce(PropertyExpression... expressions); + } + + /** + * @author Christoph Strobl + */ + public static class PropertyExpression implements AggregationExpression { + + private final String propertyName; + private final AggregationExpression aggregationExpression; + + public PropertyExpression(String propertyName, AggregationExpression aggregationExpression) { + this.propertyName = propertyName; + this.aggregationExpression = aggregationExpression; + } + + /** + * Define a result property for an {@link AggregationExpression} used in {@link Reduce}. + * + * @param name must not be {@literal null}. + * @return + */ + public static AsBuilder property(final String name) { + return new AsBuilder() { + @Override + public PropertyExpression definedAs(AggregationExpression expression) { + return new PropertyExpression(name, expression); + } + }; + } + + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return new BasicDBObject(propertyName, aggregationExpression.toDbObject(context)); + } + + interface AsBuilder { + + /** + * Set the {@link AggregationExpression} resulting in the properties value. + * + * @param expression must not be {@literal null}. + * @return + */ + PropertyExpression definedAs(AggregationExpression expression); + } + } + + public enum Variable implements Field { + THIS { + @Override + public String getName() { + return "$$this"; + } + + @Override + public String getTarget() { + return "$$this"; + } + + @Override + public boolean isAliased() { + return false; + } + + @Override + public String toString() { + return getName(); + } + }, + VALUE { + @Override + public String getName() { + return "$$value"; + } + + @Override + public String getTarget() { + return "$$value"; + } + + @Override + public boolean isAliased() { + return false; + } + + @Override + public String toString() { + return getName(); + } + }; + + /** + * Create a {@link Field} reference to a given {@literal property} prefixed with the {@link Variable} identifier. + * eg. {@code $$value.product} + * + * @param property must not be {@literal null}. + * @return + */ + public Field referingTo(final String property) { + + return new Field() { + @Override + public String getName() { + return Variable.this.getName() + "." + property; + } + + @Override + public String getTarget() { + return Variable.this.getTarget() + "." + property; + } + + @Override + public boolean isAliased() { + return false; + } + + @Override + public String toString() { + return getName(); + } + }; + } } } /** - * {@link AggregationExpression} for {@code $size}. - * - * @author Christoph Strobl + * {@link AggregationExpression} for {@code $zip}. */ - class Size extends AbstractAggregationExpression { + class Zip extends AbstractAggregationExpression { - private Size(Object value) { + protected Zip(java.util.Map value) { super(value); } @Override protected String getMongoMethod() { - return "$size"; + return "$zip"; } /** - * Creates new {@link Size}. + * Start creating new {@link Zip}. * * @param fieldReference must not be {@literal null}. * @return */ - public static Size lengthOfArray(String fieldReference) { + public static ZipBuilder arrayOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Size(Fields.field(fieldReference)); + return new ZipBuilder(Fields.field(fieldReference)); } /** - * Creates new {@link Size}. + * Start creating new {@link Zip}. * * @param expression must not be {@literal null}. * @return */ - public static Size lengthOfArray(AggregationExpression expression) { + public static ZipBuilder arrayOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new Size(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $slice}. - * - * @author Christoph Strobl - */ - class Slice extends AbstractAggregationExpression { - - private Slice(List value) { - super(value); + return new ZipBuilder(expression); } - @Override - protected String getMongoMethod() { - return "$slice"; + /** + * Create new {@link Zip} and set the {@code useLongestLength} property to {@literal true}. + * + * @return + */ + public Zip useLongestLength() { + return new Zip(append("useLongestLength", true)); } /** - * Creates new {@link Slice}. + * Optionally provide a default value. * * @param fieldReference must not be {@literal null}. * @return */ - public static Slice sliceArrayOf(String fieldReference) { + public Zip defaultTo(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Slice(asFields(fieldReference)); + return new Zip(append("defaults", Fields.field(fieldReference))); } /** - * Creates new {@link Slice}. + * Optionally provide a default value. * * @param expression must not be {@literal null}. * @return */ - public static Slice sliceArrayOf(AggregationExpression expression) { + public Zip defaultTo(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new Slice(Collections.singletonList(expression)); + return new Zip(append("defaults", expression)); } - public Slice itemCount(int nrElements) { - return new Slice(append(nrElements)); + /** + * Optionally provide a default value. + * + * @param array must not be {@literal null}. + * @return + */ + public Zip defaultTo(Object[] array) { + + Assert.notNull(array, "Array must not be null!"); + return new Zip(append("defaults", array)); } - public SliceElementsBuilder offset(final int position) { + public static class ZipBuilder { - return new SliceElementsBuilder() { + private final List sourceArrays; + + public ZipBuilder(Object sourceArray) { + + this.sourceArrays = new ArrayList(); + this.sourceArrays.add(sourceArray); + } + + /** + * Creates new {@link Zip} that transposes an array of input arrays so that the first element of the output array + * would be an array containing, the first element of the first input array, the first element of the second input + * array, etc + * + * @param arrays arrays to zip the referenced one with. must not be {@literal null}. + * @return + */ + public Zip zip(Object... arrays) { + + Assert.notNull(arrays, "Arrays must not be null!"); + for (Object value : arrays) { + + if (value instanceof String) { + sourceArrays.add(Fields.field((String) value)); + } else { + sourceArrays.add(value); + } + } + + return new Zip(Collections. singletonMap("inputs", sourceArrays)); + } + } + } + + /** + * {@link AggregationExpression} for {@code $in}. + */ + class In extends AbstractAggregationExpression { + + private In(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$in"; + } + public static InBuilder arrayOf(final String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new InBuilder() { @Override - public Slice itemCount(int nrElements) { - return new Slice(append(position)).itemCount(nrElements); + public In containsValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new In(Arrays.asList(value, Fields.field(fieldReference))); } }; } - public interface SliceElementsBuilder { - Slice itemCount(int nrElements); + public static InBuilder arrayOf(final AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new InBuilder() { + @Override + public In containsValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new In(Arrays.asList(value, expression)); + } + }; + } + + public interface InBuilder { + In containsValue(Object value); } + } + // ############ + // LITERAL OPERATORS + // ############ + /** * {@link AggregationExpression} for {@code $literal}. * @@ -4747,6 +5994,129 @@ public interface FormatBuilder { } } + /** + * {@link AggregationExpression} for {@code $isoDayOfWeek}. + * + * @author Christoph Strobl + */ + class IsoDayOfWeek extends AbstractAggregationExpression { + + private IsoDayOfWeek(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$isoDayOfWeek"; + } + + /** + * Creates new {@link IsoDayOfWeek}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IsoDayOfWeek isoDayOfWeek(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IsoDayOfWeek(Fields.field(fieldReference)); + } + + /** + * Creates new {@link IsoDayOfWeek}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IsoDayOfWeek isoDayOfWeek(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IsoDayOfWeek(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $isoWeek}. + * + * @author Christoph Strobl + */ + class IsoWeek extends AbstractAggregationExpression { + + private IsoWeek(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$isoWeek"; + } + + /** + * Creates new {@link IsoWeek}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IsoWeek isoWeekOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IsoWeek(Fields.field(fieldReference)); + } + + /** + * Creates new {@link IsoWeek}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IsoWeek isoWeekOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IsoWeek(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $isoWeekYear}. + * + * @author Christoph Strobl + */ + class IsoWeekYear extends AbstractAggregationExpression { + + private IsoWeekYear(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$isoWeekYear"; + } + + /** + * Creates new {@link IsoWeekYear}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IsoWeekYear isoWeekYearOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IsoWeekYear(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Millisecond}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IsoWeekYear isoWeekYearOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IsoWeekYear(expression); + } + } + /** * {@link AggregationExpression} for {@code $sum}. * @@ -6864,4 +8234,111 @@ public ExpressionVariable forExpression(DBObject expressionObject) { } } } + + + /** + * {@link AggregationExpression} for {@code $switch}. + * + * @author Christoph Strobl + */ + class Switch extends AbstractAggregationExpression { + + private Switch(java.util.Map values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$switch"; + } + + public static Switch switchCases(CaseOperator... conditions) { + + Assert.notNull(conditions, "Conditions must not be null!"); + return switchCases(Arrays.asList(conditions)); + } + + public static Switch switchCases(List conditions) { + + Assert.notNull(conditions, "Conditions must not be null!"); + return new Switch(Collections. singletonMap("branches", new ArrayList(conditions))); + } + + public Switch defaultTo(Object value) { + return new Switch(append("default", value)); + } + + public static class CaseOperator implements AggregationExpression { + + private final AggregationExpression when; + private final Object then; + + private CaseOperator(AggregationExpression when, Object then) { + + this.when = when; + this.then = then; + } + + public static ThenBuilder when(final AggregationExpression condition) { + + Assert.notNull(condition, "Condition must not be null!"); + return new ThenBuilder() { + @Override + public CaseOperator then(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new CaseOperator(condition, value); + } + }; + } + + @Override + public DBObject toDbObject(AggregationOperationContext context) { + DBObject dbo = new BasicDBObject("case", when.toDbObject(context)); + + if (then instanceof AggregationExpression) { + dbo.put("then", ((AggregationExpression) then).toDbObject(context)); + } else if (then instanceof Field) { + dbo.put("then", context.getReference((Field) then).toString()); + } else { + dbo.put("then", then); + } + + return dbo; + } + + public interface ThenBuilder { + CaseOperator then(Object value); + } + } + } + + /** + * {@link AggregationExpression} for {@code $type}. + * + * @author Christoph Strobl + */ + class Type extends AbstractAggregationExpression { + + private Type(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$type"; + } + + /** + * Creates new {@link Type}. + * + * @param field must not be {@literal null}. + * @return + */ + public static Type typeOf(String field) { + + Assert.notNull(field, "Field must not be null!"); + return new Type(Fields.field(field)); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java index f9033743db..ce51f40624 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java @@ -22,6 +22,7 @@ import java.util.List; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; import org.springframework.util.Assert; import org.springframework.util.CompositeIterator; import org.springframework.util.ObjectUtils; @@ -406,6 +407,11 @@ public Object getReferenceValue() { */ @Override public String toString() { + + if(getRaw().startsWith("$")) { + return getRaw(); + } + return String.format("$%s", getRaw()); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java index 183d526520..2ba33412a4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java @@ -187,13 +187,13 @@ public Iterator iterator() { } /** - * * @return * @since 1.10 */ public List asList() { return Collections.unmodifiableList(fields); } + /** * Value object to encapsulate a field in an aggregation operation. * @@ -201,6 +201,7 @@ public List asList() { */ static class AggregationField implements Field { + private final String raw; private final String name; private final String target; @@ -225,6 +226,7 @@ public AggregationField(String name) { */ public AggregationField(String name, String target) { + raw = name; String nameToSet = cleanUp(name); String targetToSet = cleanUp(target); @@ -266,6 +268,11 @@ public String getName() { * @see org.springframework.data.mongodb.core.aggregation.Field#getAlias() */ public String getTarget() { + + if (isLocalVar()) { + return this.getRaw(); + } + return StringUtils.hasText(this.target) ? this.target : this.name; } @@ -278,6 +285,22 @@ public boolean isAliased() { return !getName().equals(getTarget()); } + /** + * @return {@literal true} in case the field name starts with {@code $$}. + * @since 1.10 + */ + public boolean isLocalVar() { + return raw.startsWith("$$") && !raw.startsWith("$$$"); + } + + /** + * @return + * @since 1.10 + */ + public String getRaw() { + return raw; + } + /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java index d381020b5c..587f0b327e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java @@ -491,8 +491,10 @@ protected Object convert(AggregationExpressionTransformationContext(5L, 9L))) + .as("byteLocation").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, isBsonObject().containing("$project.byteLocation.$indexOfBytes.[2]", 5L) + .containing("$project.byteLocation.$indexOfBytes.[3]", 9L)); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderIndexOfCPCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("item").indexOfCP("foo")).as("cpLocation") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project: { cpLocation: { $indexOfCP: [ \"$item\", \"foo\" ] } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderIndexOfCPWithRangeCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("item").indexOfCP("foo").within(new Range(5L, 9L))) + .as("cpLocation").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, isBsonObject().containing("$project.cpLocation.$indexOfCP.[2]", 5L) + .containing("$project.cpLocation.$indexOfCP.[3]", 9L)); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderSplitCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("city").split(", ")).as("city_state") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { city_state : { $split: [\"$city\", \", \"] }} }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderStrLenBytesCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("name").length()).as("length") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { \"length\": { $strLenBytes: \"$name\" } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderStrLenCPCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("name").lengthCP()).as("length") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { \"length\": { $strLenCP: \"$name\" } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderSubstrCPCorrectly() { + + DBObject agg = project().and(StringOperators.valueOf("quarter").substringCP(0, 2)).as("yearSubstring") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { yearSubstring: { $substrCP: [ \"$quarter\", 0, 2 ] } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderIndexOfArrayCorrectly() { + + DBObject agg = project().and(ArrayOperators.arrayOf("items").indexOf(2)).as("index") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { index: { $indexOfArray: [ \"$items\", 2 ] } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderRangeCorrectly() { + + DBObject agg = project().and(RangeOperator.rangeStartingAt(0L).to("distance").withStepSize(25L)).as("rest_stops") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, isBsonObject().containing("$project.rest_stops.$range.[0]", 0L) + .containing("$project.rest_stops.$range.[1]", "$distance").containing("$project.rest_stops.$range.[2]", 25L)); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderReverseArrayCorrectly() { + + DBObject agg = project().and(ArrayOperators.arrayOf("favorites").reverse()).as("reverseFavorites") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { reverseFavorites: { $reverseArray: \"$favorites\" } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderReduceWithSimpleObjectCorrectly() { + + DBObject agg = project() + .and(ArrayOperators.arrayOf("probabilityArr") + .reduce(ArithmeticOperators.valueOf("$$value").multiplyBy("$$this")).startingWith(1)) + .as("results").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse( + "{ $project : { \"results\": { $reduce: { input: \"$probabilityArr\", initialValue: 1, in: { $multiply: [ \"$$value\", \"$$this\" ] } } } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderReduceWithComplexObjectCorrectly() { + + PropertyExpression sum = PropertyExpression.property("sum").definedAs( + ArithmeticOperators.valueOf(Variable.VALUE.referingTo("sum").getName()).add(Variable.THIS.getName())); + PropertyExpression product = PropertyExpression.property("product").definedAs(ArithmeticOperators + .valueOf(Variable.VALUE.referingTo("product").getName()).multiplyBy(Variable.THIS.getName())); + + DBObject agg = project() + .and(ArrayOperators.arrayOf("probabilityArr").reduce(sum, product) + .startingWith(new BasicDBObjectBuilder().add("sum", 5).add("product", 2).get())) + .as("results").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse( + "{ $project : { \"results\": { $reduce: { input: \"$probabilityArr\", initialValue: { \"sum\" : 5 , \"product\" : 2} , in: { \"sum\": { $add : [\"$$value.sum\", \"$$this\"] }, \"product\": { $multiply: [ \"$$value.product\", \"$$this\" ] } } } } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderZipCorrectly() { + + AggregationExpression elemAt0 = ArrayOperators.arrayOf("matrix").elementAt(0); + AggregationExpression elemAt1 = ArrayOperators.arrayOf("matrix").elementAt(1); + AggregationExpression elemAt2 = ArrayOperators.arrayOf("matrix").elementAt(2); + + DBObject agg = project().and( + ArrayOperators.arrayOf(elemAt0).zipWith(elemAt1, elemAt2).useLongestLength().defaultTo(new Object[] { 1, 2 })) + .as("transposed").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse( + "{ $project : { transposed: { $zip: { inputs: [ { $arrayElemAt: [ \"$matrix\", 0 ] }, { $arrayElemAt: [ \"$matrix\", 1 ] }, { $arrayElemAt: [ \"$matrix\", 2 ] } ], useLongestLength : true, defaults: [1,2] } } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderInCorrectly() { + + DBObject agg = project().and(ArrayOperators.arrayOf("in_stock").containsValue("bananas")).as("has_bananas") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { has_bananas : { $in : [\"bananas\", \"$in_stock\" ] } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderIsoDayOfWeekCorrectly() { + + DBObject agg = project().and(DateOperators.dateOf("birthday").isoDayOfWeek()).as("dayOfWeek") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { dayOfWeek: { $isoDayOfWeek: \"$birthday\" } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderIsoWeekCorrectly() { + + DBObject agg = project().and(DateOperators.dateOf("date").isoWeek()).as("weekNumber") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { weekNumber: { $isoWeek: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderIsoWeekYearCorrectly() { + + DBObject agg = project().and(DateOperators.dateOf("date").isoWeekYear()).as("yearNumber") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { yearNumber: { $isoWeekYear: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderSwitchCorrectly() { + + String expected = "$switch:\n" + // + "{\n" + // + " branches: [\n" + // + " {\n" + // + " case: { $gte : [ { $avg : \"$scores\" }, 90 ] },\n" + // + " then: \"Doing great!\"\n" + // + " },\n" + // + " {\n" + // + " case: { $and : [ { $gte : [ { $avg : \"$scores\" }, 80 ] },\n" + // + " { $lt : [ { $avg : \"$scores\" }, 90 ] } ] },\n" + // + " then: \"Doing pretty well.\"\n" + // + " },\n" + // + " {\n" + // + " case: { $lt : [ { $avg : \"$scores\" }, 80 ] },\n" + // + " then: \"Needs improvement.\"\n" + // + " }\n" + // + " ],\n" + // + " default: \"No scores found.\"\n" + // + " }\n" + // + "}"; + + CaseOperator cond1 = CaseOperator.when(Gte.valueOf(Avg.avgOf("scores")).greaterThanEqualToValue(90)) + .then("Doing great!"); + CaseOperator cond2 = CaseOperator.when(And.and(Gte.valueOf(Avg.avgOf("scores")).greaterThanEqualToValue(80), + Lt.valueOf(Avg.avgOf("scores")).lessThanValue(90))).then("Doing pretty well."); + CaseOperator cond3 = CaseOperator.when(Lt.valueOf(Avg.avgOf("scores")).lessThanValue(80)) + .then("Needs improvement."); + + DBObject agg = project().and(ConditionalOperators.switchCases(cond1, cond2, cond3).defaultTo("No scores found.")) + .as("summary").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { summary: {" + expected + "} } }"))); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldTypeCorrectly() { + + DBObject agg = project().and(Type.typeOf("a")).as("a") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, Matchers.is(JSON.parse("{ $project : { a: { $type: \"$a\" } } }"))); + } + private static DBObject exctractOperation(String field, DBObject fromProjectClause) { return (DBObject) fromProjectClause.get(field); } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java index 770145a80a..013d6189e7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java @@ -745,13 +745,13 @@ public void shouldRenderMethodReferenceNodeMin() { assertThat(transform("min(a, b)"), is("{ \"$min\" : [ \"$a\" , \"$b\"]}")); } - /** * @see DATAMONGO-1530 */ @Test public void shouldRenderMethodReferenceNodePush() { - assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"), is("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}")); + assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"), + is("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}")); } /** @@ -884,6 +884,129 @@ public void shouldRenderComplexNotCorrectly() { assertThat(transform("!(foo > 10)"), is("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}")); } + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodReferenceIndexOfBytes() { + assertThat(transform("indexOfBytes(item, 'foo')"), is("{ \"$indexOfBytes\" : [ \"$item\" , \"foo\"]}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodReferenceIndexOfCP() { + assertThat(transform("indexOfCP(item, 'foo')"), is("{ \"$indexOfCP\" : [ \"$item\" , \"foo\"]}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodReferenceSplit() { + assertThat(transform("split(item, ',')"), is("{ \"$split\" : [ \"$item\" , \",\"]}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodReferenceStrLenBytes() { + assertThat(transform("strLenBytes(item)"), is("{ \"$strLenBytes\" : \"$item\"}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodReferenceStrLenCP() { + assertThat(transform("strLenCP(item)"), is("{ \"$strLenCP\" : \"$item\"}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodSubstrCP() { + assertThat(transform("substrCP(item, 0, 5)"), is("{ \"$substrCP\" : [ \"$item\" , 0 , 5]}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodReferenceReverseArray() { + assertThat(transform("reverseArray(array)"), is("{ \"$reverseArray\" : \"$array\"}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodReferenceReduce() { + assertThat(transform("reduce(field, '', {'$concat':new String[]{'$$value','$$this'}})"), is( + "{ \"$reduce\" : { \"input\" : \"$field\" , \"initialValue\" : \"\" , \"in\" : { \"$concat\" : [ \"$$value\" , \"$$this\"]}}}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodReferenceZip() { + assertThat(transform("zip(new String[]{'$array1', '$array2'})"), + is("{ \"$zip\" : { \"inputs\" : [ \"$array1\" , \"$array2\"]}}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodReferenceZipWithOptionalArgs() { + assertThat(transform("zip(new String[]{'$array1', '$array2'}, true, new int[]{1,2})"), is( + "{ \"$zip\" : { \"inputs\" : [ \"$array1\" , \"$array2\"] , \"useLongestLength\" : true , \"defaults\" : [ 1 , 2]}}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodIn() { + assertThat(transform("in('item', array)"), is("{ \"$in\" : [ \"item\" , \"$array\"]}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodRefereneIsoDayOfWeek() { + assertThat(transform("isoDayOfWeek(date)"), is("{ \"$isoDayOfWeek\" : \"$date\"}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodRefereneIsoWeek() { + assertThat(transform("isoWeek(date)"), is("{ \"$isoWeek\" : \"$date\"}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodRefereneIsoWeekYear() { + assertThat(transform("isoWeekYear(date)"), is("{ \"$isoWeekYear\" : \"$date\"}")); + } + + /** + * @see DATAMONGO-1548 + */ + @Test + public void shouldRenderMethodRefereneType() { + assertThat(transform("type(a)"), is("{ \"$type\" : \"$a\"}")); + } + private String transform(String expression, Object... params) { Object result = transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params); return result == null ? null : result.toString(); diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 0b9b03fc0c..d6ff11f630 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1686,26 +1686,28 @@ At the time of this writing we provide support for the following Aggregation Ope | abs, add (*via plus), ceil, divide, exp, floor, ln, log, log10, mod, multiply, pow, sqrt, subtract (*via minus), trunc | String Aggregation Operators -| concat, substr, toLower, toUpper, stcasecmp +| concat, substr, toLower, toUpper, stcasecmp, indexOfBytes, indexOfCP, split, strLenBytes, strLenCP, substrCP, | Comparison Aggregation Operators | eq (*via: is), gt, gte, lt, lte, ne | Array Aggregation Operators -| arrayElementAt, concatArrays, filter, isArray, size, slice +| arrayElementAt, concatArrays, filter, in, indexOfArray, isArray, range, reverseArray, reduce, size, slice, zip | Literal Operators | literal | Date Aggregation Operators -| dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString +| dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString, isoDayOfWeek, isoWeek, isoWeekYear | Variable Operators | map - | Conditional Aggregation Operators -| cond, ifNull +| cond, ifNull, switch + +| Type Aggregation Operators +| type |=== From b74f1d92ed0424a8271cf53f54e5f1808317655c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 12 Dec 2016 11:55:51 +0100 Subject: [PATCH 3/3] DATAMONGO-1548 - Polishing. Enhance JavaDoc. Minor formatting. Fix typos. --- .../aggregation/AggregationExpressions.java | 406 ++++++++++++++++-- .../ProjectionOperationUnitTests.java | 4 +- 2 files changed, 361 insertions(+), 49 deletions(-) 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 76812cbb31..0c86caf1e7 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 @@ -24,6 +24,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Range; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators.ArithmeticOperatorFactory; 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; @@ -31,7 +32,6 @@ import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Reduce.PropertyExpression; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Switch.CaseOperator; 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; @@ -268,8 +268,8 @@ public static IfNull.ThenBuilder ifNull(AggregationExpression expression) { /** * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it - * finds an expression which evaluates to true, {@code $switch} executes a specified expression and breaks out of - * the control flow. + * finds an expression which evaluates to {@literal true}, {@code $switch} executes a specified expression and + * breaks out of the control flow. * * @param conditions must not be {@literal null}. * @return @@ -280,8 +280,8 @@ public static Switch switchCases(CaseOperator... conditions) { /** * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it - * finds an expression which evaluates to true, {@code $switch} executes a specified expression and breaks out of - * the control flow. + * finds an expression which evaluates to {@literal true}, {@code $switch} executes a specified expression and + * breaks out of the control flow. * * @param conditions must not be {@literal null}. * @return @@ -1595,8 +1595,8 @@ private StrCaseCmp createStrCaseCmp() { /** * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a - * string for an occurence of a given {@literal substring} and returns the UTF-8 byte index (zero-based) of the - * first occurence. + * string for an occurrence of a given {@literal substring} and returns the UTF-8 byte index (zero-based) of the + * first occurrence. * * @param substring must not be {@literal null}. * @return @@ -1609,8 +1609,8 @@ public IndexOfBytes indexOf(String substring) { /** * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a - * string for an occurence of a substring contained in the given {@literal field reference} and returns the UTF-8 - * byte index (zero-based) of the first occurence. + * string for an occurrence of a substring contained in the given {@literal field reference} and returns the UTF-8 + * byte index (zero-based) of the first occurrence. * * @param fieldReference must not be {@literal null}. * @return @@ -1623,8 +1623,8 @@ public IndexOfBytes indexOf(Field fieldReference) { /** * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a - * string for an occurence of a substring resulting from the given {@link AggregationExpression} and returns the - * UTF-8 byte index (zero-based) of the first occurence. + * string for an occurrence of a substring resulting from the given {@link AggregationExpression} and returns the + * UTF-8 byte index (zero-based) of the first occurrence. * * @param expression must not be {@literal null}. * @return @@ -1641,8 +1641,8 @@ private IndexOfBytes.SubstringBuilder createIndexOfBytesSubstringBuilder() { /** * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a - * string for an occurence of a given {@literal substring} and returns the UTF-8 code point index (zero-based) of - * the first occurence. + * string for an occurrence of a given {@literal substring} and returns the UTF-8 code point index (zero-based) of + * the first occurrence. * * @param substring must not be {@literal null}. * @return @@ -1655,8 +1655,8 @@ public IndexOfCP indexOfCP(String substring) { /** * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a - * string for an occurence of a substring contained in the given {@literal field reference} and returns the UTF-8 - * code point index (zero-based) of the first occurence. + * string for an occurrence of a substring contained in the given {@literal field reference} and returns the UTF-8 + * code point index (zero-based) of the first occurrence. * * @param fieldReference must not be {@literal null}. * @return @@ -1669,8 +1669,8 @@ public IndexOfCP indexOfCP(Field fieldReference) { /** * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a - * string for an occurence of a substring resulting from the given {@link AggregationExpression} and returns the - * UTF-8 code point index (zero-based) of the first occurence. + * string for an occurrence of a substring resulting from the given {@link AggregationExpression} and returns the + * UTF-8 code point index (zero-based) of the first occurrence. * * @param expression must not be {@literal null}. * @return @@ -1938,8 +1938,8 @@ public Slice slice() { } /** - * Creates new {@link AggregationExpressions} that searches the associated array for an occurence of a specified - * value and returns the array index (zero-based) of the first occurence. + * Creates new {@link AggregationExpressions} that searches the associated array for an occurrence of a specified + * value and returns the array index (zero-based) of the first occurrence. * * @param value must not be {@literal null}. * @return @@ -1967,6 +1967,7 @@ public ReverseArray reverse() { */ public ReduceInitialValueBuilder reduce(final AggregationExpression expression) { return new ReduceInitialValueBuilder() { + @Override public Reduce startingWith(Object initialValue) { return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression)) @@ -1985,6 +1986,7 @@ public Reduce startingWith(Object initialValue) { public ReduceInitialValueBuilder reduce(final PropertyExpression... expressions) { return new ReduceInitialValueBuilder() { + @Override public Reduce startingWith(Object initialValue) { return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression)) @@ -1996,7 +1998,7 @@ public Reduce startingWith(Object initialValue) { /** * Creates new {@link AggregationExpressions} that transposes an array of input arrays so that the first element * of the output array would be an array containing, the first element of the first input array, the first element - * of the second input array, etc + * of the second input array, etc. * * @param arrays must not be {@literal null}. * @return @@ -2007,7 +2009,7 @@ public Zip zipWith(Object... arrays) { /** * Creates new {@link AggregationExpressions} that returns a boolean indicating whether a specified value is in - * the associcated array. + * the associated array. * * @param value must not be {@literal null}. * @return @@ -2016,7 +2018,17 @@ public In containsValue(Object value) { return (usesFieldRef() ? In.arrayOf(fieldReference) : In.arrayOf(expression)).containsValue(value); } + /** + * @author Christoph Strobl + */ public interface ReduceInitialValueBuilder { + + /** + * Define the initial cumulative value set before in is applied to the first element of the input array. + * + * @param initialValue must not be {@literal null}. + * @return + */ Reduce startingWith(Object initialValue); } @@ -2340,6 +2352,9 @@ protected AbstractAggregationExpression(Object value) { 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) { return toDbObject(this.value, context); @@ -4147,14 +4162,32 @@ private SubstringBuilder(Object stringExpression) { this.stringExpression = stringExpression; } + /** + * Creates a new {@link IndexOfBytes} given {@literal substring}. + * + * @param substring must not be {@literal null}. + * @return + */ public IndexOfBytes indexOf(String substring) { return new IndexOfBytes(Arrays.asList(stringExpression, substring)); } + /** + * Creates a new {@link IndexOfBytes} given {@link AggregationExpression} that resolves to the substring. + * + * @param expression must not be {@literal null}. + * @return + */ public IndexOfBytes indexOf(AggregationExpression expression) { return new IndexOfBytes(Arrays.asList(stringExpression, expression)); } + /** + * Creates a new {@link IndexOfBytes} given {@link Field} that resolves to the substring. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public IndexOfBytes indexOf(Field fieldReference) { return new IndexOfBytes(Arrays.asList(stringExpression, fieldReference)); } @@ -4228,14 +4261,32 @@ private SubstringBuilder(Object stringExpression) { this.stringExpression = stringExpression; } + /** + * Creates a new {@link IndexOfCP} given {@literal substring}. + * + * @param substring must not be {@literal null}. + * @return + */ public IndexOfCP indexOf(String substring) { return new IndexOfCP(Arrays.asList(stringExpression, substring)); } + /** + * Creates a new {@link IndexOfCP} given {@link AggregationExpression} that resolves to the substring. + * + * @param expression must not be {@literal null}. + * @return + */ public IndexOfCP indexOf(AggregationExpression expression) { return new IndexOfCP(Arrays.asList(stringExpression, expression)); } + /** + * Creates a new {@link IndexOfCP} given {@link Field} that resolves to the substring. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public IndexOfCP indexOf(Field fieldReference) { return new IndexOfCP(Arrays.asList(stringExpression, fieldReference)); } @@ -4244,6 +4295,8 @@ public IndexOfCP indexOf(Field fieldReference) { /** * {@link AggregationExpression} for {@code $split}. + * + * @author Christoph Strobl */ class Split extends AbstractAggregationExpression { @@ -4281,19 +4334,19 @@ public static Split valueOf(AggregationExpression expression) { } /** - * Use given {@link String} as deliminator + * Use given {@link String} as delimiter. * - * @param deliminator must not be {@literal null}. + * @param delimiter must not be {@literal null}. * @return */ - public Split split(String deliminator) { + public Split split(String delimiter) { - Assert.notNull(deliminator, "Deliminator must not be null!"); - return new Split(append(deliminator)); + Assert.notNull(delimiter, "Delimiter must not be null!"); + return new Split(append(delimiter)); } /** - * Usge value of referenced field as deliminator. + * Use value of referenced field as delimiter. * * @param fieldReference must not be {@literal null}. * @return @@ -4305,7 +4358,7 @@ public Split split(Field fieldReference) { } /** - * Use value resulting from {@link AggregationExpression} as deliminator. + * Use value resulting from {@link AggregationExpression} as delimiter. * * @param expression must not be {@literal null}. * @return @@ -4319,6 +4372,8 @@ public Split split(AggregationExpression expression) { /** * {@link AggregationExpression} for {@code $strLenBytes}. + * + * @author Christoph Strobl */ class StrLenBytes extends AbstractAggregationExpression { @@ -4331,17 +4386,33 @@ protected String getMongoMethod() { return "$strLenBytes"; } + /** + * Creates new {@link StrLenBytes}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static StrLenBytes stringLengthOf(String fieldReference) { return new StrLenBytes(Fields.field(fieldReference)); } + /** + * Creates new {@link StrLenBytes}. + * + * @param expression must not be {@literal null}. + * @return + */ public static StrLenBytes stringLengthOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); return new StrLenBytes(expression); } } /** * {@link AggregationExpression} for {@code $strLenCP}. + * + * @author Christoph Strobl */ class StrLenCP extends AbstractAggregationExpression { @@ -4354,11 +4425,25 @@ protected String getMongoMethod() { return "$strLenCP"; } + /** + * Creates new {@link StrLenCP}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static StrLenCP stringLengthOfCP(String fieldReference) { return new StrLenCP(Fields.field(fieldReference)); } + /** + * Creates new {@link StrLenCP}. + * + * @param expression must not be {@literal null}. + * @return + */ public static StrLenCP stringLengthOfCP(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); return new StrLenCP(expression); } } @@ -4909,7 +4994,17 @@ public Slice itemCount(int nrElements) { }; } + /** + * @author Christoph Strobl + */ public interface SliceElementsBuilder { + + /** + * Set the number of elements given {@literal nrElements}. + * + * @param nrElements + * @return + */ Slice itemCount(int nrElements); } } @@ -4967,6 +5062,9 @@ public IndexOfArray within(Range range) { return new IndexOfArray(append(rangeValues)); } + /** + * @author Christoph Strobl + */ public static class IndexOfArrayBuilder { private final Object targetArray; @@ -4975,6 +5073,12 @@ private IndexOfArrayBuilder(Object targetArray) { this.targetArray = targetArray; } + /** + * Set the {@literal value} to check for its index in the array. + * + * @param value must not be {@literal null}. + * @return + */ public IndexOfArray indexOf(Object value) { Assert.notNull(value, "Value must not be null!"); @@ -4999,19 +5103,37 @@ protected String getMongoMethod() { return "$range"; } + /** + * Start creating new {@link RangeOperator}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static RangeOperatorBuilder rangeStartingAt(String fieldReference) { return new RangeOperatorBuilder(Fields.field(fieldReference)); } + /** + * Start creating new {@link RangeOperator}. + * + * @param expression must not be {@literal null}. + * @return + */ public static RangeOperatorBuilder rangeStartingAt(AggregationExpression expression) { return new RangeOperatorBuilder(expression); } - public static RangeOperatorBuilder rangeStartingAt(Long value) { + /** + * Start creating new {@link RangeOperator}. + * + * @param value + * @return + */ + public static RangeOperatorBuilder rangeStartingAt(long value) { return new RangeOperatorBuilder(value); } - public RangeOperator withStepSize(Long stepSize) { + public RangeOperator withStepSize(long stepSize) { return new RangeOperator(append(stepSize)); } @@ -5023,23 +5145,42 @@ private RangeOperatorBuilder(Object startPoint) { this.startPoint = startPoint; } - public RangeOperator to(Long index) { + /** + * Creates new {@link RangeOperator}. + * + * @param index + * @return + */ + public RangeOperator to(long index) { return new RangeOperator(Arrays.asList(startPoint, index)); } + /** + * Creates new {@link RangeOperator}. + * + * @param expression must not be {@literal null}. + * @return + */ public RangeOperator to(AggregationExpression expression) { return new RangeOperator(Arrays.asList(startPoint, expression)); } + /** + * Creates new {@link RangeOperator}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public RangeOperator to(String fieldReference) { return new RangeOperator(Arrays.asList(startPoint, Fields.field(fieldReference))); } } - } /** * {@link AggregationExpression} for {@code $reverseArray}. + * + * @author Christoph Strobl */ class ReverseArray extends AbstractAggregationExpression { @@ -5052,10 +5193,22 @@ protected String getMongoMethod() { return "$reverseArray"; } + /** + * Creates new {@link ReverseArray} given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static ReverseArray reverseArrayOf(String fieldReference) { return new ReverseArray(Fields.field(fieldReference)); } + /** + * Creates new {@link ReverseArray} given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ public static ReverseArray reverseArrayOf(AggregationExpression expression) { return new ReverseArray(expression); } @@ -5063,6 +5216,8 @@ public static ReverseArray reverseArrayOf(AggregationExpression expression) { /** * {@link AggregationExpression} for {@code $reduce}. + * + * @author Christoph Strobl */ class Reduce implements AggregationExpression { @@ -5071,11 +5226,15 @@ class Reduce implements AggregationExpression { private final List reduceExpressions; private Reduce(Object input, Object initialValue, List reduceExpressions) { + this.input = input; this.initialValue = initialValue; this.reduceExpressions = reduceExpressions; } + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ @Override public DBObject toDbObject(AggregationOperationContext context) { @@ -5112,19 +5271,37 @@ private Object getMappedValue(Object value, AggregationOperationContext context) } } + /** + * Start creating new {@link Reduce}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static InitialValueBuilder arrayOf(final String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null"); + return new InitialValueBuilder() { @Override public ReduceBuilder withInitialValue(final Object initialValue) { + + Assert.notNull(initialValue, "Initial value must not be null"); + return new ReduceBuilder() { + @Override public Reduce reduce(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null"); return new Reduce(Fields.field(fieldReference), initialValue, Collections.singletonList(expression)); } @Override public Reduce reduce(PropertyExpression... expressions) { + + Assert.notNull(expressions, "PropertyExpressions must not be null"); + return new Reduce(Fields.field(fieldReference), initialValue, Arrays. asList(expressions)); } @@ -5133,19 +5310,36 @@ public Reduce reduce(PropertyExpression... expressions) { }; } + /** + * Start creating new {@link Reduce}. + * + * @param expression must not be {@literal null}. + * @return + */ public static InitialValueBuilder arrayOf(final AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null"); + return new InitialValueBuilder() { @Override public ReduceBuilder withInitialValue(final Object initialValue) { + + Assert.notNull(initialValue, "Initial value must not be null"); + return new ReduceBuilder() { + @Override public Reduce reduce(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null"); return new Reduce(expression, initialValue, Collections.singletonList(expression)); } @Override public Reduce reduce(PropertyExpression... expressions) { + + Assert.notNull(expressions, "PropertyExpressions must not be null"); return new Reduce(expression, initialValue, Arrays. asList(expressions)); } }; @@ -5153,24 +5347,30 @@ public Reduce reduce(PropertyExpression... expressions) { }; } + /** + * @author Christoph Strobl + */ public interface InitialValueBuilder { /** * Define the initial cumulative value set before in is applied to the first element of the input array. * - * @param intialValue must not be {@literal null}. + * @param initialValue must not be {@literal null}. * @return */ - ReduceBuilder withInitialValue(Object intialValue); + ReduceBuilder withInitialValue(Object initialValue); } + /** + * @author Christoph Strobl + */ public interface ReduceBuilder { /** * Define the {@link AggregationExpression} to apply to each element in the input array in left-to-right order. *
- * NOTE: During evaulation of the in expression the variable references {@link Variable#THIS} and - * {@link Variable#VALUE} are availble. + * NOTE: During evaluation of the in expression the variable references {@link Variable#THIS} and + * {@link Variable#VALUE} are available. * * @param expression must not be {@literal null}. * @return @@ -5180,8 +5380,8 @@ public interface ReduceBuilder { /** * Define the {@link PropertyExpression}s to apply to each element in the input array in left-to-right order. *
- * NOTE: During evaulation of the in expression the variable references {@link Variable#THIS} and - * {@link Variable#VALUE} are availble. + * NOTE: During evaluation of the in expression the variable references {@link Variable#THIS} and + * {@link Variable#VALUE} are available. * * @param expression must not be {@literal null}. * @return @@ -5197,7 +5397,11 @@ public static class PropertyExpression implements AggregationExpression { private final String propertyName; private final AggregationExpression aggregationExpression; - public PropertyExpression(String propertyName, AggregationExpression aggregationExpression) { + protected PropertyExpression(String propertyName, AggregationExpression aggregationExpression) { + + Assert.notNull(propertyName, "Property name must not be null!"); + Assert.notNull(aggregationExpression, "AggregationExpression must not be null!"); + this.propertyName = propertyName; this.aggregationExpression = aggregationExpression; } @@ -5209,7 +5413,9 @@ public PropertyExpression(String propertyName, AggregationExpression aggregation * @return */ public static AsBuilder property(final String name) { + return new AsBuilder() { + @Override public PropertyExpression definedAs(AggregationExpression expression) { return new PropertyExpression(name, expression); @@ -5217,11 +5423,17 @@ public PropertyExpression definedAs(AggregationExpression 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) { return new BasicDBObject(propertyName, aggregationExpression.toDbObject(context)); } + /** + * @author Christoph Strobl + */ interface AsBuilder { /** @@ -5235,6 +5447,7 @@ interface AsBuilder { } public enum Variable implements Field { + THIS { @Override public String getName() { @@ -5256,6 +5469,7 @@ public String toString() { return getName(); } }, + VALUE { @Override public String getName() { @@ -5285,7 +5499,7 @@ public String toString() { * @param property must not be {@literal null}. * @return */ - public Field referingTo(final String property) { + public Field referringTo(final String property) { return new Field() { @Override @@ -5314,6 +5528,8 @@ public String toString() { /** * {@link AggregationExpression} for {@code $zip}. + * + * @author Christoph Strobl */ class Zip extends AbstractAggregationExpression { @@ -5399,7 +5615,7 @@ public static class ZipBuilder { private final List sourceArrays; - public ZipBuilder(Object sourceArray) { + private ZipBuilder(Object sourceArray) { this.sourceArrays = new ArrayList(); this.sourceArrays.add(sourceArray); @@ -5408,7 +5624,7 @@ public ZipBuilder(Object sourceArray) { /** * Creates new {@link Zip} that transposes an array of input arrays so that the first element of the output array * would be an array containing, the first element of the first input array, the first element of the second input - * array, etc + * array, etc. * * @param arrays arrays to zip the referenced one with. must not be {@literal null}. * @return @@ -5432,6 +5648,8 @@ public Zip zip(Object... arrays) { /** * {@link AggregationExpression} for {@code $in}. + * + * @author Christoph Strobl */ class In extends AbstractAggregationExpression { @@ -5444,10 +5662,18 @@ protected String getMongoMethod() { return "$in"; } + /** + * Start creating {@link In}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static InBuilder arrayOf(final String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new InBuilder() { + @Override public In containsValue(Object value) { @@ -5457,10 +5683,18 @@ public In containsValue(Object value) { }; } + /** + * Start creating {@link In}. + * + * @param expression must not be {@literal null}. + * @return + */ public static InBuilder arrayOf(final AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); + return new InBuilder() { + @Override public In containsValue(Object value) { @@ -5470,10 +5704,19 @@ public In containsValue(Object value) { }; } + /** + * @author Christoph Strobl + */ public interface InBuilder { + + /** + * Set the {@literal value} to check for existence in the array. + * + * @param value must not be {@literal value}. + * @return + */ In containsValue(Object value); } - } // ############ @@ -5944,7 +6187,9 @@ protected String getMongoMethod() { public static FormatBuilder dateOf(final String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new FormatBuilder() { + @Override public DateToString toString(String format) { @@ -5963,12 +6208,13 @@ public DateToString toString(String format) { public static FormatBuilder dateOf(final AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); + return new FormatBuilder() { + @Override public DateToString toString(String format) { Assert.notNull(format, "Format must not be null!"); - return new DateToString(argumentMap(expression, format)); } }; @@ -6183,6 +6429,9 @@ public Sum and(AggregationExpression expression) { return new Sum(append(expression)); } + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ @Override public DBObject toDbObject(Object value, AggregationOperationContext context) { @@ -6262,6 +6511,9 @@ public Avg and(AggregationExpression expression) { return new Avg(append(expression)); } + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ @Override public DBObject toDbObject(Object value, AggregationOperationContext context) { @@ -6341,6 +6593,9 @@ public Max and(AggregationExpression expression) { return new Max(append(expression)); } + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ @Override public DBObject toDbObject(Object value, AggregationOperationContext context) { @@ -6420,6 +6675,9 @@ public Min and(AggregationExpression expression) { return new Min(append(expression)); } + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ @Override public DBObject toDbObject(Object value, AggregationOperationContext context) { @@ -6499,6 +6757,9 @@ public StdDevPop and(AggregationExpression expression) { return new StdDevPop(append(expression)); } + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ @Override public DBObject toDbObject(Object value, AggregationOperationContext context) { @@ -6578,6 +6839,9 @@ public StdDevSamp and(AggregationExpression expression) { return new StdDevSamp(append(expression)); } + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ @Override public DBObject toDbObject(Object value, AggregationOperationContext context) { @@ -7332,15 +7596,21 @@ private Map(Object sourceArray, String itemVariableName, AggregationExpression f */ static AsBuilder itemsOf(final String fieldReference) { + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new AsBuilder() { @Override public FunctionBuilder as(final String variableName) { + Assert.notNull(variableName, "VariableName must not be null!"); + return new FunctionBuilder() { @Override public Map andApply(final AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null!"); return new Map(Fields.field(fieldReference), variableName, expression); } }; @@ -7358,15 +7628,21 @@ public Map andApply(final AggregationExpression expression) { */ public static AsBuilder itemsOf(final AggregationExpression source) { + Assert.notNull(source, "AggregationExpression must not be null!"); + return new AsBuilder() { @Override public FunctionBuilder as(final String variableName) { + Assert.notNull(variableName, "VariableName must not be null!"); + return new FunctionBuilder() { @Override public Map andApply(final AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null!"); return new Map(source, variableName, expression); } }; @@ -7374,6 +7650,9 @@ public Map andApply(final AggregationExpression expression) { }; } + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ @Override public DBObject toDbObject(final AggregationOperationContext context) { return toMap(ExposedFields.synthetic(Fields.fields(itemVariableName)), context); @@ -7446,8 +7725,8 @@ private IfNull(Object condition, Object value) { /** * Creates new {@link IfNull}. * - * @param fieldReference the field to check for a {@literal null} value, field reference must not be - * {@literal null}. + * @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) { @@ -8095,6 +8374,7 @@ public static LetBuilder define(final Collection variables) Assert.notNull(variables, "Variables must not be null!"); return new LetBuilder() { + @Override public Let andApply(final AggregationExpression expression) { @@ -8115,6 +8395,7 @@ public static LetBuilder define(final ExpressionVariable... variables) { Assert.notNull(variables, "Variables must not be null!"); return new LetBuilder() { + @Override public Let andApply(final AggregationExpression expression) { @@ -8135,6 +8416,9 @@ public interface LetBuilder { Let andApply(AggregationExpression expression); } + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ @Override public DBObject toDbObject(final AggregationOperationContext context) { return toLet(ExposedFields.synthetic(Fields.fields(getVariableNames())), context); @@ -8235,7 +8519,6 @@ public ExpressionVariable forExpression(DBObject expressionObject) { } } - /** * {@link AggregationExpression} for {@code $switch}. * @@ -8252,12 +8535,22 @@ protected String getMongoMethod() { return "$switch"; } + /** + * Creates new {@link Switch}. + * + * @param conditions must not be {@literal null}. + */ public static Switch switchCases(CaseOperator... conditions) { Assert.notNull(conditions, "Conditions must not be null!"); return switchCases(Arrays.asList(conditions)); } + /** + * Creates new {@link Switch}. + * + * @param conditions must not be {@literal null}. + */ public static Switch switchCases(List conditions) { Assert.notNull(conditions, "Conditions must not be null!"); @@ -8268,6 +8561,9 @@ public Switch defaultTo(Object value) { return new Switch(append("default", value)); } + /** + * Encapsulates the aggregation framework case document inside a {@code $switch}-operation. + */ public static class CaseOperator implements AggregationExpression { private final AggregationExpression when; @@ -8282,7 +8578,9 @@ private CaseOperator(AggregationExpression when, Object then) { public static ThenBuilder when(final AggregationExpression condition) { Assert.notNull(condition, "Condition must not be null!"); + return new ThenBuilder() { + @Override public CaseOperator then(Object value) { @@ -8292,8 +8590,12 @@ public CaseOperator then(Object 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) { + DBObject dbo = new BasicDBObject("case", when.toDbObject(context)); if (then instanceof AggregationExpression) { @@ -8307,7 +8609,17 @@ public DBObject toDbObject(AggregationOperationContext context) { return dbo; } + /** + * @author Christoph Strobl + */ public interface ThenBuilder { + + /** + * Set the then {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ CaseOperator then(Object value); } } 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 5fff2b2e59..63d825134c 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 @@ -1971,9 +1971,9 @@ public void shouldRenderReduceWithSimpleObjectCorrectly() { public void shouldRenderReduceWithComplexObjectCorrectly() { PropertyExpression sum = PropertyExpression.property("sum").definedAs( - ArithmeticOperators.valueOf(Variable.VALUE.referingTo("sum").getName()).add(Variable.THIS.getName())); + ArithmeticOperators.valueOf(Variable.VALUE.referringTo("sum").getName()).add(Variable.THIS.getName())); PropertyExpression product = PropertyExpression.property("product").definedAs(ArithmeticOperators - .valueOf(Variable.VALUE.referingTo("product").getName()).multiplyBy(Variable.THIS.getName())); + .valueOf(Variable.VALUE.referringTo("product").getName()).multiplyBy(Variable.THIS.getName())); DBObject agg = project() .and(ArrayOperators.arrayOf("probabilityArr").reduce(sum, product)