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 81b122535d..502df6cb4f 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 @@ -60,7 +60,7 @@ public ProjectionOperation() { * @param fields must not be {@literal null}. */ public ProjectionOperation(Fields fields) { - this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields, true)); + this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields)); } /** @@ -117,23 +117,34 @@ public ProjectionOperationBuilder and(String name) { /** * Excludes the given fields from the projection. * - * @param fields must not be {@literal null}. + * @param fieldNames must not be {@literal null}. * @return */ - public ProjectionOperation andExclude(String... fields) { - List excludeProjections = FieldProjection.from(Fields.fields(fields), false); + public ProjectionOperation andExclude(String... fieldNames) { + + for (String fieldName : fieldNames) { + Assert + .isTrue( + Fields.UNDERSCORE_ID.equals(fieldName), + String + .format( + "Exclusion of field %s not allowed. Projections by the mongodb aggregation framework only support the exclusion of the %s field!", + fieldName, Fields.UNDERSCORE_ID)); + } + + List excludeProjections = FieldProjection.from(Fields.fields(fieldNames), false); return new ProjectionOperation(this.projections, excludeProjections); } /** * Includes the given fields into the projection. * - * @param fields must not be {@literal null}. + * @param fieldNames must not be {@literal null}. * @return */ - public ProjectionOperation andInclude(String... fields) { + public ProjectionOperation andInclude(String... fieldNames) { - List projections = FieldProjection.from(Fields.fields(fields), true); + List projections = FieldProjection.from(Fields.fields(fieldNames), true); return new ProjectionOperation(this.projections, projections); } @@ -387,31 +398,34 @@ private FieldProjection(Field field, Object value) { this.value = value; } + /** + * Factory method to easily create {@link FieldProjection}s for the given {@link Fields}. Fields are projected as + * references with their given name. + *

+ * A field {@code foo} will be projected as: {@code foo: '$foo'} + *

+ * + * @param fields the {@link Fields} to in- or exclude, must not be {@literal null}. + * @return + */ + public static List from(Fields fields) { + return from(fields, null); + } + /** * Factory method to easily create {@link FieldProjection}s for the given {@link Fields}. * * @param fields the {@link Fields} to in- or exclude, must not be {@literal null}. - * @param include whether to include or exclude the fields. + * @param value to use for the given field. * @return */ - public static List from(Fields fields, boolean include) { + public static List from(Fields fields, Object value) { Assert.notNull(fields, "Fields must not be null!"); List projections = new ArrayList(); for (Field field : fields) { - - if (!include) { - if (!Fields.UNDERSCORE_ID.equals(field.getName())) { - throw new IllegalArgumentException( - String - .format( - "Exclusion of field %s not allowed. Projections by the mongodb aggregation framework only support the exclusion of the %s field!", - field.getName(), Fields.UNDERSCORE_ID)); - } - } - - projections.add(new FieldProjection(field, include ? null : 0)); + projections.add(new FieldProjection(field, value)); } return projections; @@ -423,13 +437,32 @@ public static List from(Fields fields, boolean include) { */ @Override public DBObject toDBObject(AggregationOperationContext context) { + return new BasicDBObject(field.getName(), renderFieldValue(context)); + } + + private Object renderFieldValue(AggregationOperationContext context) { + + // implicit reference or explicit include? + if (value == null || Boolean.TRUE.equals(value)) { + + // check whether referenced field exists in the context + FieldReference reference = context.getReference(field.getTarget()); + + if (field.getName().equals(field.getTarget())) { + + // render field as included + return 1; + } + + // render field reference + return reference.toString(); + } else if (Boolean.FALSE.equals(value)) { - if (value != null) { - return new BasicDBObject(field.getName(), value); + // render field as excluded + return 0; } - FieldReference reference = context.getReference(field.getTarget()); - return new BasicDBObject(field.getName(), reference.toString()); + return 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 65e092227e..4a7f5b6659 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 @@ -66,7 +66,7 @@ public void alwaysUsesExplicitReference() { DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); - assertThat(projectClause.get("foo"), is((Object) "$foo")); + assertThat((Integer) projectClause.get("foo"), is(1)); assertThat(projectClause.get("bar"), is((Object) "$foobar")); } @@ -197,7 +197,7 @@ public void excludeShouldThrowExceptionForFieldsOtherThanUnderscoreId() { * @see DATAMONGO-758 */ @Test - public void excludeShouldExclusionOfUnderscoreId() { + public void excludeShouldAllowExclusionOfUnderscoreId() { ProjectionOperation projectionOp = new ProjectionOperation().andExclude(Fields.UNDERSCORE_ID); DBObject dbObject = projectionOp.toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -205,6 +205,25 @@ public void excludeShouldExclusionOfUnderscoreId() { assertThat((Integer) projectClause.get(Fields.UNDERSCORE_ID), is(0)); } + /** + * @see DATAMONGO-757 + */ + @Test + public void usesImplictAndExplicitFieldAliasAndIncludeExclude() { + + ProjectionOperation operation = Aggregation.project("foo").and("foobar").as("bar").andInclude("inc1", "inc2") + .andExclude("_id"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); + + assertThat((Integer) projectClause.get("foo"), is(1)); // implicit + assertThat(projectClause.get("bar"), is((Object) "$foobar")); // explicit + assertThat((Integer) projectClause.get("inc1"), is(1)); // include shortcut + assertThat((Integer) projectClause.get("inc2"), is(1)); + assertThat((Integer) projectClause.get("_id"), is(0)); + } + @Test(expected = IllegalArgumentException.class) public void arithmenticProjectionOperationModByZeroException() {