diff --git a/pom.xml b/pom.xml
index ea80a3cb74..a0f34d1dcc 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-1491-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..01cfa0221d 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-1491-SNAPSHOT
../pom.xml
@@ -48,7 +48,7 @@
org.springframework.data
spring-data-mongodb
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1491-SNAPSHOT
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index 2d02722262..2d65fec229 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-1491-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml
index ee5e3336db..7a4caed731 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-1491-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 8072d3f665..a48186037f 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-1491-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
index 2b0a45b469..3b5d45cee9 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
@@ -25,6 +25,7 @@
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
import org.springframework.data.mongodb.core.query.Criteria;
@@ -557,7 +558,7 @@ public DBObject getMappedObject(DBObject dbObject) {
*/
@Override
public FieldReference getReference(Field field) {
- return new FieldReference(new ExposedField(field, true));
+ return new DirectFieldReference(new ExposedField(field, true));
}
/*
@@ -566,7 +567,7 @@ public FieldReference getReference(Field field) {
*/
@Override
public FieldReference getReference(String name) {
- return new FieldReference(new ExposedField(new AggregationField(name), true));
+ return new DirectFieldReference(new ExposedField(new AggregationField(name), true));
}
}
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
new file mode 100644
index 0000000000..b8a78b31a4
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2016. the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb.core.aggregation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
+import org.springframework.util.Assert;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
+
+/**
+ * @author Christoph Strobl
+ * @since 1.10
+ */
+public interface AggregationExpressions {
+
+ /**
+ * {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the
+ * specified condition.
+ *
+ * @author Christoph Strobl
+ * @since 1.10
+ */
+ class Filter implements AggregationExpression {
+
+ private Object input;
+ private ExposedField as;
+ private Object condition;
+
+ private Filter() {
+ // used by builder
+ }
+
+ /**
+ * Set the {@literal field} to apply the {@code $filter} to.
+ *
+ * @param field must not be {@literal null}.
+ * @return never {@literal null}.
+ */
+ public static AsBuilder filter(String field) {
+
+ Assert.notNull(field, "Field must not be null!");
+ return filter(Fields.field(field));
+ }
+
+ /**
+ * Set the {@literal field} to apply the {@code $filter} to.
+ *
+ * @param field must not be {@literal null}.
+ * @return never {@literal null}.
+ */
+ public static AsBuilder filter(Field field) {
+
+ Assert.notNull(field, "Field must not be null!");
+ return new FilterExpressionBuilder().filter(field);
+ }
+
+ /**
+ * Set the {@literal values} to apply the {@code $filter} to.
+ *
+ * @param values must not be {@literal null}.
+ * @return
+ */
+ public static AsBuilder filter(List> values) {
+
+ Assert.notNull(values, "Values must not be null!");
+ return new FilterExpressionBuilder().filter(values);
+ }
+
+ /*
+ * (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 toFilter(new ExposedFieldsAggregationOperationContext(ExposedFields.from(as), context) {
+
+ @Override
+ public FieldReference getReference(Field field) {
+
+ FieldReference ref = null;
+ try {
+ ref = context.getReference(field);
+ } catch (Exception e) {
+ // just ignore that one.
+ }
+ return ref != null ? ref : super.getReference(field);
+ }
+ });
+ }
+
+ private DBObject toFilter(AggregationOperationContext context) {
+
+ DBObject filterExpression = new BasicDBObject();
+
+ filterExpression.putAll(context.getMappedObject(new BasicDBObject("input", getMappedInput(context))));
+ filterExpression.put("as", as.getTarget());
+
+ filterExpression.putAll(context.getMappedObject(new BasicDBObject("cond", getMappedCondition(context))));
+
+ return new BasicDBObject("$filter", filterExpression);
+ }
+
+ private Object getMappedInput(AggregationOperationContext context) {
+ return input instanceof Field ? context.getReference((Field) input).toString() : input;
+ }
+
+ private Object getMappedCondition(AggregationOperationContext context) {
+
+ if (!(condition instanceof AggregationExpression)) {
+ return condition;
+ }
+
+ NestedDelegatingExpressionAggregationOperationContext nea = new NestedDelegatingExpressionAggregationOperationContext(context);
+ DBObject mappedCondition = ((AggregationExpression) condition).toDbObject(nea);
+ return mappedCondition;
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ public interface InputBuilder {
+
+ /**
+ * Set the {@literal values} to apply the {@code $filter} to.
+ *
+ * @param array must not be {@literal null}.
+ * @return
+ */
+ AsBuilder filter(List> array);
+
+ /**
+ * Set the {@literal field} holding an array to apply the {@code $filter} to.
+ *
+ * @param field must not be {@literal null}.
+ * @return
+ */
+ AsBuilder filter(Field field);
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ public interface AsBuilder {
+
+ /**
+ * Set the {@literal variableName} for the elements in the input array.
+ *
+ * @param variableName must not be {@literal null}.
+ * @return
+ */
+ ConditionBuilder as(String variableName);
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ public interface ConditionBuilder {
+
+ /**
+ * Set the {@link AggregationExpression} that determines whether to include the element in the resulting array.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ Filter by(AggregationExpression expression);
+
+ /**
+ * Set the {@literal expression} that determines whether to include the element in the resulting array.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ Filter by(String expression);
+
+ /**
+ * Set the {@literal expression} that determines whether to include the element in the resulting array.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ Filter by(DBObject expression);
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ static final class FilterExpressionBuilder implements InputBuilder, AsBuilder, ConditionBuilder {
+
+ private final Filter filter;
+
+ FilterExpressionBuilder() {
+ this.filter = new Filter();
+ }
+
+ public static InputBuilder newBuilder() {
+ return new FilterExpressionBuilder();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(java.util.List)
+ */
+ @Override
+ public AsBuilder filter(List> array) {
+
+ Assert.notNull(array, "Array must not be null!");
+ filter.input = new ArrayList(array);
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(org.springframework.data.mongodb.core.aggregation.Field)
+ */
+ @Override
+ public AsBuilder filter(Field field) {
+
+ Assert.notNull(field, "Field must not be null!");
+ filter.input = field;
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder#as(java.lang.String)
+ */
+ @Override
+ public ConditionBuilder as(String variableName) {
+
+ Assert.notNull(variableName, "Variable name must not be null!");
+ filter.as = new ExposedField(variableName, true);
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
+ */
+ @Override
+ public Filter by(AggregationExpression condition) {
+
+ Assert.notNull(condition, "Condition must not be null!");
+ filter.condition = condition;
+ return filter;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(java.lang.String)
+ */
+ @Override
+ public Filter by(String expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ filter.condition = expression;
+ return filter;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(com.mongodb.DBObject)
+ */
+ @Override
+ public Filter by(DBObject expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ filter.condition = expression;
+ return filter;
+ }
+ }
+
+ }
+
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java
index 0b88c039ce..cd669518d8 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationFunctionExpressions.java
@@ -33,7 +33,7 @@
*/
public enum AggregationFunctionExpressions {
- SIZE;
+ SIZE, GTE;
/**
* Returns an {@link AggregationExpression} build from the current {@link Enum} name and the given parameters.
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 02537fcbd7..f9033743db 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
@@ -24,6 +24,7 @@
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.util.Assert;
import org.springframework.util.CompositeIterator;
+import org.springframework.util.ObjectUtils;
/**
* Value object to capture the fields exposed by an {@link AggregationOperation}.
@@ -104,7 +105,7 @@ private static ExposedFields createFields(Fields fields, boolean synthetic) {
result.add(new ExposedField(field, synthetic));
}
- return ExposedFields.from(result);
+ return from(result);
}
/**
@@ -336,12 +337,36 @@ public int hashCode() {
}
}
+ /**
+ * A reference to an {@link ExposedField}.
+ *
+ * @author Christoph Strobl
+ * @since 1.10
+ */
+ interface FieldReference {
+
+ /**
+ * Returns the raw, unqualified reference, i.e. the field reference without a {@literal $} prefix.
+ *
+ * @return
+ */
+ String getRaw();
+
+ /**
+ * Returns the reference value for the given field reference. Will return 1 for a synthetic, unaliased field or the
+ * raw rendering of the reference otherwise.
+ *
+ * @return
+ */
+ Object getReferenceValue();
+ }
+
/**
* A reference to an {@link ExposedField}.
*
* @author Oliver Gierke
*/
- static class FieldReference {
+ static class DirectFieldReference implements FieldReference {
private final ExposedField field;
@@ -350,17 +375,16 @@ static class FieldReference {
*
* @param field must not be {@literal null}.
*/
- public FieldReference(ExposedField field) {
+ public DirectFieldReference(ExposedField field) {
Assert.notNull(field, "ExposedField must not be null!");
this.field = field;
}
- /**
- * Returns the raw, unqualified reference, i.e. the field reference without a {@literal $} prefix.
- *
- * @return
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getRaw()
*/
public String getRaw() {
@@ -368,11 +392,9 @@ public String getRaw() {
return field.synthetic ? target : String.format("%s.%s", Fields.UNDERSCORE_ID, target);
}
- /**
- * Returns the reference value for the given field reference. Will return 1 for a synthetic, unaliased field or the
- * raw rendering of the reference otherwise.
- *
- * @return
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getReferenceValue()
*/
public Object getReferenceValue() {
return field.synthetic && !field.isAliased() ? 1 : toString();
@@ -398,11 +420,11 @@ public boolean equals(Object obj) {
return true;
}
- if (!(obj instanceof FieldReference)) {
+ if (!(obj instanceof DirectFieldReference)) {
return false;
}
- FieldReference that = (FieldReference) obj;
+ DirectFieldReference that = (DirectFieldReference) obj;
return this.field.equals(that.field);
}
@@ -416,4 +438,78 @@ public int hashCode() {
return field.hashCode();
}
}
+
+ /**
+ * A {@link FieldReference} to a {@link Field} used within a nested {@link AggregationExpression}.
+ *
+ * @author Christoph Strobl
+ * @since 1.10
+ */
+ static class ExpressionFieldReference implements FieldReference {
+
+ private FieldReference delegate;
+
+ /**
+ * Creates a new {@link FieldReference} for the given {@link ExposedField}.
+ *
+ * @param field must not be {@literal null}.
+ */
+ public ExpressionFieldReference(FieldReference field) {
+ delegate = field;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getRaw()
+ */
+ @Override
+ public String getRaw() {
+ return delegate.getRaw();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference#getReferenceValue()
+ */
+ @Override
+ public Object getReferenceValue() {
+ return delegate.getReferenceValue();
+ }
+
+ @Override
+ public String toString() {
+
+ String fieldRef = delegate.toString();
+
+ if (fieldRef.startsWith("$$")) {
+ return fieldRef;
+ }
+
+ if (fieldRef.startsWith("$")) {
+ return "$" + fieldRef;
+ }
+
+ return fieldRef;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof ExpressionFieldReference)) {
+ return false;
+ }
+
+ ExpressionFieldReference that = (ExpressionFieldReference) obj;
+ return ObjectUtils.nullSafeEquals(this.delegate, that.delegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+ }
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java
index e4c11ae541..9f2f8ba822 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsAggregationOperationContext.java
@@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core.aggregation;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.util.Assert;
@@ -112,10 +113,10 @@ protected FieldReference resolveExposedField(Field field, String name) {
if (field != null) {
// we return a FieldReference to the given field directly to make sure that we reference the proper alias here.
- return new FieldReference(new ExposedField(field, exposedField.isSynthetic()));
+ return new DirectFieldReference(new ExposedField(field, exposedField.isSynthetic()));
}
- return new FieldReference(exposedField);
+ return new DirectFieldReference(exposedField);
}
if (name.contains(".")) {
@@ -126,7 +127,7 @@ protected FieldReference resolveExposedField(Field field, String name) {
if (rootField != null) {
// We have to synthetic to true, in order to render the field-name as is.
- return new FieldReference(new ExposedField(name, true));
+ return new DirectFieldReference(new ExposedField(name, true));
}
}
return null;
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java
new file mode 100644
index 0000000000..0e9a71dc9b
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016. the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb.core.aggregation;
+
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExpressionFieldReference;
+import org.springframework.util.Assert;
+
+import com.mongodb.DBObject;
+
+/**
+ * {@link AggregationOperationContext} that delegates {@link FieldReference} resolution and mapping to a parent one, but
+ * assures {@link FieldReference} get converted into {@link ExpressionFieldReference} using {@code $$} to ref an inner
+ * variable.
+ *
+ * @author Christoph Strobl
+ * @since 1.10
+ */
+class NestedDelegatingExpressionAggregationOperationContext implements AggregationOperationContext {
+
+ private final AggregationOperationContext delegate;
+
+ /**
+ * Creates new {@link NestedDelegatingExpressionAggregationOperationContext}.
+ *
+ * @param referenceContext must not be {@literal null}.
+ */
+ public NestedDelegatingExpressionAggregationOperationContext(AggregationOperationContext referenceContext) {
+
+ Assert.notNull(referenceContext, "Reference context must not be null!");
+ this.delegate = referenceContext;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.DBObject)
+ */
+ @Override
+ public DBObject getMappedObject(DBObject dbObject) {
+ return delegate.getMappedObject(dbObject);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.Field)
+ */
+ @Override
+ public FieldReference getReference(Field field) {
+ return new ExpressionFieldReference(delegate.getReference(field));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String)
+ */
+ @Override
+ public FieldReference getReference(String name) {
+ return new ExpressionFieldReference(delegate.getReference(name));
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java
index 73e11bf4dd..f6930c5e4c 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java
@@ -641,6 +641,19 @@ public ProjectionOperationBuilder slice(int count, int offset) {
return project("slice", offset, count);
}
+ /**
+ * Generates a {@code $filter} expression that returns a subset of the array held by the given field.
+ *
+ * @param as The variable name for the element in the input array. Must not be {@literal null}.
+ * @param condition The {@link AggregationExpression} that determines whether to include the element in the
+ * resulting array. Must not be {@literal null}.
+ * @return never {@literal null}.
+ * @since 1.10
+ */
+ public ProjectionOperationBuilder filter(String as, AggregationExpression condition) {
+ return this.operation.and(AggregationExpressions.Filter.filter(name).as(as).by(condition));
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java
index c800c419c6..d671d97830 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContext.java
@@ -22,6 +22,7 @@
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
@@ -98,6 +99,6 @@ private FieldReference getReferenceFor(Field field) {
Field mappedField = field(propertyPath.getLeafProperty().getName(),
propertyPath.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE));
- return new FieldReference(new ExposedField(mappedField, true));
+ return new DirectFieldReference(new ExposedField(mappedField, true));
}
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java
index f35391e626..f3701bbe16 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java
@@ -18,6 +18,8 @@
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
+import java.util.List;
+
import com.mongodb.BasicDBList;
import com.mongodb.DBObject;
@@ -54,6 +56,10 @@ public static BasicDBList getAsDBList(DBObject source, String key) {
return getTypedValue(source, key, BasicDBList.class);
}
+ public static List