diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java index 37f01b4a2..08d2f977b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.aggregation; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -58,13 +59,25 @@ public class ArrayOperators { return new ArrayOperatorFactory(expression); } + /** + * Take the given {@link Collection values} {@link AggregationExpression}. + * + * @param values must not be {@literal null}. + * @return new instance of {@link ArrayOperatorFactory}. + * @since 2.2 + */ + public static ArrayOperatorFactory arrayOf(Collection values) { + return new ArrayOperatorFactory(values); + } + /** * @author Christoph Strobl */ public static class ArrayOperatorFactory { - private final String fieldReference; - private final AggregationExpression expression; + private final @Nullable String fieldReference; + private final @Nullable AggregationExpression expression; + private final @Nullable Collection values; /** * Creates new {@link ArrayOperatorFactory} for given {@literal fieldReference}. @@ -76,6 +89,7 @@ public class ArrayOperators { Assert.notNull(fieldReference, "FieldReference must not be null!"); this.fieldReference = fieldReference; this.expression = null; + this.values = null; } /** @@ -88,6 +102,21 @@ public class ArrayOperators { Assert.notNull(expression, "Expression must not be null!"); this.fieldReference = null; this.expression = expression; + this.values = null; + } + + /** + * Creates new {@link ArrayOperatorFactory} for given values. + * + * @param values must not be {@literal null}. + * @since 2.2 + */ + public ArrayOperatorFactory(Collection values) { + + Assert.notNull(values, "Values must not be null!"); + this.fieldReference = null; + this.expression = null; + this.values = values; } /** @@ -128,7 +157,12 @@ public class ArrayOperators { } private ArrayElemAt createArrayElemAt() { - return usesFieldRef() ? ArrayElemAt.arrayOf(fieldReference) : ArrayElemAt.arrayOf(expression); + + if (usesFieldRef()) { + return ArrayElemAt.arrayOf(fieldReference); + } + + return usesExpression() ? ArrayElemAt.arrayOf(expression) : ArrayElemAt.arrayOf(values); } /** @@ -158,7 +192,12 @@ public class ArrayOperators { } private ConcatArrays createConcatArrays() { - return usesFieldRef() ? ConcatArrays.arrayOf(fieldReference) : ConcatArrays.arrayOf(expression); + + if (usesFieldRef()) { + return ConcatArrays.arrayOf(fieldReference); + } + + return usesExpression() ? ConcatArrays.arrayOf(expression) : ConcatArrays.arrayOf(values); } /** @@ -168,7 +207,13 @@ public class ArrayOperators { * @return */ public AsBuilder filter() { - return Filter.filter(fieldReference); + + if (usesFieldRef()) { + return Filter.filter(fieldReference); + } + + Assert.state(values != null, "Values must not be null!"); + return Filter.filter(new ArrayList<>(values)); } /** @@ -177,6 +222,9 @@ public class ArrayOperators { * @return */ public IsArray isArray() { + + Assert.state(values == null, "Does it make sense to call isArray on an array? Maybe just skip it?"); + return usesFieldRef() ? IsArray.isArray(fieldReference) : IsArray.isArray(expression); } @@ -186,7 +234,12 @@ public class ArrayOperators { * @return */ public Size length() { - return usesFieldRef() ? Size.lengthOfArray(fieldReference) : Size.lengthOfArray(expression); + + if (usesFieldRef()) { + return Size.lengthOfArray(fieldReference); + } + + return usesExpression() ? Size.lengthOfArray(expression) : Size.lengthOfArray(values); } /** @@ -195,7 +248,12 @@ public class ArrayOperators { * @return */ public Slice slice() { - return usesFieldRef() ? Slice.sliceArrayOf(fieldReference) : Slice.sliceArrayOf(expression); + + if (usesFieldRef()) { + return Slice.sliceArrayOf(fieldReference); + } + + return usesExpression() ? Slice.sliceArrayOf(expression) : Slice.sliceArrayOf(values); } /** @@ -206,8 +264,13 @@ public class ArrayOperators { * @return */ public IndexOfArray indexOf(Object value) { - return usesFieldRef() ? IndexOfArray.arrayOf(fieldReference).indexOf(value) - : IndexOfArray.arrayOf(expression).indexOf(value); + + if (usesFieldRef()) { + return IndexOfArray.arrayOf(fieldReference).indexOf(value); + } + + return usesExpression() ? IndexOfArray.arrayOf(expression).indexOf(value) + : IndexOfArray.arrayOf(values).indexOf(value); } /** @@ -216,7 +279,13 @@ public class ArrayOperators { * @return */ public ReverseArray reverse() { - return usesFieldRef() ? ReverseArray.reverseArrayOf(fieldReference) : ReverseArray.reverseArrayOf(expression); + + if (usesFieldRef()) { + return ReverseArray.reverseArrayOf(fieldReference); + } + + return usesExpression() ? ReverseArray.reverseArrayOf(expression) + : ReverseArray.reverseArrayOf(Collections.singletonList(values)); } /** @@ -254,7 +323,12 @@ public class ArrayOperators { * @return */ public Zip zipWith(Object... arrays) { - return (usesFieldRef() ? Zip.arrayOf(fieldReference) : Zip.arrayOf(expression)).zip(arrays); + + if (usesFieldRef()) { + return Zip.arrayOf(fieldReference).zip(arrays); + } + + return (usesExpression() ? Zip.arrayOf(expression) : Zip.arrayOf(values)).zip(arrays); } /** @@ -265,7 +339,12 @@ public class ArrayOperators { * @return */ public In containsValue(Object value) { - return (usesFieldRef() ? In.arrayOf(fieldReference) : In.arrayOf(expression)).containsValue(value); + + if (usesFieldRef()) { + return In.arrayOf(fieldReference).containsValue(value); + } + + return (usesExpression() ? In.arrayOf(expression) : In.arrayOf(values)).containsValue(value); } /** @@ -277,8 +356,11 @@ public class ArrayOperators { */ public ArrayToObject toObject() { - return usesFieldRef() ? ArrayToObject.arrayValueOfToObject(fieldReference) - : ArrayToObject.arrayValueOfToObject(expression); + if (usesFieldRef()) { + return ArrayToObject.arrayValueOfToObject(fieldReference); + } + + return usesExpression() ? ArrayToObject.arrayValueOfToObject(expression) : ArrayToObject.arrayToObject(values); } /** @@ -295,9 +377,20 @@ public class ArrayOperators { Reduce startingWith(Object initialValue); } + /** + * @return {@literal true} if {@link #fieldReference} is not {@literal null}. + */ private boolean usesFieldRef() { return fieldReference != null; } + + /** + * @return {@literal true} if {@link #expression} is not {@literal null}. + * @since 2.2 + */ + private boolean usesExpression() { + return expression != null; + } } /** @@ -340,6 +433,19 @@ public class ArrayOperators { return new ArrayElemAt(Collections.singletonList(expression)); } + /** + * Creates new {@link ArrayElemAt}. + * + * @param values The array members. Must not be {@literal null}. + * @return new instance of {@link ArrayElemAt}. + * @since 2.2 + */ + public static ArrayElemAt arrayOf(Collection values) { + + Assert.notNull(values, "Values must not be null!"); + return new ArrayElemAt(Collections.singletonList(values)); + } + public ArrayElemAt elementAt(int index) { return new ArrayElemAt(append(index)); } @@ -397,6 +503,19 @@ public class ArrayOperators { return new ConcatArrays(Collections.singletonList(expression)); } + /** + * Creates new {@link ConcatArrays}. + * + * @param values The array members. Must not be {@literal null}. + * @return new instance of {@link ConcatArrays}. + * @since 2.2 + */ + public static ConcatArrays arrayOf(Collection values) { + + Assert.notNull(values, "Values must not be null!"); + return new ConcatArrays(Collections.singletonList(values)); + } + public ConcatArrays concat(String arrayFieldReference) { Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!"); @@ -741,6 +860,19 @@ public class ArrayOperators { Assert.notNull(expression, "Expression must not be null!"); return new Size(expression); } + + /** + * Creates new {@link Size}. + * + * @param values must not be {@literal null}. + * @return new instance of {@link Size}. + * @since 2.2 + */ + public static Size lengthOfArray(Collection values) { + + Assert.notNull(values, "Values must not be null!"); + return new Size(Collections.singletonList(values)); + } } /** @@ -783,6 +915,19 @@ public class ArrayOperators { return new Slice(Collections.singletonList(expression)); } + /** + * Creates new {@link Slice}. + * + * @param values must not be {@literal null}. + * @return new instance of {@link Slice}. + * @since 2.2 + */ + public static Slice sliceArrayOf(Collection values) { + + Assert.notNull(values, "Values must not be null!"); + return new Slice(Collections.singletonList(values)); + } + public Slice itemCount(int nrElements) { return new Slice(append(nrElements)); } @@ -853,6 +998,19 @@ public class ArrayOperators { return new IndexOfArrayBuilder(expression); } + /** + * Start creating new {@link IndexOfArray}. + * + * @param values must not be {@literal null}. + * @return new instance of {@link IndexOfArray}. + * @since 2.2 + */ + public static IndexOfArrayBuilder arrayOf(Collection values) { + + Assert.notNull(values, "Values must not be null!"); + return new IndexOfArrayBuilder(values); + } + public IndexOfArray within(Range range) { return new IndexOfArray(append(AggregationUtils.toRangeValues(range))); } @@ -1007,6 +1165,17 @@ public class ArrayOperators { public static ReverseArray reverseArrayOf(AggregationExpression expression) { return new ReverseArray(expression); } + + /** + * Creates new {@link ReverseArray}. + * + * @param values must not be {@literal null}. + * @return new instance of {@link ReverseArray}. + * @since 2.2 + */ + public static ReverseArray reverseArrayOf(Collection values) { + return new ReverseArray(values); + } } /** @@ -1359,6 +1528,19 @@ public class ArrayOperators { return new ZipBuilder(expression); } + /** + * Start creating new {@link Zip}. + * + * @param values must not be {@literal null}. + * @return new instance of {@link Zip}. + * @since 2.2 + */ + public static ZipBuilder arrayOf(Collection values) { + + Assert.notNull(values, "Expression must not be null!"); + return new ZipBuilder(values); + } + /** * Create new {@link Zip} and set the {@code useLongestLength} property to {@literal true}. * @@ -1483,7 +1665,7 @@ public class ArrayOperators { * @param expression must not be {@literal null}. * @return */ - public static InBuilder arrayOf(final AggregationExpression expression) { + public static InBuilder arrayOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -1501,13 +1683,13 @@ public class ArrayOperators { /** * Support for Aggregation In Search an Element in List of Objects to Filter Start creating {@link In}. * - * @author Shashank Sharma - * @param elementList must not be {@literal null}. - * @return + * @param values must not be {@literal null}. + * @return new instance of {@link InBuilder}. + * @since 2.2 */ - public static InBuilder arrayOf(final List elementList) { + public static InBuilder arrayOf(Collection values) { - Assert.notNull(elementList, "Elements must not be null!"); + Assert.notNull(values, "Values must not be null!"); return new InBuilder() { @@ -1515,7 +1697,7 @@ public class ArrayOperators { public In containsValue(Object value) { Assert.notNull(value, "Value must not be null!"); - return new In(Arrays.asList(value, elementList)); + return new In(Arrays.asList(value, values)); } }; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ArrayOperatorsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ArrayOperatorsUnitTests.java index d017f6b0b..4fbfc5dac 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ArrayOperatorsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ArrayOperatorsUnitTests.java @@ -34,6 +34,8 @@ import org.springframework.data.mongodb.core.aggregation.ArrayOperators.ArrayToO */ public class ArrayOperatorsUnitTests { + static final List VALUE_LIST = Arrays.asList(1, "2", new Document("_id", 3)); + static final String VALUE_LIST_STRING = "[1, \"2\", { \"_id\" : 3 }]"; static final String EXPRESSION_STRING = "{ \"$stablemaster\" : \"burrich\" }"; static final Document EXPRESSION_DOC = Document.parse(EXPRESSION_STRING); static final AggregationExpression EXPRESSION = context -> EXPRESSION_DOC; @@ -64,10 +66,67 @@ public class ArrayOperatorsUnitTests { } @Test // DATAMONGO-2287 - public void inArrayAggregationWithArgumentList() { + public void arrayElementAtWithValueList() { - assertThat(ArrayOperators.In.arrayOf(Arrays.asList("Shashank", "Sharma")).containsValue("$userName") + assertThat(ArrayOperators.arrayOf(VALUE_LIST).elementAt(1).toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ $arrayElemAt: [ " + VALUE_LIST_STRING + ", 1] } ")); + } + + @Test // DATAMONGO-2287 + public void concatWithValueList() { + + assertThat(ArrayOperators.arrayOf(VALUE_LIST).concat("field").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ $concatArrays: [ " + VALUE_LIST_STRING + ", \"$field\"] } ")); + } + + @Test // DATAMONGO-2287 + public void filterWithValueList() { + + assertThat(ArrayOperators.arrayOf(VALUE_LIST).filter().as("var").by(new Document()) .toDocument(Aggregation.DEFAULT_CONTEXT)) - .isEqualTo(Document.parse("{ \"$in\" : [\"$userName\", [\"Shashank\", \"Sharma\"]] }")); + .isEqualTo(Document + .parse("{ $filter: { \"input\" : " + VALUE_LIST_STRING + ", \"as\" : \"var\", \"cond\" : {} } } ")); + } + + @Test // DATAMONGO-2287 + public void lengthWithValueList() { + + assertThat(ArrayOperators.arrayOf(VALUE_LIST).length().toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ $size: [ " + VALUE_LIST_STRING + "] } ")); + } + + @Test // DATAMONGO-2287 + public void sliceWithValueList() { + + assertThat(ArrayOperators.arrayOf(VALUE_LIST).slice().itemCount(3).toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ $slice: [ " + VALUE_LIST_STRING + ", 3] } ")); + } + + @Test // DATAMONGO-2287 + public void indexOfWithValueList() { + + assertThat(ArrayOperators.arrayOf(VALUE_LIST).indexOf("s1p").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ $indexOfArray: [ " + VALUE_LIST_STRING + ", \"s1p\"] } ")); + } + + @Test // DATAMONGO-2287 + public void reverseWithValueList() { + + assertThat(ArrayOperators.arrayOf(VALUE_LIST).reverse().toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ $reverseArray: [ " + VALUE_LIST_STRING + "] } ")); + } + + @Test // DATAMONGO-2287 + public void zipWithValueList() { + + assertThat(ArrayOperators.arrayOf(VALUE_LIST).zipWith("field").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ $zip: { \"inputs\": [" + VALUE_LIST_STRING + ", \"$field\"]} } ")); + } + + @Test // DATAMONGO-2287 + public void inWithValueList() { + + assertThat(ArrayOperators.arrayOf(VALUE_LIST).containsValue("$userName").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ \"$in\" : [\"$userName\", " + VALUE_LIST_STRING + "] }")); } }