From 5bd0e21173e7924a44b42db3eeafbfcb1c169cb6 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 23 Nov 2016 10:35:01 +0100 Subject: [PATCH 001/118] DATAMONGO-1527 - Updated changelog. --- src/main/resources/changelog.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 6ca345936c..72b5907c92 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,18 @@ Spring Data MongoDB Changelog ============================= +Changes in version 2.0.0.M1 (2016-11-23) +---------------------------------------- +* DATAMONGO-1527 - Release 2.0 M1 (Kay). +* DATAMONGO-1509 - Inconsistent type alias placement in list of classes. +* DATAMONGO-1461 - Upgrade Hibernate/JPA dependencies to match Spring 5 baseline. +* DATAMONGO-1448 - Set up 2.0 development. +* DATAMONGO-1444 - Reactive support in Spring Data MongoDB. +* DATAMONGO-1176 - Use org.bson types instead of com.mongodb. +* DATAMONGO-563 - Upgrade to MongoDB driver 2.9.2 as it fixes a serious regression introduced in 2.9.0. +* DATAMONGO-562 - Cannot create entity with OptimisticLocking (@Version) and initial id. + + Changes in version 1.9.5.RELEASE (2016-11-03) --------------------------------------------- * DATAMONGO-1521 - Aggregation.skip(...) expects int but new SkipOperation(...) supports long. From 36838ffe31165bdf69c3bfb8e19ab8a657875278 Mon Sep 17 00:00:00 2001 From: gustavodegeus Date: Sat, 9 Apr 2016 11:46:55 -0400 Subject: [PATCH 002/118] DATAMONGO-1327 - Added support for $stdDevSamp and $stdDevPop to aggregation $group stage. Original Pull Request: #360 CLA: 171720160409030719 (Gustavo de Geus) --- .../core/aggregation/GroupOperation.java | 26 ++++++++++++++-- .../aggregation/GroupOperationUnitTests.java | 31 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java index 4ee8b37ed8..a3d7149707 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-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. @@ -39,6 +39,7 @@ * @author Sebastian Herold * @author Thomas Darimont * @author Oliver Gierke + * @author Gustavo de Geus * @since 1.3 */ public class GroupOperation implements FieldsExposingAggregationOperation { @@ -307,6 +308,27 @@ public GroupOperationBuilder max(AggregationExpression expr) { return newBuilder(GroupOps.MAX, null, expr); } + /** + * Generates an {@link GroupOperationBuilder} for an {@code $stdDevSamp}-expression that for the given + * field-reference. + * + * @param reference + * @return + */ + public GroupOperationBuilder stdDevSamp(String reference) { + return newBuilder(GroupOps.STD_DEV_SAMP, reference, null); + } + + /** + * Generates an {@link GroupOperationBuilder} for an {@code $stdDevPop}-expression that for the given field-reference. + * + * @param reference + * @return + */ + public GroupOperationBuilder stdDevPop(String reference) { + return newBuilder(GroupOps.STD_DEV_POP, reference, null); + } + private GroupOperationBuilder newBuilder(Keyword keyword, String reference, Object value) { return new GroupOperationBuilder(this, new Operation(keyword, null, reference, value)); } @@ -371,7 +393,7 @@ interface Keyword { private static enum GroupOps implements Keyword { - SUM, LAST, FIRST, PUSH, AVG, MIN, MAX, ADD_TO_SET, COUNT; + SUM, LAST, FIRST, PUSH, AVG, MIN, MAX, ADD_TO_SET, COUNT, STD_DEV_SAMP, STD_DEV_POP; @Override public String toString() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java index e2bcb939dd..0bb274e851 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-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. @@ -33,6 +33,7 @@ * * @author Oliver Gierke * @author Thomas Darimont + * @author Gustavo de Geus */ public class GroupOperationUnitTests { @@ -204,6 +205,34 @@ public void shouldRenderSizeExpressionInGroup() { assertThat(tagsCount.get("$first"), is((Object) new BasicDBObject("$size", Arrays.asList("$tags")))); } + /** + * @see DATAMONGO-1327 + */ + @Test + public void groupOperationStdDevSampWithValue() { + + GroupOperation groupOperation = Aggregation.group("a", "b").stdDevSamp("field").as("fieldStdDevSamp"); + + DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); + DBObject push = DBObjectTestUtils.getAsDBObject(groupClause, "fieldStdDevSamp"); + + assertThat(push, is((DBObject) new BasicDBObject("$stdDevSamp", "$field"))); + } + + /** + * @see DATAMONGO-1327 + */ + @Test + public void groupOperationStdDevPopWithValue() { + + GroupOperation groupOperation = Aggregation.group("a", "b").stdDevPop("field").as("fieldStdDevPop"); + + DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); + DBObject push = DBObjectTestUtils.getAsDBObject(groupClause, "fieldStdDevPop"); + + assertThat(push, is((DBObject) new BasicDBObject("$stdDevPop", "$field"))); + } + private DBObject extractDbObjectFromGroupOperation(GroupOperation groupOperation) { DBObject dbObject = groupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject groupClause = DBObjectTestUtils.getAsDBObject(dbObject, "$group"); From 578441ee9ff9a445d7d3e9963303d3376ecd76ca Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 23 Nov 2016 15:09:48 +0100 Subject: [PATCH 003/118] DATAMONGO-1327 - Polishing. Just added overloads for stdDevSamp and stdDevPop taking AggregationExpression and updated the doc. Also replaced String operation based MongoDB operation building by using operators directly. Original Pull Request: #360 --- .../core/aggregation/GroupOperation.java | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java index a3d7149707..092d88c185 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java @@ -40,6 +40,7 @@ * @author Thomas Darimont * @author Oliver Gierke * @author Gustavo de Geus + * @author Christoph Strobl * @since 1.3 */ public class GroupOperation implements FieldsExposingAggregationOperation { @@ -312,23 +313,47 @@ public GroupOperationBuilder max(AggregationExpression expr) { * Generates an {@link GroupOperationBuilder} for an {@code $stdDevSamp}-expression that for the given * field-reference. * - * @param reference - * @return + * @param reference must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 */ public GroupOperationBuilder stdDevSamp(String reference) { return newBuilder(GroupOps.STD_DEV_SAMP, reference, null); } + /** + * Generates an {@link GroupOperationBuilder} for an {@code $stdDevSamp}-expression that for the given {@link AggregationExpression}. + * + * @param expr must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public GroupOperationBuilder stdDevSamp(AggregationExpression expr) { + return newBuilder(GroupOps.STD_DEV_SAMP, null, expr); + } + /** * Generates an {@link GroupOperationBuilder} for an {@code $stdDevPop}-expression that for the given field-reference. * - * @param reference - * @return + * @param reference must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 */ public GroupOperationBuilder stdDevPop(String reference) { return newBuilder(GroupOps.STD_DEV_POP, reference, null); } + /** + * Generates an {@link GroupOperationBuilder} for an {@code $stdDevPop}-expression that for the given {@link AggregationExpression}. + * + * @param expr must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public GroupOperationBuilder stdDevPop(AggregationExpression expr) { + return newBuilder(GroupOps.STD_DEV_POP, null, expr); + } + private GroupOperationBuilder newBuilder(Keyword keyword, String reference, Object value) { return new GroupOperationBuilder(this, new Operation(keyword, null, reference, value)); } @@ -393,21 +418,18 @@ interface Keyword { private static enum GroupOps implements Keyword { - SUM, LAST, FIRST, PUSH, AVG, MIN, MAX, ADD_TO_SET, COUNT, STD_DEV_SAMP, STD_DEV_POP; - - @Override - public String toString() { + SUM("$sum"), LAST("$last"), FIRST("$first"), PUSH("$push"), AVG("$avg"), MIN("$min"), MAX("$max"), ADD_TO_SET("$addToSet"), STD_DEV_POP("$stdDevPop"), STD_DEV_SAMP("$stdDevSamp"); - String[] parts = name().split("_"); + private String mongoOperator; - StringBuilder builder = new StringBuilder(); + GroupOps(String mongoOperator) { + this.mongoOperator = mongoOperator; + } - for (String part : parts) { - String lowerCase = part.toLowerCase(Locale.US); - builder.append(builder.length() == 0 ? lowerCase : StringUtils.capitalize(lowerCase)); - } - return "$" + builder.toString(); + @Override + public String toString() { + return mongoOperator; } } From 2985b4ca3d5313e48a2d2d80e28e9253e640c4d3 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 25 Oct 2016 15:43:55 +0200 Subject: [PATCH 004/118] DATAMONGO-1491 - Add support for $filter (aggregation). We new support $filter in aggregation pipeline. Aggregation.newAggregation(Sales.class, Aggregation.project() .and(filter("items").as("item").by(GTE.of(field("item.price"), 100))) .as("items")) Original pull request: #412. --- .../mongodb/core/aggregation/Aggregation.java | 5 +- .../aggregation/AggregationExpressions.java | 293 ++++++++++++++++++ .../AggregationFunctionExpressions.java | 2 +- .../core/aggregation/ExposedFields.java | 124 +++++++- ...osedFieldsAggregationOperationContext.java | 7 +- ...ExpressionAggregationOperationContext.java | 73 +++++ .../core/aggregation/ProjectionOperation.java | 13 + .../TypeBasedAggregationOperationContext.java | 3 +- .../data/mongodb/core/DBObjectTestUtils.java | 6 + .../core/aggregation/AggregationTests.java | 57 ++++ .../FilterExpressionUnitTests.java | 141 +++++++++ ...dAggregationOperationContextUnitTests.java | 3 +- src/main/asciidoc/reference/mongodb.adoc | 2 +- 13 files changed, 706 insertions(+), 23 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/NestedDelegatingExpressionAggregationOperationContext.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java 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 getAsList(DBObject source, String key) { + return getTypedValue(source, key, List.class); + } + /** * Expects the list element with the given index to be a non-{@literal null} {@link DBObject} and returns it. * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 6a4b8ae81c..0a75c536f0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -29,6 +29,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Scanner; @@ -72,6 +73,8 @@ import com.mongodb.MongoException; import com.mongodb.util.JSON; +import lombok.Builder; + /** * Tests for {@link MongoTemplate#aggregate(String, AggregationPipeline, Class)}. * @@ -1500,6 +1503,42 @@ public void sliceShouldBeAppliedCorrectly() { } } + /** + * @see DATAMONGO-1491 + */ + @Test + public void filterShouldBeAppliedCorrectly() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + + Item item43 = Item.builder().itemId("43").quantity(2).price(2L).build(); + Item item2 = Item.builder().itemId("2").quantity(1).price(240L).build(); + Sales sales1 = Sales.builder().id("0") + .items(Arrays.asList( // + item43, item2)) // + .build(); + + Item item23 = Item.builder().itemId("23").quantity(3).price(110L).build(); + Item item103 = Item.builder().itemId("103").quantity(4).price(5L).build(); + Item item38 = Item.builder().itemId("38").quantity(1).price(300L).build(); + Sales sales2 = Sales.builder().id("1").items(Arrays.asList( // + item23, item103, item38)).build(); + + Item item4 = Item.builder().itemId("4").quantity(1).price(23L).build(); + Sales sales3 = Sales.builder().id("2").items(Arrays.asList( // + item4)).build(); + + mongoTemplate.insert(Arrays.asList(sales1, sales2, sales3), Sales.class); + + TypedAggregation agg = newAggregation(Sales.class, project().and("items") + .filter("item", AggregationFunctionExpressions.GTE.of(field("item.price"), 100)).as("items")); + + assertThat(mongoTemplate.aggregate(agg, Sales.class).getMappedResults(), + contains(Sales.builder().id("0").items(Collections.singletonList(item2)).build(), + Sales.builder().id("1").items(Arrays.asList(item23, item38)).build(), + Sales.builder().id("2").items(Collections. emptyList()).build())); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); @@ -1740,4 +1779,22 @@ public InventoryItem(int id, String item, String description, int qty) { this.qty = qty; } } + + @lombok.Data + @Builder + static class Sales { + + @Id String id; + List items; + } + + @lombok.Data + @Builder + static class Item { + + @org.springframework.data.mongodb.core.mapping.Field("item_id") // + String itemId; + Integer quantity; + Long price; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java new file mode 100644 index 0000000000..5cc4e799f8 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java @@ -0,0 +1,141 @@ +/* + * Copyright 2016. the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import static org.hamcrest.core.Is.*; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.DBObjectTestUtils; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.QueryMapper; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * @author Christoph Strobl + */ +@RunWith(MockitoJUnitRunner.class) +public class FilterExpressionUnitTests { + + @Mock MongoDbFactory mongoDbFactory; + + private AggregationOperationContext aggregationContext; + private MongoMappingContext mappingContext; + + @Before + public void setUp() { + + mappingContext = new MongoMappingContext(); + aggregationContext = new TypeBasedAggregationOperationContext(Sales.class, mappingContext, + new QueryMapper(new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), mappingContext))); + } + + /** + * @see DATAMONGO-1491 + */ + @Test + public void shouldConstructFilterExpressionCorrectly() { + + TypedAggregation agg = Aggregation.newAggregation(Sales.class, + Aggregation.project() + .and(filter("items").as("item").by(AggregationFunctionExpressions.GTE.of(Fields.field("item.price"), 100))) + .as("items")); + + DBObject dbo = agg.toDbObject("sales", aggregationContext); + + List pipeline = DBObjectTestUtils.getAsList(dbo, "pipeline"); + DBObject $project = DBObjectTestUtils.getAsDBObject((DBObject) pipeline.get(0), "$project"); + DBObject items = DBObjectTestUtils.getAsDBObject($project, "items"); + DBObject $filter = DBObjectTestUtils.getAsDBObject(items, "$filter"); + + DBObject expected = (DBObject) JSON.parse("{" + // + "input: \"$items\"," + // + "as: \"item\"," + // + "cond: { $gte: [ \"$$item.price\", 100 ] }" + // + "}"); + + assertThat($filter, is(expected)); + } + + /** + * @see DATAMONGO-1491 + */ + @Test + public void shouldConstructFilterExpressionCorrectlyWhenUsingFilterOnProjectionBuilder() { + + TypedAggregation agg = Aggregation.newAggregation(Sales.class, Aggregation.project().and("items") + .filter("item", AggregationFunctionExpressions.GTE.of(Fields.field("item.price"), 100)).as("items")); + + DBObject dbo = agg.toDbObject("sales", aggregationContext); + + List pipeline = DBObjectTestUtils.getAsList(dbo, "pipeline"); + DBObject $project = DBObjectTestUtils.getAsDBObject((DBObject) pipeline.get(0), "$project"); + DBObject items = DBObjectTestUtils.getAsDBObject($project, "items"); + DBObject $filter = DBObjectTestUtils.getAsDBObject(items, "$filter"); + + DBObject expected = (DBObject) JSON.parse("{" + // + "input: \"$items\"," + // + "as: \"item\"," + // + "cond: { $gte: [ \"$$item.price\", 100 ] }" + // + "}"); + + assertThat($filter, is(expected)); + } + + /** + * @see DATAMONGO-1491 + */ + @Test + public void shouldConstructFilterExpressionCorrectlyWhenInputMapToArray() { + + TypedAggregation agg = Aggregation.newAggregation(Sales.class, + Aggregation.project().and(filter(Arrays. asList(1, "a", 2, null, 3.1D, 4, "5")).as("num") + .by(AggregationFunctionExpressions.GTE.of(Fields.field("num"), 3))).as("items")); + + DBObject dbo = agg.toDbObject("sales", aggregationContext); + + List pipeline = DBObjectTestUtils.getAsList(dbo, "pipeline"); + DBObject $project = DBObjectTestUtils.getAsDBObject((DBObject) pipeline.get(0), "$project"); + DBObject items = DBObjectTestUtils.getAsDBObject($project, "items"); + DBObject $filter = DBObjectTestUtils.getAsDBObject(items, "$filter"); + + DBObject expected = (DBObject) JSON.parse("{" + // + "input: [ 1, \"a\", 2, null, 3.1, 4, \"5\" ]," + // + "as: \"num\"," + // + "cond: { $gte: [ \"$$num\", 3 ] }" + // + "}"); + + assertThat($filter, is(expected)); + } + + static class Sales { + + List items; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java index b1371c00c3..c65b98fe15 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java @@ -38,6 +38,7 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.convert.CustomConversions; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; @@ -105,7 +106,7 @@ public void returnsReferencesToNestedFieldsCorrectly() { public void aliasesIdFieldCorrectly() { AggregationOperationContext context = getContext(Foo.class); - assertThat(context.getReference("id"), is(new FieldReference(new ExposedField(field("id", "_id"), true)))); + assertThat(context.getReference("id"), is((FieldReference) new DirectFieldReference(new ExposedField(field("id", "_id"), true)))); } /** diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 6e4c4e7b02..0dad55c133 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1686,7 +1686,7 @@ At the time of this writing we provide support for the following Aggregation Ope | eq (*via: is), gt, gte, lt, lte, ne | Array Aggregation Operators -| size, slice +| size, slice, filter | Conditional Aggregation Operators | cond, ifNull From 3dc1e9355a6119b4e3b6d664cce0ed434816421c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 24 Nov 2016 12:14:04 +0100 Subject: [PATCH 005/118] DATAMONGO-1491 - Polishing. Remove variable before returning value. Add generics for list creation. Original pull request: #412. --- .../mongodb/core/aggregation/AggregationExpressions.java | 7 ++----- 1 file changed, 2 insertions(+), 5 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 b8a78b31a4..e06fa43fb5 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 @@ -130,8 +130,7 @@ private Object getMappedCondition(AggregationOperationContext context) { } NestedDelegatingExpressionAggregationOperationContext nea = new NestedDelegatingExpressionAggregationOperationContext(context); - DBObject mappedCondition = ((AggregationExpression) condition).toDbObject(nea); - return mappedCondition; + return ((AggregationExpression) condition).toDbObject(nea); } /** @@ -223,7 +222,7 @@ public static InputBuilder newBuilder() { public AsBuilder filter(List array) { Assert.notNull(array, "Array must not be null!"); - filter.input = new ArrayList(array); + filter.input = new ArrayList(array); return this; } @@ -287,7 +286,5 @@ public Filter by(DBObject expression) { return filter; } } - } - } From e631e2d7c5fca537207938f984f9845bd0926d54 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 23 Nov 2016 11:38:25 +0100 Subject: [PATCH 006/118] DATAMONGO-784 - Add support for comparison aggregation operators to group & project. We now directly support comparison aggregation operators ($cmp, $eq, $gt, $gte, $lt, $lte and $ne) on both group and project stages. Original pull request: #414. --- .../AggregationFunctionExpressions.java | 2 +- .../core/aggregation/ProjectionOperation.java | 72 ++++++++++++++++ .../ProjectionOperationUnitTests.java | 85 +++++++++++++++++++ 3 files changed, 158 insertions(+), 1 deletion(-) 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 cd669518d8..761f2c3164 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, GTE; + SIZE, CMP, EQ, GT, GTE, LT, LTE, NE; /** * 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/ProjectionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java index f6930c5e4c..7b74aa8faf 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 @@ -616,6 +616,78 @@ public ProjectionOperationBuilder size() { return project("size"); } + /** + * Generates a {@code $cmp} expression (compare to) that compares the value of the field to a given value or field. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder cmp(Object compareValue) { + return project("cmp", compareValue); + } + + /** + * Generates a {@code $eq} expression (equal) that compares the value of the field to a given value or field. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder eq(Object compareValue) { + return project("eq", compareValue); + } + + /** + * Generates a {@code $gt} expression (greater than) that compares the value of the field to a given value or field. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder gt(Object compareValue) { + return project("gt", compareValue); + } + + /** + * Generates a {@code $gte} expression (greater than equal) that compares the value of the field to a given value or + * field. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder gte(Object compareValue) { + return project("gte", compareValue); + } + + /** + * Generates a {@code $lt} expression (less than) that compares the value of the field to a given value or field. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder lt(Object compareValue) { + return project("lt", compareValue); + } + + /** + * Generates a {@code $lte} expression (less than equal) that compares the value of the field to a given value or + * field. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder lte(Object compareValue) { + return project("lte", compareValue); + } + + /** + * Generates a {@code $ne} expression (not equal) that compares the value of the field to a given value or field. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder ne(Object compareValue) { + return project("ne", compareValue); + } + /** * Generates a {@code $slice} expression that returns a subset of the array held by the given field.
* If {@literal n} is positive, $slice returns up to the first n elements in the array.
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 bc1a06e51f..3807e30d73 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 @@ -19,6 +19,7 @@ import static org.junit.Assert.*; import static org.springframework.data.mongodb.core.aggregation.AggregationFunctionExpressions.*; import static org.springframework.data.mongodb.core.aggregation.Fields.*; +import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import static org.springframework.data.mongodb.util.DBObjectUtils.*; import java.util.Arrays; @@ -402,6 +403,90 @@ public void shouldRenderSliceWithPositionCorrectly() throws Exception { is((Object) new BasicDBObject("$slice", Arrays. asList("$field", 5, 10)))); } + /** + * @see DATAMONGO-784 + */ + @Test + public void shouldRenderCmpCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").cmp(10).as("cmp10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.cmp10.$cmp.[0]", "$field").containing("$project.cmp10.$cmp.[1]", 10)); + } + + /** + * @see DATAMONGO-784 + */ + @Test + public void shouldRenderEqCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").eq(10).as("eq10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.eq10.$eq.[0]", "$field").containing("$project.eq10.$eq.[1]", 10)); + } + + /** + * @see DATAMONGO-784 + */ + @Test + public void shouldRenderGtCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").gt(10).as("gt10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.gt10.$gt.[0]", "$field").containing("$project.gt10.$gt.[1]", 10)); + } + + /** + * @see DATAMONGO-784 + */ + @Test + public void shouldRenderGteCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").gte(10).as("gte10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.gte10.$gte.[0]", "$field").containing("$project.gte10.$gte.[1]", 10)); + } + + /** + * @see DATAMONGO-784 + */ + @Test + public void shouldRenderLtCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").lt(10).as("lt10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.lt10.$lt.[0]", "$field").containing("$project.lt10.$lt.[1]", 10)); + } + + /** + * @see DATAMONGO-784 + */ + @Test + public void shouldRenderLteCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").lte(10).as("lte10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.lte10.$lte.[0]", "$field").containing("$project.lte10.$lte.[1]", 10)); + } + + /** + * @see DATAMONGO-784 + */ + @Test + public void shouldRenderNeCorrectly() { + + ProjectionOperation operation = Aggregation.project().and("field").ne(10).as("ne10"); + + assertThat(operation.toDBObject(Aggregation.DEFAULT_CONTEXT), + isBsonObject().containing("$project.ne10.$ne.[0]", "$field").containing("$project.ne10.$ne.[1]", 10)); + } + private static DBObject exctractOperation(String field, DBObject fromProjectClause) { return (DBObject) fromProjectClause.get(field); } From 710770e88de4a72d69d26565850c05b55fa3db5c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 24 Nov 2016 13:45:38 +0100 Subject: [PATCH 007/118] DATAMONGO-784 - Polishing. Add JavaDoc for compareValue. Original pull request: #414. --- .../data/mongodb/core/aggregation/ProjectionOperation.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 7b74aa8faf..2eaba433dd 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 @@ -619,6 +619,7 @@ public ProjectionOperationBuilder size() { /** * Generates a {@code $cmp} expression (compare to) that compares the value of the field to a given value or field. * + * @param compareValue compare value or a {@link Field} object. * @return never {@literal null}. * @since 1.10 */ @@ -629,6 +630,7 @@ public ProjectionOperationBuilder cmp(Object compareValue) { /** * Generates a {@code $eq} expression (equal) that compares the value of the field to a given value or field. * + * @param compareValue compare value or a {@link Field} object. * @return never {@literal null}. * @since 1.10 */ @@ -639,6 +641,7 @@ public ProjectionOperationBuilder eq(Object compareValue) { /** * Generates a {@code $gt} expression (greater than) that compares the value of the field to a given value or field. * + * @param compareValue compare value or a {@link Field} object. * @return never {@literal null}. * @since 1.10 */ @@ -650,6 +653,7 @@ public ProjectionOperationBuilder gt(Object compareValue) { * Generates a {@code $gte} expression (greater than equal) that compares the value of the field to a given value or * field. * + * @param compareValue compare value or a {@link Field} object. * @return never {@literal null}. * @since 1.10 */ @@ -660,6 +664,7 @@ public ProjectionOperationBuilder gte(Object compareValue) { /** * Generates a {@code $lt} expression (less than) that compares the value of the field to a given value or field. * + * @param compareValue compare value or a {@link Field} object. * @return never {@literal null}. * @since 1.10 */ @@ -671,6 +676,7 @@ public ProjectionOperationBuilder lt(Object compareValue) { * Generates a {@code $lte} expression (less than equal) that compares the value of the field to a given value or * field. * + * @param compareValue the compare value or a {@link Field} object. * @return never {@literal null}. * @since 1.10 */ @@ -681,6 +687,7 @@ public ProjectionOperationBuilder lte(Object compareValue) { /** * Generates a {@code $ne} expression (not equal) that compares the value of the field to a given value or field. * + * @param compareValue compare value or a {@link Field} object. * @return never {@literal null}. * @since 1.10 */ From b786b8220ad2d9b4cfbfdca888b747b48917c162 Mon Sep 17 00:00:00 2001 From: Sebastien Gerard Date: Wed, 16 Nov 2016 15:06:44 +0100 Subject: [PATCH 008/118] DATAMONGO-1530 - Add support for missing MongoDB 3.2 aggregation pipeline operators. Original Pull Request: #410 --- .../core/spel/MethodReferenceNode.java | 79 ++++++++++++++++--- .../data/mongodb/core/spel/OperatorNode.java | 8 +- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java index a32c49c3c1..12dd51d0f8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java @@ -15,32 +15,89 @@ */ package org.springframework.data.mongodb.core.spel; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.ast.MethodReference; + import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.springframework.expression.spel.ExpressionState; -import org.springframework.expression.spel.ast.MethodReference; - /** * An {@link ExpressionNode} representing a method reference. - * + * * @author Oliver Gierke * @author Thomas Darimont + * @author Sebastien Gerard */ public class MethodReferenceNode extends ExpressionNode { private static final Map FUNCTIONS; static { - Map map = new HashMap(); + map.put("and", "$and"); // Returns true only when all its expressions evaluate to true. + map.put("or", "$or"); // Returns true when any of its expressions evaluates to true. + map.put("not", "$not"); // Returns the boolean value that is the opposite of its argument expression. + + map.put("setEquals", "$setEquals"); // Returns true if the input sets have the same distinct elements. + map.put("setIntersection", "$setIntersection"); // Returns a set with elements that appear in all of the input sets. + map.put("setUnion", "$setUnion"); // Returns a set with elements that appear in any of the input sets. + map.put("setDifference", "$setDifference"); // Returns a set with elements that appear in the 1st set but not in the + // 2nd. + map.put("setIsSubset", "$setIsSubset"); // Returns true if all elements of the 1st set appear in the 2nd set. + map.put("anyElementTrue", "$anyElementTrue"); // Returns whether any elements of a set evaluate to true. + map.put("allElementsTrue", "$allElementsTrue"); // Returns whether no element of a set evaluates to false. + + map.put("cmp", "$cmp"); // Returns: 0 if the two values are equivalent, 1 if the first value is greater than the + // second, and -1 if the first value is less than the second. + map.put("eq", "$eq"); // Returns true if the values are equivalent. + map.put("gt", "$gt"); // Returns true if the first value is greater than the second. + map.put("gte", "$gte"); // Returns true if the first value is greater than or equal to the second. + map.put("lt", "$lt"); // Returns true if the first value is less than the second. + map.put("lte", "$lte"); // Returns true if the first value is less than or equal to the second. + map.put("ne", "$ne"); // Returns true if the values are not equivalent. + + map.put("abs", "$abs"); // Returns the absolute value of a number.; + map.put("add", "$add"); // Adds numbers to return the sum, or adds numbers and a date to return a new date. + map.put("ceil", "$ceil"); // Returns the smallest integer greater than or equal to the specified number. + map.put("divide", "$divide"); // Returns the result of dividing the first number by the second. + map.put("exp", "$exp"); // Raises e to the specified exponent. + map.put("floor", "$floor"); // Returns the largest integer less than or equal to the specified number. + map.put("ln", "$ln"); // Calculates the natural log of a number. + map.put("log", "$log"); // Calculates the log of a number in the specified base. + map.put("log10", "$log10"); // Calculates the log base 10 of a number. + map.put("mod", "$mod"); // Returns the remainder of the first number divided by the second. + map.put("multiply", "$multiply"); // Multiplies numbers to return the product. + map.put("pow", "$pow"); // Raises a number to the specified exponent. + map.put("sqrt", "$sqrt"); // Calculates the square root. + map.put("subtract", "$subtract"); // Returns the result of subtracting the second value from the first. If the + // two values are numbers, return the difference. If the two values are dates, return the difference in + // milliseconds. + map.put("trunc", "$trunc"); // Truncates a number to its integer. + map.put("concat", "$concat"); // Concatenates two strings. - map.put("strcasecmp", "$strcasecmp"); // Compares two strings and returns an integer that reflects the comparison. map.put("substr", "$substr"); // Takes a string and returns portion of that string. map.put("toLower", "$toLower"); // Converts a string to lowercase. map.put("toUpper", "$toUpper"); // Converts a string to uppercase. + map.put("strcasecmp", "$strcasecmp"); // Compares two strings and returns an integer that reflects the comparison. + + map.put("meta", "$meta"); // Access text search metadata. + + map.put("arrayElemAt", "$arrayElemAt"); // Returns the element at the specified array index. + map.put("concatArrays", "$concatArrays"); // Concatenates arrays to return the concatenated array. + map.put("filter", "$filter"); // Selects a subset of the array to return an array with only the elements that + // match the filter condition. + map.put("isArray", "$isArray"); // Determines if the operand is an array. Returns a boolean. + map.put("size", "$size"); // Returns the number of elements in the array. + map.put("slice", "$slice"); // Returns a subset of an array. + + map.put("map", "$map"); // Applies a subexpression to each element of an array and returns the array of + // resulting values in order. + map.put("let", "$let"); // Defines variables for use within the scope of a subexpression and returns the result + // of the subexpression. + + map.put("literal", "$literal"); // Return a value without parsing. map.put("dayOfYear", "$dayOfYear"); // Converts a date to a number between 1 and 366. map.put("dayOfMonth", "$dayOfMonth"); // Converts a date to a number between 1 and 31. @@ -53,6 +110,13 @@ public class MethodReferenceNode extends ExpressionNode { map.put("second", "$second"); // Converts a date into a number between 0 and 59. May be 60 to account for leap // seconds. map.put("millisecond", "$millisecond"); // Returns the millisecond portion of a date as an integer between 0 and + // 999. + map.put("dateToString", "$dateToString"); // Returns the date as a formatted string. + + map.put("cond", "$cond"); // A ternary operator that evaluates one expression, and depending on the result, + // returns the value of one of the other two expressions. + map.put("ifNull", "$ifNull"); // Returns either the non-null result of the first expression or the result of the + // second expression if the first expression results in a null result. FUNCTIONS = Collections.unmodifiableMap(map); } @@ -63,11 +127,8 @@ public class MethodReferenceNode extends ExpressionNode { /** * Returns the name of the method. - * - * @return */ public String getMethodName() { - String name = getName(); String methodName = name.substring(0, name.indexOf('(')); return FUNCTIONS.get(methodName); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java index 55a11bd7ec..4f8d8f3d1e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java @@ -29,7 +29,7 @@ /** * An {@link ExpressionNode} representing an operator. - * + * * @author Oliver Gierke * @author Thomas Darimont */ @@ -47,6 +47,10 @@ public class OperatorNode extends ExpressionNode { map.put("/", "$divide"); map.put("%", "$mod"); + map.put("and", "and"); + map.put("or", "or"); + map.put("!", "not"); + OPERATORS = Collections.unmodifiableMap(map); } @@ -54,7 +58,7 @@ public class OperatorNode extends ExpressionNode { /** * Creates a new {@link OperatorNode} from the given {@link Operator} and {@link ExpressionState}. - * + * * @param node must not be {@literal null}. * @param state must not be {@literal null}. */ From a741400e9b1a369ffba8b54b862c1c13119d306f Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 21 Nov 2016 08:17:55 +0100 Subject: [PATCH 009/118] DATAMONGO-1530 - Polishing. Add missing transformations for ConstructorReference, OperatorNot, OpNE, OpEQ, OpGT, OpGE, OpLT, OpLE, OperatorPower, OpOr and OpAnd. This allows usage of logical operators &, || and ! as part of the expression, while ConstructorReference allows instantiating eg. arrays via an expression `new int[]{4,5,6}`. This can be useful eg. comparing arrays using $setEquals. More complex aggregation operators like $filter can be created by defining the variable references as string inside the expression like filter(a, 'num', '$$num' > 10). Commands like $let requires usage of InlineMap to pass in required arguments like eg. let({low:1, high:'$$low'}, gt('$$low', '$$high')). Original Pull Request: #410 --- .../SpelExpressionTransformer.java | 135 +++- .../mongodb/core/spel/ExpressionNode.java | 18 +- .../data/mongodb/core/spel/LiteralNode.java | 28 +- .../core/spel/MethodReferenceNode.java | 326 +++++--- .../mongodb/core/spel/NotOperatorNode.java | 45 ++ .../data/mongodb/core/spel/OperatorNode.java | 64 +- .../SpelExpressionTransformerUnitTests.java | 712 +++++++++++++++++- src/main/asciidoc/reference/mongodb.adoc | 43 ++ 8 files changed, 1248 insertions(+), 123 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java 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 58dc08b364..d381020b5c 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-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. @@ -26,19 +26,26 @@ import org.springframework.data.mongodb.core.spel.ExpressionTransformationContextSupport; import org.springframework.data.mongodb.core.spel.LiteralNode; import org.springframework.data.mongodb.core.spel.MethodReferenceNode; +import org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference; +import org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference.ArgumentType; +import org.springframework.data.mongodb.core.spel.NotOperatorNode; import org.springframework.data.mongodb.core.spel.OperatorNode; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelNode; import org.springframework.expression.spel.SpelParserConfiguration; import org.springframework.expression.spel.ast.CompoundExpression; +import org.springframework.expression.spel.ast.ConstructorReference; import org.springframework.expression.spel.ast.Indexer; import org.springframework.expression.spel.ast.InlineList; +import org.springframework.expression.spel.ast.InlineMap; +import org.springframework.expression.spel.ast.OperatorNot; import org.springframework.expression.spel.ast.PropertyOrFieldReference; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; import org.springframework.util.NumberUtils; +import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; @@ -48,6 +55,7 @@ * Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression. * * @author Thomas Darimont + * @author Christoph Strobl */ class SpelExpressionTransformer implements AggregationExpressionTransformer { @@ -69,6 +77,8 @@ public SpelExpressionTransformer() { conversions.add(new PropertyOrFieldReferenceNodeConversion(this)); conversions.add(new CompoundExpressionNodeConversion(this)); conversions.add(new MethodReferenceNodeConversion(this)); + conversions.add(new NotOperatorNodeConversion(this)); + conversions.add(new ValueRetrievingNodeConversion(this)); this.conversions = Collections.unmodifiableList(conversions); } @@ -131,8 +141,8 @@ private ExpressionNodeConversion lookupConversionFor(ExpressionN * @author Thomas Darimont * @author Oliver Gierke */ - private static abstract class ExpressionNodeConversion implements - AggregationExpressionTransformer { + private static abstract class ExpressionNodeConversion + implements AggregationExpressionTransformer { private final AggregationExpressionTransformer transformer; private final Class nodeType; @@ -235,8 +245,17 @@ public OperatorNodeConversion(AggregationExpressionTransformer transformer) { protected Object convert(AggregationExpressionTransformationContext context) { OperatorNode currentNode = context.getCurrentNode(); - DBObject operationObject = createOperationObjectAndAddToPreviousArgumentsIfNecessary(context, currentNode); + + if (currentNode.isLogicalOperator()) { + + for (ExpressionNode expressionNode : currentNode) { + transform(expressionNode, currentNode, operationObject, context); + } + + return operationObject; + } + Object leftResult = transform(currentNode.getLeft(), currentNode, operationObject, context); if (currentNode.isUnaryMinus()) { @@ -271,7 +290,8 @@ private DBObject createOperationObjectAndAddToPreviousArgumentsIfNecessary( return nextDbObject; } - private Object convertUnaryMinusOp(ExpressionTransformationContextSupport context, Object leftResult) { + private Object convertUnaryMinusOp(ExpressionTransformationContextSupport context, + Object leftResult) { Object result = leftResult instanceof Number ? leftResult : new BasicDBObject("$multiply", dbList(-1, leftResult)); @@ -289,7 +309,7 @@ private Object convertUnaryMinusOp(ExpressionTransformationContextSupport context) { MethodReferenceNode node = context.getCurrentNode(); - List args = new ArrayList(); + AggregationMethodReference methodReference = node.getMethodReference(); - for (ExpressionNode childNode : node) { - args.add(transform(childNode, context)); + Object args = null; + + if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.SINGLE)) { + args = transform(node.getChild(0), context); + } else if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.MAP)) { + + DBObject dbo = new BasicDBObject(); + for (int i = 0; i < methodReference.getArgumentMap().length; i++) { + dbo.put(methodReference.getArgumentMap()[i], transform(node.getChild(i), context)); + } + args = dbo; + } else { + + List argList = new ArrayList(); + + for (ExpressionNode childNode : node) { + argList.add(transform(childNode, context)); + } + + args = dbList(argList.toArray()); } - return context.addToPreviousOrReturn(new BasicDBObject(node.getMethodName(), dbList(args.toArray()))); + return context.addToPreviousOrReturn(new BasicDBObject(methodReference.getMongoOperator(), args)); } } @@ -510,4 +548,81 @@ protected boolean supports(ExpressionNode node) { return node.isOfType(CompoundExpression.class); } } + + /** + * @author Christoph Strobl + * @since 1.10 + */ + static class NotOperatorNodeConversion extends ExpressionNodeConversion { + + /** + * Creates a new {@link ExpressionNodeConversion}. + * + * @param transformer must not be {@literal null}. + */ + public NotOperatorNodeConversion(AggregationExpressionTransformer transformer) { + super(transformer); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext) + */ + @Override + protected Object convert(AggregationExpressionTransformationContext context) { + + NotOperatorNode node = context.getCurrentNode(); + List args = new ArrayList(); + + for (ExpressionNode childNode : node) { + args.add(transform(childNode, context)); + } + + return context.addToPreviousOrReturn(new BasicDBObject(node.getMongoOperator(), dbList(args.toArray()))); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode) + */ + @Override + protected boolean supports(ExpressionNode node) { + return node.isOfType(OperatorNot.class); + } + } + + /** + * @author Christoph Strobl + * @since 1.10 + */ + static class ValueRetrievingNodeConversion extends ExpressionNodeConversion { + + /** + * Creates a new {@link ExpressionNodeConversion}. + * + * @param transformer must not be {@literal null}. + */ + public ValueRetrievingNodeConversion(AggregationExpressionTransformer transformer) { + super(transformer); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext) + */ + @Override + protected Object convert(AggregationExpressionTransformationContext context) { + return context.getCurrentNode().getValue(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode) + */ + @Override + protected boolean supports(ExpressionNode node) { + return node.isOfType(InlineMap.class) || node.isOfType(InlineList.class) + || node.isOfType(ConstructorReference.class); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java index b323b9cf3b..01c7c18100 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -23,12 +23,14 @@ import org.springframework.expression.spel.ast.Literal; import org.springframework.expression.spel.ast.MethodReference; import org.springframework.expression.spel.ast.Operator; +import org.springframework.expression.spel.ast.OperatorNot; import org.springframework.util.Assert; /** * A value object for nodes in an expression. Allows iterating ove potentially available child {@link ExpressionNode}s. * * @author Oliver Gierke + * @author Christoph Strobl */ public class ExpressionNode implements Iterable { @@ -79,6 +81,10 @@ public static ExpressionNode from(SpelNode node, ExpressionState state) { return new LiteralNode((Literal) node, state); } + if (node instanceof OperatorNot) { + return new NotOperatorNode((OperatorNot) node, state); + } + return new ExpressionNode(node, state); } @@ -122,6 +128,16 @@ public boolean isMathematicalOperation() { return false; } + /** + * Returns whether the {@link ExpressionNode} is a logical conjunction operation like {@code &&, ||}. + * + * @return + * @since 1.10 + */ + public boolean isLogicalOperator() { + return false; + } + /** * Returns whether the {@link ExpressionNode} is a literal. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java index 68c53860f3..1f55294524 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -15,7 +15,12 @@ */ package org.springframework.data.mongodb.core.spel; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.ast.BooleanLiteral; import org.springframework.expression.spel.ast.FloatLiteral; import org.springframework.expression.spel.ast.IntLiteral; import org.springframework.expression.spel.ast.Literal; @@ -26,13 +31,29 @@ /** * A node representing a literal in an expression. - * + * * @author Oliver Gierke + * @author Christoph Strobl */ public class LiteralNode extends ExpressionNode { + private static final Set> SUPPORTED_LITERAL_TYPES; private final Literal literal; + static { + + Set> supportedTypes = new HashSet>(7, 1); + supportedTypes.add(BooleanLiteral.class); + supportedTypes.add(FloatLiteral.class); + supportedTypes.add(IntLiteral.class); + supportedTypes.add(LongLiteral.class); + supportedTypes.add(NullLiteral.class); + supportedTypes.add(RealLiteral.class); + supportedTypes.add(StringLiteral.class); + + SUPPORTED_LITERAL_TYPES = Collections.unmodifiableSet(supportedTypes); + } + /** * Creates a new {@link LiteralNode} from the given {@link Literal} and {@link ExpressionState}. * @@ -66,7 +87,6 @@ public boolean isUnaryMinus(ExpressionNode parent) { */ @Override public boolean isLiteral() { - return literal instanceof FloatLiteral || literal instanceof RealLiteral || literal instanceof IntLiteral - || literal instanceof LongLiteral || literal instanceof StringLiteral || literal instanceof NullLiteral; + return SUPPORTED_LITERAL_TYPES.contains(literal.getClass()); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java index 12dd51d0f8..18ffc5a441 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -15,108 +15,132 @@ */ package org.springframework.data.mongodb.core.spel; -import org.springframework.expression.spel.ExpressionState; -import org.springframework.expression.spel.ast.MethodReference; +import static org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference.*; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.ast.MethodReference; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + /** * An {@link ExpressionNode} representing a method reference. * * @author Oliver Gierke * @author Thomas Darimont * @author Sebastien Gerard + * @author Christoph Strobl */ public class MethodReferenceNode extends ExpressionNode { - private static final Map FUNCTIONS; + private static final Map FUNCTIONS; static { - Map map = new HashMap(); - map.put("and", "$and"); // Returns true only when all its expressions evaluate to true. - map.put("or", "$or"); // Returns true when any of its expressions evaluates to true. - map.put("not", "$not"); // Returns the boolean value that is the opposite of its argument expression. + Map map = new HashMap(); - map.put("setEquals", "$setEquals"); // Returns true if the input sets have the same distinct elements. - map.put("setIntersection", "$setIntersection"); // Returns a set with elements that appear in all of the input sets. - map.put("setUnion", "$setUnion"); // Returns a set with elements that appear in any of the input sets. - map.put("setDifference", "$setDifference"); // Returns a set with elements that appear in the 1st set but not in the + // BOOLEAN OPERATORS + map.put("and", arrayArgumentAggregationMethodReference().forOperator("$and")); + map.put("or", arrayArgumentAggregationMethodReference().forOperator("$or")); + map.put("not", arrayArgumentAggregationMethodReference().forOperator("$not")); + + // SET OPERATORS + map.put("setEquals", arrayArgumentAggregationMethodReference().forOperator("$setEquals")); + map.put("setIntersection", arrayArgumentAggregationMethodReference().forOperator("$setIntersection")); + map.put("setUnion", arrayArgumentAggregationMethodReference().forOperator("$setUnion")); + map.put("setDifference", arrayArgumentAggregationMethodReference().forOperator("$setDifference")); // 2nd. - map.put("setIsSubset", "$setIsSubset"); // Returns true if all elements of the 1st set appear in the 2nd set. - map.put("anyElementTrue", "$anyElementTrue"); // Returns whether any elements of a set evaluate to true. - map.put("allElementsTrue", "$allElementsTrue"); // Returns whether no element of a set evaluates to false. - - map.put("cmp", "$cmp"); // Returns: 0 if the two values are equivalent, 1 if the first value is greater than the - // second, and -1 if the first value is less than the second. - map.put("eq", "$eq"); // Returns true if the values are equivalent. - map.put("gt", "$gt"); // Returns true if the first value is greater than the second. - map.put("gte", "$gte"); // Returns true if the first value is greater than or equal to the second. - map.put("lt", "$lt"); // Returns true if the first value is less than the second. - map.put("lte", "$lte"); // Returns true if the first value is less than or equal to the second. - map.put("ne", "$ne"); // Returns true if the values are not equivalent. - - map.put("abs", "$abs"); // Returns the absolute value of a number.; - map.put("add", "$add"); // Adds numbers to return the sum, or adds numbers and a date to return a new date. - map.put("ceil", "$ceil"); // Returns the smallest integer greater than or equal to the specified number. - map.put("divide", "$divide"); // Returns the result of dividing the first number by the second. - map.put("exp", "$exp"); // Raises e to the specified exponent. - map.put("floor", "$floor"); // Returns the largest integer less than or equal to the specified number. - map.put("ln", "$ln"); // Calculates the natural log of a number. - map.put("log", "$log"); // Calculates the log of a number in the specified base. - map.put("log10", "$log10"); // Calculates the log base 10 of a number. - map.put("mod", "$mod"); // Returns the remainder of the first number divided by the second. - map.put("multiply", "$multiply"); // Multiplies numbers to return the product. - map.put("pow", "$pow"); // Raises a number to the specified exponent. - map.put("sqrt", "$sqrt"); // Calculates the square root. - map.put("subtract", "$subtract"); // Returns the result of subtracting the second value from the first. If the - // two values are numbers, return the difference. If the two values are dates, return the difference in - // milliseconds. - map.put("trunc", "$trunc"); // Truncates a number to its integer. - - map.put("concat", "$concat"); // Concatenates two strings. - map.put("substr", "$substr"); // Takes a string and returns portion of that string. - map.put("toLower", "$toLower"); // Converts a string to lowercase. - map.put("toUpper", "$toUpper"); // Converts a string to uppercase. - map.put("strcasecmp", "$strcasecmp"); // Compares two strings and returns an integer that reflects the comparison. - - map.put("meta", "$meta"); // Access text search metadata. - - map.put("arrayElemAt", "$arrayElemAt"); // Returns the element at the specified array index. - map.put("concatArrays", "$concatArrays"); // Concatenates arrays to return the concatenated array. - map.put("filter", "$filter"); // Selects a subset of the array to return an array with only the elements that - // match the filter condition. - map.put("isArray", "$isArray"); // Determines if the operand is an array. Returns a boolean. - map.put("size", "$size"); // Returns the number of elements in the array. - map.put("slice", "$slice"); // Returns a subset of an array. - - map.put("map", "$map"); // Applies a subexpression to each element of an array and returns the array of - // resulting values in order. - map.put("let", "$let"); // Defines variables for use within the scope of a subexpression and returns the result - // of the subexpression. - - map.put("literal", "$literal"); // Return a value without parsing. - - map.put("dayOfYear", "$dayOfYear"); // Converts a date to a number between 1 and 366. - map.put("dayOfMonth", "$dayOfMonth"); // Converts a date to a number between 1 and 31. - map.put("dayOfWeek", "$dayOfWeek"); // Converts a date to a number between 1 and 7. - map.put("year", "$year"); // Converts a date to the full year. - map.put("month", "$month"); // Converts a date into a number between 1 and 12. - map.put("week", "$week"); // Converts a date into a number between 0 and 53 - map.put("hour", "$hour"); // Converts a date into a number between 0 and 23. - map.put("minute", "$minute"); // Converts a date into a number between 0 and 59. - map.put("second", "$second"); // Converts a date into a number between 0 and 59. May be 60 to account for leap - // seconds. - map.put("millisecond", "$millisecond"); // Returns the millisecond portion of a date as an integer between 0 and - // 999. - map.put("dateToString", "$dateToString"); // Returns the date as a formatted string. - - map.put("cond", "$cond"); // A ternary operator that evaluates one expression, and depending on the result, - // returns the value of one of the other two expressions. - map.put("ifNull", "$ifNull"); // Returns either the non-null result of the first expression or the result of the - // second expression if the first expression results in a null result. + map.put("setIsSubset", arrayArgumentAggregationMethodReference().forOperator("$setIsSubset")); + map.put("anyElementTrue", arrayArgumentAggregationMethodReference().forOperator("$anyElementTrue")); + map.put("allElementsTrue", arrayArgumentAggregationMethodReference().forOperator("$allElementsTrue")); + + // COMPARISON OPERATORS + map.put("cmp", arrayArgumentAggregationMethodReference().forOperator("$cmp")); + map.put("eq", arrayArgumentAggregationMethodReference().forOperator("$eq")); + map.put("gt", arrayArgumentAggregationMethodReference().forOperator("$gt")); + map.put("gte", arrayArgumentAggregationMethodReference().forOperator("$gte")); + map.put("lt", arrayArgumentAggregationMethodReference().forOperator("$lt")); + map.put("lte", arrayArgumentAggregationMethodReference().forOperator("$lte")); + map.put("ne", arrayArgumentAggregationMethodReference().forOperator("$ne")); + + // ARITHMETIC OPERATORS + map.put("abs", singleArgumentAggregationMethodReference().forOperator("$abs")); + map.put("add", arrayArgumentAggregationMethodReference().forOperator("$add")); + map.put("ceil", singleArgumentAggregationMethodReference().forOperator("$ceil")); + map.put("divide", arrayArgumentAggregationMethodReference().forOperator("$divide")); + map.put("exp", singleArgumentAggregationMethodReference().forOperator("$exp")); + map.put("floor", singleArgumentAggregationMethodReference().forOperator("$floor")); + map.put("ln", singleArgumentAggregationMethodReference().forOperator("$ln")); + map.put("log", arrayArgumentAggregationMethodReference().forOperator("$log")); + map.put("log10", singleArgumentAggregationMethodReference().forOperator("$log10")); + map.put("mod", arrayArgumentAggregationMethodReference().forOperator("$mod")); + map.put("multiply", arrayArgumentAggregationMethodReference().forOperator("$multiply")); + map.put("pow", arrayArgumentAggregationMethodReference().forOperator("$pow")); + map.put("sqrt", singleArgumentAggregationMethodReference().forOperator("$sqrt")); + map.put("subtract", arrayArgumentAggregationMethodReference().forOperator("$subtract")); + map.put("trunc", singleArgumentAggregationMethodReference().forOperator("$trunc")); + + // STRING OPERATORS + map.put("concat", arrayArgumentAggregationMethodReference().forOperator("$concat")); + map.put("strcasecmp", arrayArgumentAggregationMethodReference().forOperator("$strcasecmp")); + map.put("substr", arrayArgumentAggregationMethodReference().forOperator("$substr")); + map.put("toLower", singleArgumentAggregationMethodReference().forOperator("$toLower")); + map.put("toUpper", singleArgumentAggregationMethodReference().forOperator("$toUpper")); + map.put("strcasecmp", arrayArgumentAggregationMethodReference().forOperator("$strcasecmp")); + + // TEXT SEARCH OPERATORS + map.put("meta", singleArgumentAggregationMethodReference().forOperator("$meta")); + + // ARRAY OPERATORS + map.put("arrayElemAt", arrayArgumentAggregationMethodReference().forOperator("$arrayElemAt")); + map.put("concatArrays", arrayArgumentAggregationMethodReference().forOperator("$concatArrays")); + map.put("filter", mapArgumentAggregationMethodReference().forOperator("$filter") // + .mappingParametersTo("input", "as", "cond")); + map.put("isArray", singleArgumentAggregationMethodReference().forOperator("$isArray")); + map.put("size", singleArgumentAggregationMethodReference().forOperator("$size")); + map.put("slice", arrayArgumentAggregationMethodReference().forOperator("$slice")); + + // VARIABLE OPERATORS + map.put("map", mapArgumentAggregationMethodReference().forOperator("$map") // + .mappingParametersTo("input", "as", "in")); + map.put("let", mapArgumentAggregationMethodReference().forOperator("$let").mappingParametersTo("vars", "in")); + + // LITERAL OPERATORS + map.put("literal", singleArgumentAggregationMethodReference().forOperator("$literal")); + + // DATE OPERATORS + map.put("dayOfYear", singleArgumentAggregationMethodReference().forOperator("$dayOfYear")); + map.put("dayOfMonth", singleArgumentAggregationMethodReference().forOperator("$dayOfMonth")); + map.put("dayOfWeek", singleArgumentAggregationMethodReference().forOperator("$dayOfWeek")); + map.put("year", singleArgumentAggregationMethodReference().forOperator("$year")); + map.put("month", singleArgumentAggregationMethodReference().forOperator("$month")); + map.put("week", singleArgumentAggregationMethodReference().forOperator("$week")); + map.put("hour", singleArgumentAggregationMethodReference().forOperator("$hour")); + map.put("minute", singleArgumentAggregationMethodReference().forOperator("$minute")); + map.put("second", singleArgumentAggregationMethodReference().forOperator("$second")); + map.put("millisecond", singleArgumentAggregationMethodReference().forOperator("$millisecond")); + map.put("dateToString", mapArgumentAggregationMethodReference().forOperator("$dateToString") // + .mappingParametersTo("format", "date")); + + // CONDITIONAL OPERATORS + map.put("cond", mapArgumentAggregationMethodReference().forOperator("$cond") // + .mappingParametersTo("if", "then", "else")); + map.put("ifNull", arrayArgumentAggregationMethodReference().forOperator("$ifNull")); + + // GROUP OPERATORS + map.put("sum", arrayArgumentAggregationMethodReference().forOperator("$sum")); + map.put("avg", arrayArgumentAggregationMethodReference().forOperator("$avg")); + map.put("first", singleArgumentAggregationMethodReference().forOperator("$first")); + map.put("last", singleArgumentAggregationMethodReference().forOperator("$last")); + map.put("max", arrayArgumentAggregationMethodReference().forOperator("$max")); + map.put("min", arrayArgumentAggregationMethodReference().forOperator("$min")); + map.put("push", singleArgumentAggregationMethodReference().forOperator("$push")); + map.put("addToSet", singleArgumentAggregationMethodReference().forOperator("$addToSet")); + map.put("stdDevPop", arrayArgumentAggregationMethodReference().forOperator("$stdDevPop")); + map.put("stdDevSamp", arrayArgumentAggregationMethodReference().forOperator("$stdDevSamp")); FUNCTIONS = Collections.unmodifiableMap(map); } @@ -127,10 +151,144 @@ public class MethodReferenceNode extends ExpressionNode { /** * Returns the name of the method. + * + * @Deprecated since 1.10. Please use {@link #getMethodReference()}. */ + @Deprecated public String getMethodName() { + + AggregationMethodReference methodReference = getMethodReference(); + return methodReference != null ? methodReference.getMongoOperator() : null; + } + + /** + * Return the {@link AggregationMethodReference}. + * + * @return can be {@literal null}. + * @since 1.10 + */ + public AggregationMethodReference getMethodReference() { + String name = getName(); String methodName = name.substring(0, name.indexOf('(')); return FUNCTIONS.get(methodName); } + + /** + * @author Christoph Strobl + * @since 1.10 + */ + public static final class AggregationMethodReference { + + private final String mongoOperator; + private final ArgumentType argumentType; + private final String[] argumentMap; + + /** + * Creates new {@link AggregationMethodReference}. + * + * @param mongoOperator can be {@literal null}. + * @param argumentType can be {@literal null}. + * @param argumentMap can be {@literal null}. + */ + private AggregationMethodReference(String mongoOperator, ArgumentType argumentType, String[] argumentMap) { + + this.mongoOperator = mongoOperator; + this.argumentType = argumentType; + this.argumentMap = argumentMap; + } + + /** + * Get the MongoDB specific operator. + * + * @return can be {@literal null}. + */ + public String getMongoOperator() { + return this.mongoOperator; + } + + /** + * Get the {@link ArgumentType} used by the MongoDB. + * + * @return never {@literal null}. + */ + public ArgumentType getArgumentType() { + return this.argumentType; + } + + /** + * Get the property names in order order of appearance in resulting operation. + * + * @return never {@literal null}. + */ + public String[] getArgumentMap() { + return argumentMap != null ? argumentMap : new String[] {}; + } + + /** + * Create a new {@link AggregationMethodReference} for a {@link ArgumentType#SINGLE} argument. + * + * @return never {@literal null}. + */ + static AggregationMethodReference singleArgumentAggregationMethodReference() { + return new AggregationMethodReference(null, ArgumentType.SINGLE, null); + } + + /** + * Create a new {@link AggregationMethodReference} for an {@link ArgumentType#ARRAY} argument. + * + * @return never {@literal null}. + */ + static AggregationMethodReference arrayArgumentAggregationMethodReference() { + return new AggregationMethodReference(null, ArgumentType.ARRAY, null); + } + + /** + * Create a new {@link AggregationMethodReference} for a {@link ArgumentType#MAP} argument. + * + * @return never {@literal null}. + */ + static AggregationMethodReference mapArgumentAggregationMethodReference() { + return new AggregationMethodReference(null, ArgumentType.MAP, null); + } + + /** + * Create a new {@link AggregationMethodReference} for a given {@literal aggregationExpressionOperator} reusing + * previously set arguments. + * + * @param aggregationExpressionOperator should not be {@literal null}. + * @return never {@literal null}. + */ + AggregationMethodReference forOperator(String aggregationExpressionOperator) { + return new AggregationMethodReference(aggregationExpressionOperator, argumentType, argumentMap); + } + + /** + * Create a new {@link AggregationMethodReference} for mapping actual parameters within the AST to the given + * {@literal aggregationExpressionProperties} reusing previously set arguments.
+ * NOTE: Can only be applied to {@link AggregationMethodReference} of type + * {@link ArgumentType#MAP}. + * + * @param aggregationExpressionProperties should not be {@literal null}. + * @return never {@literal null}. + * @throws IllegalArgumentException + */ + AggregationMethodReference mappingParametersTo(String... aggregationExpressionProperties) { + + Assert.isTrue(ObjectUtils.nullSafeEquals(argumentType, ArgumentType.MAP), + "Parameter mapping can only be applied to AggregationMethodReference with MAPPED ArgumentType."); + return new AggregationMethodReference(mongoOperator, argumentType, aggregationExpressionProperties); + } + + /** + * The actual argument type to use when mapping parameters to MongoDB specific format. + * + * @author Christoph Strobl + * @since 1.10 + */ + public enum ArgumentType { + SINGLE, ARRAY, MAP + } + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java new file mode 100644 index 0000000000..1809307f51 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java @@ -0,0 +1,45 @@ +/* + * 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.spel; + +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelNode; +import org.springframework.expression.spel.ast.OperatorNot; + +/** + * @author Christoph Strobl + * @since 1.10 + */ +public class NotOperatorNode extends ExpressionNode { + + private final OperatorNot operatorNode; + + /** + * Creates a new {@link ExpressionNode} from the given {@link OperatorNot} and {@link ExpressionState}. + * + * @param node must not be {@literal null}. + * @param state must not be {@literal null}. + */ + protected NotOperatorNode(OperatorNot node, ExpressionState state) { + + super(node, state); + this.operatorNode = node; + } + + public String getMongoOperator() { + return "$not"; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java index 4f8d8f3d1e..948c9544fe 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-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. @@ -17,41 +17,76 @@ import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.ast.OpAnd; import org.springframework.expression.spel.ast.OpDivide; +import org.springframework.expression.spel.ast.OpEQ; +import org.springframework.expression.spel.ast.OpGE; +import org.springframework.expression.spel.ast.OpGT; +import org.springframework.expression.spel.ast.OpLE; +import org.springframework.expression.spel.ast.OpLT; import org.springframework.expression.spel.ast.OpMinus; import org.springframework.expression.spel.ast.OpModulus; import org.springframework.expression.spel.ast.OpMultiply; +import org.springframework.expression.spel.ast.OpNE; +import org.springframework.expression.spel.ast.OpOr; import org.springframework.expression.spel.ast.OpPlus; import org.springframework.expression.spel.ast.Operator; +import org.springframework.expression.spel.ast.OperatorPower; /** * An {@link ExpressionNode} representing an operator. * * @author Oliver Gierke * @author Thomas Darimont + * @author Christoph Strobl */ public class OperatorNode extends ExpressionNode { private static final Map OPERATORS; + private static final Set SUPPORTED_MATH_OPERATORS; static { - Map map = new HashMap(6); + Map map = new HashMap(14, 1); map.put("+", "$add"); map.put("-", "$subtract"); map.put("*", "$multiply"); map.put("/", "$divide"); map.put("%", "$mod"); + map.put("^", "$pow"); + map.put("==", "$eq"); + map.put("!=", "$ne"); + map.put(">", "$gt"); + map.put(">=", "$gte"); + map.put("<", "$lt"); + map.put("<=", "$lte"); - map.put("and", "and"); - map.put("or", "or"); - map.put("!", "not"); + map.put("and", "$and"); + map.put("or", "$or"); OPERATORS = Collections.unmodifiableMap(map); + + Set set = new HashSet(12, 1); + set.add(OpMinus.class); + set.add(OpPlus.class); + set.add(OpMultiply.class); + set.add(OpDivide.class); + set.add(OpModulus.class); + set.add(OperatorPower.class); + set.add(OpNE.class); + set.add(OpEQ.class); + set.add(OpGT.class); + set.add(OpGE.class); + set.add(OpLT.class); + set.add(OpLE.class); + + SUPPORTED_MATH_OPERATORS = Collections.unmodifiableSet(set); } private final Operator operator; @@ -73,8 +108,16 @@ public class OperatorNode extends ExpressionNode { */ @Override public boolean isMathematicalOperation() { - return operator instanceof OpMinus || operator instanceof OpPlus || operator instanceof OpMultiply - || operator instanceof OpDivide || operator instanceof OpModulus; + return SUPPORTED_MATH_OPERATORS.contains(operator.getClass()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.spel.ExpressionNode#isConjunctionOperator() + */ + @Override + public boolean isLogicalOperator() { + return operator instanceof OpOr || operator instanceof OpAnd; } /** @@ -92,6 +135,13 @@ public boolean isUnaryOperator() { * @return */ public String getMongoOperator() { + + if (!OPERATORS.containsKey(operator.getOperatorName())) { + throw new IllegalArgumentException(String.format( + "Unknown operator name. Cannot translate %s into its MongoDB aggregation function representation.", + operator.getOperatorName())); + } + return OPERATORS.get(operator.getOperatorName()); } 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 ad72d97fa4..770145a80a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-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. @@ -29,6 +29,7 @@ * @see DATAMONGO-774 * @author Thomas Darimont * @author Oliver Gierke + * @author Christoph Strobl */ public class SpelExpressionTransformerUnitTests { @@ -69,7 +70,7 @@ public void shouldSupportKnownOperands() { @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionOnUnknownOperand() { - transform("a ^ 1"); + transform("a++"); } @Test @@ -80,17 +81,15 @@ public void shouldRenderSumExpression() { @Test public void shouldRenderFormula() { - assertThat( - transform("(netPrice + surCharge) * taxrate + 42"), - is("{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); + assertThat(transform("(netPrice + surCharge) * taxrate + 42"), is( + "{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); } @Test public void shouldRenderFormulaInCurlyBrackets() { - assertThat( - transform("{(netPrice + surCharge) * taxrate + 42}"), - is("{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); + assertThat(transform("{(netPrice + surCharge) * taxrate + 42}"), is( + "{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); } @Test @@ -136,9 +135,8 @@ public void shouldRenderComplexExpression1() { @Test public void shouldRenderComplexExpression2() { - assertThat( - transform("(q + 1 + 4 - 5) / (q + 1 + 3 + 4)"), - is("{ \"$divide\" : [ { \"$subtract\" : [ { \"$add\" : [ \"$q\" , 1 , 4]} , 5]} , { \"$add\" : [ \"$q\" , 1 , 3 , 4]}]}")); + assertThat(transform("(q + 1 + 4 - 5) / (q + 1 + 3 + 4)"), is( + "{ \"$divide\" : [ { \"$subtract\" : [ { \"$add\" : [ \"$q\" , 1 , 4]} , 5]} , { \"$add\" : [ \"$q\" , 1 , 3 , 4]}]}")); } @Test @@ -195,15 +193,695 @@ public void shouldRenderCompoundExpressionsWithOnlyFieldReferences() { assertThat(transform("a.b + a.c"), is("{ \"$add\" : [ \"$a.b\" , \"$a.c\"]}")); } + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeAnd() { + assertThat(transform("and(a, b)"), is("{ \"$and\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeOr() { + assertThat(transform("or(a, b)"), is("{ \"$or\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeNot() { + assertThat(transform("not(a)"), is("{ \"$not\" : [ \"$a\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSetEquals() { + assertThat(transform("setEquals(a, b)"), is("{ \"$setEquals\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSetEqualsForArrays() { + assertThat(transform("setEquals(new int[]{1,2,3}, new int[]{4,5,6})"), + is("{ \"$setEquals\" : [ [ 1 , 2 , 3] , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSetEqualsMixedArrays() { + assertThat(transform("setEquals(a, new int[]{4,5,6})"), is("{ \"$setEquals\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceSetIntersection() { + assertThat(transform("setIntersection(a, new int[]{4,5,6})"), + is("{ \"$setIntersection\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ @Test - public void shouldRenderStringFunctions() { + public void shouldRenderMethodReferenceSetUnion() { + assertThat(transform("setUnion(a, new int[]{4,5,6})"), is("{ \"$setUnion\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } - assertThat(transform("concat(a, b)"), is("{ \"$concat\" : [ \"$a\" , \"$b\"]}")); - assertThat(transform("substr(a, 1, 2)"), is("{ \"$substr\" : [ \"$a\" , 1 , 2]}")); + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceSeDifference() { + assertThat(transform("setDifference(a, new int[]{4,5,6})"), is("{ \"$setDifference\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceSetIsSubset() { + assertThat(transform("setIsSubset(a, new int[]{4,5,6})"), is("{ \"$setIsSubset\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceAnyElementTrue() { + assertThat(transform("anyElementTrue(a)"), is("{ \"$anyElementTrue\" : [ \"$a\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceAllElementsTrue() { + assertThat(transform("allElementsTrue(a, new int[]{4,5,6})"), + is("{ \"$allElementsTrue\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceCmp() { + assertThat(transform("cmp(a, 250)"), is("{ \"$cmp\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceEq() { + assertThat(transform("eq(a, 250)"), is("{ \"$eq\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceGt() { + assertThat(transform("gt(a, 250)"), is("{ \"$gt\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceGte() { + assertThat(transform("gte(a, 250)"), is("{ \"$gte\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLt() { + assertThat(transform("lt(a, 250)"), is("{ \"$lt\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLte() { + assertThat(transform("lte(a, 250)"), is("{ \"$lte\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNe() { + assertThat(transform("ne(a, 250)"), is("{ \"$ne\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceAbs() { + assertThat(transform("abs(1)"), is("{ \"$abs\" : 1}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceAdd() { + assertThat(transform("add(a, 250)"), is("{ \"$add\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceCeil() { + assertThat(transform("ceil(7.8)"), is("{ \"$ceil\" : 7.8}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceDivide() { + assertThat(transform("divide(a, 250)"), is("{ \"$divide\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceExp() { + assertThat(transform("exp(2)"), is("{ \"$exp\" : 2}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceFloor() { + assertThat(transform("floor(2)"), is("{ \"$floor\" : 2}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLn() { + assertThat(transform("ln(2)"), is("{ \"$ln\" : 2}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLog() { + assertThat(transform("log(100, 10)"), is("{ \"$log\" : [ 100 , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLog10() { + assertThat(transform("log10(100)"), is("{ \"$log10\" : 100}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeMod() { + assertThat(transform("mod(a, b)"), is("{ \"$mod\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeMultiply() { + assertThat(transform("multiply(a, b)"), is("{ \"$multiply\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodePow() { + assertThat(transform("pow(a, 2)"), is("{ \"$pow\" : [ \"$a\" , 2]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceSqrt() { + assertThat(transform("sqrt(2)"), is("{ \"$sqrt\" : 2}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSubtract() { + assertThat(transform("subtract(a, b)"), is("{ \"$subtract\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceTrunc() { + assertThat(transform("trunc(2.1)"), is("{ \"$trunc\" : 2.1}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeConcat() { + assertThat(transform("concat(a, b, 'c')"), is("{ \"$concat\" : [ \"$a\" , \"$b\" , \"c\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSubstrc() { + assertThat(transform("substr(a, 0, 1)"), is("{ \"$substr\" : [ \"$a\" , 0 , 1]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceToLower() { + assertThat(transform("toLower(a)"), is("{ \"$toLower\" : \"$a\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceToUpper() { + assertThat(transform("toUpper(a)"), is("{ \"$toUpper\" : \"$a\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeStrCaseCmp() { assertThat(transform("strcasecmp(a, b)"), is("{ \"$strcasecmp\" : [ \"$a\" , \"$b\"]}")); - assertThat(transform("toLower(a)"), is("{ \"$toLower\" : [ \"$a\"]}")); - assertThat(transform("toUpper(a)"), is("{ \"$toUpper\" : [ \"$a\"]}")); - assertThat(transform("toUpper(toLower(a))"), is("{ \"$toUpper\" : [ { \"$toLower\" : [ \"$a\"]}]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceMeta() { + assertThat(transform("meta('textScore')"), is("{ \"$meta\" : \"textScore\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeArrayElemAt() { + assertThat(transform("arrayElemAt(a, 10)"), is("{ \"$arrayElemAt\" : [ \"$a\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeConcatArrays() { + assertThat(transform("concatArrays(a, b, c)"), is("{ \"$concatArrays\" : [ \"$a\" , \"$b\" , \"$c\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeFilter() { + assertThat(transform("filter(a, 'num', '$$num' > 10)"), + is("{ \"$filter\" : { \"input\" : \"$a\" , \"as\" : \"num\" , \"cond\" : { \"$gt\" : [ \"$$num\" , 10]}}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceIsArray() { + assertThat(transform("isArray(a)"), is("{ \"$isArray\" : \"$a\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceIsSize() { + assertThat(transform("size(a)"), is("{ \"$size\" : \"$a\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSlice() { + assertThat(transform("slice(a, 10)"), is("{ \"$slice\" : [ \"$a\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeMap() { + assertThat(transform("map(quizzes, 'grade', '$$grade' + 2)"), is( + "{ \"$map\" : { \"input\" : \"$quizzes\" , \"as\" : \"grade\" , \"in\" : { \"$add\" : [ \"$$grade\" , 2]}}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeLet() { + assertThat(transform("let({low:1, high:'$$low'}, gt('$$low', '$$high'))"), is( + "{ \"$let\" : { \"vars\" : { \"low\" : 1 , \"high\" : \"$$low\"} , \"in\" : { \"$gt\" : [ \"$$low\" , \"$$high\"]}}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLiteral() { + assertThat(transform("literal($1)"), is("{ \"$literal\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceDayOfYear() { + assertThat(transform("dayOfYear($1)"), is("{ \"$dayOfYear\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceDayOfMonth() { + assertThat(transform("dayOfMonth($1)"), is("{ \"$dayOfMonth\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceDayOfWeek() { + assertThat(transform("dayOfWeek($1)"), is("{ \"$dayOfWeek\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceYear() { + assertThat(transform("year($1)"), is("{ \"$year\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceMonth() { + assertThat(transform("month($1)"), is("{ \"$month\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceWeek() { + assertThat(transform("week($1)"), is("{ \"$week\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceHour() { + assertThat(transform("hour($1)"), is("{ \"$hour\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceMinute() { + assertThat(transform("minute($1)"), is("{ \"$minute\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceSecond() { + assertThat(transform("second($1)"), is("{ \"$second\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceMillisecond() { + assertThat(transform("millisecond($1)"), is("{ \"$millisecond\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceDateToString() { + assertThat(transform("dateToString('%Y-%m-%d', $date)"), + is("{ \"$dateToString\" : { \"format\" : \"%Y-%m-%d\" , \"date\" : \"$date\"}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceCond() { + assertThat(transform("cond(qty > 250, 30, 20)"), + is("{ \"$cond\" : { \"if\" : { \"$gt\" : [ \"$qty\" , 250]} , \"then\" : 30 , \"else\" : 20}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeIfNull() { + assertThat(transform("ifNull(a, 10)"), is("{ \"$ifNull\" : [ \"$a\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSum() { + assertThat(transform("sum(a, b)"), is("{ \"$sum\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeAvg() { + assertThat(transform("avg(a, b)"), is("{ \"$avg\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceFirst() { + assertThat(transform("first($1)"), is("{ \"$first\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLast() { + assertThat(transform("last($1)"), is("{ \"$last\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeMax() { + assertThat(transform("max(a, b)"), is("{ \"$max\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + 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\"}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceAddToSet() { + assertThat(transform("addToSet($1)"), is("{ \"$addToSet\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeStdDevPop() { + assertThat(transform("stdDevPop(scores.score)"), is("{ \"$stdDevPop\" : [ \"$scores.score\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeStdDevSamp() { + assertThat(transform("stdDevSamp(age)"), is("{ \"$stdDevSamp\" : [ \"$age\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeEq() { + assertThat(transform("foo == 10"), is("{ \"$eq\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeNe() { + assertThat(transform("foo != 10"), is("{ \"$ne\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeGt() { + assertThat(transform("foo > 10"), is("{ \"$gt\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeGte() { + assertThat(transform("foo >= 10"), is("{ \"$gte\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeLt() { + assertThat(transform("foo < 10"), is("{ \"$lt\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeLte() { + assertThat(transform("foo <= 10"), is("{ \"$lte\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodePow() { + assertThat(transform("foo^2"), is("{ \"$pow\" : [ \"$foo\" , 2]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeOr() { + assertThat(transform("true || false"), is("{ \"$or\" : [ true , false]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderComplexOperationNodeOr() { + assertThat(transform("1+2 || concat(a, b) || true"), + is("{ \"$or\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeAnd() { + assertThat(transform("true && false"), is("{ \"$and\" : [ true , false]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderComplexOperationNodeAnd() { + assertThat(transform("1+2 && concat(a, b) && true"), + is("{ \"$and\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderNotCorrectly() { + assertThat(transform("!true"), is("{ \"$not\" : [ true]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderComplexNotCorrectly() { + assertThat(transform("!(foo > 10)"), is("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}")); } private String transform(String expression, Object... params) { diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 0dad55c133..c375facbba 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1745,6 +1745,49 @@ will be translated into the following projection expression part: Have a look at an example in more context in <> and <>. You can find more usage examples for supported SpEL expression constructs in `SpelExpressionTransformerUnitTests`. +.Supported SpEL transformations +[cols="2"] +|=== +| a == b +| { $eq : [$a, $b] } +| a != b +| { $ne : [$a , $b] } +| a > b +| { $gt : [$a, $b] } +| a >= b +| { $gte : [$a, $b] } +| a < b +| { $lt : [$a, $b] } +| a <= b +| { $lte : [$a, $b] } +| a + b +| { $add : [$a, $b] } +| a - b +| { $subtract : [$a, $b] } +| a * b +| { $multiply : [$a, $b] } +| a / b +| { $divide : [$a, $b] } +| a^b +| { $pow : [$a, $b] } +| a % b +| { $mod : [$a, $b] } +| a && b +| { $and : [$a, $b] } +| a \|\| b +| { $or : [$a, $b] } +| !a +| { $not : [$a] } +|=== + +Next to the transformations shown in <> it is possible to use standard SpEL operations like `new` to eg. create arrays and reference expressions via their name followed by the arguments to use in brackets. + +[source,java] +---- +// { $setEquals : [$a, [5, 8, 13] ] } +.andExpression("setEquals(a, new int[]{5, 8, 13})"); +---- + [[mongo.aggregation.examples]] ==== Aggregation Framework Examples From 9059a77712796c83555bdfe32fa36123cbc7f53f Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 24 Nov 2016 13:16:35 +0100 Subject: [PATCH 010/118] DATAMONGO-1534 - Fix bulk operations missing to write type info. We now correctly convert entities into their MongoDB representation including type information via _class property. Original pull request: #415. --- .../mongodb/core/DefaultBulkOperations.java | 12 ++++++++++- ...DefaultBulkOperationsIntegrationTests.java | 20 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java index 40f3bf77c9..440301857a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java @@ -25,6 +25,7 @@ import org.springframework.data.util.Pair; import org.springframework.util.Assert; +import com.mongodb.BasicDBObject; import com.mongodb.BulkWriteException; import com.mongodb.BulkWriteOperation; import com.mongodb.BulkWriteRequestBuilder; @@ -38,6 +39,7 @@ * * @author Tobias Trelle * @author Oliver Gierke + * @author Christoph Strobl * @since 1.9 */ class DefaultBulkOperations implements BulkOperations { @@ -117,7 +119,15 @@ public BulkOperations insert(Object document) { Assert.notNull(document, "Document must not be null!"); - bulk.insert((DBObject) mongoOperations.getConverter().convertToMongoType(document)); + if (document instanceof DBObject) { + + bulk.insert((DBObject) document); + return this; + } + + DBObject sink = new BasicDBObject(); + mongoOperations.getConverter().write(document, sink); + bulk.insert(sink); return this; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java index 0f3f0b2043..26c09f53fa 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java @@ -46,6 +46,7 @@ * * @author Tobias Trelle * @author Oliver Gierke + * @author Christoph Strobl */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") @@ -270,6 +271,25 @@ public void mixedBulkOrderedWithList() { assertThat(result.getRemovedCount(), is(1)); } + /** + * @see DATAMONGO-1534 + */ + @Test + public void insertShouldConsiderInheritance() { + + SpecialDoc specialDoc = new SpecialDoc(); + specialDoc.id = "id-special"; + specialDoc.value = "normal-value"; + specialDoc.specialValue = "special-value"; + + createBulkOps(BulkMode.ORDERED).insert(Arrays.asList(specialDoc)).execute(); + + BaseDoc doc = operations.findOne(where("_id", specialDoc.id), BaseDoc.class, COLLECTION_NAME); + + assertThat(doc, notNullValue()); + assertThat(doc, instanceOf(SpecialDoc.class)); + } + private void testUpdate(BulkMode mode, boolean multi, int expectedUpdates) { BulkOperations bulkOps = createBulkOps(mode); From a79930145dbeb0d5ee701b64ac66d74ab999b7db Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 7 Nov 2016 15:23:23 +0100 Subject: [PATCH 011/118] DATAMONGO-1525 - Improved creation of empty collections, esp. EnumSet. We now use more type information to create a better empty collection in the first place. The previous algorithm always used an empty HashSet plus a subsequent conversion using the raw collection type. Especially the latter caused problems for EnumSets as the conversion into one requires the presence of component type information. We now use Spring's collection factory and more available type information to create a proper collection in the first place and only rely on a subsequent conversion for arrays. --- .../mongodb/core/convert/MappingMongoConverter.java | 8 ++++---- .../core/convert/MappingMongoConverterUnitTests.java | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 344170d9ab..4ffd911c3f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -892,10 +892,6 @@ private Object readCollectionOrArray(TypeInformation targetType, BasicDBList Class collectionType = targetType.getType(); - if (sourceValue.isEmpty()) { - return getPotentiallyConvertedSimpleRead(new HashSet(), collectionType); - } - TypeInformation componentType = targetType.getComponentType(); Class rawComponentType = componentType == null ? null : componentType.getType(); @@ -903,6 +899,10 @@ private Object readCollectionOrArray(TypeInformation targetType, BasicDBList Collection items = targetType.getType().isArray() ? new ArrayList() : CollectionFactory.createCollection(collectionType, rawComponentType, sourceValue.size()); + if (sourceValue.isEmpty()) { + return getPotentiallyConvertedSimpleRead(items, collectionType); + } + if (!DBRef.class.equals(rawComponentType) && isCollectionOfDbRefWhereBulkFetchIsPossible(sourceValue)) { return bulkReadAndConvertDBRefs((List) (List) (sourceValue), componentType, path, rawComponentType); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index f8d00bc8ba..a1530194a0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -2086,6 +2086,17 @@ public void readsPropertyFromNestedFieldCorrectly() { assertThat(result.sample, is("value")); } + /** + * @see DATAMONGO-1525 + */ + @Test + public void readsEmptyEnumSet() { + + DBObject source = new BasicDBObject("enumSet", new BasicDBList()); + + assertThat(converter.read(ClassWithEnumProperty.class, source).enumSet, is(EnumSet.noneOf(SampleEnum.class))); + } + static class GenericType { T content; } From 8c838e83505f7ab80f907a726f85f170f298f327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C4=B1rat=20KU=CC=88C=CC=A7U=CC=88K?= Date: Thu, 24 Nov 2016 20:56:27 +0100 Subject: [PATCH 012/118] DATAMONGO-1539 - Introduce @CountQuery and @DeleteQuery. Introducing dedicated annotations for manually defined count and delete queries to avoid misconfiguration and generally simplifying the declaration. Original pull request: 416. --- .../data/mongodb/repository/Count.java | 49 +++++++++++++++++++ .../data/mongodb/repository/Delete.java | 49 +++++++++++++++++++ ...tractPersonRepositoryIntegrationTests.java | 22 +++++++++ .../mongodb/repository/PersonRepository.java | 24 +++++++++ 4 files changed, 144 insertions(+) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Count.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Delete.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Count.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Count.java new file mode 100644 index 0000000000..b3b7aff69c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Count.java @@ -0,0 +1,49 @@ +/* + * Copyright 2011-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.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; + +/** + * Annotation to declare finder count queries directly on repository methods. Both attributes allow using a placeholder + * notation of {@code ?0}, {@code ?1} and so on. + * + * @see DATAMONGO-1539 + * + * @author Fırat KÜÇÜK + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +@Query(count = true) +public @interface Count { + + /** + * Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the + * method name then. + * + * Alias for {@link Query#value}. + * + * @return + */ + @AliasFor(annotation = Query.class) + String value() default ""; +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Delete.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Delete.java new file mode 100644 index 0000000000..7c3de4bbdc --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Delete.java @@ -0,0 +1,49 @@ +/* + * Copyright 2011-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.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; + +/** + * Annotation to declare finder delete queries directly on repository methods. Both attributes allow using a placeholder + * notation of {@code ?0}, {@code ?1} and so on. + * + * @see DATAMONGO-1539 + * + * @author Fırat KÜÇÜK + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +@Query(delete = true) +public @interface Delete { + + /** + * Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the + * method name then. + * + * Alias for {@link Query#value}. + * + * @return + */ + @AliasFor(annotation = Query.class) + String value() default ""; +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 84bad2d14c..f2189a1d50 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -65,6 +65,7 @@ * @author Thomas Darimont * @author Christoph Strobl * @author Mark Paluch + * @author Fırat KÜÇÜK */ @RunWith(SpringJUnit4ClassRunner.class) public abstract class AbstractPersonRepositoryIntegrationTests { @@ -1311,4 +1312,25 @@ public void findsPersonsByFirstnameNotLike() throws Exception { assertThat(result, not(hasItem(boyd))); } + /** + * @see DATAMONGO-1539 + */ + @Test + public void countsPersonsByFirstname() { + + long result = repository.countByThePersonsFirstname("Dave"); + assertThat(result, is(1L)); + } + + /** + * @see DATAMONGO-1539 + */ + @Test + public void deletesPersonsByFirstname() { + + repository.deleteByThePersonsFirstname("Dave"); + + long result = repository.countByThePersonsFirstname("Dave"); + assertThat(result, is(0L)); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index f9f2f1fbb5..fe98a3f695 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -42,6 +42,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Fırat KÜÇÜK */ public interface PersonRepository extends MongoRepository, QueryDslPredicateExecutor { @@ -367,4 +368,27 @@ Page findByCustomQueryLastnameAndAddressStreetInList(String lastname, Li */ @Query("{ firstname : :#{#firstname}}") List findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname); + + /** + * Returns the count of {@link Person} with the given firstname. Uses {@link Count} annotation to define the query + * to be executed. + * + * @see DATAMONGO-1539 + * + * @param firstname + * @return + */ + @Count(value = "{ 'firstname' : ?0 }") + long countByThePersonsFirstname(String firstname); + + /** + * Deletes {@link Person} entities with the given firstname. Uses {@link Delete} annotation to define the query + * to be executed. + * + * @see DATAMONGO-1539 + * + * @param firstname + */ + @Delete(value = "{ 'firstname' : ?0 }") + void deleteByThePersonsFirstname(String firstname); } From 9ff86feb4fa6377e41891f968825f46a04f3493f Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 2 Dec 2016 11:53:39 +0100 Subject: [PATCH 013/118] DATAMONGO-1539 - Polishing. Renamed @Count and @Delete to @CountQuery and @DeleteQuery. Minor polishing in test cases and test repository methods. JavaDoc, formatting. Original pull request: #416. --- .../{Count.java => CountQuery.java} | 13 ++++++------- .../{Delete.java => DeleteQuery.java} | 13 ++++++------- ...tractPersonRepositoryIntegrationTests.java | 19 ++++++++----------- .../mongodb/repository/PersonRepository.java | 12 +++++------- 4 files changed, 25 insertions(+), 32 deletions(-) rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/{Count.java => CountQuery.java} (89%) rename spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/{Delete.java => DeleteQuery.java} (88%) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Count.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/CountQuery.java similarity index 89% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Count.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/CountQuery.java index b3b7aff69c..c3341f3099 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Count.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/CountQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * 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. @@ -20,27 +20,26 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; + import org.springframework.core.annotation.AliasFor; /** * Annotation to declare finder count queries directly on repository methods. Both attributes allow using a placeholder * notation of {@code ?0}, {@code ?1} and so on. * - * @see DATAMONGO-1539 - * * @author Fırat KÜÇÜK + * @author Oliver Gierke + * @since 1.10 */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Documented @Query(count = true) -public @interface Count { +public @interface CountQuery { /** * Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the - * method name then. - * - * Alias for {@link Query#value}. + * method name then. Alias for {@link Query#value}. * * @return */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Delete.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/DeleteQuery.java similarity index 88% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Delete.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/DeleteQuery.java index 7c3de4bbdc..d33a18e950 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Delete.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/DeleteQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * 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. @@ -20,27 +20,26 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; + import org.springframework.core.annotation.AliasFor; /** * Annotation to declare finder delete queries directly on repository methods. Both attributes allow using a placeholder * notation of {@code ?0}, {@code ?1} and so on. * - * @see DATAMONGO-1539 - * * @author Fırat KÜÇÜK + * @author Oliver Gierke + * @since 1.10 */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Documented @Query(delete = true) -public @interface Delete { +public @interface DeleteQuery { /** * Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the - * method name then. - * - * Alias for {@link Query#value}. + * method name then. Alias for {@link Query#value}. * * @return */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index f2189a1d50..3451512079 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -1315,22 +1315,19 @@ public void findsPersonsByFirstnameNotLike() throws Exception { /** * @see DATAMONGO-1539 */ - @Test - public void countsPersonsByFirstname() { - - long result = repository.countByThePersonsFirstname("Dave"); - assertThat(result, is(1L)); - } + @Test + public void countsPersonsByFirstname() { + assertThat(repository.countByThePersonsFirstname("Dave"), is(1L)); + } /** * @see DATAMONGO-1539 */ @Test - public void deletesPersonsByFirstname() { + public void deletesPersonsByFirstname() { - repository.deleteByThePersonsFirstname("Dave"); + repository.deleteByThePersonsFirstname("Dave"); - long result = repository.countByThePersonsFirstname("Dave"); - assertThat(result, is(0L)); - } + assertThat(repository.countByThePersonsFirstname("Dave"), is(0L)); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index fe98a3f695..52081b67f0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -370,25 +370,23 @@ Page findByCustomQueryLastnameAndAddressStreetInList(String lastname, Li List findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname); /** - * Returns the count of {@link Person} with the given firstname. Uses {@link Count} annotation to define the query - * to be executed. + * Returns the count of {@link Person} with the given firstname. Uses {@link CountQuery} annotation to define the + * query to be executed. * * @see DATAMONGO-1539 - * * @param firstname * @return */ - @Count(value = "{ 'firstname' : ?0 }") + @CountQuery("{ 'firstname' : ?0 }") long countByThePersonsFirstname(String firstname); /** - * Deletes {@link Person} entities with the given firstname. Uses {@link Delete} annotation to define the query + * Deletes {@link Person} entities with the given firstname. Uses {@link DeleteQuery} annotation to define the query * to be executed. * * @see DATAMONGO-1539 - * * @param firstname */ - @Delete(value = "{ 'firstname' : ?0 }") + @DeleteQuery("{ 'firstname' : ?0 }") void deleteByThePersonsFirstname(String firstname); } From 63d6234446e3b68ffc77c44006b4f3758974efd9 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 28 Nov 2016 14:11:59 +0100 Subject: [PATCH 014/118] DATAMONGO-1536 - Add aggregation operators for array, arithmetic, date and set operations. We now support the following aggregation framework operators: - setEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTrue - stdDevPop, stdDevSamp - abs, ceil, exp, floor, ln, log, log10, pow, sqrt, trunc - arrayElementAt, concatArrays, isArray - literal - dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString Original pull request: #418. --- .../aggregation/AggregationExpressions.java | 5140 ++++++++++++++++- .../AggregationFunctionExpressions.java | 11 +- .../data/mongodb/core/aggregation/Fields.java | 9 + .../core/aggregation/ProjectionOperation.java | 475 +- .../ProjectionOperationUnitTests.java | 1200 +++- src/main/asciidoc/reference/mongodb.adoc | 18 +- 6 files changed, 6678 insertions(+), 175 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 e06fa43fb5..be93d1e190 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 @@ -16,11 +16,17 @@ package org.springframework.data.mongodb.core.aggregation; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; @@ -32,259 +38,5075 @@ public interface AggregationExpressions { /** - * {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the - * specified condition. + * Gateway to {@literal boolean expressions} that evaluate their argument expressions as booleans and return a boolean + * as the result. * * @author Christoph Strobl - * @since 1.10 */ - class Filter implements AggregationExpression { - - private Object input; - private ExposedField as; - private Object condition; - - private Filter() { - // used by builder - } + class BooleanOperators { /** - * Set the {@literal field} to apply the {@code $filter} to. + * Take the array referenced by given {@literal fieldReference}. * - * @param field must not be {@literal null}. - * @return never {@literal null}. + * @param fieldReference must not be {@literal null}. + * @return */ - public static AsBuilder filter(String field) { - - Assert.notNull(field, "Field must not be null!"); - return filter(Fields.field(field)); + public static BooleanOperatorFactory valueOf(String fieldReference) { + return new BooleanOperatorFactory(fieldReference); } /** - * Set the {@literal field} to apply the {@code $filter} to. + * Take the array referenced by given {@literal fieldReference}. * - * @param field must not be {@literal null}. - * @return never {@literal null}. + * @param fieldReference must not be {@literal null}. + * @return */ - public static AsBuilder filter(Field field) { - - Assert.notNull(field, "Field must not be null!"); - return new FilterExpressionBuilder().filter(field); + public static BooleanOperatorFactory valueOf(AggregationExpression fieldReference) { + return new BooleanOperatorFactory(fieldReference); } /** - * Set the {@literal values} to apply the {@code $filter} to. + * Creates new {@link AggregationExpressions} that evaluates the boolean value of the referenced field and returns + * the opposite boolean value. * - * @param values must not be {@literal null}. + * @param fieldReference 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); + public static Not not(String fieldReference) { + return Not.not(fieldReference); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + /** + * Creates new {@link AggregationExpressions} that evaluates the boolean value of {@link AggregationExpression} + * result and returns the opposite boolean value. + * + * @param fieldReference must not be {@literal null}. + * @return */ - @Override - public DBObject toDbObject(final AggregationOperationContext context) { + public static Not not(AggregationExpression expression) { + return Not.not(expression); + } - return toFilter(new ExposedFieldsAggregationOperationContext(ExposedFields.from(as), context) { + public static class BooleanOperatorFactory { - @Override - public FieldReference getReference(Field field) { + private final String fieldReference; + private final AggregationExpression expression; - FieldReference ref = null; - try { - ref = context.getReference(field); - } catch (Exception e) { - // just ignore that one. - } - return ref != null ? ref : super.getReference(field); - } - }); - } + /** + * Creates new {@link ComparisonOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public BooleanOperatorFactory(String fieldReference) { - private DBObject toFilter(AggregationOperationContext context) { + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } - DBObject filterExpression = new BasicDBObject(); + /** + * Creats new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public BooleanOperatorFactory(AggregationExpression expression) { - filterExpression.putAll(context.getMappedObject(new BasicDBObject("input", getMappedInput(context)))); - filterExpression.put("as", as.getTarget()); + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } - filterExpression.putAll(context.getMappedObject(new BasicDBObject("cond", getMappedCondition(context)))); + /** + * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} + * if all of the expressions are {@literal true}. + * + * @param expression must not be {@literal null}. + * @return + */ + public And and(AggregationExpression expression) { - return new BasicDBObject("$filter", filterExpression); - } + Assert.notNull(expression, "Expression must not be null!"); + return createAnd().andExpression(expression); + } - private Object getMappedInput(AggregationOperationContext context) { - return input instanceof Field ? context.getReference((Field) input).toString() : input; - } + /** + * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} + * if all of the expressions are {@literal true}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public And and(String fieldReference) { - private Object getMappedCondition(AggregationOperationContext context) { + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createAnd().andField(fieldReference); + } - if (!(condition instanceof AggregationExpression)) { - return condition; + private And createAnd() { + return usesFieldRef() ? And.and(Fields.field(fieldReference)) : And.and(expression); } - NestedDelegatingExpressionAggregationOperationContext nea = new NestedDelegatingExpressionAggregationOperationContext(context); - return ((AggregationExpression) condition).toDbObject(nea); - } + /** + * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} + * if any of the expressions are {@literal true}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Or or(AggregationExpression expression) { - /** - * @author Christoph Strobl - */ - public interface InputBuilder { + Assert.notNull(expression, "Expression must not be null!"); + return createOr().orExpression(expression); + } /** - * Set the {@literal values} to apply the {@code $filter} to. + * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} + * if any of the expressions are {@literal true}. * - * @param array must not be {@literal null}. + * @param fieldReference must not be {@literal null}. * @return */ - AsBuilder filter(List array); + public Or or(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createOr().orField(fieldReference); + } + + private Or createOr() { + return usesFieldRef() ? Or.or(Fields.field(fieldReference)) : Or.or(expression); + } /** - * Set the {@literal field} holding an array to apply the {@code $filter} to. + * Creates new {@link AggregationExpression} that evaluates a boolean and returns the opposite boolean value. * - * @param field must not be {@literal null}. * @return */ - AsBuilder filter(Field field); + public Not not() { + return usesFieldRef() ? Not.not(fieldReference) : Not.not(expression); + } + + private boolean usesFieldRef() { + return this.fieldReference != null; + } } + } + + /** + * Gateway to {@literal Set expressions} which perform {@literal set} operation on arrays, treating arrays as sets. + * + * @author Christoph Strobl + */ + class SetOperators { /** - * @author Christoph Strobl + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return */ - public interface AsBuilder { + public static SetOperatorFactory arrayAsSet(String fieldReference) { + return new SetOperatorFactory(fieldReference); + } + + /** + * Take the array resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetOperatorFactory arrayAsSet(AggregationExpression expression) { + return new SetOperatorFactory(expression); + } + + public static class SetOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; /** - * Set the {@literal variableName} for the elements in the input array. + * Creates new {@link SetOperatorFactory} for given {@literal fieldReference}. * - * @param variableName must not be {@literal null}. - * @return + * @param fieldReference must not be {@literal null}. */ - ConditionBuilder as(String variableName); - } + public SetOperatorFactory(String fieldReference) { - /** - * @author Christoph Strobl - */ - public interface ConditionBuilder { + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } /** - * Set the {@link AggregationExpression} that determines whether to include the element in the resulting array. + * Creates new {@link SetOperatorFactory} for given {@link AggregationExpression}. * * @param expression must not be {@literal null}. - * @return */ - Filter by(AggregationExpression expression); + public SetOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } /** - * Set the {@literal expression} that determines whether to include the element in the resulting array. + * Creates new {@link AggregationExpressions} that compares the previously mentioned field to one or more arrays + * and returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. * - * @param expression must not be {@literal null}. + * @param arrayReferences must not be {@literal null}. * @return */ - Filter by(String expression); + public SetEquals isEqualTo(String... arrayReferences) { + return createSetEquals().isEqualTo(arrayReferences); + } /** - * Set the {@literal expression} that determines whether to include the element in the resulting array. + * Creates new {@link AggregationExpressions} that compares the previously mentioned field to one or more arrays + * and returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. * - * @param expression must not be {@literal null}. + * @param expressions must not be {@literal null}. * @return */ - Filter by(DBObject expression); - } + public SetEquals isEqualTo(AggregationExpression... expressions) { + return createSetEquals().isEqualTo(expressions); + } - /** - * @author Christoph Strobl - */ - static final class FilterExpressionBuilder implements InputBuilder, AsBuilder, ConditionBuilder { + private SetEquals createSetEquals() { + return usesFieldRef() ? SetEquals.arrayAsSet(fieldReference) : SetEquals.arrayAsSet(expression); + } - private final Filter filter; + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in every of those. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetIntersection intersects(String... arrayReferences) { + return createSetIntersection().intersects(arrayReferences); + } - FilterExpressionBuilder() { - this.filter = new Filter(); + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in every of those. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetIntersection intersects(AggregationExpression... expressions) { + return createSetIntersection().intersects(expressions); } - public static InputBuilder newBuilder() { - return new FilterExpressionBuilder(); + private SetIntersection createSetIntersection() { + return usesFieldRef() ? SetIntersection.arrayAsSet(fieldReference) : SetIntersection.arrayAsSet(expression); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(java.util.List) + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in any of those. + * + * @param arrayReferences must not be {@literal null}. + * @return */ - @Override - public AsBuilder filter(List array) { - - Assert.notNull(array, "Array must not be null!"); - filter.input = new ArrayList(array); - return this; + public SetUnion union(String... arrayReferences) { + return createSetUnion().union(arrayReferences); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(org.springframework.data.mongodb.core.aggregation.Field) + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in any of those. + * + * @param expressions must not be {@literal null}. + * @return */ - @Override - public AsBuilder filter(Field field) { + public SetUnion union(AggregationExpression... expressions) { + return createSetUnion().union(expressions); + } - Assert.notNull(field, "Field must not be null!"); - filter.input = field; - return this; + private SetUnion createSetUnion() { + return usesFieldRef() ? SetUnion.arrayAsSet(fieldReference) : SetUnion.arrayAsSet(expression); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder#as(java.lang.String) + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns an + * array containing the elements that do not exist in the given {@literal arrayReference}. + * + * @param arrayReference must not be {@literal null}. + * @return */ - @Override - public ConditionBuilder as(String variableName) { - - Assert.notNull(variableName, "Variable name must not be null!"); - filter.as = new ExposedField(variableName, true); - return this; + public SetDifference differenceTo(String arrayReference) { + return createSetDifference().differenceTo(arrayReference); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns an + * array containing the elements that do not exist in the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return */ - @Override - public Filter by(AggregationExpression condition) { + public SetDifference differenceTo(AggregationExpression expression) { + return createSetDifference().differenceTo(expression); + } - Assert.notNull(condition, "Condition must not be null!"); - filter.condition = condition; - return filter; + private SetDifference createSetDifference() { + return usesFieldRef() ? SetDifference.arrayAsSet(fieldReference) : SetDifference.arrayAsSet(expression); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(java.lang.String) + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns + * {@literal true} if it is a subset of the given {@literal arrayReference}. + * + * @param arrayReference must not be {@literal null}. + * @return */ - @Override - public Filter by(String expression) { - - Assert.notNull(expression, "Expression must not be null!"); - filter.condition = expression; - return filter; + public SetIsSubset isSubsetOf(String arrayReference) { + return createSetIsSubset().isSubsetOf(arrayReference); } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(com.mongodb.DBObject) + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns + * {@literal true} if it is a subset of the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return */ - @Override - public Filter by(DBObject expression) { + public SetIsSubset isSubsetOf(AggregationExpression expression) { + return createSetIsSubset().isSubsetOf(expression); + } - Assert.notNull(expression, "Expression must not be null!"); - filter.condition = expression; - return filter; + private SetIsSubset createSetIsSubset() { + return usesFieldRef() ? SetIsSubset.arrayAsSet(fieldReference) : SetIsSubset.arrayAsSet(expression); } - } - } + + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns + * {@literal true} if any of the elements are {@literal true} and {@literal false} otherwise. + * + * @return + */ + public AnyElementTrue anyElementTrue() { + return usesFieldRef() ? AnyElementTrue.arrayAsSet(fieldReference) : AnyElementTrue.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpressions} that tkes array of the previously mentioned field and returns + * {@literal true} if no elements is {@literal false}. + * + * @return + */ + public AllElementsTrue allElementsTrue() { + return usesFieldRef() ? AllElementsTrue.arrayAsSet(fieldReference) : AllElementsTrue.arrayAsSet(expression); + } + + private boolean usesFieldRef() { + return this.fieldReference != null; + } + } + } + + /** + * Gateway to {@literal comparison expressions}. + * + * @author Christoph Strobl + */ + class ComparisonOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ComparisonOperatorFactory valueOf(String fieldReference) { + return new ComparisonOperatorFactory(fieldReference); + } + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ComparisonOperatorFactory valueOf(AggregationExpression fieldReference) { + return new ComparisonOperatorFactory(fieldReference); + } + + public static class ComparisonOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link ComparisonOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ComparisonOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creats new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ComparisonOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that compares two values. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Cmp compareTo(String fieldReference) { + return createCmp().compareTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values. + * + * @param expression must not be {@literal null}. + * @return + */ + public Cmp compareTo(AggregationExpression expression) { + return createCmp().compareTo(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values. + * + * @param value must not be {@literal null}. + * @return + */ + public Cmp compareToValue(Object value) { + return createCmp().compareToValue(value); + } + + private Cmp createCmp() { + return usesFieldRef() ? Cmp.valueOf(fieldReference) : Cmp.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is equal to the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Eq equalTo(String fieldReference) { + return createEq().equalTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is equal to the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Eq equalTo(AggregationExpression expression) { + return createEq().equalTo(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is equal to the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Eq equalToValue(Object value) { + return createEq().equalToValue(value); + } + + private Eq createEq() { + return usesFieldRef() ? Eq.valueOf(fieldReference) : Eq.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gt greaterThan(String fieldReference) { + return createGt().greaterThan(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gt greaterThan(AggregationExpression expression) { + return createGt().greaterThan(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Gt greaterThanValue(Object value) { + return createGt().greaterThanValue(value); + } + + private Gt createGt() { + return usesFieldRef() ? Gt.valueOf(fieldReference) : Gt.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than or equivalent to the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(String fieldReference) { + return createGte().greaterThanEqualTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than or equivalent to the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(AggregationExpression expression) { + return createGte().greaterThanEqualTo(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than or equivalent to the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualToValue(Object value) { + return createGte().greaterThanEqualToValue(value); + } + + private Gte createGte() { + return usesFieldRef() ? Gte.valueOf(fieldReference) : Gte.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lt lessThan(String fieldReference) { + return createLt().lessThan(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lt lessThan(AggregationExpression expression) { + return createLt().lessThan(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than to the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Lt lessThanValue(Object value) { + return createLt().lessThanValue(value); + } + + private Lt createLt() { + return usesFieldRef() ? Lt.valueOf(fieldReference) : Lt.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than or equivalent to the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(String fieldReference) { + return createLte().lessThanEqualTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than or equivalent to the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(AggregationExpression expression) { + return createLte().lessThanEqualTo(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than or equivalent to the given value. + * + * @param value + * @return + */ + public Lte lessThanEqualToValue(Object value) { + return createLte().lessThanEqualToValue(value); + } + + private Lte createLte() { + return usesFieldRef() ? Lte.valueOf(fieldReference) : Lte.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values + * are not equivalent. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Ne notEqualTo(String fieldReference) { + return createNe().notEqualTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values + * are not equivalent. + * + * @param expression must not be {@literal null}. + * @return + */ + public Ne notEqualTo(AggregationExpression expression) { + return createNe().notEqualTo(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values + * are not equivalent. + * + * @param value must not be {@literal null}. + * @return + */ + public Ne notEqualToValue(Object value) { + return createNe().notEqualToValue(value); + } + + private Ne createNe() { + return usesFieldRef() ? Ne.valueOf(fieldReference) : Ne.valueOf(expression); + } + + private boolean usesFieldRef() { + return fieldReference != null; + } + } + + } + + /** + * Gateway to {@literal Arithmetic} aggregation operations that perform mathematic operations on numbers. + * + * @author Christoph Strobl + */ + class ArithmeticOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArithmeticOperatorFactory valueOf(String fieldReference) { + return new ArithmeticOperatorFactory(fieldReference); + } + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArithmeticOperatorFactory valueOf(AggregationExpression fieldReference) { + return new ArithmeticOperatorFactory(fieldReference); + } + + public static class ArithmeticOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ArithmeticOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ArithmeticOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that returns the absolute value of the associated number. + * + * @return + */ + public Abs abs() { + return fieldReference != null ? Abs.absoluteValueOf(fieldReference) : Abs.absoluteValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that adds the value of {@literal fieldReference} to the associated + * number. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Add add(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createAdd().add(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that adds the resulting value of the given + * {@link AggregationExpression} to the associated number. + * + * @param expression must not be {@literal null}. + * @return + */ + public Add add(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createAdd().add(expression); + } + + /** + * Creates new {@link AggregationExpressions} that adds the given {@literal value} to the associated number. + * + * @param value must not be {@literal null}. + * @return + */ + public Add add(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createAdd().add(value); + } + + private Add createAdd() { + return fieldReference != null ? Add.valueOf(fieldReference) : Add.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the smallest integer greater than or equal to the + * assoicated number. + * + * @return + */ + public Ceil ceil() { + return fieldReference != null ? Ceil.ceilValueOf(fieldReference) : Ceil.ceilValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that ivides the associated number by number referenced via + * {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Divide divideBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createDivide().divideBy(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that divides the associated number by number extracted via + * {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Divide divideBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createDivide().divideBy(expression); + } + + /** + * Creates new {@link AggregationExpressions} that divides the associated number by given {@literal value}. + * + * @param value + * @return + */ + public Divide divideBy(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createDivide().divideBy(value); + } + + private Divide createDivide() { + return fieldReference != null ? Divide.valueOf(fieldReference) : Divide.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that raises Euler’s number (i.e. e ) on the associated number. + * + * @return + */ + public Exp exp() { + return fieldReference != null ? Exp.expValueOf(fieldReference) : Exp.expValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the largest integer less than or equal to the + * associated number. + * + * @return + */ + public Floor floor() { + return fieldReference != null ? Floor.floorValueOf(fieldReference) : Floor.floorValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the natural logarithm ln (i.e loge) of the + * assoicated number. + * + * @return + */ + public Ln ln() { + return fieldReference != null ? Ln.lnValueOf(fieldReference) : Ln.lnValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the log of the associated number in the specified + * base referenced via {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Log log(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createLog().log(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the log of the associated number in the specified + * base extracted by given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Log log(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createLog().log(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the log of a the associated number in the specified + * {@literal base}. + * + * @param base must not be {@literal null}. + * @return + */ + public Log log(Number base) { + + Assert.notNull(base, "Base must not be null!"); + return createLog().log(base); + } + + private Log createLog() { + return fieldReference != null ? Log.valueOf(fieldReference) : Log.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the log base 10 for the associated number. + * + * @return + */ + public Log10 log10() { + return fieldReference != null ? Log10.log10ValueOf(fieldReference) : Log10.log10ValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the + * remainder. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Mod mod(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createMod().mod(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the + * remainder. + * + * @param expression must not be {@literal null}. + * @return + */ + public Mod mod(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createMod().mod(expression); + } + + /** + * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the + * remainder. + * + * @param value must not be {@literal null}. + * @return + */ + public Mod mod(Number value) { + + Assert.notNull(value, "Base must not be null!"); + return createMod().mod(value); + } + + private Mod createMod() { + return fieldReference != null ? Mod.valueOf(fieldReference) : Mod.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that multiplies the associated number with another. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Multiply multiplyBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createMultiply().multiplyBy(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that multiplies the associated number with another. + * + * @param expression must not be {@literal null}. + * @return + */ + public Multiply multiplyBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createMultiply().multiplyBy(expression); + } + + /** + * Creates new {@link AggregationExpressions} that multiplies the associated number with another. + * + * @param value must not be {@literal null}. + * @return + */ + public Multiply multiplyBy(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createMultiply().multiplyBy(value); + } + + private Multiply createMultiply() { + return fieldReference != null ? Multiply.valueOf(fieldReference) : Multiply.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Pow pow(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createPow().pow(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. + * + * @param expression must not be {@literal null}. + * @return + */ + public Pow pow(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createPow().pow(expression); + } + + /** + * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. + * + * @param value must not be {@literal null}. + * @return + */ + public Pow pow(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createPow().pow(value); + } + + private Pow createPow() { + return fieldReference != null ? Pow.valueOf(fieldReference) : Pow.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the square root of the associated number. + * + * @return + */ + public Sqrt sqrt() { + return fieldReference != null ? Sqrt.sqrtOf(fieldReference) : Sqrt.sqrtOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that subtracts value of given from the associated number. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Subtract subtract(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createSubtract().subtract(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that subtracts value of given from the associated number. + * + * @param expression must not be {@literal null}. + * @return + */ + public Subtract subtract(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createSubtract().subtract(expression); + } + + /** + * Creates new {@link AggregationExpressions} that subtracts value from the associated number. + * + * @param value + * @return + */ + public Subtract subtract(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createSubtract().subtract(value); + } + + private Subtract createSubtract() { + return fieldReference != null ? Subtract.valueOf(fieldReference) : Subtract.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that truncates a number to its integer. + * + * @return + */ + public Trunc trunc() { + return fieldReference != null ? Trunc.truncValueOf(fieldReference) : Trunc.truncValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates and returns the sum of numeric values. + * + * @return + */ + public Sum sum() { + return fieldReference != null ? Sum.sumOf(fieldReference) : Sum.sumOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the average value of the numeric values. + * + * @return + */ + public Avg avg() { + return fieldReference != null ? Avg.avgOf(fieldReference) : Avg.avgOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the maximum value. + * + * @return + */ + public Max max() { + return fieldReference != null ? Max.maxOf(fieldReference) : Max.maxOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the minimum value. + * + * @return + */ + public Min min() { + return fieldReference != null ? Min.minOf(fieldReference) : Min.minOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the population standard deviation of the input + * values. + * + * @return + */ + public StdDevPop stdDevPop() { + return fieldReference != null ? StdDevPop.stdDevPopOf(fieldReference) : StdDevPop.stdDevPopOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the sample standard deviation of the input values. + * + * @return + */ + public StdDevSamp stdDevSamp() { + return fieldReference != null ? StdDevSamp.stdDevSampOf(fieldReference) : StdDevSamp.stdDevSampOf(expression); + } + } + } + + /** + * Gateway to {@literal String} aggregation operations. + * + * @author Christoph Strobl + */ + class StringOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StringOperatorFactory valueOf(String fieldReference) { + return new StringOperatorFactory(fieldReference); + } + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StringOperatorFactory valueOf(AggregationExpression fieldReference) { + return new StringOperatorFactory(fieldReference); + } + + public static class StringOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + public StringOperatorFactory(String fieldReference) { + this.fieldReference = fieldReference; + this.expression = null; + } + + public StringOperatorFactory(AggregationExpression expression) { + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and concats the + * value of the referenced field to it. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Concat concatValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createConcat().concatValueOf(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and concats the + * result of the given {@link AggregationExpression} to it. + * + * @param expression must not be {@literal null}. + * @return + */ + public Concat concatValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createConcat().concatValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and concats given + * {@literal value} to it. + * + * @param value must not be {@literal null}. + * @return + */ + public Concat concat(String value) { + + Assert.notNull(value, "Value must not be null!"); + return createConcat().concat(value); + } + + private Concat createConcat() { + return fieldReference != null ? Concat.valueOf(fieldReference) : Concat.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a + * substring starting at a specified index position. + * + * @param start + * @return + */ + public Substr substring(int start) { + return substring(start, -1); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a + * substring starting at a specified index position including the specified number of characters. + * + * @param start + * @param nrOfChars + * @return + */ + public Substr substring(int start, int nrOfChars) { + return createSubstr().substring(start, nrOfChars); + } + + private Substr createSubstr() { + return fieldReference != null ? Substr.valueOf(fieldReference) : Substr.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and lowers it. + * + * @return + */ + public ToLower toLower() { + return fieldReference != null ? ToLower.lowerValueOf(fieldReference) : ToLower.lowerValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and uppers it. + * + * @return + */ + public ToUpper toUpper() { + return fieldReference != null ? ToUpper.upperValueOf(fieldReference) : ToUpper.upperValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and performs + * case-insensitive comparison to the given {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + public StrCaseCmp strCaseCmp(String value) { + + Assert.notNull(value, "Value must not be null!"); + return createStrCaseCmp().strcasecmp(value); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and performs + * case-insensitive comparison to the referenced {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public StrCaseCmp strCaseCmpValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createStrCaseCmp().strcasecmpValueOf(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and performs + * case-insensitive comparison to the result of the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public StrCaseCmp strCaseCmpValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createStrCaseCmp().strcasecmpValueOf(expression); + } + + private StrCaseCmp createStrCaseCmp() { + return fieldReference != null ? StrCaseCmp.valueOf(fieldReference) : StrCaseCmp.valueOf(expression); + } + } + } + + /** + * Gateway to {@litearl array} aggregation operations. + * + * @author Christoph Strobl + */ + class ArrayOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArrayOperatorFactory arrayOf(String fieldReference) { + return new ArrayOperatorFactory(fieldReference); + } + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArrayOperatorFactory arrayOf(AggregationExpression expression) { + return new ArrayOperatorFactory(expression); + } + + public static class ArrayOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + public ArrayOperatorFactory(String fieldReference) { + this.fieldReference = fieldReference; + this.expression = null; + } + + public ArrayOperatorFactory(AggregationExpression expression) { + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the + * specified array {@literal position}. + * + * @param position + * @return + */ + public ArrayElemtAt elementAt(int position) { + return createArrayElemAt().elementAt(position); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the + * position resulting form the given {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public ArrayElemtAt elementAt(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createArrayElemAt().elementAt(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the + * position defined by the referenced {@literal field}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public ArrayElemtAt elementAt(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createArrayElemAt().elementAt(fieldReference); + } + + private ArrayElemtAt createArrayElemAt() { + return usesFieldRef() ? ArrayElemtAt.arrayOf(fieldReference) : ArrayElemtAt.arrayOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and concats the given + * {@literal arrayFieldReference} to it. + * + * @param arrayFieldReference must not be {@literal null}. + * @return + */ + public ConcatArrays concat(String arrayFieldReference) { + + Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!"); + return createConcatArrays().concat(arrayFieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and concats the array resulting form + * the given {@literal expression} to it. + * + * @param expression must not be {@literal null}. + * @return + */ + public ConcatArrays concat(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createConcatArrays().concat(expression); + } + + private ConcatArrays createConcatArrays() { + return usesFieldRef() ? ConcatArrays.arrayOf(fieldReference) : ConcatArrays.arrayOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and selects a subset of the array to + * return based on the specified condition. + * + * @return + */ + public AsBuilder filter() { + return Filter.filter(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and an check if its an array. + * + * @return + */ + public IsArray isArray() { + return usesFieldRef() ? IsArray.isArray(fieldReference) : IsArray.isArray(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and retrieves its length. + * + * @return + */ + public Size length() { + return usesFieldRef() ? Size.lengthOfArray(fieldReference) : Size.lengthOfArray(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and selects a subset from it. + * + * @return + */ + public Slice slice() { + return usesFieldRef() ? Slice.sliceArrayOf(fieldReference) : Slice.sliceArrayOf(expression); + } + + private boolean usesFieldRef() { + return fieldReference != null; + } + } + } + + /** + * @author Christoph Strobl + */ + class LiteralOperators { + + /** + * Take the value referenced by given {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static LiteralOperatorFactory valueOf(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new LiteralOperatorFactory(value); + } + + public static class LiteralOperatorFactory { + + private final Object value; + + public LiteralOperatorFactory(Object value) { + this.value = value; + } + + /** + * Creates new {@link AggregationExpressions} that returns the associated value without parsing. + * + * @return + */ + public Literal asLiteral() { + return Literal.asLiteral(value); + } + } + } + + /** + * @author Christoph Strobl + */ + class DateOperators { + + /** + * Take the date referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static DateOperatorFactory dateOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DateOperatorFactory(fieldReference); + } + + /** + * Take the date resulting from the given {@literal expression}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static DateOperatorFactory dateOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DateOperatorFactory(expression); + } + + public static class DateOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + public DateOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + public DateOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that returns the day of the year for a date as a number between 1 + * and 366. + * + * @return + */ + public DayOfYear dayOfYear() { + return usesFieldRef() ? DayOfYear.dayOfYear(fieldReference) : DayOfYear.dayOfYear(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the day of the month for a date as a number between 1 + * and 31. + * + * @return + */ + public DayOfMonth dayOfMonth() { + return usesFieldRef() ? DayOfMonth.dayOfMonth(fieldReference) : DayOfMonth.dayOfMonth(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the day of the week for a date as a number between 1 + * (Sunday) and 7 (Saturday). + * + * @return + */ + public DayOfWeek dayOfWeek() { + return usesFieldRef() ? DayOfWeek.dayOfWeek(fieldReference) : DayOfWeek.dayOfWeek(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the year portion of a date. + * + * @return + */ + public Year year() { + return usesFieldRef() ? Year.yearOf(fieldReference) : Year.yearOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the month of a date as a number between 1 and 12. + * + * @return + */ + public Month month() { + return usesFieldRef() ? Month.monthOf(fieldReference) : Month.monthOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the week of the year for a date as a number between 0 + * and 53. + * + * @return + */ + public Week week() { + return usesFieldRef() ? Week.weekOf(fieldReference) : Week.weekOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the hour portion of a date as a number between 0 and + * 23. + * + * @return + */ + public Hour hour() { + return usesFieldRef() ? Hour.hourOf(fieldReference) : Hour.hourOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the minute portion of a date as a number between 0 and + * 59. + * + * @return + */ + public Minute minute() { + return usesFieldRef() ? Minute.minuteOf(fieldReference) : Minute.minuteOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the second portion of a date as a number between 0 and + * 59, but can be 60 to account for leap seconds. + * + * @return + */ + public Second second() { + return usesFieldRef() ? Second.secondOf(fieldReference) : Second.secondOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the millisecond portion of a date as an integer between + * 0 and 999. + * + * @return + */ + public Millisecond millisecond() { + return usesFieldRef() ? Millisecond.millisecondOf(fieldReference) : Millisecond.millisecondOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that converts a date object to a string according to a + * user-specified {@literal format}. + * + * @param format must not be {@literal null}. + * @return + */ + public DateToString toString(String format) { + return (usesFieldRef() ? DateToString.dateOf(fieldReference) : DateToString.dateOf(expression)) + .toString(format); + } + + private boolean usesFieldRef() { + return fieldReference != null; + } + } + } + + /** + * @author Christoph Strobl + */ + abstract class AbstractAggregationExpression implements AggregationExpression { + + private final Object value; + + protected AbstractAggregationExpression(Object value) { + this.value = value; + } + + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return toDbObject(this.value, context); + } + + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + Object valueToUse; + if (value instanceof List) { + + List arguments = (List) value; + List args = new ArrayList(arguments.size()); + + for (Object val : arguments) { + args.add(unpack(val, context)); + } + valueToUse = args; + } else if (value instanceof Map) { + + DBObject dbo = new BasicDBObject(); + for (Map.Entry entry : ((Map) value).entrySet()) { + dbo.put(entry.getKey(), unpack(entry.getValue(), context)); + } + valueToUse = dbo; + } else { + valueToUse = unpack(value, context); + } + + return new BasicDBObject(getMongoMethod(), valueToUse); + } + + protected static List asFields(String... fieldRefs) { + + if (ObjectUtils.isEmpty(fieldRefs)) { + return Collections.emptyList(); + } + + return Fields.fields(fieldRefs).asList(); + } + + private Object unpack(Object value, AggregationOperationContext context) { + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + if (value instanceof Field) { + return context.getReference((Field) value).toString(); + } + + return value; + } + + protected List append(Object value) { + + if (this.value instanceof List) { + + List clone = new ArrayList((List) this.value); + + if (value instanceof List) { + for (Object val : (List) value) { + clone.add(val); + } + } else { + clone.add(value); + } + return clone; + } + + return Arrays.asList(this.value, value); + } + + protected Object append(String key, Object value) { + + if (!(value instanceof Map)) { + throw new IllegalArgumentException("o_O"); + } + Map clone = new LinkedHashMap((Map) value); + clone.put(key, value); + return clone; + + } + + public abstract String getMongoMethod(); + } + + /** + * {@link AggregationExpression} for {@code $setEquals}. + * + * @author Christoph Strobl + */ + class SetEquals extends AbstractAggregationExpression { + + private SetEquals(List arrays) { + super(arrays); + } + + @Override + public String getMongoMethod() { + return "$setEquals"; + } + + /** + * Create new {@link SetEquals}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetEquals arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetEquals(asFields(arrayReference)); + } + + /** + * Create new {@link SetEquals}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetEquals arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetEquals(Collections.singletonList(expression)); + } + + /** + * Creates new {@link java.util.Set} with all previously added arguments appending the given one. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(String... arrayReferences) { + + Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); + return new SetEquals(append(Fields.fields(arrayReferences).asList())); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(AggregationExpression... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new SetEquals(append(Arrays.asList(expressions))); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one. + * + * @param array must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(Object[] array) { + + Assert.notNull(array, "Array must not be null!"); + return new SetEquals(append(array)); + } + } + + /** + * {@link AggregationExpression} for {@code $setIntersection}. + * + * @author Christoph Strobl + */ + class SetIntersection extends AbstractAggregationExpression { + + private SetIntersection(List arrays) { + super(arrays); + } + + @Override + public String getMongoMethod() { + return "$setIntersection"; + } + + /** + * Creates new {@link SetIntersection} + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetIntersection arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetIntersection(asFields(arrayReference)); + } + + /** + * Creates new {@link SetIntersection}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetIntersection arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetIntersection(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetIntersection} with all previously added arguments appending the given one. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetIntersection intersects(String... arrayReferences) { + + Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); + return new SetIntersection(append(asFields(arrayReferences))); + } + + /** + * Creates new {@link SetIntersection} with all previously added arguments appending the given one. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetIntersection intersects(AggregationExpression... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new SetIntersection(append(Arrays.asList(expressions))); + } + } + + /** + * {@link AggregationExpression} for {@code $setUnion}. + * + * @author Christoph Strobl + */ + class SetUnion extends AbstractAggregationExpression { + + private SetUnion(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$setUnion"; + } + + /** + * Creates new {@link SetUnion}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetUnion arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetUnion(asFields(arrayReference)); + } + + /** + * Creates new {@link SetUnion}. + * + * @param expressions must not be {@literal null}. + * @return + */ + public static SetUnion arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetUnion(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetUnion} with all previously added arguments appending the given one. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetUnion union(String... arrayReferences) { + + Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); + return new SetUnion(append(asFields(arrayReferences))); + } + + /** + * Creates new {@link SetUnion} with all previously added arguments appending the given one. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetUnion union(AggregationExpression... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new SetUnion(append(Arrays.asList(expressions))); + } + } + + /** + * {@link AggregationExpression} for {@code $setDifference}. + * + * @author Christoph Strobl + */ + class SetDifference extends AbstractAggregationExpression { + + private SetDifference(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$setDifference"; + } + + /** + * Creates new {@link SetDifference}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetDifference arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetDifference(asFields(arrayReference)); + } + + /** + * Creates new {@link SetDifference}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetDifference arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetDifference(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetDifference} with all previously added arguments appending the given one. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public SetDifference differenceTo(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetDifference(append(Fields.field(arrayReference))); + } + + /** + * Creates new {@link SetDifference} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public SetDifference differenceTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetDifference(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $setIsSubset}. + * + * @author Christoph Strobl + */ + class SetIsSubset extends AbstractAggregationExpression { + + private SetIsSubset(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$setIsSubset"; + } + + /** + * Creates new {@link SetIsSubset}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetIsSubset arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetIsSubset(asFields(arrayReference)); + } + + /** + * Creates new {@link SetIsSubset}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetIsSubset arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetIsSubset(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetIsSubset} with all previously added arguments appending the given one. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public SetIsSubset isSubsetOf(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetIsSubset(append(Fields.field(arrayReference))); + } + + /** + * Creates new {@link SetIsSubset} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public SetIsSubset isSubsetOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetIsSubset(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $anyElementTrue}. + * + * @author Christoph Strobl + */ + class AnyElementTrue extends AbstractAggregationExpression { + + private AnyElementTrue(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$anyElementTrue"; + } + + /** + * Creates new {@link AnyElementTrue}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static AnyElementTrue arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new AnyElementTrue(asFields(arrayReference)); + } + + /** + * Creats new {@link AnyElementTrue}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static AnyElementTrue arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new AnyElementTrue(Collections.singletonList(expression)); + } + + public AnyElementTrue anyElementTrue() { + return this; + } + } + + /** + * {@link AggregationExpression} for {@code $allElementsTrue}. + * + * @author Christoph Strobl + */ + class AllElementsTrue extends AbstractAggregationExpression { + + private AllElementsTrue(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$allElementsTrue"; + } + + /** + * Creates new {@link AllElementsTrue}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static AllElementsTrue arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new AllElementsTrue(asFields(arrayReference)); + } + + /** + * Creates new {@link AllElementsTrue}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static AllElementsTrue arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new AllElementsTrue(Collections.singletonList(expression)); + } + + public AllElementsTrue allElementsTrue() { + return this; + } + } + + /** + * {@link AggregationExpression} for {@code $abs}. + * + * @author Christoph Strobl + */ + class Abs extends AbstractAggregationExpression { + + private Abs(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$abs"; + } + + public static Abs absoluteValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Abs(Fields.field(fieldReference)); + } + + public static Abs absoluteValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Abs(expression); + } + + public static Abs absoluteValueOf(Number value) { + return new Abs(value); + } + } + + /** + * {@link AggregationExpression} for {@code $add}. + * + * @author Christoph Strobl + */ + class Add extends AbstractAggregationExpression { + + protected Add(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$add"; + } + + public static Add valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Add(asFields(fieldReference)); + } + + public static Add valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Add(Collections.singletonList(expression)); + } + + public static Add valueOf(Number value) { + return new Add(Collections.singletonList(value)); + } + + public Add add(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Add(append(Fields.field(fieldReference))); + } + + public Add add(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Add(append(expression)); + } + + public Add add(Number value) { + return new Add(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $ceil}. + * + * @author Christoph Strobl + */ + class Ceil extends AbstractAggregationExpression { + + private Ceil(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$ceil"; + } + + public static Ceil ceilValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ceil(Fields.field(fieldReference)); + } + + public static Ceil ceilValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ceil(expression); + } + + public static Ceil ceilValueOf(Number value) { + return new Ceil(value); + } + } + + /** + * {@link AggregationExpression} for {@code $divide}. + * + * @author Christoph Strobl + */ + class Divide extends AbstractAggregationExpression { + + private Divide(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$divide"; + } + + public static Divide valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Divide(asFields(fieldReference)); + } + + public static Divide valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Divide(Collections.singletonList(expression)); + } + + public static Divide valueOf(Number value) { + return new Divide(Collections.singletonList(value)); + } + + public Divide divideBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Divide(append(Fields.field(fieldReference))); + } + + public Divide divideBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Divide(append(expression)); + } + + public Divide divideBy(Number value) { + return new Divide(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $exp}. + * + * @author Christoph Strobl + */ + class Exp extends AbstractAggregationExpression { + + protected Exp(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$exp"; + } + + public static Exp expValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Exp(Fields.field(fieldReference)); + } + + public static Exp expValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Exp(expression); + } + + public static Exp expValueOf(Number value) { + return new Exp(value); + } + } + + /** + * {@link AggregationExpression} for {@code $floor}. + * + * @author Christoph Strobl + */ + class Floor extends AbstractAggregationExpression { + + protected Floor(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$floor"; + } + + public static Floor floorValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Floor(Fields.field(fieldReference)); + } + + public static Floor floorValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Floor(expression); + } + + public static Floor floorValueOf(Number value) { + return new Floor(value); + } + } + + /** + * {@link AggregationExpression} for {@code $ln}. + * + * @author Christoph Strobl + */ + class Ln extends AbstractAggregationExpression { + + private Ln(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$ln"; + } + + public static Ln lnValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ln(Fields.field(fieldReference)); + } + + public static Ln lnValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ln(expression); + } + + public static Ln lnValueOf(Number value) { + return new Ln(value); + } + } + + /** + * {@link AggregationExpression} for {@code $log}. + * + * @author Christoph Strobl + */ + class Log extends AbstractAggregationExpression { + + private Log(List values) { + super(values); + } + + @Override + public String getMongoMethod() { + return "$log"; + } + + public static Log valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Log(asFields(fieldReference)); + } + + public static Log valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Log(Collections.singletonList(expression)); + } + + public static Log valueOf(Number value) { + return new Log(Collections.singletonList(value)); + } + + public Log log(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Log(append(Fields.field(fieldReference))); + } + + public Log log(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Log(append(expression)); + } + + public Log log(Number base) { + return new Log(append(base)); + } + } + + /** + * {@link AggregationExpression} for {@code $log10}. + * + * @author Christoph Strobl + */ + class Log10 extends AbstractAggregationExpression { + + private Log10(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$log10"; + } + + public static Log10 log10ValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Log10(Fields.field(fieldReference)); + } + + public static Log10 log10ValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Log10(expression); + } + + public static Log10 log10ValueOf(Number value) { + return new Log10(value); + } + } + + /** + * {@link AggregationExpression} for {@code $mod}. + * + * @author Christoph Strobl + */ + class Mod extends AbstractAggregationExpression { + + private Mod(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$mod"; + } + + public static Mod valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Mod(asFields(fieldReference)); + } + + public static Mod valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Mod(Collections.singletonList(expression)); + } + + public static Mod valueOf(Number value) { + return new Mod(Collections.singletonList(value)); + } + + public Mod mod(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Mod(append(Fields.field(fieldReference))); + } + + public Mod mod(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Mod(append(expression)); + } + + public Mod mod(Number base) { + return new Mod(append(base)); + } + } + + /** + * {@link AggregationExpression} for {@code $multiply}. + * + * @author Christoph Strobl + */ + class Multiply extends AbstractAggregationExpression { + + private Multiply(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$multiply"; + } + + public static Multiply valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Multiply(asFields(fieldReference)); + } + + public static Multiply valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Multiply(Collections.singletonList(expression)); + } + + public static Multiply valueOf(Number value) { + return new Multiply(Collections.singletonList(value)); + } + + public Multiply multiplyBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Multiply(append(Fields.field(fieldReference))); + } + + public Multiply multiplyBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Multiply(append(expression)); + } + + public Multiply multiplyBy(Number value) { + return new Multiply(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $pow}. + * + * @author Christoph Strobl + */ + class Pow extends AbstractAggregationExpression { + + private Pow(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$pow"; + } + + public static Pow valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Pow(asFields(fieldReference)); + } + + public static Pow valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Pow(Collections.singletonList(expression)); + } + + public static Pow valueOf(Number value) { + return new Pow(Collections.singletonList(value)); + } + + public Pow pow(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Pow(append(Fields.field(fieldReference))); + } + + public Pow pow(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Pow(append(expression)); + } + + public Pow pow(Number value) { + return new Pow(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $sqrt}. + * + * @author Christoph Strobl + */ + class Sqrt extends AbstractAggregationExpression { + + private Sqrt(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$sqrt"; + } + + public static Sqrt sqrtOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Sqrt(Fields.field(fieldReference)); + } + + public static Sqrt sqrtOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Sqrt(expression); + } + + public static Sqrt sqrtOf(Number value) { + return new Sqrt(value); + } + } + + /** + * {@link AggregationExpression} for {@code $subtract}. + * + * @author Christoph Strobl + */ + class Subtract extends AbstractAggregationExpression { + + protected Subtract(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$subtract"; + } + + public static Subtract valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Subtract(asFields(fieldReference)); + } + + public static Subtract valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Subtract(Collections.singletonList(expression)); + } + + public static Subtract valueOf(Number value) { + return new Subtract(Collections.singletonList(value)); + } + + public Subtract subtract(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Subtract(append(Fields.field(fieldReference))); + } + + public Subtract subtract(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Subtract(append(expression)); + } + + public Subtract subtract(Number value) { + return new Subtract(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $trunc}. + * + * @author Christoph Strobl + */ + class Trunc extends AbstractAggregationExpression { + + private Trunc(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$trunc"; + } + + public static Trunc truncValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Trunc(Fields.field(fieldReference)); + } + + public static Trunc truncValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Trunc(expression); + } + + public static Trunc truncValueOf(Number value) { + return new Trunc(value); + } + } + + /** + * {@link AggregationExpression} for {@code $concat}. + * + * @author Christoph Strobl + */ + class Concat extends AbstractAggregationExpression { + + private Concat(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$concat"; + } + + public static Concat valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Concat(asFields(fieldReference)); + } + + public static Concat valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Concat(Collections.singletonList(expression)); + } + + public static Concat stringValue(String value) { + return new Concat(Collections.singletonList(value)); + } + + public Concat concatValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Concat(append(Fields.field(fieldReference))); + } + + public Concat concatValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Concat(append(expression)); + } + + public Concat concat(String value) { + return new Concat(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $substr}. + * + * @author Christoph Strobl + */ + class Substr extends AbstractAggregationExpression { + + private Substr(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$substr"; + } + + public static Substr valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Substr(asFields(fieldReference)); + } + + public static Substr valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Substr(Collections.singletonList(expression)); + } + + public Substr substring(int start) { + return substring(start, -1); + } + + public Substr substring(int start, int nrOfChars) { + return new Substr(append(Arrays.asList(start, nrOfChars))); + } + } + + /** + * {@link AggregationExpression} for {@code $toLower}. + * + * @author Christoph Strobl + */ + class ToLower extends AbstractAggregationExpression { + + private ToLower(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$toLower"; + } + + public static ToLower lower(String value) { + return new ToLower(value); + } + + public static ToLower lowerValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ToLower(Fields.field(fieldReference)); + } + + public static ToLower lowerValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ToLower(Collections.singletonList(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $toUpper}. + * + * @author Christoph Strobl + */ + class ToUpper extends AbstractAggregationExpression { + + private ToUpper(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$toUpper"; + } + + public static ToUpper upper(String value) { + return new ToUpper(value); + } + + public static ToUpper upperValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ToUpper(Fields.field(fieldReference)); + } + + public static ToUpper upperValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ToUpper(Collections.singletonList(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $strcasecmp}. + * + * @author Christoph Strobl + */ + class StrCaseCmp extends AbstractAggregationExpression { + + private StrCaseCmp(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$strcasecmp"; + } + + public static StrCaseCmp valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StrCaseCmp(asFields(fieldReference)); + } + + public static StrCaseCmp valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StrCaseCmp(Collections.singletonList(expression)); + } + + public static StrCaseCmp stringValue(String value) { + return new StrCaseCmp(Collections.singletonList(value)); + } + + public StrCaseCmp strcasecmp(String value) { + return new StrCaseCmp(append(value)); + } + + public StrCaseCmp strcasecmpValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StrCaseCmp(append(Fields.field(fieldReference))); + } + + public StrCaseCmp strcasecmpValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StrCaseCmp(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $arrayElementAt}. + * + * @author Christoph Strobl + */ + class ArrayElemtAt extends AbstractAggregationExpression { + + private ArrayElemtAt(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$arrayElemAt"; + } + + public static ArrayElemtAt arrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ArrayElemtAt(asFields(fieldReference)); + } + + public static ArrayElemtAt arrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ArrayElemtAt(Collections.singletonList(expression)); + } + + public ArrayElemtAt elementAt(int index) { + return new ArrayElemtAt(append(index)); + } + + public ArrayElemtAt elementAt(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ArrayElemtAt(append(expression)); + } + + public ArrayElemtAt elementAt(String arrayFieldReference) { + + Assert.notNull(arrayFieldReference, "ArrayReference must not be null!"); + return new ArrayElemtAt(append(Fields.field(arrayFieldReference))); + } + } + + /** + * {@link AggregationExpression} for {@code $concatArrays}. + * + * @author Christoph Strobl + */ + class ConcatArrays extends AbstractAggregationExpression { + + private ConcatArrays(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$concatArrays"; + } + + public static ConcatArrays arrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ConcatArrays(asFields(fieldReference)); + } + + 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 + * @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); + return ((AggregationExpression) condition).toDbObject(nea); + } + + /** + * @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; + } + } + } + + /** + * {@link AggregationExpression} for {@code $isArray}. + * + * @author Christoph Strobl + */ + class IsArray extends AbstractAggregationExpression { + + private IsArray(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$isArray"; + } + + public static IsArray isArray(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IsArray(Fields.field(fieldReference)); + } + + public static IsArray isArray(AggregationExpression 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 + public String getMongoMethod() { + return "$size"; + } + + public static Size lengthOfArray(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Size(Fields.field(fieldReference)); + } + + 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 + public String getMongoMethod() { + return "$slice"; + } + + public static Slice sliceArrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Slice(asFields(fieldReference)); + } + + 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 $literal}. + * + * @author Christoph Strobl + */ + class Literal extends AbstractAggregationExpression { + + private Literal(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$literal"; + } + + public static Literal asLiteral(Object value) { + return new Literal(value); + } + } + + /** + * {@link AggregationExpression} for {@code $dayOfYear}. + * + * @author Christoph Strobl + */ + class DayOfYear extends AbstractAggregationExpression { + + private DayOfYear(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$dayOfYear"; + } + + public static DayOfYear dayOfYear(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DayOfYear(Fields.field(fieldReference)); + } + + public static DayOfYear dayOfYear(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DayOfYear(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $dayOfMonth}. + * + * @author Christoph Strobl + */ + class DayOfMonth extends AbstractAggregationExpression { + + private DayOfMonth(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$dayOfMonth"; + } + + public static DayOfMonth dayOfMonth(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DayOfMonth(Fields.field(fieldReference)); + } + + public static DayOfMonth dayOfMonth(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DayOfMonth(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $dayOfWeek}. + * + * @author Christoph Strobl + */ + class DayOfWeek extends AbstractAggregationExpression { + + private DayOfWeek(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$dayOfWeek"; + } + + public static DayOfWeek dayOfWeek(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DayOfWeek(Fields.field(fieldReference)); + } + + public static DayOfWeek dayOfWeek(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DayOfWeek(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $year}. + * + * @author Christoph Strobl + */ + class Year extends AbstractAggregationExpression { + + private Year(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$year"; + } + + public static Year yearOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Year(Fields.field(fieldReference)); + } + + public static Year yearOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Year(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $month}. + * + * @author Christoph Strobl + */ + class Month extends AbstractAggregationExpression { + + private Month(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$month"; + } + + public static Month monthOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Month(Fields.field(fieldReference)); + } + + public static Month monthOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Month(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $week}. + * + * @author Christoph Strobl + */ + class Week extends AbstractAggregationExpression { + + private Week(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$week"; + } + + public static Week weekOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Week(Fields.field(fieldReference)); + } + + public static Week weekOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Week(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $hour}. + * + * @author Christoph Strobl + */ + class Hour extends AbstractAggregationExpression { + + private Hour(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$hour"; + } + + public static Hour hourOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Hour(Fields.field(fieldReference)); + } + + public static Hour hourOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Hour(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $minute}. + * + * @author Christoph Strobl + */ + class Minute extends AbstractAggregationExpression { + + private Minute(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$minute"; + } + + public static Minute minuteOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Minute(Fields.field(fieldReference)); + } + + public static Minute minuteOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Minute(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $second}. + * + * @author Christoph Strobl + */ + class Second extends AbstractAggregationExpression { + + private Second(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$second"; + } + + /** + * Creates new {@link Second}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Second secondOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Second(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Second}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Second secondOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Second(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $millisecond}. + * + * @author Christoph Strobl + */ + class Millisecond extends AbstractAggregationExpression { + + private Millisecond(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$millisecond"; + } + + /** + * Creates new {@link Millisecond}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Millisecond millisecondOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Millisecond(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Millisecond}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Millisecond millisecondOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Millisecond(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $dateToString}. + * + * @author Christoph Strobl + */ + class DateToString extends AbstractAggregationExpression { + + private DateToString(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$dateToString"; + } + + /** + * Creates new {@link FormatBuilder} allowing to define the date format to apply. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static FormatBuilder dateOf(final String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference 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(Fields.field(fieldReference), format)); + } + }; + } + + /** + * Creates new {@link FormatBuilder} allowing to define the date format to apply. + * + * @param expression must not be {@literal null}. + * @return + */ + 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)); + } + }; + } + + private static Map argumentMap(Object date, String format) { + + Map args = new LinkedHashMap(2); + args.put("format", format); + args.put("date", date); + return args; + } + + public interface FormatBuilder { + + /** + * Creates new {@link DateToString} with all previously added arguments appending the given one. + * + * @param format must not be {@literal null}. + * @return + */ + DateToString toString(String format); + } + } + + /** + * {@link AggregationExpression} for {@code $sum}. + * + * @author Christoph Strobl + */ + class Sum extends AbstractAggregationExpression { + + private Sum(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$sum"; + } + + /** + * Creates new {@link Sum}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Sum sumOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Sum(asFields(fieldReference)); + } + + /** + * Creates new {@link Sum}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Sum sumOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Sum(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Sum and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Sum(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Sum and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Sum(append(expression)); + } + + @Override + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $avg}. + * + * @author Christoph Strobl + */ + class Avg extends AbstractAggregationExpression { + + private Avg(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$avg"; + } + + /** + * Creates new {@link Avg}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Avg avgOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Avg(asFields(fieldReference)); + } + + /** + * Creates new {@link Avg}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Avg avgOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Avg(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Avg} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Avg and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Avg(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Avg} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Avg and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Avg(append(expression)); + } + + @Override + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $max}. + * + * @author Christoph Strobl + */ + class Max extends AbstractAggregationExpression { + + private Max(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$max"; + } + + /** + * Creates new {@link Max}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Max maxOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Max(asFields(fieldReference)); + } + + /** + * Creates new {@link Max}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Max maxOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Max(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Max} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Max and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Max(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Max} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Max and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Max(append(expression)); + } + + @Override + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $min}. + * + * @author Christoph Strobl + */ + class Min extends AbstractAggregationExpression { + + private Min(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$min"; + } + + /** + * Creates new {@link Min}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Min minOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Min(asFields(fieldReference)); + } + + /** + * Creates new {@link Min}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Min minOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Min(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Min} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Min and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Min(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Min} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Min and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Min(append(expression)); + } + + @Override + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $stdDevPop}. + * + * @author Christoph Strobl + */ + class StdDevPop extends AbstractAggregationExpression { + + private StdDevPop(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$stdDevPop"; + } + + /** + * Creates new {@link StdDevPop}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StdDevPop stdDevPopOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevPop(asFields(fieldReference)); + } + + /** + * Creates new {@link StdDevPop} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public static StdDevPop stdDevPopOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StdDevPop(Collections.singletonList(expression)); + } + + /** + * Creates new {@link StdDevPop} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public StdDevPop and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevPop(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public StdDevPop and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StdDevPop(append(expression)); + } + + @Override + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $stdDevSamp}. + * + * @author Christoph Strobl + */ + class StdDevSamp extends AbstractAggregationExpression { + + private StdDevSamp(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$stdDevSamp"; + } + + /** + * Creates new {@link StdDevSamp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StdDevSamp stdDevSampOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevSamp(asFields(fieldReference)); + } + + /** + * Creates new {@link StdDevSamp}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static StdDevSamp stdDevSampOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StdDevSamp(Collections.singletonList(expression)); + } + + /** + * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public StdDevSamp and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevSamp(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public StdDevSamp and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StdDevSamp(append(expression)); + } + + @Override + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $cmp}. + * + * @author Christoph Strobl + */ + class Cmp extends AbstractAggregationExpression { + + private Cmp(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$cmp"; + } + + /** + * Creates new {@link Cmp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Cmp valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Cmp(asFields(fieldReference)); + } + + /** + * Creates new {@link Cmp}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Cmp valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Cmp(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Cmp} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Cmp compareTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Cmp(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Cmp} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Cmp compareTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Cmp(append(expression)); + } + + /** + * Creates new {@link Cmp} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Cmp compareToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Cmp(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $eq}. + * + * @author Christoph Strobl + */ + class Eq extends AbstractAggregationExpression { + + private Eq(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$eq"; + } + + /** + * Creates new {@link Eq}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Eq valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Eq(asFields(fieldReference)); + } + + /** + * Creates new {@link Eq}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Eq valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Eq(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Eq equalTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Eq(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Eq equalTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Eq(append(expression)); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Eq equalToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Eq(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $gt}. + * + * @author Christoph Strobl + */ + class Gt extends AbstractAggregationExpression { + + private Gt(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$gt"; + } + + /** + * Creates new {@link Gt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Gt valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gt(asFields(fieldReference)); + } + + /** + * Creates new {@link Gt}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Gt valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gt(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Gt} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gt greaterThan(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gt(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Gt} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gt greaterThan(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gt(append(expression)); + } + + /** + * Creates new {@link Gt} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Gt greaterThanValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Gt(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $lt}. + * + * @author Christoph Strobl + */ + class Lt extends AbstractAggregationExpression { + + private Lt(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$lt"; + } + + /** + * Creates new {@link Lt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Lt valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lt(asFields(fieldReference)); + } + + /** + * Creates new {@link Lt}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Lt valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lt(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Lt} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lt lessThan(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lt(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Lt} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lt lessThan(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lt(append(expression)); + } + + /** + * Creates new {@link Lt} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Lt lessThanValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Lt(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $gte}. + * + * @author Christoph Strobl + */ + class Gte extends AbstractAggregationExpression { + + private Gte(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$gte"; + } + + /** + * Creates new {@link Gte}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Gte valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gte(asFields(fieldReference)); + } + + /** + * Creates new {@link Gte}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Gte valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gte(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Gte} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gte(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Gte} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gte(append(expression)); + } + + /** + * Creates new {@link Gte} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Gte(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $lte}. + * + * @author Christoph Strobl + */ + class Lte extends AbstractAggregationExpression { + + private Lte(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$lte"; + } + + /** + * Creates new {@link Lte}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Lte valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lte(asFields(fieldReference)); + } + + /** + * Creates new {@link Lte}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Lte valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lte(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Lte} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lte(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Lte} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lte(append(expression)); + } + + /** + * Creates new {@link Lte} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Lte lessThanEqualToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Lte(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $ne}. + * + * @author Christoph Strobl + */ + class Ne extends AbstractAggregationExpression { + + private Ne(List value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$ne"; + } + + /** + * Creates new {@link Ne}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Ne valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ne(asFields(fieldReference)); + } + + /** + * Creates new {@link Ne}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Ne valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ne(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Ne} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Ne notEqualTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ne(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Ne} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Ne notEqualTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ne(append(expression)); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Ne notEqualToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Ne(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $and}. + * + * @author Christoph Strobl + */ + class And extends AbstractAggregationExpression { + + private And(List values) { + super(values); + } + + @Override + public String getMongoMethod() { + return "$and"; + } + + /** + * Creates new {@link And} that evaluates one or more expressions and returns {@literal true} if all of the + * expressions are {@literal true}. + * + * @param expressions + * @return + */ + public static And and(Object... expressions) { + return new And(Arrays.asList(expressions)); + } + + /** + * Creates new {@link And} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public And andExpression(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new And(append(expression)); + } + + /** + * Creates new {@link And} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public And andField(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new And(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link And} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public And andValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new And(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $or}. + * + * @author Christoph Strobl + */ + class Or extends AbstractAggregationExpression { + + private Or(List values) { + super(values); + } + + @Override + public String getMongoMethod() { + return "$or"; + } + + /** + * Creates new {@link Or} that evaluates one or more expressions and returns {@literal true} if any of the + * expressions are {@literal true}. + * + * @param expressions must not be {@literal null}. + * @return + */ + public static Or or(Object... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new Or(Arrays.asList(expressions)); + } + + /** + * Creates new {@link Or} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Or orExpression(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Or(append(expression)); + } + + /** + * Creates new {@link Or} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Or orField(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Or(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Or} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Or orValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Or(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $not}. + * + * @author Christoph Strobl + */ + class Not extends AbstractAggregationExpression { + + private Not(Object value) { + super(value); + } + + @Override + public String getMongoMethod() { + return "$not"; + } + + /** + * Creates new {@link Not} that evaluates the boolean value of the referenced field and returns the opposite boolean + * value. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Not not(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Not(asFields(fieldReference)); + } + + /** + * Creates new {@link Not} that evaluates the resulting boolean value of the given {@link AggregationExpression} and + * returns the opposite boolean value. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Not not(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Not(Collections.singletonList(expression)); + } + } + } 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 761f2c3164..1e862f33a2 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -29,11 +29,14 @@ * * @author Thomas Darimont * @author Oliver Gierke - * @since 1.10 + * @author Christoph Strobl + * @since 1.7 + * @deprecated since 1.10. Please use {@link AggregationExpressions} instead. */ +@Deprecated public enum AggregationFunctionExpressions { - SIZE, CMP, EQ, GT, GTE, LT, LTE, NE; + SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT; /** * Returns an {@link AggregationExpression} build from the current {@link Enum} name and the given parameters. @@ -52,7 +55,7 @@ public AggregationExpression of(Object... parameters) { * * @author Thomas Darimont * @author Oliver Gierke - * @since 1.10 + * @since 1.7 */ static class FunctionExpression implements AggregationExpression { 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 70cd2070a3..183d526520 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 @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -185,6 +186,14 @@ public Iterator iterator() { return fields.iterator(); } + /** + * + * @return + * @since 1.10 + */ + public List asList() { + return Collections.unmodifiableList(fields); + } /** * Value object to encapsulate a field in an aggregation operation. * 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 2eaba433dd..cc80896223 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 @@ -528,6 +528,20 @@ public ProjectionOperationBuilder minus(String fieldReference) { return project("subtract", Fields.field(fieldReference)); } + /** + * Generates an {@code $subtract} expression that subtracts the result of the given {@link AggregationExpression} + * from the previously mentioned field. + * + * @param expression must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder minus(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return project("subtract", expression); + } + /** * Generates an {@code $multiply} expression that multiplies the given number with the previously mentioned field. * @@ -553,6 +567,20 @@ public ProjectionOperationBuilder multiply(String fieldReference) { return project("multiply", Fields.field(fieldReference)); } + /** + * Generates an {@code $multiply} expression that multiplies the previously with the result of the + * {@link AggregationExpression}. mentioned field. + * + * @param expression must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder multiply(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return project("multiply", expression); + } + /** * Generates an {@code $divide} expression that divides the previously mentioned field by the given number. * @@ -579,6 +607,20 @@ public ProjectionOperationBuilder divide(String fieldReference) { return project("divide", Fields.field(fieldReference)); } + /** + * Generates an {@code $divide} expression that divides the value of the previously mentioned by the result of the + * {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder divide(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return project("divide", expression); + } + /** * Generates an {@code $mod} expression that divides the previously mentioned field by the given number and returns * the remainder. @@ -596,7 +638,7 @@ public ProjectionOperationBuilder mod(Number number) { /** * Generates an {@code $mod} expression that divides the value of the given field by the previously mentioned field * and returns the remainder. - * + * * @param fieldReference * @return */ @@ -606,6 +648,20 @@ public ProjectionOperationBuilder mod(String fieldReference) { return project("mod", Fields.field(fieldReference)); } + /** + * Generates an {@code $mod} expression that divides the value of the previously mentioned field by the result of + * the {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder mod(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return project("mod", expression); + } + /** * Generates a {@code $size} expression that returns the size of the array held by the given field.
* @@ -733,6 +789,410 @@ public ProjectionOperationBuilder filter(String as, AggregationExpression condit return this.operation.and(AggregationExpressions.Filter.filter(name).as(as).by(condition)); } + // SET OPERATORS + + /** + * Generates a {@code $setEquals} expression that compares the previously mentioned field to one or more arrays and + * returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. + * + * @param arrays must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder equalsArrays(String... arrays) { + + Assert.notEmpty(arrays, "Arrays must not be null or empty!"); + return project("setEquals", Fields.fields(arrays)); + } + + /** + * Generates a {@code $setIntersection} expression that takes array of the previously mentioned field and one or + * more arrays and returns an array that contains the elements that appear in every of those. + * + * @param arrays must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder intersectsArrays(String... arrays) { + + Assert.notEmpty(arrays, "Arrays must not be null or empty!"); + return project("setIntersection", Fields.fields(arrays)); + } + + /** + * Generates a {@code $setUnion} expression that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in any of those. + * + * @param arrays must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder unionArrays(String... arrays) { + + Assert.notEmpty(arrays, "Arrays must not be null or empty!"); + return project("setUnion", Fields.fields(arrays)); + } + + /** + * Generates a {@code $setDifference} expression that takes array of the previously mentioned field and returns an + * array containing the elements that do not exist in the given {@literal array}. + * + * @param array must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder differenceToArray(String array) { + + Assert.hasText(array, "Array must not be null or empty!"); + return project("setDifference", Fields.fields(array)); + } + + /** + * Generates a {@code $setIsSubset} expression that takes array of the previously mentioned field and returns + * {@literal true} if it is a subset of the given {@literal array}. + * + * @param array must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder subsetOfArray(String array) { + + Assert.hasText(array, "Array must not be null or empty!"); + return project("setIsSubset", Fields.fields(array)); + } + + /** + * Generates an {@code $anyElementTrue} expression that Takes array of the previously mentioned field and returns + * {@literal true} if any of the elements are {@literal true} and {@literal false} otherwise. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder anyElementInArrayTrue() { + return project("anyElementTrue"); + } + + /** + * Generates an {@code $allElementsTrue} expression that takes array of the previously mentioned field and returns + * {@literal true} if no elements is {@literal false}. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder allElementsInArrayTrue() { + return project("allElementsTrue"); + } + + /** + * Generates a {@code $abs} expression that takes the number of the previously mentioned field and returns the + * absolute value of it. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder absoluteValue() { + return this.operation.and(AggregationExpressions.Abs.absoluteValueOf(name)); + } + + /** + * Generates a {@code $ceil} expression that takes the number of the previously mentioned field and returns the + * smallest integer greater than or equal to the specified number. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder ceil() { + return this.operation.and(AggregationExpressions.Ceil.ceilValueOf(name)); + } + + /** + * Generates a {@code $exp} expression that takes the number of the previously mentioned field and raises Euler’s + * number (i.e. e ) on it. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder exp() { + return this.operation.and(AggregationExpressions.Exp.expValueOf(name)); + } + + /** + * Generates a {@code $floor} expression that takes the number of the previously mentioned field and returns the + * largest integer less than or equal to it. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder floor() { + return this.operation.and(AggregationExpressions.Floor.floorValueOf(name)); + } + + /** + * Generates a {@code $ln} expression that takes the number of the previously mentioned field and calculates the + * natural logarithm ln (i.e loge) of it. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder ln() { + return this.operation.and(AggregationExpressions.Ln.lnValueOf(name)); + } + + /** + * Generates a {@code $log} expression that takes the number of the previously mentioned field and calculates the + * log of the associated number in the specified base. + * + * @param baseFieldRef must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder log(String baseFieldRef) { + return this.operation.and(AggregationExpressions.Log.valueOf(name).log(baseFieldRef)); + } + + /** + * Generates a {@code $log} expression that takes the number of the previously mentioned field and calculates the + * log of the associated number in the specified base. + * + * @param base must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder log(Number base) { + return this.operation.and(AggregationExpressions.Log.valueOf(name).log(base)); + } + + /** + * Generates a {@code $log} expression that takes the number of the previously mentioned field and calculates the + * log of the associated number in the specified base. + * + * @param base must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder log(AggregationExpression base) { + return this.operation.and(AggregationExpressions.Log.valueOf(name).log(base)); + } + + /** + * Generates a {@code $log10} expression that takes the number of the previously mentioned field and calculates the + * log base 10. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder log10() { + return this.operation.and(AggregationExpressions.Log10.log10ValueOf(name)); + } + + /** + * Generates a {@code $pow} expression that takes the number of the previously mentioned field and raises it by the + * specified exponent. + * + * @param exponentFieldRef must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder pow(String exponentFieldRef) { + return this.operation.and(AggregationExpressions.Pow.valueOf(name).pow(exponentFieldRef)); + } + + /** + * Generates a {@code $pow} expression that takes the number of the previously mentioned field and raises it by the + * specified exponent. + * + * @param exponent must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder pow(Number exponent) { + return this.operation.and(AggregationExpressions.Pow.valueOf(name).pow(exponent)); + } + + /** + * Generates a {@code $pow} expression that Takes the number of the previously mentioned field and raises it by the + * specified exponent. + * + * @param exponentExpression must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder pow(AggregationExpression exponentExpression) { + return this.operation.and(AggregationExpressions.Pow.valueOf(name).pow(exponentExpression)); + } + + /** + * Generates a {@code $sqrt} expression that takes the number of the previously mentioned field and calculates the + * square root. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder sqrt() { + return this.operation.and(AggregationExpressions.Sqrt.sqrtOf(name)); + } + + /** + * Takes the number of the previously mentioned field and truncates it to its integer value. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder trunc() { + return this.operation.and(AggregationExpressions.Trunc.truncValueOf(name)); + } + + /** + * Generates a {@code $concat} expression that takes the string representation of the previously mentioned field and + * concats given values to it. + * + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder concat(Object... values) { + return project("concat", values); + } + + /** + * Generates a {@code $substr} expression that Takes the string representation of the previously mentioned field and + * returns a substring starting at a specified index position. + * + * @param start + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder substring(int start) { + return substring(start, -1); + } + + /** + * Generates a {@code $substr} expression that takes the string representation of the previously mentioned field and + * returns a substring starting at a specified index position including the specified number of characters. + * + * @param start + * @param nrOfChars + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder substring(int start, int nrOfChars) { + return project("substr", start, nrOfChars); + } + + /** + * Generates a {@code $toLower} expression that takes the string representation of the previously mentioned field + * and lowers it. + * + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder toLower() { + return this.operation.and(AggregationExpressions.ToLower.lowerValueOf(name)); + } + + /** + * Generates a {@code $toUpper} expression that takes the string representation of the previously mentioned field + * and uppers it. + * + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder toUpper() { + return this.operation.and(AggregationExpressions.ToUpper.upperValueOf(name)); + } + + /** + * Generates a {@code $strcasecmp} expression that takes the string representation of the previously mentioned field + * and performs case-insensitive comparison to the given {@literal value}. + * + * @param value must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder strCaseCmp(String value) { + return project("strcasecmp", value); + } + + /** + * Generates a {@code $strcasecmp} expression that takes the string representation of the previously mentioned field + * and performs case-insensitive comparison to the referenced {@literal fieldRef}. + * + * @param fieldRef must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder strCaseCmpValueOf(String fieldRef) { + return project("strcasecmp", fieldRef); + } + + /** + * Generates a {@code $strcasecmp} expression that takes the string representation of the previously mentioned field + * and performs case-insensitive comparison to the result of the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder strCaseCmp(AggregationExpression expression) { + return project("strcasecmp", expression); + } + + /** + * Generates a {@code $arrayElemAt} expression that takes the string representation of the previously mentioned + * field and returns the element at the specified array {@literal position}. + * + * @param position + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder arrayElementAt(int position) { + return project("arrayElemAt", position); + } + + /** + * Generates a {@code $concatArrays} expression that takes the string representation of the previously mentioned + * field and concats it with the arrays from the referenced {@literal fields}. + * + * @param fields must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder concatArrays(String... fields) { + return project("concatArrays", Fields.fields(fields)); + } + + /** + * Generates a {@code $isArray} expression that takes the string representation of the previously mentioned field + * and checks if its an array. + * + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder isArray() { + return this.operation.and(AggregationExpressions.IsArray.isArray(name)); + } + + /** + * Generates a {@code $literal} expression that Takes the value previously and uses it as literal. + * + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder asLiteral() { + return this.operation.and(AggregationExpressions.Literal.asLiteral(name)); + } + + /** + * Generates a {@code $dateToString} expression that takes the date representation of the previously mentioned field + * and applies given {@literal format} to it. + * + * @param format must not be {@literal null}. + * @return + * @since 1.10 + */ + public ProjectionOperationBuilder dateAsFormattedString(String format) { + return this.operation.and(AggregationExpressions.DateToString.dateOf(name).toString(format)); + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) @@ -917,7 +1377,18 @@ protected List getOperationArguments(AggregationOperationContext context result.add(context.getReference(getField().getName()).toString()); for (Object element : values) { - result.add(element instanceof Field ? context.getReference((Field) element).toString() : element); + + if (element instanceof Field) { + result.add(context.getReference((Field) element).toString()); + } else if (element instanceof Fields) { + for (Field field : (Fields) element) { + result.add(context.getReference(field).toString()); + } + } else if (element instanceof AggregationExpression) { + result.add(((AggregationExpression) element).toDbObject(context)); + } else { + result.add(element); + } } return result; 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 3807e30d73..59ba03bc3e 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 @@ -17,6 +17,7 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; import static org.springframework.data.mongodb.core.aggregation.AggregationFunctionExpressions.*; import static org.springframework.data.mongodb.core.aggregation.Fields.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; @@ -27,10 +28,19 @@ import org.junit.Test; import org.springframework.data.mongodb.core.DBObjectTestUtils; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArrayOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.BooleanOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ComparisonOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.DateOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.LiteralOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.SetOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.StringOperators; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import com.mongodb.util.JSON; /** * Unit tests for {@link ProjectionOperation}. @@ -275,9 +285,8 @@ public void projectionExpressions() { .and("foo").as("bar"); // DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); - assertThat( - dbObject.toString(), - is("{ \"$project\" : { \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"bar\" : \"$foo\"}}")); + assertThat(dbObject.toString(), is( + "{ \"$project\" : { \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"bar\" : \"$foo\"}}")); } /** @@ -332,10 +341,8 @@ public void shouldRenderDateTimeFragmentExtractionsForExpressionProjectionsCorre assertThat(dbObject, is(notNullValue())); DBObject projected = exctractOperation("$project", dbObject); - assertThat( - projected.get("dayOfYearPlus1Day"), - is((Object) new BasicDBObject("$dayOfYear", Arrays.asList(new BasicDBObject("$add", Arrays. asList( - "$date", 86400000)))))); + assertThat(projected.get("dayOfYearPlus1Day"), is((Object) new BasicDBObject("$dayOfYear", + Arrays.asList(new BasicDBObject("$add", Arrays. asList("$date", 86400000)))))); } /** @@ -487,6 +494,1185 @@ public void shouldRenderNeCorrectly() { isBsonObject().containing("$project.ne10.$ne.[0]", "$field").containing("$project.ne10.$ne.[1]", 10)); } + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSetEquals() { + + DBObject agg = project("A", "B").and("A").equalsArrays("B").as("sameElements") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, sameElements: { $setEquals: [ \"$A\", \"$B\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSetEqualsAggregationExpresssion() { + + DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").isEqualTo("B")).as("sameElements") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, sameElements: { $setEquals: [ \"$A\", \"$B\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSetIntersection() { + + DBObject agg = project("A", "B").and("A").intersectsArrays("B").as("commonToBoth") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { A: 1, B: 1, commonToBoth: { $setIntersection: [ \"$A\", \"$B\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSetIntersectionAggregationExpresssion() { + + DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").intersects("B")).as("commonToBoth") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { A: 1, B: 1, commonToBoth: { $setIntersection: [ \"$A\", \"$B\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSetUnion() { + + DBObject agg = project("A", "B").and("A").unionArrays("B").as("allValues").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, allValues: { $setUnion: [ \"$A\", \"$B\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSetUnionAggregationExpresssion() { + + DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").union("B")).as("allValues") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, allValues: { $setUnion: [ \"$A\", \"$B\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSetDifference() { + + DBObject agg = project("A", "B").and("B").differenceToArray("A").as("inBOnly") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, inBOnly: { $setDifference: [ \"$B\", \"$A\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSetDifferenceAggregationExpresssion() { + + DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("B").differenceTo("A")).as("inBOnly") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, inBOnly: { $setDifference: [ \"$B\", \"$A\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSetIsSubset() { + + DBObject agg = project("A", "B").and("A").subsetOfArray("B").as("aIsSubsetOfB") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, aIsSubsetOfB: { $setIsSubset: [ \"$A\", \"$B\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSetIsSubsetAggregationExpresssion() { + + DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").isSubsetOf("B")).as("aIsSubsetOfB") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, aIsSubsetOfB: { $setIsSubset: [ \"$A\", \"$B\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderAnyElementTrue() { + + DBObject agg = project("responses").and("responses").anyElementInArrayTrue().as("isAnyTrue") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { responses: 1, isAnyTrue: { $anyElementTrue: [ \"$responses\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderAnyElementTrueAggregationExpresssion() { + + DBObject agg = project("responses").and(SetOperators.arrayAsSet("responses").anyElementTrue()).as("isAnyTrue") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { responses: 1, isAnyTrue: { $anyElementTrue: [ \"$responses\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderAllElementsTrue() { + + DBObject agg = project("responses").and("responses").allElementsInArrayTrue().as("isAllTrue") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { responses: 1, isAllTrue: { $allElementsTrue: [ \"$responses\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderAllElementsTrueAggregationExpresssion() { + + DBObject agg = project("responses").and(SetOperators.arrayAsSet("responses").allElementsTrue()).as("isAllTrue") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { responses: 1, isAllTrue: { $allElementsTrue: [ \"$responses\" ] }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderAbs() { + + DBObject agg = project().and("anyNumber").absoluteValue().as("absoluteValue") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { absoluteValue : { $abs: \"$anyNumber\" }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderAbsAggregationExpresssion() { + + DBObject agg = project() + .and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).abs()) + .as("delta").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { delta: { $abs: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderAddAggregationExpresssion() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("price").add("fee")).as("total") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse(" { $project: { total: { $add: [ \"$price\", \"$fee\" ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderCeil() { + + DBObject agg = project().and("anyNumber").ceil().as("ceilValue").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { ceilValue : { $ceil: \"$anyNumber\" }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderCeilAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).ceil()) + .as("delta").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { delta: { $ceil: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderDivide() { + + DBObject agg = project().and("value") + .divide(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).as("result") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $divide: [ \"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderDivideAggregationExpresssion() { + + DBObject agg = project() + .and(ArithmeticOperators.valueOf("anyNumber") + .divideBy(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end")))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON + .parse("{ $project: { result: { $divide: [ \"$anyNumber\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderExp() { + + DBObject agg = project().and("value").exp().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $exp: \"$value\" } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderExpAggregationExpresssion() { + + DBObject agg = project() + .and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).exp()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $exp: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderFloor() { + + DBObject agg = project().and("value").floor().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $floor: \"$value\" } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderFloorAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).floor()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $floor: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLn() { + + DBObject agg = project().and("value").ln().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $ln: \"$value\"} }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLnAggregationExpresssion() { + + DBObject agg = project() + .and(ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).ln()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $ln: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLog() { + + DBObject agg = project().and("value").log(2).as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $log: [ \"$value\", 2] } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLogAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).log(2)) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $log: [ { $subtract: [ \"$start\", \"$end\" ] }, 2] } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLog10() { + + DBObject agg = project().and("value").log10().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $log10: \"$value\" } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLog10AggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).log10()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $log10: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderMod() { + + DBObject agg = project().and("value").mod(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $mod: [\"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderModAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).mod(2)) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $mod: [{ $subtract: [ \"$start\", \"$end\" ] }, 2] } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderMultiply() { + + DBObject agg = project().and("value") + .multiply(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).as("result") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is( + JSON.parse("{ $project: { result: { $multiply: [\"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderMultiplyAggregationExpresssion() { + + DBObject agg = project() + .and(ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))) + .multiplyBy(2).multiplyBy("refToAnotherNumber")) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse( + "{ $project: { result: { $multiply: [{ $subtract: [ \"$start\", \"$end\" ] }, 2, \"$refToAnotherNumber\"] } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderPow() { + + DBObject agg = project().and("value").pow(2).as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $pow: [\"$value\", 2] } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderPowAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).pow(2)) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $pow: [{ $subtract: [ \"$start\", \"$end\" ] }, 2] } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSqrt() { + + DBObject agg = project().and("value").sqrt().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $sqrt: \"$value\" } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSqrtAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).sqrt()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $sqrt: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSubtract() { + + DBObject agg = project().and("numericField").minus(AggregationFunctionExpressions.SIZE.of(field("someArray"))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $subtract: [ \"$numericField\", { $size : [\"$someArray\"]}] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSubtractAggregationExpresssion() { + + DBObject agg = project() + .and(ArithmeticOperators.valueOf("numericField") + .subtract(AggregationFunctionExpressions.SIZE.of(field("someArray")))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $subtract: [ \"$numericField\", { $size : [\"$someArray\"]}] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderTrunc() { + + DBObject agg = project().and("value").trunc().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result : { $trunc: \"$value\" }}}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderTruncAggregationExpresssion() { + + DBObject agg = project().and( + ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).trunc()) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $trunc: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderConcat() { + + DBObject agg = project().and("item").concat(" - ", field("description")).as("itemDescription") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { itemDescription: { $concat: [ \"$item\", \" - \", \"$description\" ] } } }"))); + + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderConcatAggregationExpression() { + + DBObject agg = project().and(StringOperators.valueOf("item").concat(" - ").concatValueOf("description")) + .as("itemDescription").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { itemDescription: { $concat: [ \"$item\", \" - \", \"$description\" ] } } }"))); + + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSubstr() { + + DBObject agg = project().and("quarter").substring(0, 2).as("yearSubstring").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { yearSubstring: { $substr: [ \"$quarter\", 0, 2 ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSubstrAggregationExpression() { + + DBObject agg = project().and(StringOperators.valueOf("quarter").substring(0, 2)).as("yearSubstring") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { yearSubstring: { $substr: [ \"$quarter\", 0, 2 ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderToLower() { + + DBObject agg = project().and("item").toLower().as("item").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { item: { $toLower: \"$item\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderToLowerAggregationExpression() { + + DBObject agg = project().and(StringOperators.valueOf("item").toLower()).as("item") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { item: { $toLower: \"$item\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderToUpper() { + + DBObject agg = project().and("item").toUpper().as("item").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { item: { $toUpper: \"$item\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderToUpperAggregationExpression() { + + DBObject agg = project().and(StringOperators.valueOf("item").toUpper()).as("item") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { item: { $toUpper: \"$item\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderStrCaseCmp() { + + DBObject agg = project().and("quarter").strCaseCmp("13q4").as("comparisonResult") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { comparisonResult: { $strcasecmp: [ \"$quarter\", \"13q4\" ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderStrCaseCmpAggregationExpression() { + + DBObject agg = project().and(StringOperators.valueOf("quarter").strCaseCmp("13q4")).as("comparisonResult") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { comparisonResult: { $strcasecmp: [ \"$quarter\", \"13q4\" ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderArrayElementAt() { + + DBObject agg = project().and("favorites").arrayElementAt(0).as("first").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { first: { $arrayElemAt: [ \"$favorites\", 0 ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderArrayElementAtAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("favorites").elementAt(0)).as("first") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { first: { $arrayElemAt: [ \"$favorites\", 0 ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderConcatArrays() { + + DBObject agg = project().and("instock").concatArrays("ordered").as("items").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { items: { $concatArrays: [ \"$instock\", \"$ordered\" ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderConcatArraysAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("instock").concat("ordered")).as("items") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { items: { $concatArrays: [ \"$instock\", \"$ordered\" ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderIsArray() { + + DBObject agg = project().and("instock").isArray().as("isAnArray").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { isAnArray: { $isArray: \"$instock\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderIsArrayAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("instock").isArray()).as("isAnArray") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { isAnArray: { $isArray: \"$instock\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSizeAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("instock").length()).as("arraySize") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { arraySize: { $size: \"$instock\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSliceAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("favorites").slice().itemCount(3)).as("threeFavorites") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { threeFavorites: { $slice: [ \"$favorites\", 3 ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSliceWithPositionAggregationExpression() { + + DBObject agg = project().and(ArrayOperators.arrayOf("favorites").slice().offset(2).itemCount(3)) + .as("threeFavorites").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { threeFavorites: { $slice: [ \"$favorites\", 2, 3 ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLiteral() { + + DBObject agg = project().and("$1").asLiteral().as("literalOnly").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { literalOnly: { $literal: \"$1\"} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLiteralAggregationExpression() { + + DBObject agg = project().and(LiteralOperators.valueOf("$1").asLiteral()).as("literalOnly") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { literalOnly: { $literal: \"$1\"} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderDayOfYearAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").dayOfYear()).as("dayOfYear") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { dayOfYear: { $dayOfYear: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderDayOfMonthAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").dayOfMonth()).as("day") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { day: { $dayOfMonth: \"$date\" }} }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderDayOfWeekAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").dayOfWeek()).as("dayOfWeek") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { dayOfWeek: { $dayOfWeek: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderYearAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").year()).as("year") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { year: { $year: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderMonthAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").month()).as("month") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { month: { $month: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderWeekAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").week()).as("week") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { week: { $week: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderHourAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").hour()).as("hour") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { hour: { $hour: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderMinuteAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").minute()).as("minute") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { minute: { $minute: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSecondAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").second()).as("second") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { second: { $second: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderMillisecondAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").millisecond()).as("msec") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { msec: { $millisecond: \"$date\" } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderDateToString() { + + DBObject agg = project().and("date").dateAsFormattedString("%H:%M:%S:%L").as("time") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderDateToStringAggregationExpression() { + + DBObject agg = project().and(DateOperators.dateOf("date").toString("%H:%M:%S:%L")).as("time") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSumAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").sum()).as("quizTotal") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { quizTotal: { $sum: \"$quizzes\"} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderSumWithMultipleArgsAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("final").sum().and("midterm")).as("examTotal") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { examTotal: { $sum: [ \"$final\", \"$midterm\" ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderAvgAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").avg()).as("quizAvg") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { quizAvg: { $avg: \"$quizzes\"} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderAvgWithMultipleArgsAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("final").avg().and("midterm")).as("examAvg") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { examAvg: { $avg: [ \"$final\", \"$midterm\" ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderMaxAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").max()).as("quizMax") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { quizMax: { $max: \"$quizzes\"} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderMaxWithMultipleArgsAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("final").max().and("midterm")).as("examMax") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { examMax: { $max: [ \"$final\", \"$midterm\" ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderMinAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").min()).as("quizMin") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { quizMin: { $min: \"$quizzes\"} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderMinWithMultipleArgsAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("final").min().and("midterm")).as("examMin") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { examMin: { $min: [ \"$final\", \"$midterm\" ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderStdDevPopAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("scores").stdDevPop()).as("stdDev") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { stdDev: { $stdDevPop: \"$scores\"} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderStdDevSampAggregationExpression() { + + DBObject agg = project().and(ArithmeticOperators.valueOf("scores").stdDevSamp()).as("stdDev") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { stdDev: { $stdDevSamp: \"$scores\"} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderCmpAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").compareToValue(250)).as("cmp250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { cmp250: { $cmp: [\"$qty\", 250]} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderEqAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").equalToValue(250)).as("eq250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { eq250: { $eq: [\"$qty\", 250]} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderGtAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").greaterThanValue(250)).as("gt250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { gt250: { $gt: [\"$qty\", 250]} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderGteAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").greaterThanEqualToValue(250)).as("gte250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { gte250: { $gte: [\"$qty\", 250]} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLtAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").lessThanValue(250)).as("lt250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { lt250: { $lt: [\"$qty\", 250]} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLteAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").lessThanEqualToValue(250)).as("lte250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { lte250: { $lte: [\"$qty\", 250]} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderNeAggregationExpression() { + + DBObject agg = project().and(ComparisonOperators.valueOf("qty").notEqualToValue(250)).as("ne250") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { ne250: { $ne: [\"$qty\", 250]} } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLogicAndAggregationExpression() { + + DBObject agg = project() + .and(BooleanOperators.valueOf(ComparisonOperators.valueOf("qty").greaterThanValue(100)) + .and(ComparisonOperators.valueOf("qty").lessThanValue(250))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is( + JSON.parse("{ $project: { result: { $and: [ { $gt: [ \"$qty\", 100 ] }, { $lt: [ \"$qty\", 250 ] } ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderLogicOrAggregationExpression() { + + DBObject agg = project() + .and(BooleanOperators.valueOf(ComparisonOperators.valueOf("qty").greaterThanValue(250)) + .or(ComparisonOperators.valueOf("qty").lessThanValue(200))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is( + JSON.parse("{ $project: { result: { $or: [ { $gt: [ \"$qty\", 250 ] }, { $lt: [ \"$qty\", 200 ] } ] } } }"))); + } + + /** + * @see DATAMONGO-1536 + */ + @Test + public void shouldRenderNotAggregationExpression() { + + DBObject agg = project().and(BooleanOperators.not(ComparisonOperators.valueOf("qty").greaterThanValue(250))) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $not: [ { $gt: [ \"$qty\", 250 ] } ] } } }"))); + } + private static DBObject exctractOperation(String field, DBObject fromProjectClause) { return (DBObject) fromProjectClause.get(field); } diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index c375facbba..a6e2870de9 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1676,17 +1676,29 @@ At the time of this writing we provide support for the following Aggregation Ope | Pipeline Aggregation Operators | project, skip, limit, lookup, unwind, group, sort, geoNear +| Set Aggregation Operators +| setEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTrue + | Group Aggregation Operators -| addToSet, first, last, max, min, avg, push, sum, (*count) +| addToSet, first, last, max, min, avg, push, sum, (*count), stdDevPop, stdDevSamp | Arithmetic Aggregation Operators -| add (*via plus), subtract (*via minus), multiply, divide, mod +| 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 | Comparison Aggregation Operators | eq (*via: is), gt, gte, lt, lte, ne | Array Aggregation Operators -| size, slice, filter +| arrayElementAt, concatArrays, filter, isArray, size, slice + +| Literal Operators +| literal + +| Date Aggregation Operators +| dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString | Conditional Aggregation Operators | cond, ifNull From bbfa0f7b83622db243805d408ad71892c372c9a6 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 2 Dec 2016 15:24:16 +0100 Subject: [PATCH 015/118] DATAMONGO-1536 - Polishing. Add JavaDoc. Change visibility of AbstractAggregationExpression.getMongoMethod() to protected. Original pull request: #418. --- .../aggregation/AggregationExpressions.java | 796 +++++++++++++++--- 1 file changed, 701 insertions(+), 95 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 be93d1e190..e8011faaec 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 @@ -33,6 +33,7 @@ /** * @author Christoph Strobl + * @author Mark Paluch * @since 1.10 */ public interface AggregationExpressions { @@ -80,7 +81,7 @@ public static Not not(String fieldReference) { * Creates new {@link AggregationExpressions} that evaluates the boolean value of {@link AggregationExpression} * result and returns the opposite boolean value. * - * @param fieldReference must not be {@literal null}. + * @param expression must not be {@literal null}. * @return */ public static Not not(AggregationExpression expression) { @@ -1232,12 +1233,26 @@ public static class StringOperatorFactory { private final String fieldReference; private final AggregationExpression expression; + /** + * Creates new {@link StringOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ public StringOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); this.fieldReference = fieldReference; this.expression = null; } + /** + * Creates new {@link StringOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ public StringOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); this.fieldReference = null; this.expression = expression; } @@ -1376,7 +1391,7 @@ private StrCaseCmp createStrCaseCmp() { } /** - * Gateway to {@litearl array} aggregation operations. + * Gateway to {@literal array} aggregation operations. * * @author Christoph Strobl */ @@ -1407,12 +1422,26 @@ public static class ArrayOperatorFactory { private final String fieldReference; private final AggregationExpression expression; + /** + * Creates new {@link ArrayOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ public ArrayOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); this.fieldReference = fieldReference; this.expression = null; } + /** + * Creates new {@link ArrayOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ public ArrayOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); this.fieldReference = null; this.expression = expression; } @@ -1424,7 +1453,7 @@ public ArrayOperatorFactory(AggregationExpression expression) { * @param position * @return */ - public ArrayElemtAt elementAt(int position) { + public ArrayElemAt elementAt(int position) { return createArrayElemAt().elementAt(position); } @@ -1435,7 +1464,7 @@ public ArrayElemtAt elementAt(int position) { * @param expression must not be {@literal null}. * @return */ - public ArrayElemtAt elementAt(AggregationExpression expression) { + public ArrayElemAt elementAt(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return createArrayElemAt().elementAt(expression); @@ -1448,14 +1477,14 @@ public ArrayElemtAt elementAt(AggregationExpression expression) { * @param fieldReference must not be {@literal null}. * @return */ - public ArrayElemtAt elementAt(String fieldReference) { + public ArrayElemAt elementAt(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return createArrayElemAt().elementAt(fieldReference); } - private ArrayElemtAt createArrayElemAt() { - return usesFieldRef() ? ArrayElemtAt.arrayOf(fieldReference) : ArrayElemtAt.arrayOf(expression); + private ArrayElemAt createArrayElemAt() { + return usesFieldRef() ? ArrayElemAt.arrayOf(fieldReference) : ArrayElemAt.arrayOf(expression); } /** @@ -1532,6 +1561,8 @@ private boolean usesFieldRef() { } /** + * Gateway to {@literal literal} aggregation operations. + * * @author Christoph Strobl */ class LiteralOperators { @@ -1552,7 +1583,14 @@ public static class LiteralOperatorFactory { private final Object value; + /** + * Creates new {@link LiteralOperatorFactory} for given {@literal value}. + * + * @param value must not be {@literal null}. + */ public LiteralOperatorFactory(Object value) { + + Assert.notNull(value, "Value must not be null!"); this.value = value; } @@ -1568,6 +1606,8 @@ public Literal asLiteral() { } /** + * Gateway to {@literal Date} aggregation operations. + * * @author Christoph Strobl */ class DateOperators { @@ -1587,7 +1627,7 @@ public static DateOperatorFactory dateOf(String fieldReference) { /** * Take the date resulting from the given {@literal expression}. * - * @param fieldReference must not be {@literal null}. + * @param expression must not be {@literal null}. * @return */ public static DateOperatorFactory dateOf(AggregationExpression expression) { @@ -1601,6 +1641,11 @@ public static class DateOperatorFactory { private final String fieldReference; private final AggregationExpression expression; + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ public DateOperatorFactory(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); @@ -1608,6 +1653,11 @@ public DateOperatorFactory(String fieldReference) { this.expression = null; } + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ public DateOperatorFactory(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -1825,7 +1875,7 @@ protected Object append(String key, Object value) { } - public abstract String getMongoMethod(); + protected abstract String getMongoMethod(); } /** @@ -1840,7 +1890,7 @@ private SetEquals(List arrays) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$setEquals"; } @@ -1917,7 +1967,7 @@ private SetIntersection(List arrays) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$setIntersection"; } @@ -1982,7 +2032,7 @@ private SetUnion(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$setUnion"; } @@ -2001,7 +2051,7 @@ public static SetUnion arrayAsSet(String arrayReference) { /** * Creates new {@link SetUnion}. * - * @param expressions must not be {@literal null}. + * @param expression must not be {@literal null}. * @return */ public static SetUnion arrayAsSet(AggregationExpression expression) { @@ -2047,7 +2097,7 @@ private SetDifference(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$setDifference"; } @@ -2112,7 +2162,7 @@ private SetIsSubset(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$setIsSubset"; } @@ -2177,7 +2227,7 @@ private AnyElementTrue(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$anyElementTrue"; } @@ -2222,7 +2272,7 @@ private AllElementsTrue(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$allElementsTrue"; } @@ -2267,23 +2317,42 @@ private Abs(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$abs"; } + /** + * Creates new {@link Abs}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Abs absoluteValueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Abs(Fields.field(fieldReference)); } + /** + * Creates new {@link Abs}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Abs absoluteValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Abs(expression); } - + /** + * Creates new {@link Abs}. + * + * @param value must not be {@literal null}. + * @return + */ public static Abs absoluteValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Abs(value); } } @@ -2300,23 +2369,43 @@ protected Add(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$add"; } + /** + * Creates new {@link Add}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Add valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Add(asFields(fieldReference)); } + /** + * Creates new {@link Add}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Add valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Add(Collections.singletonList(expression)); } + /** + * Creates new {@link Add}. + * + * @param value must not be {@literal null}. + * @return + */ public static Add valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Add(Collections.singletonList(value)); } @@ -2349,23 +2438,43 @@ private Ceil(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$ceil"; } + /** + * Creates new {@link Ceil}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Ceil ceilValueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Ceil(Fields.field(fieldReference)); } + /** + * Creates new {@link Ceil}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Ceil ceilValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Ceil(expression); } + /** + * Creates new {@link Ceil}. + * + * @param value must not be {@literal null}. + * @return + */ public static Ceil ceilValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Ceil(value); } } @@ -2382,23 +2491,44 @@ private Divide(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$divide"; } + + /** + * Creates new {@link Divide}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Divide valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Divide(asFields(fieldReference)); } + /** + * Creates new {@link Divide}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Divide valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Divide(Collections.singletonList(expression)); } + /** + * Creates new {@link Divide}. + * + * @param value must not be {@literal null}. + * @return + */ public static Divide valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Divide(Collections.singletonList(value)); } @@ -2426,28 +2556,48 @@ public Divide divideBy(Number value) { */ class Exp extends AbstractAggregationExpression { - protected Exp(Object value) { + private Exp(Object value) { super(value); } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$exp"; } + /** + * Creates new {@link Exp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Exp expValueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Exp(Fields.field(fieldReference)); } + /** + * Creates new {@link Exp}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Exp expValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Exp(expression); } + /** + * Creates new {@link Exp}. + * + * @param value must not be {@literal null}. + * @return + */ public static Exp expValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Exp(value); } } @@ -2459,28 +2609,48 @@ public static Exp expValueOf(Number value) { */ class Floor extends AbstractAggregationExpression { - protected Floor(Object value) { + private Floor(Object value) { super(value); } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$floor"; } + /** + * Creates new {@link Floor}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Floor floorValueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Floor(Fields.field(fieldReference)); } + /** + * Creates new {@link Floor}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Floor floorValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Floor(expression); } + /** + * Creates new {@link Floor}. + * + * @param value must not be {@literal null}. + * @return + */ public static Floor floorValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Floor(value); } } @@ -2497,23 +2667,43 @@ private Ln(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$ln"; } + /** + * Creates new {@link Ln}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Ln lnValueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Ln(Fields.field(fieldReference)); } + /** + * Creates new {@link Ln}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Ln lnValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Ln(expression); } + /** + * Creates new {@link Ln}. + * + * @param value must not be {@literal null}. + * @return + */ public static Ln lnValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Ln(value); } } @@ -2530,23 +2720,43 @@ private Log(List values) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$log"; } + /** + * Creates new {@link Min}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Log valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Log(asFields(fieldReference)); } + /** + * Creates new {@link Log}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Log valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Log(Collections.singletonList(expression)); } + /** + * Creates new {@link Log}. + * + * @param value must not be {@literal null}. + * @return + */ public static Log valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Log(Collections.singletonList(value)); } @@ -2579,23 +2789,43 @@ private Log10(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$log10"; } + /** + * Creates new {@link Log10}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Log10 log10ValueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Log10(Fields.field(fieldReference)); } + /** + * Creates new {@link Log10}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Log10 log10ValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Log10(expression); } + /** + * Creates new {@link Log10}. + * + * @param value must not be {@literal null}. + * @return + */ public static Log10 log10ValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Log10(value); } } @@ -2612,23 +2842,43 @@ private Mod(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$mod"; } + /** + * Creates new {@link Mod}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Mod valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Mod(asFields(fieldReference)); } + /** + * Creates new {@link Mod}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Mod valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Mod(Collections.singletonList(expression)); } + /** + * Creates new {@link Mod}. + * + * @param value must not be {@literal null}. + * @return + */ public static Mod valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Mod(Collections.singletonList(value)); } @@ -2661,23 +2911,43 @@ private Multiply(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$multiply"; } + /** + * Creates new {@link Multiply}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Multiply valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Multiply(asFields(fieldReference)); } + /** + * Creates new {@link Multiply}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Multiply valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Multiply(Collections.singletonList(expression)); } + /** + * Creates new {@link Multiply}. + * + * @param value must not be {@literal null}. + * @return + */ public static Multiply valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Multiply(Collections.singletonList(value)); } @@ -2710,23 +2980,43 @@ private Pow(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$pow"; } + /** + * Creates new {@link Pow}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Pow valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Pow(asFields(fieldReference)); } + /** + * Creates new {@link Pow}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Pow valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Pow(Collections.singletonList(expression)); } + /** + * Creates new {@link Pow}. + * + * @param value must not be {@literal null}. + * @return + */ public static Pow valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Pow(Collections.singletonList(value)); } @@ -2759,23 +3049,43 @@ private Sqrt(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$sqrt"; } - public static Sqrt sqrtOf(String fieldReference) { + /** + * Creates new {@link Sqrt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Sqrt sqrtOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Sqrt(Fields.field(fieldReference)); } + /** + * Creates new {@link Sqrt}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Sqrt sqrtOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Sqrt(expression); } + /** + * Creates new {@link Sqrt}. + * + * @param value must not be {@literal null}. + * @return + */ public static Sqrt sqrtOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Sqrt(value); } } @@ -2787,28 +3097,48 @@ public static Sqrt sqrtOf(Number value) { */ class Subtract extends AbstractAggregationExpression { - protected Subtract(List value) { + private Subtract(List value) { super(value); } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$subtract"; } + /** + * Creates new {@link Subtract}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Subtract valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Subtract(asFields(fieldReference)); } + /** + * Creates new {@link Subtract}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Subtract valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Subtract(Collections.singletonList(expression)); } + /** + * Creates new {@link Subtract}. + * + * @param value must not be {@literal null}. + * @return + */ public static Subtract valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Subtract(Collections.singletonList(value)); } @@ -2841,23 +3171,43 @@ private Trunc(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$trunc"; } + /** + * Creates new {@link Trunc}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Trunc truncValueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Trunc(Fields.field(fieldReference)); } + /** + * Creates new {@link Trunc}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Trunc truncValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Trunc(expression); } + /** + * Creates new {@link Trunc}. + * + * @param value must not be {@literal null}. + * @return + */ public static Trunc truncValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); return new Trunc(value); } } @@ -2874,23 +3224,43 @@ private Concat(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$concat"; } + /** + * Creates new {@link Concat}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Concat valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Concat(asFields(fieldReference)); } + /** + * Creates new {@link Concat}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Concat valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Concat(Collections.singletonList(expression)); } + /** + * Creates new {@link Concat}. + * + * @param value must not be {@literal null}. + * @return + */ public static Concat stringValue(String value) { + + Assert.notNull(value, "Value must not be null!"); return new Concat(Collections.singletonList(value)); } @@ -2923,16 +3293,28 @@ private Substr(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$substr"; } + /** + * Creates new {@link Substr}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Substr valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Substr(asFields(fieldReference)); } + /** + * Creates new {@link Substr}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Substr valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -2960,25 +3342,45 @@ private ToLower(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$toLower"; } - public static ToLower lower(String value) { - return new ToLower(value); - } - + /** + * Creates new {@link ToLower}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static ToLower lowerValueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new ToLower(Fields.field(fieldReference)); } + /** + * Creates new {@link ToLower}. + * + * @param expression must not be {@literal null}. + * @return + */ public static ToLower lowerValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new ToLower(Collections.singletonList(expression)); } + + /** + * Creates new {@link ToLower}. + * + * @param value must not be {@literal null}. + * @return + */ + public static ToLower lower(String value) { + + Assert.notNull(value, "Value must not be null!"); + return new ToLower(value); + } } /** @@ -2993,25 +3395,45 @@ private ToUpper(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$toUpper"; } - public static ToUpper upper(String value) { - return new ToUpper(value); - } - + /** + * Creates new {@link ToUpper}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static ToUpper upperValueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new ToUpper(Fields.field(fieldReference)); } + /** + * Creates new {@link ToUpper}. + * + * @param expression must not be {@literal null}. + * @return + */ public static ToUpper upperValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new ToUpper(Collections.singletonList(expression)); } + + /** + * Creates new {@link ToUpper}. + * + * @param value must not be {@literal null}. + * @return + */ + public static ToUpper upper(String value) { + + Assert.notNull(value, "Value must not be null!"); + return new ToUpper(value); + } } /** @@ -3026,23 +3448,43 @@ private StrCaseCmp(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$strcasecmp"; } + /** + * Creates new {@link StrCaseCmp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static StrCaseCmp valueOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new StrCaseCmp(asFields(fieldReference)); } + /** + * Creates new {@link StrCaseCmp}. + * + * @param expression must not be {@literal null}. + * @return + */ public static StrCaseCmp valueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new StrCaseCmp(Collections.singletonList(expression)); } + /** + * Creates new {@link StrCaseCmp}. + * + * @param value must not be {@literal null}. + * @return + */ public static StrCaseCmp stringValue(String value) { + + Assert.notNull(value, "Value must not be null!"); return new StrCaseCmp(Collections.singletonList(value)); } @@ -3068,43 +3510,55 @@ public StrCaseCmp strcasecmpValueOf(AggregationExpression expression) { * * @author Christoph Strobl */ - class ArrayElemtAt extends AbstractAggregationExpression { + class ArrayElemAt extends AbstractAggregationExpression { - private ArrayElemtAt(List value) { + private ArrayElemAt(List value) { super(value); } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$arrayElemAt"; } - public static ArrayElemtAt arrayOf(String fieldReference) { + /** + * 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 ArrayElemtAt(asFields(fieldReference)); + return new ArrayElemAt(asFields(fieldReference)); } - public static ArrayElemtAt arrayOf(AggregationExpression expression) { + /** + * 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 ArrayElemtAt(Collections.singletonList(expression)); + return new ArrayElemAt(Collections.singletonList(expression)); } - public ArrayElemtAt elementAt(int index) { - return new ArrayElemtAt(append(index)); + public ArrayElemAt elementAt(int index) { + return new ArrayElemAt(append(index)); } - public ArrayElemtAt elementAt(AggregationExpression expression) { + public ArrayElemAt elementAt(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); - return new ArrayElemtAt(append(expression)); + return new ArrayElemAt(append(expression)); } - public ArrayElemtAt elementAt(String arrayFieldReference) { + public ArrayElemAt elementAt(String arrayFieldReference) { Assert.notNull(arrayFieldReference, "ArrayReference must not be null!"); - return new ArrayElemtAt(append(Fields.field(arrayFieldReference))); + return new ArrayElemAt(append(Fields.field(arrayFieldReference))); } } @@ -3120,16 +3574,28 @@ private ConcatArrays(List value) { } @Override - public String getMongoMethod() { + 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!"); @@ -3419,16 +3885,28 @@ private IsArray(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$isArray"; } + /** + * Creates new {@link IsArray}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static IsArray isArray(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new IsArray(Fields.field(fieldReference)); } + /** + * Creates new {@link IsArray}. + * + * @param expression must not be {@literal null}. + * @return + */ public static IsArray isArray(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -3448,16 +3926,28 @@ private Size(Object value) { } @Override - public String getMongoMethod() { + 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!"); @@ -3477,16 +3967,28 @@ private Slice(List value) { } @Override - public String getMongoMethod() { + 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!"); @@ -3525,11 +4027,19 @@ private Literal(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$literal"; } + /** + * Creates new {@link Literal}. + * + * @param value must not be {@literal null}. + * @return + */ public static Literal asLiteral(Object value) { + + Assert.notNull(value, "Value must not be null!"); return new Literal(value); } } @@ -3546,16 +4056,28 @@ private DayOfYear(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$dayOfYear"; } + /** + * Creates new {@link DayOfYear}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static DayOfYear dayOfYear(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new DayOfYear(Fields.field(fieldReference)); } + /** + * Creates new {@link DayOfYear}. + * + * @param expression must not be {@literal null}. + * @return + */ public static DayOfYear dayOfYear(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -3575,16 +4097,28 @@ private DayOfMonth(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$dayOfMonth"; } + /** + * Creates new {@link DayOfMonth}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static DayOfMonth dayOfMonth(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new DayOfMonth(Fields.field(fieldReference)); } + /** + * Creates new {@link DayOfMonth}. + * + * @param expression must not be {@literal null}. + * @return + */ public static DayOfMonth dayOfMonth(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -3604,16 +4138,28 @@ private DayOfWeek(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$dayOfWeek"; } + /** + * Creates new {@link DayOfWeek}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static DayOfWeek dayOfWeek(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new DayOfWeek(Fields.field(fieldReference)); } + /** + * Creates new {@link DayOfWeek}. + * + * @param expression must not be {@literal null}. + * @return + */ public static DayOfWeek dayOfWeek(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -3633,16 +4179,28 @@ private Year(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$year"; } + /** + * Creates new {@link Year}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Year yearOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Year(Fields.field(fieldReference)); } + /** + * Creates new {@link Year}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Year yearOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -3662,16 +4220,28 @@ private Month(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$month"; } + /** + * Creates new {@link Month}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Month monthOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Month(Fields.field(fieldReference)); } + /** + * Creates new {@link Month}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Month monthOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -3691,16 +4261,28 @@ private Week(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$week"; } + /** + * Creates new {@link Week}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Week weekOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Week(Fields.field(fieldReference)); } + /** + * Creates new {@link Week}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Week weekOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -3720,16 +4302,28 @@ private Hour(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$hour"; } + /** + * Creates new {@link Hour}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Hour hourOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Hour(Fields.field(fieldReference)); } + /** + * Creates new {@link Hour}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Hour hourOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -3749,16 +4343,28 @@ private Minute(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$minute"; } + /** + * Creates new {@link Minute}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ public static Minute minuteOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null!"); return new Minute(Fields.field(fieldReference)); } + /** + * Creates new {@link Minute}. + * + * @param expression must not be {@literal null}. + * @return + */ public static Minute minuteOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); @@ -3778,7 +4384,7 @@ private Second(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$second"; } @@ -3819,7 +4425,7 @@ private Millisecond(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$millisecond"; } @@ -3860,7 +4466,7 @@ private DateToString(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$dateToString"; } @@ -3935,7 +4541,7 @@ private Sum(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$sum"; } @@ -4014,7 +4620,7 @@ private Avg(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$avg"; } @@ -4093,7 +4699,7 @@ private Max(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$max"; } @@ -4172,7 +4778,7 @@ private Min(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$min"; } @@ -4251,7 +4857,7 @@ private StdDevPop(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$stdDevPop"; } @@ -4330,7 +4936,7 @@ private StdDevSamp(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$stdDevSamp"; } @@ -4409,7 +5015,7 @@ private Cmp(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$cmp"; } @@ -4486,7 +5092,7 @@ private Eq(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$eq"; } @@ -4563,7 +5169,7 @@ private Gt(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$gt"; } @@ -4640,7 +5246,7 @@ private Lt(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$lt"; } @@ -4717,7 +5323,7 @@ private Gte(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$gte"; } @@ -4794,7 +5400,7 @@ private Lte(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$lte"; } @@ -4871,7 +5477,7 @@ private Ne(List value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$ne"; } @@ -4948,7 +5554,7 @@ private And(List values) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$and"; } @@ -5012,7 +5618,7 @@ private Or(List values) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$or"; } @@ -5078,7 +5684,7 @@ private Not(Object value) { } @Override - public String getMongoMethod() { + protected String getMongoMethod() { return "$not"; } From 3ae6aebebbaa63d897308226712f7013b8e81b8c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 28 Jun 2016 14:31:26 +0200 Subject: [PATCH 016/118] DATAMONGO-1454 - Add support for exists projection in repository query methods. We now support exists projections for query methods in query methods for derived and string queries. public PersonRepository extends Repository { boolean existsByFirstname(String firstname); @ExistsQuery(value = "{ 'lastname' : ?0 }") boolean someExistQuery(String lastname); @Query(value = "{ 'lastname' : ?0 }", exists = true) boolean anotherExistQuery(String lastname); } Original pull request: #381. --- .../data/mongodb/repository/ExistsQuery.java | 47 ++++++++++++++ .../data/mongodb/repository/Query.java | 8 +++ .../repository/query/AbstractMongoQuery.java | 17 ++++- .../repository/query/MongoQueryExecution.java | 46 +++++++++++++- .../repository/query/PartTreeMongoQuery.java | 10 +++ .../query/StringBasedMongoQuery.java | 62 ++++++++++++++++--- ...tractPersonRepositoryIntegrationTests.java | 17 +++++ .../mongodb/repository/PersonRepository.java | 12 ++++ .../query/AbstractMongoQueryUnitTests.java | 6 ++ .../query/StringBasedMongoQueryUnitTests.java | 24 +++++-- 10 files changed, 231 insertions(+), 18 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ExistsQuery.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ExistsQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ExistsQuery.java new file mode 100644 index 0000000000..de937374d9 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ExistsQuery.java @@ -0,0 +1,47 @@ +/* + * 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.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; + +/** + * Annotation to declare finder exists queries directly on repository methods. Both attributes allow using a placeholder + * notation of {@code ?0}, {@code ?1} and so on. + * + * @author Mark Paluch + * @since 1.10 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@Documented +@Query(exists = true) +public @interface ExistsQuery { + + /** + * Takes a MongoDB JSON string to define the actual query to be executed. This one will take precedence over the + * method name then. Alias for {@link Query#value}. + * + * @return + */ + @AliasFor(annotation = Query.class) + String value() default ""; +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java index 97fb25ff16..a0ad7a905d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java @@ -62,6 +62,14 @@ */ boolean count() default false; + /** + * Returns whether the query defined should be executed as exists projection. + * + * @since 1.10 + * @return + */ + boolean exists() default false; + /** * Returns whether the query should delete matching documents. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java index d6b65be255..d59efb0450 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java @@ -20,7 +20,9 @@ import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.CollectionExecution; +import org.springframework.data.mongodb.repository.query.MongoQueryExecution.CountExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution; +import org.springframework.data.mongodb.repository.query.MongoQueryExecution.ExistsExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.GeoNearExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagedExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagingGeoNearExecution; @@ -40,6 +42,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public abstract class AbstractMongoQuery implements RepositoryQuery { @@ -123,8 +126,12 @@ private MongoQueryExecution getExecutionToWrap(Query query, MongoParameterAccess return new CollectionExecution(operations, accessor.getPageable()); } else if (method.isPageQuery()) { return new PagedExecution(operations, accessor.getPageable()); + } else if (isCountQuery()) { + return new CountExecution(operations); + } else if (isExistsQuery()) { + return new ExistsExecution(operations); } else { - return new SingleEntityExecution(operations, isCountQuery()); + return new SingleEntityExecution(operations); } } @@ -164,6 +171,14 @@ protected Query createCountQuery(ConvertingParameterAccessor accessor) { */ protected abstract boolean isCountQuery(); + /** + * Returns whether the query should get an exists projection applied. + * + * @return + * @since 1.10 + */ + protected abstract boolean isExistsQuery(); + /** * Return weather the query should delete matching documents. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java index 76e98e8c7f..86c8ca1d39 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java @@ -163,7 +163,6 @@ public long get() { static final class SingleEntityExecution implements MongoQueryExecution { private final MongoOperations operations; - private final boolean countProjection; /* * (non-Javadoc) @@ -171,7 +170,50 @@ static final class SingleEntityExecution implements MongoQueryExecution { */ @Override public Object execute(Query query, Class type, String collection) { - return countProjection ? operations.count(query, type, collection) : operations.findOne(query, type, collection); + return operations.findOne(query, type, collection); + } + } + + /** + * {@link MongoQueryExecution} to perform a count projection. + * + * @author Oliver Gierke + * @author Mark Paluch + * @since 1.10 + */ + @RequiredArgsConstructor + static final class CountExecution implements MongoQueryExecution { + + private final MongoOperations operations; + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + */ + @Override + public Object execute(Query query, Class type, String collection) { + return operations.count(query, type, collection); + } + } + + /** + * {@link MongoQueryExecution} to perform an exists projection. + * + * @author Mark Paluch + * @since 1.10 + */ + @RequiredArgsConstructor + static final class ExistsExecution implements MongoQueryExecution { + + private final MongoOperations operations; + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + */ + @Override + public Object execute(Query query, Class type, String collection) { + return operations.exists(query, type, collection); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java index 4c1da7bd9b..f97b64afdb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java @@ -40,6 +40,7 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ public class PartTreeMongoQuery extends AbstractMongoQuery { @@ -141,6 +142,15 @@ protected boolean isCountQuery() { return tree.isCountProjection(); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isExistsQuery() + */ + @Override + protected boolean isExistsQuery() { + return tree.isExistsProjection(); + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery() diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java index 33d2e15e7d..c79f872ad1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-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. @@ -42,16 +42,18 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ public class StringBasedMongoQuery extends AbstractMongoQuery { - private static final String COUND_AND_DELETE = "Manually defined query for %s cannot be both a count and delete query at the same time!"; + private static final String COUNT_EXISTS_AND_DELETE = "Manually defined query for %s cannot be a count and exists or delete query at the same time!"; private static final Logger LOG = LoggerFactory.getLogger(StringBasedMongoQuery.class); private static final ParameterBindingParser BINDING_PARSER = ParameterBindingParser.INSTANCE; private final String query; private final String fieldSpec; private final boolean isCountQuery; + private final boolean isExistsQuery; private final boolean isDeleteQuery; private final List queryParameterBindings; private final List fieldSpecParameterBindings; @@ -95,14 +97,27 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati this.fieldSpec = BINDING_PARSER.parseAndCollectParameterBindingsFromQueryIntoBindings( method.getFieldSpecification(), this.fieldSpecParameterBindings); - this.isCountQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().count() : false; - this.isDeleteQuery = method.hasAnnotatedQuery() ? method.getQueryAnnotation().delete() : false; + this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider); - if (isCountQuery && isDeleteQuery) { - throw new IllegalArgumentException(String.format(COUND_AND_DELETE, method)); - } - this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider); + if (method.hasAnnotatedQuery()) { + + org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation(); + + this.isCountQuery = queryAnnotation.count(); + this.isExistsQuery = queryAnnotation.exists(); + this.isDeleteQuery = queryAnnotation.delete(); + + if (hasAmbiguousProjectionFlags(this.isCountQuery, this.isExistsQuery, this.isDeleteQuery)) { + throw new IllegalArgumentException(String.format(COUNT_EXISTS_AND_DELETE, method)); + } + + } else { + + this.isCountQuery = false; + this.isExistsQuery = false; + this.isDeleteQuery = false; + } } /* @@ -135,6 +150,15 @@ protected boolean isCountQuery() { return isCountQuery; } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isExistsQuery() + */ + @Override + protected boolean isExistsQuery() { + return isExistsQuery; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery() @@ -144,12 +168,30 @@ protected boolean isDeleteQuery() { return this.isDeleteQuery; } + private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, boolean isDeleteQuery) { + return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1; + } + + private static int countBooleanValues(boolean... values) { + + int count = 0; + + for (boolean value : values) { + + if (value) { + count++; + } + } + + return count; + } + /** * A parser that extracts the parameter bindings from a given query string. * * @author Thomas Darimont */ - private static enum ParameterBindingParser { + private enum ParameterBindingParser { INSTANCE; @@ -256,7 +298,7 @@ private static void collectParameterReferencesIntoBindings(List, QueryDslPredicateExecutor { @@ -251,6 +252,17 @@ public interface PersonRepository extends MongoRepository, Query @Query(value = "{ 'lastname' : ?0 }", count = true) long someCountQuery(String lastname); + /** + * @see DATAMONGO-1454 + */ + boolean existsByFirstname(String firstname); + + /** + * @see DATAMONGO-1454 + */ + @ExistsQuery(value = "{ 'lastname' : ?0 }") + boolean someExistQuery(String lastname); + /** * @see DATAMONGO-770 */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java index 7406e63341..83812b560f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java @@ -312,6 +312,7 @@ private MongoQueryFake createQueryForMethod(String methodName, Class... param private static class MongoQueryFake extends AbstractMongoQuery { private boolean isCountQuery; + private boolean isExistsQuery; private boolean isDeleteQuery; public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) { @@ -328,6 +329,11 @@ protected boolean isCountQuery() { return isCountQuery; } + @Override + protected boolean isExistsQuery() { + return false; + } + @Override protected boolean isDeleteQuery() { return isDeleteQuery; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index 41e0c6ef8c..f3b4fe1672 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -61,6 +61,7 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class StringBasedMongoQueryUnitTests { @@ -148,7 +149,7 @@ public void bindsNullParametersCorrectly() throws Exception { public void bindsDbrefCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByHavingSizeFansNotZero"); - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] {}); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(new BasicQuery("{ fans : { $not : { $size : 0 } } }").getQueryObject())); @@ -178,8 +179,7 @@ public void preventsDeleteAndCountFlagAtTheSameTime() throws Exception { @Test public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception { - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { - new BasicDBObject("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1) }); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new BasicDBObject("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1)); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByParameterizedCriteriaAndFields", DBObject.class, Map.class); @@ -196,7 +196,7 @@ public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception @Test public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQuoting() throws Exception { - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { "fun" }); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "fun"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByTitleBeginsWithExplicitQuoting", String.class); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); @@ -210,7 +210,7 @@ public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQu @Test public void shouldParseQueryWithParametersInExpression() throws Exception { - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { 1, 2, 3, 4 }); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, 1, 2, 3, 4); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithParametersInExpression", int.class, int.class, int.class, int.class); @@ -362,6 +362,17 @@ public void shouldSupportNonQuotedBinaryDataReplacement() throws Exception { assertThat(query.getQueryObject(), is(reference.getQueryObject())); } + /** + * @see DATAMONGO-1454 + */ + @Test + public void shouldSupportExistsProjection() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("existsByLastname", String.class); + + assertThat(mongoQuery.isExistsQuery(), is(true)); + } + private StringBasedMongoQuery createQueryForMethod(String name, Class... parameters) throws Exception { Method method = SampleRepository.class.getMethod(name, parameters); @@ -420,5 +431,8 @@ private interface SampleRepository extends Repository { @Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}") List findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2); + + @Query(value = "{ 'lastname' : ?0 }", exists = true) + boolean existsByLastname(String lastname); } } From 40da4701de5426270aa0a03cdfb7b164fe0d616c Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 2 Dec 2016 16:33:13 +0100 Subject: [PATCH 017/118] DATAMONGO-1454 - Polishing. Formatting in test case. Original pull request: #381. --- .../repository/AbstractPersonRepositoryIntegrationTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 9d38a0b5be..07ed39b7ca 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -594,6 +594,7 @@ public void executesAnnotatedCountProjection() { */ @Test public void executesDerivedExistsProjectionToBoolean() { + assertThat(repository.existsByFirstname("Oliver August"), is(true)); assertThat(repository.existsByFirstname("Hans Peter"), is(false)); } From 7f39c42eb76b81f1c0bb25bc6084833a55db6b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Vodra=CC=81z=CC=8Cka?= Date: Wed, 2 Nov 2016 18:13:24 +0100 Subject: [PATCH 018/118] DATAMONGO-1141 - Add support for $push $sort in Update. Sorting update modifier added. Supports sorting arrays by document fields and element values. Original pull request: #405. --- .../data/mongodb/core/query/Update.java | 86 +++++++++++++++++++ .../core/convert/UpdateMapperUnitTests.java | 67 +++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java index 44f2718c44..362d40e0a8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java @@ -27,6 +27,9 @@ import java.util.Set; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.domain.Sort.Order; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -44,6 +47,7 @@ * @author Thomas Darimont * @author Alexey Plotnik * @author Mark Paluch + * @author Pavel Vodrazka */ public class Update { @@ -660,6 +664,58 @@ public Object getValue() { return this.count; } } + + /** + * Implementation of {@link Modifier} representing {@code $sort}. + * + * @author Pavel Vodrazka + * @since 1.10 + */ + private static class SortModifier implements Modifier { + + private final Object sort; + + public SortModifier(Direction direction) { + this.sort = direction.isAscending() ? 1 : -1; + } + + public SortModifier(Sort sort) { + this.sort = createDBObject(sort); + } + + private DBObject createDBObject(Sort sort) { + + DBObject obj = new BasicDBObject(); + + for (Order order : sort) { + if (order.isIgnoreCase()) { + throw new IllegalArgumentException(String.format("Given sort contained an Order for %s with ignore case! " + + "MongoDB does not support sorting ignoring case currently!", order.getProperty())); + } + obj.put(order.getProperty(), order.isAscending() ? 1 : -1); + } + + return obj; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.Update.Modifier#getKey() + */ + @Override + public String getKey() { + return "$sort"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.query.Update.Modifier#getValue() + */ + @Override + public Object getValue() { + return this.sort; + } + } /** * Builder for creating {@code $push} modifiers @@ -707,6 +763,36 @@ public PushOperatorBuilder slice(int count) { return this; } + /** + * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. + * Forces elements to be sorted by values in given {@literal direction}. + * + * @param direction must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public PushOperatorBuilder sort(Direction direction) { + + Assert.notNull(direction, "Direction must not be 'null'."); + this.modifiers.addModifier(new SortModifier(direction)); + return this; + } + + /** + * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. + * Forces document elements to be sorted in given {@literal order}. + * + * @param order must not be {@literal null}. + * @return never {@literal null}. + * @since 1.10 + */ + public PushOperatorBuilder sort(Sort order) { + + Assert.notNull(order, "Order must not be 'null'."); + this.modifiers.addModifier(new SortModifier(order)); + return this; + } + /** * Forces values to be added at the given {@literal position}. * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index d6942a15f3..f3726052b0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.mongodb.core.convert; import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.collection.IsMapContaining.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -42,6 +43,9 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; import org.springframework.data.convert.WritingConverter; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.domain.Sort.Order; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.DBObjectTestUtils; @@ -68,6 +72,7 @@ * @author Christoph Strobl * @author Thomas Darimont * @author Mark Paluch + * @author Pavel Vodrazka */ @RunWith(MockitoJUnitRunner.class) public class UpdateMapperUnitTests { @@ -416,6 +421,68 @@ public void updatePushEachWithSliceShouldRenderWhenUsingMultiplePushCorrectly() assertThat(key2.containsField("$each"), is(true)); } + /** + * @see DATAMONGO-1141 + */ + @Test + public void updatePushEachWithValueSortShouldRenderCorrectly() { + + Update update = new Update().push("scores").sort(Direction.DESC).each(42, 23, 68); + + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject key = getAsDBObject(push, "scores"); + + assertThat(key.containsField("$sort"), is(true)); + assertThat((Integer) key.get("$sort"), is(-1)); + assertThat(key.containsField("$each"), is(true)); + } + + /** + * @see DATAMONGO-1141 + */ + @Test + public void updatePushEachWithDocumentSortShouldRenderCorrectly() { + + Update update = new Update().push("names").sort(new Sort(new Order(Direction.ASC, "last"), new Order(Direction.ASC, "first"))) + .each(Collections.emptyList()); + + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject key = getAsDBObject(push, "names"); + + assertThat(key.containsField("$sort"), is(true)); + assertThat((DBObject) key.get("$sort"), equalTo(new BasicDBObjectBuilder().add("last", 1).add("first", 1).get())); + assertThat(key.containsField("$each"), is(true)); + } + + /** + * @see DATAMONGO-1141 + */ + @Test + public void updatePushEachWithSortShouldRenderCorrectlyWhenUsingMultiplePush() { + + Update update = new Update().push("authors").sort(Direction.ASC).each("Harry") + .push("chapters").sort(new Sort(Direction.ASC, "order")).each(Collections.emptyList()); + + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + + DBObject push = getAsDBObject(mappedObject, "$push"); + DBObject key1 = getAsDBObject(push, "authors"); + + assertThat(key1.containsField("$sort"), is(true)); + assertThat((Integer) key1.get("$sort"), is(1)); + assertThat(key1.containsField("$each"), is(true)); + + DBObject key2 = getAsDBObject(push, "chapters"); + + assertThat(key2.containsField("$sort"), is(true)); + assertThat((DBObject) key2.get("$sort"), equalTo(new BasicDBObjectBuilder().add("order", 1).get())); + assertThat(key2.containsField("$each"), is(true)); + } + /** * @see DATAMONGO-410 */ From c6a4e7166c28230899cfa1ea88b509c62ac8d0d7 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 2 Dec 2016 17:29:23 +0100 Subject: [PATCH 019/118] DATAMONGO-1141 - Polishing. Add property to field name mapping for Sort orders by moving Sort mapping to UpdateMapper. Fix typo. Add JavaDoc. Reformat code. Remove trailing whitespaces. Original pull request: #405. --- .../mongodb/core/convert/UpdateMapper.java | 32 ++++++++++++-- .../data/mongodb/core/query/Update.java | 43 +++++++++++-------- .../core/convert/UpdateMapperUnitTests.java | 23 +++++++--- 3 files changed, 71 insertions(+), 27 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java index ea60fbb7df..a62c96444a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-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. @@ -18,6 +18,8 @@ import java.util.Map.Entry; import org.springframework.core.convert.converter.Converter; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Order; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; @@ -37,6 +39,7 @@ * @author Thomas Darimont * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch */ public class UpdateMapper extends QueryMapper { @@ -130,11 +133,23 @@ private boolean isQuery(Object value) { } private DBObject getMappedValue(Field field, Modifier modifier) { + return new BasicDBObject(modifier.getKey(), getMappedModifier(field, modifier)); + } + + private Object getMappedModifier(Field field, Modifier modifier) { + + Object value = modifier.getValue(); + + if (value instanceof Sort) { + + DBObject sortObject = getSortObject((Sort) value); + return field == null || field.getPropertyEntity() == null ? sortObject + : getMappedSort(sortObject, field.getPropertyEntity()); + } TypeInformation typeHint = field == null ? ClassTypeInformation.OBJECT : field.getTypeHint(); - Object value = converter.convertToMongoType(modifier.getValue(), typeHint); - return new BasicDBObject(modifier.getKey(), value); + return converter.convertToMongoType(value, typeHint); } private TypeInformation getTypeHintForEntity(Object source, MongoPersistentEntity entity) { @@ -153,6 +168,17 @@ private TypeInformation getTypeHintForEntity(Object source, MongoPersistentEn return NESTED_DOCUMENT; } + public DBObject getSortObject(Sort sort) { + + DBObject dbo = new BasicDBObject(); + + for (Order order : sort) { + dbo.put(order.getProperty(), order.isAscending() ? 1 : -1); + } + + return dbo; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.convert.QueryMapper#createPropertyField(org.springframework.data.mongodb.core.mapping.MongoPersistentEntity, java.lang.String, org.springframework.data.mapping.context.MappingContext) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java index 362d40e0a8..f34555ceaa 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java @@ -664,38 +664,47 @@ public Object getValue() { return this.count; } } - + /** * Implementation of {@link Modifier} representing {@code $sort}. * * @author Pavel Vodrazka + * @author Mark Paluch * @since 1.10 */ private static class SortModifier implements Modifier { private final Object sort; + /** + * Creates a new {@link SortModifier} instance given {@link Direction}. + * + * @param direction must not be {@literal null}. + */ public SortModifier(Direction direction) { + + Assert.notNull(direction, "Direction must not be null!"); this.sort = direction.isAscending() ? 1 : -1; } + /** + * Creates a new {@link SortModifier} instance given {@link Sort}. + * + * @param sort must not be {@literal null}. + */ public SortModifier(Sort sort) { - this.sort = createDBObject(sort); - } - private DBObject createDBObject(Sort sort) { - - DBObject obj = new BasicDBObject(); + Assert.notNull(sort, "Sort must not be null!"); for (Order order : sort) { + if (order.isIgnoreCase()) { throw new IllegalArgumentException(String.format("Given sort contained an Order for %s with ignore case! " + "MongoDB does not support sorting ignoring case currently!", order.getProperty())); } - obj.put(order.getProperty(), order.isAscending() ? 1 : -1); } - return obj; + this.sort = sort; } /* @@ -714,7 +723,7 @@ public String getKey() { @Override public Object getValue() { return this.sort; - } + } } /** @@ -764,8 +773,8 @@ public PushOperatorBuilder slice(int count) { } /** - * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. - * Forces elements to be sorted by values in given {@literal direction}. + * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. Forces elements to + * be sorted by values in given {@literal direction}. * * @param direction must not be {@literal null}. * @return never {@literal null}. @@ -779,17 +788,17 @@ public PushOperatorBuilder sort(Direction direction) { } /** - * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. - * Forces document elements to be sorted in given {@literal order}. + * Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. Forces document + * elements to be sorted in given {@literal order}. * - * @param order must not be {@literal null}. + * @param sort must not be {@literal null}. * @return never {@literal null}. * @since 1.10 */ - public PushOperatorBuilder sort(Sort order) { + public PushOperatorBuilder sort(Sort sort) { - Assert.notNull(order, "Order must not be 'null'."); - this.modifiers.addModifier(new SortModifier(order)); + Assert.notNull(sort, "Sort must not be 'null'."); + this.modifiers.addModifier(new SortModifier(sort)); return this; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index f3726052b0..ecdc5c6fd9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -429,7 +429,8 @@ public void updatePushEachWithValueSortShouldRenderCorrectly() { Update update = new Update().push("scores").sort(Direction.DESC).each(42, 23, 68); - DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), + context.getPersistentEntity(ParentClass.class)); DBObject push = getAsDBObject(mappedObject, "$push"); DBObject key = getAsDBObject(push, "scores"); @@ -445,16 +446,19 @@ public void updatePushEachWithValueSortShouldRenderCorrectly() { @Test public void updatePushEachWithDocumentSortShouldRenderCorrectly() { - Update update = new Update().push("names").sort(new Sort(new Order(Direction.ASC, "last"), new Order(Direction.ASC, "first"))) + Update update = new Update().push("list") + .sort(new Sort(new Order(Direction.ASC, "value"), new Order(Direction.ASC, "field"))) .each(Collections.emptyList()); - DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); + DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), + context.getPersistentEntity(EntityWithList.class)); DBObject push = getAsDBObject(mappedObject, "$push"); - DBObject key = getAsDBObject(push, "names"); + DBObject key = getAsDBObject(push, "list"); assertThat(key.containsField("$sort"), is(true)); - assertThat((DBObject) key.get("$sort"), equalTo(new BasicDBObjectBuilder().add("last", 1).add("first", 1).get())); + assertThat((DBObject) key.get("$sort"), + equalTo(new BasicDBObjectBuilder().add("renamed-value", 1).add("field", 1).get())); assertThat(key.containsField("$each"), is(true)); } @@ -464,8 +468,8 @@ public void updatePushEachWithDocumentSortShouldRenderCorrectly() { @Test public void updatePushEachWithSortShouldRenderCorrectlyWhenUsingMultiplePush() { - Update update = new Update().push("authors").sort(Direction.ASC).each("Harry") - .push("chapters").sort(new Sort(Direction.ASC, "order")).each(Collections.emptyList()); + Update update = new Update().push("authors").sort(Direction.ASC).each("Harry").push("chapters") + .sort(new Sort(Direction.ASC, "order")).each(Collections.emptyList()); DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class)); @@ -1278,9 +1282,14 @@ static class EntityWithObject { NestedDocument concreteValue; } + static class EntityWithList { + List list; + } + static class EntityWithAliasedObject { @Field("renamed-value") Object value; + Object field; } static class EntityWithObjectMap { From 407affb4589e1ef18e7fa18eb6bc5d7cf1bca71c Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 2 Dec 2016 18:12:23 +0100 Subject: [PATCH 020/118] DATAMONGO-1141 - Polishing. Aligned assertion messages for consistency. Fixed imports in UpdateMapperUnitTests. Original pull request: #405. --- .../data/mongodb/core/query/Update.java | 10 +++++----- .../mongodb/core/convert/UpdateMapperUnitTests.java | 5 ++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java index f34555ceaa..6f5e6ad453 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java @@ -316,7 +316,7 @@ public Update currentTimestamp(String key) { */ public Update multiply(String key, Number multiplier) { - Assert.notNull(multiplier, "Multiplier must not be 'null'."); + Assert.notNull(multiplier, "Multiplier must not be null."); addMultiFieldOperation("$mul", key, multiplier.doubleValue()); return this; } @@ -333,7 +333,7 @@ public Update multiply(String key, Number multiplier) { */ public Update max(String key, Object value) { - Assert.notNull(value, "Value for max operation must not be 'null'."); + Assert.notNull(value, "Value for max operation must not be null."); addMultiFieldOperation("$max", key, value); return this; } @@ -350,7 +350,7 @@ public Update max(String key, Object value) { */ public Update min(String key, Object value) { - Assert.notNull(value, "Value for min operation must not be 'null'."); + Assert.notNull(value, "Value for min operation must not be null."); addMultiFieldOperation("$min", key, value); return this; } @@ -782,7 +782,7 @@ public PushOperatorBuilder slice(int count) { */ public PushOperatorBuilder sort(Direction direction) { - Assert.notNull(direction, "Direction must not be 'null'."); + Assert.notNull(direction, "Direction must not be null."); this.modifiers.addModifier(new SortModifier(direction)); return this; } @@ -797,7 +797,7 @@ public PushOperatorBuilder sort(Direction direction) { */ public PushOperatorBuilder sort(Sort sort) { - Assert.notNull(sort, "Sort must not be 'null'."); + Assert.notNull(sort, "Sort must not be null."); this.modifiers.addModifier(new SortModifier(sort)); return this; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index ecdc5c6fd9..adcfcb2cfc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -15,9 +15,7 @@ */ package org.springframework.data.mongodb.core.convert; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.collection.IsMapContaining.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; @@ -85,6 +83,7 @@ public class UpdateMapperUnitTests { private Converter writingConverterSpy; @Before + @SuppressWarnings("unchecked") public void setUp() { this.writingConverterSpy = Mockito.spy(new NestedEntityWriteConverter()); From 438dbc4b33ef113fd496953e8c3ba1b13accd70b Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 5 Dec 2016 14:22:09 +0100 Subject: [PATCH 021/118] DATAMONGO-1546 - Register GeoJsonConfiguration via spring.factories. Related tickets: DATACMNS-952. --- .../data/mongodb/core/GeoJsonConfiguration.java | 7 +++---- .../src/main/resources/META-INF/spring.factories | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 spring-data-mongodb/src/main/resources/META-INF/spring.factories diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoJsonConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoJsonConfiguration.java index cb29dca57d..ccc81cd799 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoJsonConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoJsonConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -17,15 +17,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.data.mongodb.core.geo.GeoJsonModule; -import org.springframework.data.web.config.SpringDataWebConfigurationMixin; +import org.springframework.data.web.config.SpringDataJacksonModules; /** * Configuration class to expose {@link GeoJsonModule} as a Spring bean. * * @author Oliver Gierke */ -@SpringDataWebConfigurationMixin -public class GeoJsonConfiguration { +public class GeoJsonConfiguration implements SpringDataJacksonModules { @Bean public GeoJsonModule geoJsonModule() { diff --git a/spring-data-mongodb/src/main/resources/META-INF/spring.factories b/spring-data-mongodb/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000000..7520782694 --- /dev/null +++ b/spring-data-mongodb/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.data.web.config.SpringDataJacksonModules=org.springframework.data.mongodb.core.GeoJsonConfiguration From a0be890437d0772dedb2ab2878ffd5af955fdad2 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 5 Dec 2016 14:27:24 +0100 Subject: [PATCH 022/118] DATAMONGO-1547 - Register MongoRepositoryFactory in spring.factories. This is required for the switch in support for multi-store detection. Related ticket: DATACMNS-952. --- spring-data-mongodb/src/main/resources/META-INF/spring.factories | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-data-mongodb/src/main/resources/META-INF/spring.factories b/spring-data-mongodb/src/main/resources/META-INF/spring.factories index 7520782694..a581f2e3ac 100644 --- a/spring-data-mongodb/src/main/resources/META-INF/spring.factories +++ b/spring-data-mongodb/src/main/resources/META-INF/spring.factories @@ -1 +1,2 @@ org.springframework.data.web.config.SpringDataJacksonModules=org.springframework.data.mongodb.core.GeoJsonConfiguration +org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.mongodb.repository.support.MongoRepositoryFactory From 192399413d7428414a1a35e3f5edbc84c7875cc4 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 2 Dec 2016 15:54:30 +0100 Subject: [PATCH 023/118] DATAMONGO-1540 - Add support for $map (aggregation). We now support $map operator in aggregation. Original pull request: #420. --- .../aggregation/AggregationExpressions.java | 196 ++++++++++++++++-- .../AggregationFunctionExpressions.java | 2 +- .../ProjectionOperationUnitTests.java | 31 +++ 3 files changed, 215 insertions(+), 14 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 e8011faaec..6125c10393 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 @@ -20,9 +20,9 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Map.ArrayOfBuilder; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; import org.springframework.util.Assert; @@ -1781,6 +1781,24 @@ private boolean usesFieldRef() { } } + /** + * Gateway to {@literal Date} aggregation operations. + * + * @author Christoph Strobl + */ + class VariableOperators { + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @return + */ + public static ArrayOfBuilder map() { + return Map.map(); + } + } + /** * @author Christoph Strobl */ @@ -1809,10 +1827,10 @@ public DBObject toDbObject(Object value, AggregationOperationContext context) { args.add(unpack(val, context)); } valueToUse = args; - } else if (value instanceof Map) { + } else if (value instanceof java.util.Map) { DBObject dbo = new BasicDBObject(); - for (Map.Entry entry : ((Map) value).entrySet()) { + for (java.util.Map.Entry entry : ((java.util.Map) value).entrySet()) { dbo.put(entry.getKey(), unpack(entry.getValue(), context)); } valueToUse = dbo; @@ -1866,10 +1884,10 @@ protected List append(Object value) { protected Object append(String key, Object value) { - if (!(value instanceof Map)) { + if (!(value instanceof java.util.Map)) { throw new IllegalArgumentException("o_O"); } - Map clone = new LinkedHashMap((Map) value); + java.util.Map clone = new LinkedHashMap((java.util.Map) value); clone.put(key, value); return clone; @@ -2344,6 +2362,7 @@ public static Abs absoluteValueOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null!"); return new Abs(expression); } + /** * Creates new {@link Abs}. * @@ -2495,7 +2514,6 @@ protected String getMongoMethod() { return "$divide"; } - /** * Creates new {@link Divide}. * @@ -4390,7 +4408,7 @@ protected String getMongoMethod() { /** * Creates new {@link Second}. - * + * * @param fieldReference must not be {@literal null}. * @return */ @@ -4509,9 +4527,9 @@ public DateToString toString(String format) { }; } - private static Map argumentMap(Object date, String format) { + private static java.util.Map argumentMap(Object date, String format) { - Map args = new LinkedHashMap(2); + java.util.Map args = new LinkedHashMap(2); args.put("format", format); args.put("date", date); return args; @@ -4705,7 +4723,7 @@ protected String getMongoMethod() { /** * Creates new {@link Max}. - * + * * @param fieldReference must not be {@literal null}. * @return */ @@ -4730,7 +4748,7 @@ public static Max maxOf(AggregationExpression expression) { /** * Creates new {@link Max} with all previously added arguments appending the given one.
* NOTE: Only possible in {@code $project} stage. - * + * * @param fieldReference must not be {@literal null}. * @return */ @@ -4809,7 +4827,7 @@ public static Min minOf(AggregationExpression expression) { /** * Creates new {@link Min} with all previously added arguments appending the given one.
* NOTE: Only possible in {@code $project} stage. - * + * * @param fieldReference must not be {@literal null}. * @return */ @@ -4942,7 +4960,7 @@ protected String getMongoMethod() { /** * Creates new {@link StdDevSamp}. - * + * * @param fieldReference must not be {@literal null}. * @return */ @@ -5715,4 +5733,156 @@ public static Not not(AggregationExpression expression) { } } + /** + * {@link AggregationExpression} for {@code $map}. + */ + class Map implements AggregationExpression { + + private Object sourceArray; + private String itemVariableName; + private AggregationExpression functionToApply; + + private Map(Object sourceArray, String itemVariableName, AggregationExpression functionToApply) { + + Assert.notNull(sourceArray, "SourceArray must not be null!"); + Assert.notNull(itemVariableName, "ItemVariableName must not be null!"); + Assert.notNull(functionToApply, "FunctionToApply must not be null!"); + + this.sourceArray = sourceArray; + this.itemVariableName = itemVariableName; + this.functionToApply = functionToApply; + } + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @return + */ + static ArrayOfBuilder map() { + + return new ArrayOfBuilder() { + + @Override + public AsBuilder itemsOf(final String fieldReference) { + + return new AsBuilder() { + + @Override + public FunctionBuilder as(final String variableName) { + + return new FunctionBuilder() { + + @Override + public Map andApply(final AggregationExpression expression) { + return new Map(Fields.field(fieldReference), variableName, expression); + } + }; + } + }; + } + + @Override + public AsBuilder itemsOf(final AggregationExpression source) { + + return new AsBuilder() { + + @Override + public FunctionBuilder as(final String variableName) { + + return new FunctionBuilder() { + + @Override + public Map andApply(final AggregationExpression expression) { + return new Map(source, variableName, expression); + } + }; + } + }; + } + }; + }; + + @Override + public DBObject toDbObject(final AggregationOperationContext context) { + + return toMap(new ExposedFieldsAggregationOperationContext( + ExposedFields.synthetic(Fields.fields(itemVariableName)), 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 toMap(AggregationOperationContext context) { + + BasicDBObject map = new BasicDBObject(); + + BasicDBObject input; + if (sourceArray instanceof Field) { + input = new BasicDBObject("input", context.getReference((Field) sourceArray).toString()); + } else { + input = new BasicDBObject("input", ((AggregationExpression) sourceArray).toDbObject(context)); + } + + map.putAll(context.getMappedObject(input)); + map.put("as", itemVariableName); + map.put("in", functionToApply.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(context))); + + return new BasicDBObject("$map", map); + } + + interface ArrayOfBuilder { + + /** + * Set the field that resolves to an array on which to apply the {@link AggregationExpression}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + AsBuilder itemsOf(String fieldReference); + + /** + * Set the {@link AggregationExpression} that results in an array on which to apply the + * {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + AsBuilder itemsOf(AggregationExpression expression); + } + + interface AsBuilder { + + /** + * Define the {@literal variableName} for addressing items within the array. + * + * @param variableName must not be {@literal null}. + * @return + */ + FunctionBuilder as(String variableName); + } + + interface FunctionBuilder { + + /** + * Creates new {@link Map} that applies the given {@link AggregationExpression} to each item of the referenced + * array and returns an array with the applied results. + * + * @param expression must not be {@literal null}. + * @return + */ + Map andApply(AggregationExpression expression); + } + } + } 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 1e862f33a2..f605dc628d 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 @@ -36,7 +36,7 @@ @Deprecated public enum AggregationFunctionExpressions { - SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT; + SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT, ADD; /** * Returns an {@link AggregationExpression} build from the current {@link Enum} name and the given parameters. 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 59ba03bc3e..e242e74fb2 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 @@ -36,6 +36,7 @@ import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.LiteralOperators; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.SetOperators; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.StringOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.VariableOperators; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; import com.mongodb.BasicDBObject; @@ -1673,6 +1674,36 @@ public void shouldRenderNotAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { result: { $not: [ { $gt: [ \"$qty\", 250 ] } ] } } }"))); } + /** + * @see DATAMONGO-784 + */ + @Test + public void shouldRenderMapAggregationExpression() { + + DBObject agg = Aggregation.project() + .and(VariableOperators.map().itemsOf("quizzes").as("grade") + .andApply(AggregationFunctionExpressions.ADD.of(field("grade"), 2))) + .as("adjustedGrades").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse( + "{ $project:{ adjustedGrades:{ $map: { input: \"$quizzes\", as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}"))); + } + + /** + * @see DATAMONGO-784 + */ + @Test + public void shouldRenderMapAggregationExpressionOnExpression() { + + DBObject agg = Aggregation.project() + .and(VariableOperators.map().itemsOf(AggregationFunctionExpressions.SIZE.of("foo")).as("grade") + .andApply(AggregationFunctionExpressions.ADD.of(field("grade"), 2))) + .as("adjustedGrades").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse( + "{ $project:{ adjustedGrades:{ $map: { input: { $size : [\"foo\"]}, as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}"))); + } + private static DBObject exctractOperation(String field, DBObject fromProjectClause) { return (DBObject) fromProjectClause.get(field); } From a0ac3510a06530b17c9b70f168e2478769dab1b7 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 5 Dec 2016 16:35:53 +0100 Subject: [PATCH 024/118] DATAMONGO-1540 - Polishing. Reduce Map aggregation expression builder entrypoint. Fix JavaDoc. Original pull request: #420. --- .../aggregation/AggregationExpressions.java | 91 +++++++++---------- .../ProjectionOperationUnitTests.java | 8 +- src/main/asciidoc/reference/mongodb.adoc | 4 + 3 files changed, 50 insertions(+), 53 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 6125c10393..3ecaf6584c 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 @@ -22,7 +22,6 @@ import java.util.List; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Map.ArrayOfBuilder; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; import org.springframework.util.Assert; @@ -1782,9 +1781,10 @@ private boolean usesFieldRef() { } /** - * Gateway to {@literal Date} aggregation operations. + * Gateway to {@literal variable} aggregation operations. * * @author Christoph Strobl + * @author Mark Paluch */ class VariableOperators { @@ -1792,10 +1792,22 @@ class VariableOperators { * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array * and returns an array with the applied results. * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Map.AsBuilder mapItemsOf(String fieldReference) { + return Map.itemsOf(fieldReference); + } + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @param expression must not be {@literal null}. * @return */ - public static ArrayOfBuilder map() { - return Map.map(); + public static Map.AsBuilder mapItemsOf(AggregationExpression expression) { + return Map.itemsOf(expression); } } @@ -5757,51 +5769,52 @@ private Map(Object sourceArray, String itemVariableName, AggregationExpression f * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array * and returns an array with the applied results. * + * @param fieldReference must not be {@literal null}. * @return */ - static ArrayOfBuilder map() { + static AsBuilder itemsOf(final String fieldReference) { - return new ArrayOfBuilder() { + return new AsBuilder() { @Override - public AsBuilder itemsOf(final String fieldReference) { + public FunctionBuilder as(final String variableName) { - return new AsBuilder() { + return new FunctionBuilder() { @Override - public FunctionBuilder as(final String variableName) { - - return new FunctionBuilder() { - - @Override - public Map andApply(final AggregationExpression expression) { - return new Map(Fields.field(fieldReference), variableName, expression); - } - }; + public Map andApply(final AggregationExpression expression) { + return new Map(Fields.field(fieldReference), variableName, expression); } }; } - @Override - public AsBuilder itemsOf(final AggregationExpression source) { + }; + }; - return new AsBuilder() { + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @param source must not be {@literal null}. + * @return + */ + public static AsBuilder itemsOf(final AggregationExpression source) { - @Override - public FunctionBuilder as(final String variableName) { + return new AsBuilder() { + + @Override + public FunctionBuilder as(final String variableName) { - return new FunctionBuilder() { + return new FunctionBuilder() { - @Override - public Map andApply(final AggregationExpression expression) { - return new Map(source, variableName, expression); - } - }; + @Override + public Map andApply(final AggregationExpression expression) { + return new Map(source, variableName, expression); } }; } }; - }; + } @Override public DBObject toDbObject(final AggregationOperationContext context) { @@ -5841,26 +5854,6 @@ private DBObject toMap(AggregationOperationContext context) { return new BasicDBObject("$map", map); } - interface ArrayOfBuilder { - - /** - * Set the field that resolves to an array on which to apply the {@link AggregationExpression}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - AsBuilder itemsOf(String fieldReference); - - /** - * Set the {@link AggregationExpression} that results in an array on which to apply the - * {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - * @return - */ - AsBuilder itemsOf(AggregationExpression expression); - } - interface AsBuilder { /** 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 e242e74fb2..c659e14632 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 @@ -1675,13 +1675,13 @@ public void shouldRenderNotAggregationExpression() { } /** - * @see DATAMONGO-784 + * @see DATAMONGO-1540 */ @Test public void shouldRenderMapAggregationExpression() { DBObject agg = Aggregation.project() - .and(VariableOperators.map().itemsOf("quizzes").as("grade") + .and(VariableOperators.mapItemsOf("quizzes").as("grade") .andApply(AggregationFunctionExpressions.ADD.of(field("grade"), 2))) .as("adjustedGrades").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1690,13 +1690,13 @@ public void shouldRenderMapAggregationExpression() { } /** - * @see DATAMONGO-784 + * @see DATAMONGO-1540 */ @Test public void shouldRenderMapAggregationExpressionOnExpression() { DBObject agg = Aggregation.project() - .and(VariableOperators.map().itemsOf(AggregationFunctionExpressions.SIZE.of("foo")).as("grade") + .and(VariableOperators.mapItemsOf(AggregationFunctionExpressions.SIZE.of("foo")).as("grade") .andApply(AggregationFunctionExpressions.ADD.of(field("grade"), 2))) .as("adjustedGrades").toDBObject(Aggregation.DEFAULT_CONTEXT); diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index a6e2870de9..0b9b03fc0c 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1700,6 +1700,10 @@ At the time of this writing we provide support for the following Aggregation Ope | Date Aggregation Operators | dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString +| Variable Operators +| map + + | Conditional Aggregation Operators | cond, ifNull From ea4782c4211e3a2712e4b810c41b7b0dff046909 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 18 Nov 2016 15:23:32 +0100 Subject: [PATCH 025/118] DATAMONGO-1520 - Add overload for aggregation $match accepting CriteriaDefinition. We now also accept CriteriaDefinition next to Criteria for Aggregation.match. The existing match(Criteria) method remains to preserve binary compatibility. --- .../data/mongodb/core/aggregation/Aggregation.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 3b5d45cee9..bfe8db8db1 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 @@ -29,6 +29,7 @@ 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; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.SerializationUtils; import org.springframework.util.Assert; @@ -342,6 +343,17 @@ public static MatchOperation match(Criteria criteria) { return new MatchOperation(criteria); } + /** + * Creates a new {@link MatchOperation} using the given {@link CriteriaDefinition}. + * + * @param criteria must not be {@literal null}. + * @return + * @since 1.10 + */ + public static MatchOperation match(CriteriaDefinition criteria) { + return new MatchOperation(criteria); + } + /** * Creates a new {@link OutOperation} using the given collection name. This operation must be the last operation in * the pipeline. From 1a11877ae9098fc80d3a84aec58204fb4435584e Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 5 Dec 2016 11:45:18 +0100 Subject: [PATCH 026/118] DATAMONGO-1542 - Refactor CondOperator and IfNullOperator to children of AggregationExpressions. Renamed CondOperator to Cond and IfNullOperator to IfNull. Both conditional operations are now available from ConditionalOperators.when and ConditionalOperators.ifNull and accept AggregationExpressions for conditions and values. Original Pull Request: #421 --- .../mongodb/core/aggregation/Aggregation.java | 66 +- .../aggregation/AggregationExpressions.java | 806 +++++++++++++++++- .../core/aggregation/ConditionalOperator.java | 394 --------- .../core/aggregation/IfNullOperator.java | 197 ----- .../core/aggregation/ProjectionOperation.java | 26 +- .../core/aggregation/AggregationTests.java | 36 +- .../aggregation/AggregationUnitTests.java | 31 +- ...ests.java => CondExpressionUnitTests.java} | 62 +- .../aggregation/IfNullOperatorUnitTests.java | 82 -- .../ProjectionOperationUnitTests.java | 41 + ...dAggregationOperationContextUnitTests.java | 14 +- 11 files changed, 921 insertions(+), 834 deletions(-) delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java rename spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/{ConditionalOperatorUnitTests.java => CondExpressionUnitTests.java} (77%) delete mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java 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 bfe8db8db1..ff9ec46d14 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java @@ -23,11 +23,11 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; -import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; -import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; +import org.springframework.data.mongodb.core.aggregation.Fields.*; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.data.mongodb.core.query.NearQuery; @@ -396,68 +396,6 @@ public static LookupOperation lookup(Field from, Field localField, Field foreign return new LookupOperation(from, localField, foreignField, as); } - /** - * Creates a new {@link IfNullOperator} for the given {@code field} and {@code replacement} value. - * - * @param field must not be {@literal null}. - * @param replacement must not be {@literal null}. - * @return never {@literal null}. - * @since 1.10 - */ - public static IfNullOperator ifNull(String field, Object replacement) { - return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement); - } - - /** - * Creates a new {@link IfNullOperator} for the given {@link Field} and {@link Field} to obtain a value from. - * - * @param field must not be {@literal null}. - * @param replacement must not be {@literal null}. - * @return never {@literal null}. - * @since 1.10 - */ - public static IfNullOperator ifNull(Field field, Field replacement) { - return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement); - } - - /** - * Creates a new {@link IfNullOperator} for the given {@link Field} and {@code replacement} value. - * - * @param field must not be {@literal null}. - * @param replacement must not be {@literal null}. - * @return never {@literal null}. - * @since 1.10 - */ - public static IfNullOperator ifNull(Field field, Object replacement) { - return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement); - } - - /** - * Creates a new {@link ConditionalOperator} for the given {@link Field} that holds a {@literal boolean} value. - * - * @param booleanField must not be {@literal null}. - * @param then must not be {@literal null}. - * @param otherwise must not be {@literal null}. - * @return never {@literal null}. - * @since 1.10 - */ - public static ConditionalOperator conditional(Field booleanField, Object then, Object otherwise) { - return ConditionalOperator.newBuilder().when(booleanField).then(then).otherwise(otherwise); - } - - /** - * Creates a new {@link ConditionalOperator} for the given {@link Criteria}. - * - * @param criteria must not be {@literal null}. - * @param then must not be {@literal null}. - * @param otherwise must not be {@literal null}. - * @return never {@literal null}. - * @since 1.10 - */ - public static ConditionalOperator conditional(Criteria criteria, Object then, Object otherwise) { - return ConditionalOperator.newBuilder().when(criteria).then(then).otherwise(otherwise); - } - /** * Creates a new {@link Fields} instance for the given field names. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java index 3ecaf6584c..481be82ff1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java @@ -21,10 +21,15 @@ import java.util.LinkedHashMap; import java.util.List; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBObject; @@ -93,7 +98,7 @@ public static class BooleanOperatorFactory { private final AggregationExpression expression; /** - * Creates new {@link ComparisonOperatorFactory} for given {@literal fieldReference}. + * Creates new {@link BooleanOperatorFactory} for given {@literal fieldReference}. * * @param fieldReference must not be {@literal null}. */ @@ -105,7 +110,7 @@ public BooleanOperatorFactory(String fieldReference) { } /** - * Creats new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}. + * Creates new {@link BooleanOperatorFactory} for given {@link AggregationExpression}. * * @param expression must not be {@literal null}. */ @@ -191,6 +196,178 @@ private boolean usesFieldRef() { } } + /** + * Gateway to {@literal conditional expressions} that evaluate their argument expressions as booleans to a value. + * + * @author Mark Paluch + */ + class ConditionalOperators { + + /** + * Take the field referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ConditionalOperatorFactory when(String fieldReference) { + return new ConditionalOperatorFactory(fieldReference); + } + + /** + * Take the value resulting from the given {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ConditionalOperatorFactory when(AggregationExpression expression) { + return new ConditionalOperatorFactory(expression); + } + + /** + * Take the value resulting from the given {@literal criteriaDefinition}. + * + * @param criteriaDefinition must not be {@literal null}. + * @return + */ + public static ConditionalOperatorFactory when(CriteriaDefinition criteriaDefinition) { + return new ConditionalOperatorFactory(criteriaDefinition); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates an expression and returns the value of the expression + * if the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, + * including instances of undefined values or missing fields, returns the value of the replacement expression. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IfNull.ThenBuilder ifNull(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return IfNull.ifNull(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates an expression and returns the value of the expression + * if the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, + * including instances of undefined values or missing fields, returns the value of the replacement expression. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IfNull.ThenBuilder ifNull(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return IfNull.ifNull(expression); + } + + public static class ConditionalOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + private final CriteriaDefinition criteriaDefinition; + + /** + * Creates new {@link ConditionalOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ConditionalOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + + this.fieldReference = fieldReference; + this.expression = null; + this.criteriaDefinition = null; + } + + /** + * Creates new {@link ConditionalOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ConditionalOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + this.fieldReference = null; + this.expression = expression; + this.criteriaDefinition = null; + } + + /** + * Creates new {@link ConditionalOperatorFactory} for given {@link CriteriaDefinition}. + * + * @param criteriaDefinition must not be {@literal null}. + */ + public ConditionalOperatorFactory(CriteriaDefinition criteriaDefinition) { + + Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!"); + + this.fieldReference = null; + this.expression = null; + this.criteriaDefinition = criteriaDefinition; + } + + /** + * Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two + * specified return expressions. + * + * @param value must not be {@literal null}. + * @return + */ + public OtherwiseBuilder then(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return createThenBuilder().then(value); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates a boolean expression to return one of the two + * specified return expressions. + * + * @param expression must not be {@literal null}. + * @return + */ + public OtherwiseBuilder thenValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createThenBuilder().then(expression); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates a boolean expression to return one of the two + * specified return expressions. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public OtherwiseBuilder thenValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createThenBuilder().then(fieldReference); + } + + private ThenBuilder createThenBuilder() { + + if (usesFieldRef()) { + return Cond.newBuilder().when(fieldReference); + } + + return usesCriteriaDefinition() ? Cond.newBuilder().when(criteriaDefinition) + : Cond.newBuilder().when(expression); + } + + private boolean usesFieldRef() { + return this.fieldReference != null; + } + + private boolean usesCriteriaDefinition() { + return this.criteriaDefinition != null; + } + } + } + /** * Gateway to {@literal Set expressions} which perform {@literal set} operation on arrays, treating arrays as sets. * @@ -411,7 +588,7 @@ private boolean usesFieldRef() { class ComparisonOperators { /** - * Take the array referenced by given {@literal fieldReference}. + * Take the field referenced by given {@literal fieldReference}. * * @param fieldReference must not be {@literal null}. * @return @@ -421,13 +598,13 @@ public static ComparisonOperatorFactory valueOf(String fieldReference) { } /** - * Take the array referenced by given {@literal fieldReference}. + * Take the value resulting from the given {@literal expression}. * - * @param fieldReference must not be {@literal null}. + * @param expression must not be {@literal null}. * @return */ - public static ComparisonOperatorFactory valueOf(AggregationExpression fieldReference) { - return new ComparisonOperatorFactory(fieldReference); + public static ComparisonOperatorFactory valueOf(AggregationExpression expression) { + return new ComparisonOperatorFactory(expression); } public static class ComparisonOperatorFactory { @@ -448,7 +625,7 @@ public ComparisonOperatorFactory(String fieldReference) { } /** - * Creats new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}. + * Creates new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}. * * @param expression must not be {@literal null}. */ @@ -730,7 +907,7 @@ private boolean usesFieldRef() { class ArithmeticOperators { /** - * Take the array referenced by given {@literal fieldReference}. + * Take the field referenced by given {@literal fieldReference}. * * @param fieldReference must not be {@literal null}. * @return @@ -740,13 +917,13 @@ public static ArithmeticOperatorFactory valueOf(String fieldReference) { } /** - * Take the array referenced by given {@literal fieldReference}. + * Take the value resulting from the given {@literal expression}. * - * @param fieldReference must not be {@literal null}. + * @param expression must not be {@literal null}. * @return */ - public static ArithmeticOperatorFactory valueOf(AggregationExpression fieldReference) { - return new ArithmeticOperatorFactory(fieldReference); + public static ArithmeticOperatorFactory valueOf(AggregationExpression expression) { + return new ArithmeticOperatorFactory(expression); } public static class ArithmeticOperatorFactory { @@ -2274,7 +2451,7 @@ public static AnyElementTrue arrayAsSet(String arrayReference) { } /** - * Creats new {@link AnyElementTrue}. + * Creates new {@link AnyElementTrue}. * * @param expression must not be {@literal null}. * @return @@ -5878,4 +6055,605 @@ interface FunctionBuilder { } } + /** + * Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field + * field references}, {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be + * converted to a simple MongoDB type. + * + * @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ + * @author Mark Paluch + */ + class IfNull implements AggregationExpression { + + private final Object condition; + private final Object value; + + private IfNull(Object condition, Object value) { + + this.condition = condition; + this.value = value; + } + + /** + * Creates new {@link IfNull}. + * + * @param fieldReference the field to check for a {@literal null} value, field reference must not be + * {@literal null}. + * @return + */ + public static ThenBuilder ifNull(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IfNullOperatorBuilder().ifNull(fieldReference); + } + + /** + * Creates new {@link IfNull}. + * + * @param expression the expression to check for a {@literal null} value, field reference must not be + * {@literal null}. + * @return + */ + public static ThenBuilder ifNull(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IfNullOperatorBuilder().ifNull(expression); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + List list = new ArrayList(); + + if (condition instanceof Field) { + list.add(context.getReference((Field) condition).toString()); + } else if (condition instanceof AggregationExpression) { + list.add(((AggregationExpression) condition).toDbObject(context)); + } else { + list.add(condition); + } + + list.add(resolve(value, context)); + + return new BasicDBObject("$ifNull", list); + } + + private Object resolve(Object value, AggregationOperationContext context) { + + if (value instanceof Field) { + return context.getReference((Field) value).toString(); + } else if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } else if (value instanceof DBObject) { + return value; + } + + return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); + } + + /** + * @author Mark Paluch + */ + public static interface IfNullBuilder { + + /** + * @param fieldReference the field to check for a {@literal null} value, field reference must not be + * {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder ifNull(String fieldReference); + + /** + * @param expression the expression to check for a {@literal null} value, field name must not be {@literal null} + * or empty. + * @return the {@link ThenBuilder} + */ + ThenBuilder ifNull(AggregationExpression expression); + } + + /** + * @author Mark Paluch + */ + public static interface ThenBuilder { + + /** + * @param value the value to be used if the {@code $ifNull} condition evaluates {@literal true}. Can be a + * {@link DBObject}, a value that is supported by MongoDB or a value that can be converted to a MongoDB + * representation but must not be {@literal null}. + * @return + */ + IfNull then(Object value); + + /** + * @param fieldReference the field holding the replacement value, must not be {@literal null}. + * @return + */ + IfNull thenValueOf(String fieldReference); + + /** + * @param expression the expression yielding to the replacement value, must not be {@literal null}. + * @return + */ + public IfNull thenValueOf(AggregationExpression expression); + } + + /** + * Builder for fluent {@link IfNullOperator} creation. + * + * @author Mark Paluch + */ + static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder { + + private Object condition; + + private IfNullOperatorBuilder() {} + + /** + * Creates a new builder for {@link IfNullOperator}. + * + * @return never {@literal null}. + */ + public static IfNullOperatorBuilder newBuilder() { + return new IfNullOperatorBuilder(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.IfNullBuilder#ifNull(java.lang.String) + */ + public ThenBuilder ifNull(String fieldReference) { + + Assert.hasText(fieldReference, "FieldReference name must not be null or empty!"); + this.condition = Fields.field(fieldReference); + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public ThenBuilder ifNull(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression name must not be null or empty!"); + this.condition = expression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#then(java.lang.Object) + */ + public IfNull then(Object value) { + return new IfNull(condition, value); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#thenValueOf(java.lang.String) + */ + public IfNull thenValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IfNull(condition, Fields.field(fieldReference)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + public IfNull thenValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IfNull(condition, expression); + } + } + } + + /** + * Encapsulates the aggregation framework {@code $cond} operator. A {@link Cond} allows nested conditions + * {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition}, {@link AggregationExpression} + * or a {@link DBObject custom} condition. Replacement values can be either {@link Field field references}, + * {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be converted to a + * simple MongoDB type. + * + * @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/ + * @author Mark Paluch + * @author Christoph Strobl + */ + class Cond implements AggregationExpression { + + private final Object condition; + private final Object thenValue; + private final Object otherwiseValue; + + /** + * Creates a new {@link Cond} for a given {@link Field} and {@code then}/{@code otherwise} values. + * + * @param condition must not be {@literal null}. + * @param thenValue must not be {@literal null}. + * @param otherwiseValue must not be {@literal null}. + */ + private Cond(Field condition, Object thenValue, Object otherwiseValue) { + this((Object) condition, thenValue, otherwiseValue); + } + + /** + * Creates a new {@link Cond} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise} values. + * + * @param condition must not be {@literal null}. + * @param thenValue must not be {@literal null}. + * @param otherwiseValue must not be {@literal null}. + */ + private Cond(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) { + this((Object) condition, thenValue, otherwiseValue); + } + + private Cond(Object condition, Object thenValue, Object otherwiseValue) { + + Assert.notNull(condition, "Condition must not be null!"); + Assert.notNull(thenValue, "Then value must not be null!"); + Assert.notNull(otherwiseValue, "Otherwise value must not be null!"); + + assertNotBuilder(condition, "Condition"); + assertNotBuilder(thenValue, "Then value"); + assertNotBuilder(otherwiseValue, "Otherwise value"); + + this.condition = condition; + this.thenValue = thenValue; + this.otherwiseValue = otherwiseValue; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + BasicDBObject condObject = new BasicDBObject(); + + condObject.append("if", resolveCriteria(context, condition)); + condObject.append("then", resolveValue(context, thenValue)); + condObject.append("else", resolveValue(context, otherwiseValue)); + + return new BasicDBObject("$cond", condObject); + } + + private Object resolveValue(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject || value instanceof Field) { + return resolve(context, value); + } + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); + } + + private Object resolveCriteria(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject || value instanceof Field) { + return resolve(context, value); + } + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + if (value instanceof CriteriaDefinition) { + + DBObject mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject()); + List clauses = new ArrayList(); + + clauses.addAll(getClauses(context, mappedObject)); + + return clauses.size() == 1 ? clauses.get(0) : clauses; + } + + throw new InvalidDataAccessApiUsageException( + String.format("Invalid value in condition. Supported: DBObject, Field references, Criteria, got: %s", value)); + } + + private List getClauses(AggregationOperationContext context, DBObject mappedObject) { + + List clauses = new ArrayList(); + + for (String key : mappedObject.keySet()) { + + Object predicate = mappedObject.get(key); + clauses.addAll(getClauses(context, key, predicate)); + } + + return clauses; + } + + private List getClauses(AggregationOperationContext context, String key, Object predicate) { + + List clauses = new ArrayList(); + + if (predicate instanceof List) { + + List args = new ArrayList(); + for (Object clause : (List) predicate) { + if (clause instanceof DBObject) { + args.addAll(getClauses(context, (DBObject) clause)); + } + } + + clauses.add(new BasicDBObject(key, args)); + + } else if (predicate instanceof DBObject) { + + DBObject nested = (DBObject) predicate; + + for (String s : nested.keySet()) { + + if (!isKeyword(s)) { + continue; + } + + List args = new ArrayList(); + args.add("$" + key); + args.add(nested.get(s)); + clauses.add(new BasicDBObject(s, args)); + } + + } else if (!isKeyword(key)) { + + List args = new ArrayList(); + args.add("$" + key); + args.add(predicate); + clauses.add(new BasicDBObject("$eq", args)); + } + + return clauses; + } + + /** + * Returns whether the given {@link String} is a MongoDB keyword. + * + * @param candidate + * @return + */ + private boolean isKeyword(String candidate) { + return candidate.startsWith("$"); + } + + private Object resolve(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject) { + return context.getMappedObject((DBObject) value); + } + + return context.getReference((Field) value).toString(); + } + + private void assertNotBuilder(Object toCheck, String name) { + Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck), + String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName())); + } + + /** + * Get a builder that allows fluent creation of {@link Cond}. + * + * @return a new {@link ConditionalExpressionBuilder}. + */ + public static ConditionalExpressionBuilder newBuilder() { + return ConditionalExpressionBuilder.newBuilder(); + } + + /** + * @author Mark Paluch + */ + public static interface WhenBuilder { + + /** + * @param booleanExpression expression that yields in a boolean result, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(DBObject booleanExpression); + + /** + * @param expression expression that yields in a boolean result, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(AggregationExpression expression); + + /** + * @param booleanField name of a field holding a boolean value, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(String booleanField); + + /** + * @param criteria criteria to evaluate, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(CriteriaDefinition criteria); + } + + /** + * @author Mark Paluch + */ + public static interface ThenBuilder { + + /** + * @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link DBObject}, a + * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but + * must not be {@literal null}. + * @return the {@link OtherwiseBuilder} + */ + OtherwiseBuilder then(Object value); + + /** + * @param fieldReference must not be {@literal null}. + * @return the {@link OtherwiseBuilder} + */ + OtherwiseBuilder thenValueOf(String fieldReference); + + /** + * @param expression must not be {@literal null}. + * @return the {@link OtherwiseBuilder} + */ + OtherwiseBuilder thenValueOf(AggregationExpression expression); + } + + /** + * @author Mark Paluch + */ + public static interface OtherwiseBuilder { + + /** + * @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link DBObject}, a + * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but + * must not be {@literal null}. + * @return the {@link Cond} + */ + Cond otherwise(Object value); + + /** + * @param fieldReference must not be {@literal null}. + * @return the {@link Cond} + */ + Cond otherwiseValueOf(String fieldReference); + + /** + * @param expression must not be {@literal null}. + * @return the {@link Cond} + */ + Cond otherwiseValueOf(AggregationExpression expression); + } + + /** + * Builder for fluent {@link Cond} creation. + * + * @author Mark Paluch + */ + static class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder { + + private Object condition; + private Object thenValue; + + private ConditionalExpressionBuilder() {} + + /** + * Creates a new builder for {@link Cond}. + * + * @return never {@literal null}. + */ + public static ConditionalExpressionBuilder newBuilder() { + return new ConditionalExpressionBuilder(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(com.mongodb.DBObject) + */ + @Override + public ConditionalExpressionBuilder when(DBObject booleanExpression) { + + Assert.notNull(booleanExpression, "'Boolean expression' must not be null!"); + + this.condition = booleanExpression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition) + */ + @Override + public ThenBuilder when(CriteriaDefinition criteria) { + + Assert.notNull(criteria, "Criteria must not be null!"); + this.condition = criteria; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public ThenBuilder when(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression field must not be null!"); + this.condition = expression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(java.lang.String) + */ + @Override + public ThenBuilder when(String booleanField) { + + Assert.hasText(booleanField, "Boolean field name must not be null or empty!"); + this.condition = Fields.field(booleanField); + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#then(java.lang.Object) + */ + @Override + public OtherwiseBuilder then(Object thenValue) { + + Assert.notNull(thenValue, "Then-value must not be null!"); + this.thenValue = thenValue; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#thenValueOf(java.lang.String) + */ + @Override + public OtherwiseBuilder thenValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.thenValue = Fields.field(fieldReference); + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public OtherwiseBuilder thenValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null!"); + this.thenValue = expression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwise(java.lang.Object) + */ + @Override + public Cond otherwise(Object otherwiseValue) { + + Assert.notNull(otherwiseValue, "Value must not be null!"); + return new Cond(condition, thenValue, otherwiseValue); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwiseValueOf(java.lang.String) + */ + @Override + public Cond otherwiseValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Cond(condition, thenValue, Fields.field(fieldReference)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwiseValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public Cond otherwiseValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null!"); + return new Cond(condition, thenValue, expression); + } + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java deleted file mode 100644 index 881894a14f..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperator.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.mongodb.core.aggregation; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.mongodb.core.query.CriteriaDefinition; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; - -/** - * Encapsulates the aggregation framework {@code $cond} operator. A {@link ConditionalOperator} allows nested conditions - * {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition} or a {@link DBObject custom} - * condition. Replacement values can be either {@link Field field references}, values of simple MongoDB types or values - * that can be converted to a simple MongoDB type. - * - * @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/ - * @author Mark Paluch - * @author Christoph Strobl - * @since 1.10 - */ -public class ConditionalOperator implements AggregationExpression { - - private final Object condition; - private final Object thenValue; - private final Object otherwiseValue; - - /** - * Creates a new {@link ConditionalOperator} for a given {@link Field} and {@code then}/{@code otherwise} values. - * - * @param condition must not be {@literal null}. - * @param thenValue must not be {@literal null}. - * @param otherwiseValue must not be {@literal null}. - */ - public ConditionalOperator(Field condition, Object thenValue, Object otherwiseValue) { - this((Object) condition, thenValue, otherwiseValue); - } - - /** - * Creates a new {@link ConditionalOperator} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise} - * values. - * - * @param condition must not be {@literal null}. - * @param thenValue must not be {@literal null}. - * @param otherwiseValue must not be {@literal null}. - */ - public ConditionalOperator(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) { - this((Object) condition, thenValue, otherwiseValue); - } - - /** - * Creates a new {@link ConditionalOperator} for a given {@link DBObject criteria} and {@code then}/{@code otherwise} - * values. - * - * @param condition must not be {@literal null}. - * @param thenValue must not be {@literal null}. - * @param otherwiseValue must not be {@literal null}. - */ - public ConditionalOperator(DBObject condition, Object thenValue, Object otherwiseValue) { - this((Object) condition, thenValue, otherwiseValue); - } - - private ConditionalOperator(Object condition, Object thenValue, Object otherwiseValue) { - - Assert.notNull(condition, "Condition must not be null!"); - Assert.notNull(thenValue, "'Then value' must not be null!"); - Assert.notNull(otherwiseValue, "'Otherwise value' must not be null!"); - - assertNotBuilder(condition, "Condition"); - assertNotBuilder(thenValue, "'Then value'"); - assertNotBuilder(otherwiseValue, "'Otherwise value'"); - - this.condition = condition; - this.thenValue = thenValue; - this.otherwiseValue = otherwiseValue; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) - */ - @Override - public DBObject toDbObject(AggregationOperationContext context) { - - BasicDBObject condObject = new BasicDBObject(); - - condObject.append("if", resolveCriteria(context, condition)); - condObject.append("then", resolveValue(context, thenValue)); - condObject.append("else", resolveValue(context, otherwiseValue)); - - return new BasicDBObject("$cond", condObject); - } - - private Object resolveValue(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject || value instanceof Field) { - return resolve(context, value); - } - - if (value instanceof ConditionalOperator) { - return ((ConditionalOperator) value).toDbObject(context); - } - - return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); - } - - private Object resolveCriteria(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject || value instanceof Field) { - return resolve(context, value); - } - - if (value instanceof CriteriaDefinition) { - - DBObject mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject()); - List clauses = new ArrayList(); - - clauses.addAll(getClauses(context, mappedObject)); - - if (clauses.size() == 1) { - return clauses.get(0); - } - - return clauses; - } - - throw new InvalidDataAccessApiUsageException( - String.format("Invalid value in condition. Supported: DBObject, Field references, Criteria, got: %s", value)); - } - - private List getClauses(AggregationOperationContext context, DBObject mappedObject) { - - List clauses = new ArrayList(); - - for (String key : mappedObject.keySet()) { - - Object predicate = mappedObject.get(key); - clauses.addAll(getClauses(context, key, predicate)); - } - - return clauses; - } - - private List getClauses(AggregationOperationContext context, String key, Object predicate) { - - List clauses = new ArrayList(); - - if (predicate instanceof List) { - - List args = new ArrayList(); - for (Object clause : (List) predicate) { - if (clause instanceof DBObject) { - args.addAll(getClauses(context, (DBObject) clause)); - } - } - - clauses.add(new BasicDBObject(key, args)); - - } else if (predicate instanceof DBObject) { - - DBObject nested = (DBObject) predicate; - - for (String s : nested.keySet()) { - - if (!isKeyword(s)) { - continue; - } - - List args = new ArrayList(); - args.add("$" + key); - args.add(nested.get(s)); - clauses.add(new BasicDBObject(s, args)); - } - - } else if (!isKeyword(key)) { - - List args = new ArrayList(); - args.add("$" + key); - args.add(predicate); - clauses.add(new BasicDBObject("$eq", args)); - } - - return clauses; - } - - /** - * Returns whether the given {@link String} is a MongoDB keyword. - * - * @param candidate - * @return - */ - private boolean isKeyword(String candidate) { - return candidate.startsWith("$"); - } - - private Object resolve(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject) { - return context.getMappedObject((DBObject) value); - } - - return context.getReference((Field) value).toString(); - } - - private void assertNotBuilder(Object toCheck, String name) { - Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck), - String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName())); - } - - /** - * Get a builder that allows fluent creation of {@link ConditionalOperator}. - * - * @return a new {@link ConditionalExpressionBuilder}. - */ - public static ConditionalExpressionBuilder newBuilder() { - return ConditionalExpressionBuilder.newBuilder(); - } - - /** - * @since 1.10 - */ - public static interface WhenBuilder { - - /** - * @param booleanExpression expression that yields in a boolean result, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(DBObject booleanExpression); - - /** - * @param booleanField reference to a field holding a boolean value, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(Field booleanField); - - /** - * @param booleanField name of a field holding a boolean value, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(String booleanField); - - /** - * @param criteria criteria to evaluate, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(CriteriaDefinition criteria); - } - - /** - * @since 1.10 - */ - public static interface ThenBuilder { - - /** - * @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link DBObject}, a value - * that is supported by MongoDB or a value that can be converted to a MongoDB representation but must not - * be {@literal null}. - * @return the {@link OtherwiseBuilder} - */ - OtherwiseBuilder then(Object value); - } - - /** - * @since 1.10 - */ - public static interface OtherwiseBuilder { - - /** - * @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link DBObject}, a value - * that is supported by MongoDB or a value that can be converted to a MongoDB representation but must not - * be {@literal null}. - * @return the {@link ConditionalOperator} - */ - ConditionalOperator otherwise(Object value); - } - - /** - * Builder for fluent {@link ConditionalOperator} creation. - * - * @author Mark Paluch - * @since 1.10 - */ - public static final class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder { - - private Object condition; - private Object thenValue; - - private ConditionalExpressionBuilder() {} - - /** - * Creates a new builder for {@link ConditionalOperator}. - * - * @return never {@literal null}. - */ - public static ConditionalExpressionBuilder newBuilder() { - return new ConditionalExpressionBuilder(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(com.mongodb.DBObject) - */ - @Override - public ConditionalExpressionBuilder when(DBObject booleanExpression) { - - Assert.notNull(booleanExpression, "'Boolean expression' must not be null!"); - - this.condition = booleanExpression; - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition) - */ - @Override - public ThenBuilder when(CriteriaDefinition criteria) { - - Assert.notNull(criteria, "Criteria must not be null!"); - - this.condition = criteria; - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.Field) - */ - @Override - public ThenBuilder when(Field booleanField) { - - Assert.notNull(booleanField, "Boolean field must not be null!"); - - this.condition = booleanField; - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(java.lang.String) - */ - @Override - public ThenBuilder when(String booleanField) { - - Assert.hasText(booleanField, "Boolean field name must not be null or empty!"); - - this.condition = Fields.field(booleanField); - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.ThenBuilder#then(java.lang.Object) - */ - @Override - public OtherwiseBuilder then(Object thenValue) { - - Assert.notNull(thenValue, "'Then-value' must not be null!"); - - this.thenValue = thenValue; - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.OtherwiseBuilder#otherwise(java.lang.Object) - */ - @Override - public ConditionalOperator otherwise(Object otherwiseValue) { - - Assert.notNull(otherwiseValue, "'Otherwise-value' must not be null!"); - - return new ConditionalOperator(condition, thenValue, otherwiseValue); - } - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java deleted file mode 100644 index b51aba01bb..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/IfNullOperator.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.mongodb.core.aggregation; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.util.Assert; - -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; - -/** - * Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field field - * references}, values of simple MongoDB types or values that can be converted to a simple MongoDB type. - * - * @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ - * @author Mark Paluch - * @since 1.10 - */ -public class IfNullOperator implements AggregationExpression { - - private final Field field; - private final Object value; - - /** - * Creates a new {@link IfNullOperator} for the given {@link Field} and replacement {@code value}. - * - * @param field must not be {@literal null}. - * @param value must not be {@literal null}. - */ - public IfNullOperator(Field field, Object value) { - - Assert.notNull(field, "Field must not be null!"); - Assert.notNull(value, "'Replacement-value' must not be null!"); - - this.field = field; - this.value = value; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) - */ - @Override - public DBObject toDbObject(AggregationOperationContext context) { - - List list = new ArrayList(); - - list.add(context.getReference(field).toString()); - list.add(resolve(value, context)); - - return new BasicDBObject("$ifNull", list); - } - - private Object resolve(Object value, AggregationOperationContext context) { - - if (value instanceof Field) { - return context.getReference((Field) value).toString(); - } else if (value instanceof DBObject) { - return value; - } - - return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); - } - - /** - * Get a builder that allows fluent creation of {@link IfNullOperator}. - * - * @return a new {@link IfNullBuilder}. - */ - public static IfNullBuilder newBuilder() { - return IfNullOperatorBuilder.newBuilder(); - } - - /** - * @since 1.10 - */ - public static interface IfNullBuilder { - - /** - * @param field the field to check for a {@literal null} value, field reference must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder ifNull(Field field); - - /** - * @param field the field to check for a {@literal null} value, field name must not be {@literal null} or empty. - * @return the {@link ThenBuilder} - */ - ThenBuilder ifNull(String field); - } - - /** - * @since 1.10 - */ - public static interface ThenBuilder { - - /** - * @param field the field holding the replacement value, must not be {@literal null}. - * @return the {@link IfNullOperator} - */ - IfNullOperator thenReplaceWith(Field field); - - /** - * @param value the value to be used if the {@code $ifNull }condition evaluates {@literal true}. Can be a - * {@link DBObject}, a value that is supported by MongoDB or a value that can be converted to a MongoDB - * representation but must not be {@literal null}. - * @return the {@link IfNullOperator} - */ - IfNullOperator thenReplaceWith(Object value); - } - - /** - * Builder for fluent {@link IfNullOperator} creation. - * - * @author Mark Paluch - * @since 1.10 - */ - public static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder { - - private Field field; - - private IfNullOperatorBuilder() {} - - /** - * Creates a new builder for {@link IfNullOperator}. - * - * @return never {@literal null}. - */ - public static IfNullOperatorBuilder newBuilder() { - return new IfNullOperatorBuilder(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.Field) - */ - public ThenBuilder ifNull(Field field) { - - Assert.notNull(field, "Field must not be null!"); - - this.field = field; - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.IfNullBuilder#ifNull(java.lang.String) - */ - public ThenBuilder ifNull(String name) { - - Assert.hasText(name, "Field name must not be null or empty!"); - - this.field = Fields.field(name); - return this; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.ThenReplaceBuilder#thenReplaceWith(org.springframework.data.mongodb.core.aggregation.Field) - */ - @Override - public IfNullOperator thenReplaceWith(Field replacementField) { - - Assert.notNull(replacementField, "Replacement field must not be null!"); - - return new IfNullOperator(this.field, replacementField); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.ThenReplaceBuilder#thenReplaceWith(java.lang.Object) - */ - @Override - public IfNullOperator thenReplaceWith(Object value) { - - Assert.notNull(value, "'Replacement-value' must not be null!"); - - return new IfNullOperator(this.field, value); - } - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java index cc80896223..428abeb5ee 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.List; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection; @@ -243,22 +245,22 @@ public DBObject toDBObject(AggregationOperationContext context) { public abstract ProjectionOperation as(String alias); /** - * Apply a conditional projection using {@link ConditionalOperator}. + * Apply a conditional projection using {@link Cond}. * - * @param conditionalOperator must not be {@literal null}. + * @param cond must not be {@literal null}. * @return never {@literal null}. * @since 1.10 */ - public abstract ProjectionOperation applyCondition(ConditionalOperator conditionalOperator); + public abstract ProjectionOperation applyCondition(Cond cond); /** - * Apply a conditional value replacement for {@literal null} values using {@link IfNullOperator}. + * Apply a conditional value replacement for {@literal null} values using {@link IfNull}. * - * @param ifNullOperator must not be {@literal null}. + * @param ifNull must not be {@literal null}. * @return never {@literal null}. * @since 1.10 */ - public abstract ProjectionOperation applyCondition(IfNullOperator ifNullOperator); + public abstract ProjectionOperation applyCondition(IfNull ifNull); } /** @@ -462,10 +464,10 @@ public ProjectionOperation as(String alias) { * @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#transform(org.springframework.data.mongodb.core.aggregation.ConditionalOperator) */ @Override - public ProjectionOperation applyCondition(ConditionalOperator conditionalOperator) { + public ProjectionOperation applyCondition(Cond cond) { - Assert.notNull(conditionalOperator, "ConditionalOperator must not be null!"); - return this.operation.and(new ExpressionProjection(Fields.field(name), conditionalOperator)); + Assert.notNull(cond, "ConditionalOperator must not be null!"); + return this.operation.and(new ExpressionProjection(Fields.field(name), cond)); } /* @@ -473,10 +475,10 @@ public ProjectionOperation applyCondition(ConditionalOperator conditionalOperato * @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#transform(org.springframework.data.mongodb.core.aggregation.IfNullOperator) */ @Override - public ProjectionOperation applyCondition(IfNullOperator ifNullOperator) { + public ProjectionOperation applyCondition(IfNull ifNull) { - Assert.notNull(ifNullOperator, "IfNullOperator must not be null!"); - return this.operation.and(new ExpressionProjection(Fields.field(name), ifNullOperator)); + Assert.notNull(ifNull, "IfNullOperator must not be null!"); + return this.operation.and(new ExpressionProjection(Fields.field(name), ifNull)); } /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 0a75c536f0..100c59e128 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -24,6 +24,8 @@ import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; +import lombok.Builder; + import java.io.BufferedInputStream; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -55,6 +57,8 @@ import org.springframework.data.mongodb.core.CollectionCallback; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.Venue; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry; import org.springframework.data.mongodb.core.index.GeospatialIndex; import org.springframework.data.mongodb.core.mapping.Document; @@ -73,8 +77,6 @@ import com.mongodb.MongoException; import com.mongodb.util.JSON; -import lombok.Builder; - /** * Tests for {@link MongoTemplate#aggregate(String, AggregationPipeline, Class)}. * @@ -522,7 +524,7 @@ public void aggregationUsingConditionalProjectionToCalculateDiscount() { TypedAggregation aggregation = newAggregation(InventoryItem.class, // project("item") // .and("discount")// - .applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("qty").gte(250)) // + .applyCondition(Cond.newBuilder().when(Criteria.where("qty").gte(250)) // .then(30) // .otherwise(20))); @@ -570,7 +572,7 @@ public void aggregationUsingIfNullToProjectSaneDefaults() { TypedAggregation aggregation = newAggregation(InventoryItem.class, // project("item") // - .and(ifNull("description", "Unspecified")) // + .and(ConditionalOperators.ifNull("description").then("Unspecified")) // .as("description")// ); @@ -597,7 +599,7 @@ public void aggregationUsingConditionalProjection() { TypedAggregation aggregation = newAggregation(ZipInfo.class, // project() // .and("largePopulation")// - .applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(20000)) // + .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // .then(true) // .otherwise(false)) // .and("population").as("population")); @@ -622,9 +624,9 @@ public void aggregationUsingNestedConditionalProjection() { TypedAggregation aggregation = newAggregation(ZipInfo.class, // project() // .and("size")// - .applyCondition(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(20000)) // - .then(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(200000)).then("huge") - .otherwise("small")) // + .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // + .then( + ConditionalOperators.when(Criteria.where("population").gte(200000)).then("huge").otherwise("small")) // .otherwise("small")) // .and("population").as("population")); @@ -649,9 +651,9 @@ public void aggregationUsingIfNullProjection() { mongoTemplate.insert(new LineItem("idonly", null, 0)); TypedAggregation aggregation = newAggregation(LineItem.class, // - project("id") // - .and("caption")// - .applyCondition(ifNull(field("caption"), "unknown")), +project("id") // + .and("caption")// + .applyCondition(ConditionalOperators.ifNull("caption").then("unknown")), sort(ASC, "id")); assertThat(aggregation.toString(), is(notNullValue())); @@ -678,7 +680,7 @@ public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() { TypedAggregation aggregation = newAggregation(LineItem.class, // project("id") // .and("caption")// - .applyCondition(ifNull(field("caption"), field("id"))), + .applyCondition(ConditionalOperators.ifNull("caption").thenValueOf("id")), sort(ASC, "id")); assertThat(aggregation.toString(), is(notNullValue())); @@ -714,12 +716,16 @@ public void shouldAllowGroupingUsingConditionalExpressions() { TypedAggregation agg = Aggregation.newAggregation(CarPerson.class, unwind("descriptors.carDescriptor.entries"), // project() // - .and(new ConditionalOperator(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1"), "good", - "meh")) + .and(ConditionalOperators // + .when(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1")).then("good") + .otherwise("meh")) .as("make") // .and("descriptors.carDescriptor.entries.model").as("model") // .and("descriptors.carDescriptor.entries.year").as("year"), // - group("make").avg(new ConditionalOperator(Criteria.where("year").gte(2012), 1, 9000)).as("score"), + group("make").avg(ConditionalOperators // + .when(Criteria.where("year").gte(2012)) // + .then(1) // + .otherwise(9000)).as("score"), sort(ASC, "make")); AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index 84408bbcb8..684bda8326 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -19,7 +19,6 @@ import static org.junit.Assert.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; -import static org.springframework.data.mongodb.core.aggregation.Fields.*; import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; @@ -31,6 +30,8 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; import org.springframework.data.mongodb.core.query.Criteria; import com.mongodb.BasicDBObject; @@ -391,7 +392,9 @@ public void conditionExpressionBasedFieldsShouldBeReferencableInFollowingOperati DBObject agg = newAggregation( // project("a"), // - group("a").first(conditional(Criteria.where("a").gte(42), "answer", "no-answer")).as("foosum") // + group("a") + .first(ConditionalOperators.when(Criteria.where("a").gte(42)).thenValueOf("answer").otherwise("no-answer")) + .as("foosum") // ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); @SuppressWarnings("unchecked") @@ -409,7 +412,7 @@ public void conditionExpressionBasedFieldsShouldBeReferencableInFollowingOperati public void shouldRenderProjectionConditionalExpressionCorrectly() { DBObject agg = Aggregation.newAggregation(// - project().and(ConditionalOperator.newBuilder() // + project().and(Cond.newBuilder() // .when("isYellow") // .then("bright") // .otherwise("dark")).as("color")) @@ -432,7 +435,7 @@ public void shouldRenderProjectionConditionalCorrectly() { DBObject agg = Aggregation.newAggregation(// project().and("color") - .applyCondition(ConditionalOperator.newBuilder() // + .applyCondition(Cond.newBuilder() // .when("isYellow") // .then("bright") // .otherwise("dark"))) @@ -456,7 +459,8 @@ public void shouldRenderProjectionConditionalWithCriteriaCorrectly() { DBObject agg = Aggregation .newAggregation(project()// .and("color")// - .applyCondition(conditional(Criteria.where("key").gt(5), "bright", "dark"))) // + .applyCondition(Cond.newBuilder().when(Criteria.where("key").gt(5)) // + .then("bright").otherwise("dark"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 0, "$project"); @@ -478,7 +482,10 @@ public void referencingProjectionAliasesShouldRenderProjectionConditionalWithFie .newAggregation(// project().and("color").as("chroma"), project().and("luminosity") // - .applyCondition(conditional(field("chroma"), "bright", "dark"))) // + .applyCondition(ConditionalOperators // + .when("chroma") // + .thenValueOf("bright") // + .otherwise("dark"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); @@ -500,7 +507,10 @@ public void referencingProjectionAliasesShouldRenderProjectionConditionalWithCri .newAggregation(// project().and("color").as("chroma"), project().and("luminosity") // - .applyCondition(conditional(Criteria.where("chroma").is(100), "bright", "dark"))) // + .applyCondition(Cond.newBuilder() + .when(Criteria.where("chroma") // + .is(100)) // + .then("bright").otherwise("dark"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); @@ -522,7 +532,9 @@ public void shouldRenderProjectionIfNullWithFieldReferenceCorrectly() { .newAggregation(// project().and("color"), // project().and("luminosity") // - .applyCondition(ifNull(field("chroma"), "unknown"))) // + .applyCondition(ConditionalOperators // + .ifNull("chroma") // + .then("unknown"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); @@ -541,7 +553,8 @@ public void shouldRenderProjectionIfNullWithFallbackFieldReferenceCorrectly() { .newAggregation(// project("fallback").and("color").as("chroma"), project().and("luminosity") // - .applyCondition(ifNull(field("chroma"), field("fallback")))) // + .applyCondition(ConditionalOperators.ifNull("chroma") // + .thenValueOf("fallback"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); DBObject project = extractPipelineElement(agg, 1, "$project"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java similarity index 77% rename from spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperatorUnitTests.java rename to spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java index 475559a0a1..b86fb27d87 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java @@ -16,48 +16,26 @@ package org.springframework.data.mongodb.core.aggregation; import static org.junit.Assert.*; -import static org.springframework.data.mongodb.core.aggregation.ConditionalOperator.*; +import static org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import java.util.Arrays; import org.junit.Test; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; import org.springframework.data.mongodb.core.query.Criteria; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** - * Unit tests for {@link ConditionalOperator}. + * Unit tests for {@link Cond}. * * @author Mark Paluch * @author Christoph Strobl */ -public class ConditionalOperatorUnitTests { - - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) - public void shouldRejectNullCondition() { - new ConditionalOperator((Field) null, "", ""); - } - - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) - public void shouldRejectThenValue() { - new ConditionalOperator(Fields.field("field"), null, ""); - } - - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) - public void shouldRejectOtherwiseValue() { - new ConditionalOperator(Fields.field("field"), "", null); - } +public class CondExpressionUnitTests { /** * @see DATAMONGO-861 @@ -92,12 +70,12 @@ public void builderRejectsBuilderAsThenValue() { } /** - * @see DATAMONGO-861 + * @see DATAMONGO-861, DATAMONGO-1542 */ @Test public void simpleBuilderShouldRenderCorrectly() { - ConditionalOperator operator = newBuilder().when("isYellow").then("bright").otherwise("dark"); + Cond operator = ConditionalOperators.when("isYellow").thenValueOf("bright").otherwise("dark"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); DBObject expectedCondition = new BasicDBObject() // @@ -109,12 +87,12 @@ public void simpleBuilderShouldRenderCorrectly() { } /** - * @see DATAMONGO-861 + * @see DATAMONGO-861, DATAMONGO-1542 */ @Test public void simpleCriteriaShouldRenderCorrectly() { - ConditionalOperator operator = newBuilder().when(Criteria.where("luminosity").gte(100)).then("bright") + Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100)).thenValueOf("bright") .otherwise("dark"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); @@ -132,11 +110,10 @@ public void simpleCriteriaShouldRenderCorrectly() { @Test public void andCriteriaShouldRenderCorrectly() { - ConditionalOperator operator = newBuilder() // - .when(Criteria.where("luminosity").gte(100) // - .andOperator(Criteria.where("hue").is(50), // - Criteria.where("saturation").lt(11))) - .then("bright").otherwise("dark"); + Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100) // + .andOperator(Criteria.where("hue").is(50), // + Criteria.where("saturation").lt(11))) + .thenValueOf("bright").otherwiseValueOf("dark-field"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); @@ -147,20 +124,20 @@ public void andCriteriaShouldRenderCorrectly() { DBObject expectedCondition = new BasicDBObject() // .append("if", Arrays. asList(luminosity, new BasicDBObject("$and", Arrays.asList(hue, saturation)))) // .append("then", "bright") // - .append("else", "dark"); + .append("else", "$dark-field"); assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition)); } /** - * @see DATAMONGO-861 + * @see DATAMONGO-861, DATAMONGO-1542 */ @Test public void twoArgsCriteriaShouldRenderCorrectly() { Criteria criteria = Criteria.where("luminosity").gte(100) // .and("saturation").and("chroma").is(200); - ConditionalOperator operator = newBuilder().when(criteria).then("bright").otherwise("dark"); + Cond operator = ConditionalOperators.when(criteria).thenValueOf("bright").otherwise("dark"); DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); @@ -176,14 +153,13 @@ public void twoArgsCriteriaShouldRenderCorrectly() { } /** - * @see DATAMONGO-861 + * @see DATAMONGO-861, DATAMONGO-1542 */ @Test public void nestedCriteriaShouldRenderCorrectly() { - ConditionalOperator operator = newBuilder() // - .when(Criteria.where("luminosity").gte(100)) // - .then(newBuilder() // + Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100)) // + .thenValueOf(newBuilder() // .when(Criteria.where("luminosity").gte(200)) // .then("verybright") // .otherwise("not-so-bright")) // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java deleted file mode 100644 index a7809644cb..0000000000 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/IfNullOperatorUnitTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.mongodb.core.aggregation; - -import static org.junit.Assert.*; -import static org.springframework.data.mongodb.test.util.IsBsonObject.*; - -import java.util.Arrays; - -import org.junit.Test; - -import com.mongodb.DBObject; - -/** - * Unit tests for {@link IfNullOperator}. - * - * @author Mark Paluch - * @author Christoph Strobl - */ -public class IfNullOperatorUnitTests { - - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) - public void shouldRejectNullCondition() { - new IfNullOperator(null, ""); - } - - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) - public void shouldRejectThenValue() { - new IfNullOperator(Fields.field("aa"), null); - } - - /** - * @see DATAMONGO-861 - */ - @Test - public void simpleIfNullShouldRenderCorrectly() { - - IfNullOperator operator = IfNullOperator.newBuilder() // - .ifNull("optional") // - .thenReplaceWith("a more sophisticated value"); - - DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); - - assertThat(dbObject, - isBsonObject().containing("$ifNull", Arrays. asList("$optional", "a more sophisticated value"))); - } - - /** - * @see DATAMONGO-861 - */ - @Test - public void fieldReplacementIfNullShouldRenderCorrectly() { - - IfNullOperator operator = IfNullOperator.newBuilder() // - .ifNull(Fields.field("optional")) // - .thenReplaceWith(Fields.field("never-null")); - - DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT); - - assertThat(dbObject, isBsonObject().containing("$ifNull", Arrays. asList("$optional", "$never-null"))); - } -} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java index c659e14632..f3ea25a215 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java @@ -32,6 +32,7 @@ import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArrayOperators; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.BooleanOperators; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ComparisonOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.DateOperators; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.LiteralOperators; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.SetOperators; @@ -49,6 +50,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public class ProjectionOperationUnitTests { @@ -1707,4 +1709,43 @@ public void shouldRenderMapAggregationExpressionOnExpression() { private static DBObject exctractOperation(String field, DBObject fromProjectClause) { return (DBObject) fromProjectClause.get(field); } + + /** + * @see DATAMONGO-861, DATAMONGO-1542 + */ + @Test + public void shouldRenderIfNullConditionAggregationExpression() { + + DBObject agg = project().and(ConditionalOperators.ifNull(ArrayOperators.arrayOf("array").elementAt(1)).then("a more sophisticated value")) + .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $ifNull: [ { $arrayElemAt: [\"$array\", 1] }, \"a more sophisticated value\" ] } } }"))); + } + + /** + * @see DATAMONGO-1542 + */ + @Test + public void shouldRenderIfNullValueAggregationExpression() { + + DBObject agg = project() + .and(ConditionalOperators.ifNull("field").then(ArrayOperators.arrayOf("array").elementAt(1))).as("result") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project: { result: { $ifNull: [ \"$field\", { $arrayElemAt: [\"$array\", 1] } ] } } }"))); + } + + /** + * @see DATAMONGO-861, DATAMONGO-1542 + */ + @Test + public void fieldReplacementIfNullShouldRenderCorrectly() { + + DBObject agg = project().and(ConditionalOperators.ifNull("optional").thenValueOf("$never-null")).as("result") + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $project: { result: { $ifNull: [ \"$optional\", \"$never-null\" ] } } }"))); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java index c65b98fe15..e80bb17815 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java @@ -36,9 +36,10 @@ import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; -import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.convert.CustomConversions; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; @@ -49,6 +50,7 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import com.mongodb.util.JSON; /** * Unit tests for {@link TypeBasedAggregationOperationContext}. @@ -292,7 +294,8 @@ public void rendersAggregationConditionalInTypedAggregationContextCorrectly() { TypedAggregation agg = newAggregation(FooPerson.class, project("name") // .and("age") // - .applyCondition(conditional(Criteria.where("age.value").lt(10), new Age(0), field("age"))) // + .applyCondition( + ConditionalOperators.when(Criteria.where("age.value").lt(10)).then(new Age(0)).otherwiseValueOf("age")) // ); DBObject dbo = agg.toDbObject("person", context); @@ -309,7 +312,7 @@ public void rendersAggregationConditionalInTypedAggregationContextCorrectly() { } /** - * @see DATAMONGO-861 + * @see DATAMONGO-861, DATAMONGO-1542 */ @Test public void rendersAggregationIfNullInTypedAggregationContextCorrectly() { @@ -318,7 +321,7 @@ public void rendersAggregationIfNullInTypedAggregationContextCorrectly() { TypedAggregation agg = newAggregation(FooPerson.class, project("name") // .and("age") // - .applyCondition(ifNull("age", new Age(0))) // + .applyCondition(ConditionalOperators.ifNull("age").then(new Age(0))) // ); DBObject dbo = agg.toDbObject("person", context); @@ -329,6 +332,9 @@ public void rendersAggregationIfNullInTypedAggregationContextCorrectly() { DBObject project = getValue(projection, "$project"); DBObject age = getValue(project, "age"); + assertThat(age, is(JSON.parse( + "{ $ifNull: [ \"$age\", { \"_class\":\"org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContextUnitTests$Age\", \"value\": 0} ] }"))); + assertThat(age, isBsonObject().containing("$ifNull.[0]", "$age")); assertThat(age, isBsonObject().containing("$ifNull.[1].value", 0)); assertThat(age, isBsonObject().containing("$ifNull.[1]._class", Age.class.getName())); From c9dfeea0c7fe009085169818fcea4bcc7086ea92 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 6 Dec 2016 08:58:08 +0100 Subject: [PATCH 027/118] DATAMONGO-1542 - Polishing. Added some static entry points for better readability. Original Pull Request: #421 --- .../aggregation/AggregationExpressions.java | 44 ++++++++++++++++++- .../ProjectionOperationUnitTests.java | 8 ++-- 2 files changed, 46 insertions(+), 6 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 481be82ff1..a2a835fb31 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 @@ -6438,12 +6438,52 @@ private void assertNotBuilder(Object toCheck, String name) { /** * Get a builder that allows fluent creation of {@link Cond}. * - * @return a new {@link ConditionalExpressionBuilder}. + * @return never {@literal null}. */ - public static ConditionalExpressionBuilder newBuilder() { + public static WhenBuilder newBuilder() { return ConditionalExpressionBuilder.newBuilder(); } + /** + * Start creating new {@link Cond} by providing the boolean expression used in {@code if}. + * + * @param booleanExpression must not be {@literal null}. + * @return never {@literal null}. + */ + public static ThenBuilder when(DBObject booleanExpression) { + return ConditionalExpressionBuilder.newBuilder().when(booleanExpression); + } + + /** + * Start creating new {@link Cond} by providing the {@link AggregationExpression} used in {@code if}. + * + * @param expression expression that yields in a boolean result, must not be {@literal null}. + * @return never {@literal null}. + */ + public static ThenBuilder when(AggregationExpression expression) { + return ConditionalExpressionBuilder.newBuilder().when(expression); + } + + /** + * Start creating new {@link Cond} by providing the field reference used in {@code if}. + * + * @param booleanField name of a field holding a boolean value, must not be {@literal null}. + * @return never {@literal null}. + */ + public static ThenBuilder when(String booleanField) { + return ConditionalExpressionBuilder.newBuilder().when(booleanField); + } + + /** + * Start creating new {@link Cond} by providing the {@link CriteriaDefinition} used in {@code if}. + * + * @param criteria criteria to evaluate, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + public static ThenBuilder when(CriteriaDefinition criteria) { + return ConditionalExpressionBuilder.newBuilder().when(criteria); + } + /** * @author Mark Paluch */ 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 f3ea25a215..73a8b94a06 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 @@ -1706,10 +1706,6 @@ public void shouldRenderMapAggregationExpressionOnExpression() { "{ $project:{ adjustedGrades:{ $map: { input: { $size : [\"foo\"]}, as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}"))); } - private static DBObject exctractOperation(String field, DBObject fromProjectClause) { - return (DBObject) fromProjectClause.get(field); - } - /** * @see DATAMONGO-861, DATAMONGO-1542 */ @@ -1748,4 +1744,8 @@ public void fieldReplacementIfNullShouldRenderCorrectly() { assertThat(agg, is(JSON.parse("{ $project: { result: { $ifNull: [ \"$optional\", \"$never-null\" ] } } }"))); } + + private static DBObject exctractOperation(String field, DBObject fromProjectClause) { + return (DBObject) fromProjectClause.get(field); + } } From 68db0d4cb08938f86ebe1a418df1dc57b124f8e8 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 24 Nov 2016 15:25:20 +0100 Subject: [PATCH 028/118] DATAMONGO-1538 - Add support for $let to aggregation. We now support $let in aggregation $project stage. ExpressionVariable total = newExpressionVariable("total").forExpression(ADD.of(field("price"), field("tax"))); ExpressionVariable discounted = newExpressionVariable("discounted").forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); newAggregation(Sales.class, project() .and(define(total, discounted) .andApply(MULTIPLY.of(field("total"), field("discounted")))) .as("finalTotal")); Original pull request: #417. --- .../aggregation/AggregationExpressions.java | 205 ++++++++++++++++++ .../AggregationFunctionExpressions.java | 2 +- .../core/aggregation/ProjectionOperation.java | 34 ++- .../core/aggregation/AggregationTests.java | 58 ++++- .../ProjectionOperationUnitTests.java | 73 +++++-- 5 files changed, 353 insertions(+), 19 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 a2a835fb31..e04193d142 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 @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -25,6 +26,7 @@ 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.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; import org.springframework.data.mongodb.core.query.CriteriaDefinition; @@ -1986,6 +1988,28 @@ public static Map.AsBuilder mapItemsOf(String fieldReference) { public static Map.AsBuilder mapItemsOf(AggregationExpression expression) { return Map.itemsOf(expression); } + + /** + * Start creating new {@link Let} that allows definition of {@link ExpressionVariable} that can be used within a + * nested {@link AggregationExpression}. + * + * @param variables must not be {@literal null}. + * @return + */ + public static Let.LetBuilder define(ExpressionVariable... variables) { + return Let.define(variables); + } + + /** + * Start creating new {@link Let} that allows definition of {@link ExpressionVariable} that can be used within a + * nested {@link AggregationExpression}. + * + * @param variables must not be {@literal null}. + * @return + */ + public static Let.LetBuilder define(Collection variables) { + return Let.define(variables); + } } /** @@ -6696,4 +6720,185 @@ public Cond otherwiseValueOf(AggregationExpression expression) { } } } + + /** + * {@link AggregationExpression} for {@code $let} that binds {@link AggregationExpression} to variables for use in the + * specified {@code in} expression, and returns the result of the expression. + * + * @author Christoph Strobl + * @since 1.10 + */ + class Let implements AggregationExpression { + + private final List vars; + private final AggregationExpression expression; + + private Let(List vars, AggregationExpression expression) { + + this.vars = vars; + this.expression = expression; + } + + /** + * Start creating new {@link Let} by defining the variables for {@code $vars}. + * + * @param variables must not be {@literal null}. + * @return + */ + 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) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Let(new ArrayList(variables), expression); + } + }; + } + + /** + * Start creating new {@link Let} by defining the variables for {@code $vars}. + * + * @param variables must not be {@literal null}. + * @return + */ + 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) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Let(Arrays.asList(variables), expression); + } + }; + } + + public interface LetBuilder { + + /** + * Define the {@link AggregationExpression} to evaluate. + * + * @param expression must not be {@literal null}. + * @return + */ + Let andApply(AggregationExpression expression); + } + + @Override + public DBObject toDbObject(final AggregationOperationContext context) { + + return toLet(new ExposedFieldsAggregationOperationContext( + ExposedFields.synthetic(Fields.fields(getVariableNames())), 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 String[] getVariableNames() { + + String[] varNames = new String[this.vars.size()]; + for (int i = 0; i < this.vars.size(); i++) { + varNames[i] = this.vars.get(i).variableName; + } + return varNames; + } + + private DBObject toLet(AggregationOperationContext context) { + + DBObject letExpression = new BasicDBObject(); + + DBObject mappedVars = new BasicDBObject(); + for (ExpressionVariable var : this.vars) { + mappedVars.putAll(getMappedVariable(var, context)); + } + + letExpression.put("vars", mappedVars); + letExpression.put("in", getMappedIn(context)); + + return new BasicDBObject("$let", letExpression); + } + + private DBObject getMappedVariable(ExpressionVariable var, AggregationOperationContext context) { + + return new BasicDBObject(var.variableName, var.expression instanceof AggregationExpression + ? ((AggregationExpression) var.expression).toDbObject(context) : var.expression); + } + + private Object getMappedIn(AggregationOperationContext context) { + return expression.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(context)); + } + + /** + * @author Christoph Strobl + */ + public static class ExpressionVariable { + + private final String variableName; + private final Object expression; + + /** + * Creates new {@link ExpressionVariable}. + * + * @param variableName can be {@literal null}. + * @param expression can be {@literal null}. + */ + private ExpressionVariable(String variableName, Object expression) { + + this.variableName = variableName; + this.expression = expression; + } + + /** + * Create a new {@link ExpressionVariable} with given name. + * + * @param variableName must not be {@literal null}. + * @return never {@literal null}. + */ + public static ExpressionVariable newVariable(String variableName) { + + Assert.notNull(variableName, "VariableName must not be null!"); + return new ExpressionVariable(variableName, null); + } + + /** + * Create a new {@link ExpressionVariable} with current name and given {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return never {@literal null}. + */ + public ExpressionVariable forExpression(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ExpressionVariable(variableName, expression); + } + + /** + * Create a new {@link ExpressionVariable} with current name and given {@literal expressionObject}. + * + * @param expressionObject must not be {@literal null}. + * @return never {@literal null}. + */ + public ExpressionVariable forExpression(DBObject expressionObject) { + + Assert.notNull(expressionObject, "Expression must not be null!"); + return new ExpressionVariable(variableName, expressionObject); + } + } + } } 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 f605dc628d..219a552ac1 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 @@ -36,7 +36,7 @@ @Deprecated public enum AggregationFunctionExpressions { - SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT, ADD; + SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT, ADD, MULTIPLY; /** * 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/ProjectionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java index 428abeb5ee..f1d67b02c4 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 @@ -17,9 +17,11 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; @@ -852,7 +854,7 @@ public ProjectionOperationBuilder differenceToArray(String array) { /** * Generates a {@code $setIsSubset} expression that takes array of the previously mentioned field and returns * {@literal true} if it is a subset of the given {@literal array}. - * + * * @param array must not be {@literal null}. * @return never {@literal null}. * @since 1.10 @@ -1195,7 +1197,35 @@ public ProjectionOperationBuilder dateAsFormattedString(String format) { return this.operation.and(AggregationExpressions.DateToString.dateOf(name).toString(format)); } - /* + /** + * Generates a {@code $let} expression that binds variables for use in the specified expression, and returns the + * result of the expression. + * + * @param valueExpression The {@link AggregationExpression} bound to {@literal variableName}. + * @param variableName The variable name to be used in the {@literal in} {@link AggregationExpression}. + * @param in The {@link AggregationExpression} to evaluate. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder let(AggregationExpression valueExpression, String variableName, + AggregationExpression in) { + return this.operation.and(AggregationExpressions.Let.define(ExpressionVariable.newVariable(variableName).forExpression(valueExpression)).andApply(in)); + } + + /** + * Generates a {@code $let} expression that binds variables for use in the specified expression, and returns the + * result of the expression. + * + * @param variables The bound {@link ExpressionVariable}s. + * @param in The {@link AggregationExpression} to evaluate. + * @return never {@literal null}. + * @since 1.10 + */ + public ProjectionOperationBuilder let(Collection variables, AggregationExpression in) { + return this.operation.and(AggregationExpressions.Let.define(variables).andApply(in)); + } + + /* * (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/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 100c59e128..4faddf85dc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -59,6 +59,8 @@ import org.springframework.data.mongodb.core.Venue; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable; import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry; import org.springframework.data.mongodb.core.index.GeospatialIndex; import org.springframework.data.mongodb.core.mapping.Document; @@ -71,6 +73,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; import com.mongodb.CommandResult; import com.mongodb.DBCollection; import com.mongodb.DBObject; @@ -651,9 +654,9 @@ public void aggregationUsingIfNullProjection() { mongoTemplate.insert(new LineItem("idonly", null, 0)); TypedAggregation aggregation = newAggregation(LineItem.class, // -project("id") // - .and("caption")// - .applyCondition(ConditionalOperators.ifNull("caption").then("unknown")), + project("id") // + .and("caption")// + .applyCondition(ConditionalOperators.ifNull("caption").then("unknown")), sort(ASC, "id")); assertThat(aggregation.toString(), is(notNullValue())); @@ -1545,6 +1548,36 @@ public void filterShouldBeAppliedCorrectly() { Sales.builder().id("2").items(Collections. emptyList()).build())); } + /** + * @see DATAMONGO-1538 + */ + @Test + public void letShouldBeAppliedCorrectly() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + + Sales2 sales1 = Sales2.builder().id("1").price(10).tax(0.5F).applyDiscount(true).build(); + Sales2 sales2 = Sales2.builder().id("2").price(10).tax(0.25F).applyDiscount(false).build(); + + mongoTemplate.insert(Arrays.asList(sales1, sales2), Sales2.class); + + ExpressionVariable total = ExpressionVariable.newVariable("total") + .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))); + ExpressionVariable discounted = ExpressionVariable.newVariable("discounted") + .forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); + + TypedAggregation agg = Aggregation.newAggregation(Sales2.class, + Aggregation.project() + .and(Let.define(total, discounted).andApply( + AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) + .as("finalTotal")); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + assertThat(result.getMappedResults(), + contains(new BasicDBObjectBuilder().add("_id", "1").add("finalTotal", 9.450000000000001D).get(), + new BasicDBObjectBuilder().add("_id", "2").add("finalTotal", 10.25D).get())); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); @@ -1786,6 +1819,9 @@ public InventoryItem(int id, String item, String description, int qty) { } } + /** + * @DATAMONGO-1491 + */ @lombok.Data @Builder static class Sales { @@ -1794,6 +1830,9 @@ static class Sales { List items; } + /** + * @DATAMONGO-1491 + */ @lombok.Data @Builder static class Item { @@ -1803,4 +1842,17 @@ static class Item { Integer quantity; Long price; } + + /** + * @DATAMONGO-1538 + */ + @lombok.Data + @Builder + static class Sales2 { + + String id; + Integer price; + Float tax; + boolean applyDiscount; + } } 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 73a8b94a06..96d45ef1e9 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 @@ -16,8 +16,10 @@ package org.springframework.data.mongodb.core.aggregation; import static org.hamcrest.Matchers.*; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; +import static org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable.*; import static org.springframework.data.mongodb.core.aggregation.AggregationFunctionExpressions.*; import static org.springframework.data.mongodb.core.aggregation.Fields.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; @@ -28,17 +30,9 @@ import org.junit.Test; import org.springframework.data.mongodb.core.DBObjectTestUtils; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArrayOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.BooleanOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ComparisonOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.DateOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.LiteralOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.SetOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.StringOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.VariableOperators; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.*; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; @@ -1712,11 +1706,12 @@ public void shouldRenderMapAggregationExpressionOnExpression() { @Test public void shouldRenderIfNullConditionAggregationExpression() { - DBObject agg = project().and(ConditionalOperators.ifNull(ArrayOperators.arrayOf("array").elementAt(1)).then("a more sophisticated value")) + DBObject agg = project().and( + ConditionalOperators.ifNull(ArrayOperators.arrayOf("array").elementAt(1)).then("a more sophisticated value")) .as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); - assertThat(agg, - is(JSON.parse("{ $project: { result: { $ifNull: [ { $arrayElemAt: [\"$array\", 1] }, \"a more sophisticated value\" ] } } }"))); + assertThat(agg, is(JSON.parse( + "{ $project: { result: { $ifNull: [ { $arrayElemAt: [\"$array\", 1] }, \"a more sophisticated value\" ] } } }"))); } /** @@ -1745,6 +1740,58 @@ public void fieldReplacementIfNullShouldRenderCorrectly() { assertThat(agg, is(JSON.parse("{ $project: { result: { $ifNull: [ \"$optional\", \"$never-null\" ] } } }"))); } + /** + * @see DATAMONGO-1538 + */ + @Test + public void shouldRenderLetExpressionCorrectly() { + + DBObject agg = Aggregation.project() + .and(VariableOperators + .define( + newVariable("total") + .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))), + newVariable("discounted").forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D))) + .andApply(AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) // + .as("finalTotal").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project:{ \"finalTotal\" : { \"$let\": {" + // + "\"vars\": {" + // + "\"total\": { \"$add\": [ \"$price\", \"$tax\" ] }," + // + "\"discounted\": { \"$cond\": { \"if\": \"$applyDiscount\", \"then\": 0.9, \"else\": 1.0 } }" + // + "}," + // + "\"in\": { \"$multiply\": [ \"$$total\", \"$$discounted\" ] }" + // + "}}}}"))); + } + + /** + * @see DATAMONGO-1538 + */ + @Test + public void shouldRenderLetExpressionCorrectlyWhenUsingLetOnProjectionBuilder() { + + ExpressionVariable var1 = newVariable("total") + .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))); + + ExpressionVariable var2 = newVariable("discounted") + .forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); + + DBObject agg = Aggregation.project().and("foo") + .let(Arrays.asList(var1, var2), + AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted"))) + .as("finalTotal").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $project:{ \"finalTotal\" : { \"$let\": {" + // + "\"vars\": {" + // + "\"total\": { \"$add\": [ \"$price\", \"$tax\" ] }," + // + "\"discounted\": { \"$cond\": { \"if\": \"$applyDiscount\", \"then\": 0.9, \"else\": 1.0 } }" + // + "}," + // + "\"in\": { \"$multiply\": [ \"$$total\", \"$$discounted\" ] }" + // + "}}}}"))); + } + private static DBObject exctractOperation(String field, DBObject fromProjectClause) { return (DBObject) fromProjectClause.get(field); } From 3d8b6868c78f09b535d5b7e371f002dffe57bc2e Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 6 Dec 2016 15:26:35 +0100 Subject: [PATCH 029/118] DATAMONGO-1538 - Polishing. Use InheritingExposedFieldsAggregationOperationContext instead of anonymous context class for condition mapping. Drop aggregation input collections before tests. Minor reformatting. Original pull request: #417. --- .../aggregation/AggregationExpressions.java | 73 +++++-------------- ...osedFieldsAggregationOperationContext.java | 5 +- .../core/aggregation/AggregationTests.java | 2 + 3 files changed, 22 insertions(+), 58 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 e04193d142..2b5e873747 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 @@ -28,7 +28,6 @@ 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.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; @@ -3905,31 +3904,19 @@ public static AsBuilder filter(List values) { */ @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); - } - }); + return toFilter(ExposedFields.from(as), context); } - private DBObject toFilter(AggregationOperationContext context) { + private DBObject toFilter(ExposedFields exposedFields, AggregationOperationContext context) { DBObject filterExpression = new BasicDBObject(); + InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( + exposedFields, context); filterExpression.putAll(context.getMappedObject(new BasicDBObject("input", getMappedInput(context)))); filterExpression.put("as", as.getTarget()); - filterExpression.putAll(context.getMappedObject(new BasicDBObject("cond", getMappedCondition(context)))); + filterExpression.putAll(context.getMappedObject(new BasicDBObject("cond", getMappedCondition(operationContext)))); return new BasicDBObject("$filter", filterExpression); } @@ -6019,27 +6006,14 @@ public Map andApply(final AggregationExpression expression) { @Override public DBObject toDbObject(final AggregationOperationContext context) { - - return toMap(new ExposedFieldsAggregationOperationContext( - ExposedFields.synthetic(Fields.fields(itemVariableName)), 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); - } - }); + return toMap(ExposedFields.synthetic(Fields.fields(itemVariableName)), context); } - private DBObject toMap(AggregationOperationContext context) { + private DBObject toMap(ExposedFields exposedFields, AggregationOperationContext context) { BasicDBObject map = new BasicDBObject(); + InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( + exposedFields, context); BasicDBObject input; if (sourceArray instanceof Field) { @@ -6050,7 +6024,8 @@ private DBObject toMap(AggregationOperationContext context) { map.putAll(context.getMappedObject(input)); map.put("as", itemVariableName); - map.put("in", functionToApply.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(context))); + map.put("in", + functionToApply.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(operationContext))); return new BasicDBObject("$map", map); } @@ -6792,22 +6767,7 @@ public interface LetBuilder { @Override public DBObject toDbObject(final AggregationOperationContext context) { - - return toLet(new ExposedFieldsAggregationOperationContext( - ExposedFields.synthetic(Fields.fields(getVariableNames())), 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); - } - }); + return toLet(ExposedFields.synthetic(Fields.fields(getVariableNames())), context); } private String[] getVariableNames() { @@ -6816,20 +6776,23 @@ private String[] getVariableNames() { for (int i = 0; i < this.vars.size(); i++) { varNames[i] = this.vars.get(i).variableName; } + return varNames; } - private DBObject toLet(AggregationOperationContext context) { + private DBObject toLet(ExposedFields exposedFields, AggregationOperationContext context) { DBObject letExpression = new BasicDBObject(); - DBObject mappedVars = new BasicDBObject(); + InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( + exposedFields, context); + for (ExpressionVariable var : this.vars) { mappedVars.putAll(getMappedVariable(var, context)); } letExpression.put("vars", mappedVars); - letExpression.put("in", getMappedIn(context)); + letExpression.put("in", getMappedIn(operationContext)); return new BasicDBObject("$let", letExpression); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/InheritingExposedFieldsAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/InheritingExposedFieldsAggregationOperationContext.java index c25b567328..2071dc0b6c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/InheritingExposedFieldsAggregationOperationContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/InheritingExposedFieldsAggregationOperationContext.java @@ -13,17 +13,16 @@ * 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.util.Assert; /** * {@link ExposedFieldsAggregationOperationContext} that inherits fields from its parent * {@link AggregationOperationContext}. * * @author Mark Paluch + * @since 1.9 */ class InheritingExposedFieldsAggregationOperationContext extends ExposedFieldsAggregationOperationContext { @@ -40,7 +39,7 @@ public InheritingExposedFieldsAggregationOperationContext(ExposedFields exposedF AggregationOperationContext previousContext) { super(exposedFields, previousContext); - Assert.notNull(previousContext, "PreviousContext must not be null!"); + this.previousContext = previousContext; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 4faddf85dc..d89d1a782a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -143,6 +143,8 @@ private void cleanDb() { mongoTemplate.dropCollection(MeterData.class); mongoTemplate.dropCollection(LineItem.class); mongoTemplate.dropCollection(InventoryItem.class); + mongoTemplate.dropCollection(Sales.class); + mongoTemplate.dropCollection(Sales2.class); } /** From 0449719a16a57a161f9874d2d77765e1025348a1 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 6 Dec 2016 20:15:45 +0100 Subject: [PATCH 030/118] 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. Original pull request: #423. --- .../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 dc57b66adfd60b4d69d1d349b4fcfa4ab0da95e7 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 12 Dec 2016 11:55:51 +0100 Subject: [PATCH 031/118] DATAMONGO-1548 - Polishing. Enhance JavaDoc. Minor formatting. Fix typos. Original pull request: #423. --- .../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) From 7b49b120e3122e57657ee29055223cf812545811 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 12 Dec 2016 15:50:12 +0100 Subject: [PATCH 032/118] DATAMONGO-1558 - Upgrade MongoDB server version to, and add build profile for MongoDB 3.4. Added MongoDB 3.4 profile to pom.xml and upgraded to MongoDB 3.4 on travis-ci. --- .travis.yml | 3 ++- pom.xml | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ed1d391663..62909f34f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ env: - PROFILE=mongo31 - PROFILE=mongo32 - PROFILE=mongo33 + - PROFILE=mongo34 - PROFILE=mongo34-next # Current MongoDB version is 2.4.2 as of 2016-04, see https://github.com/travis-ci/travis-ci/issues/3694 @@ -22,7 +23,7 @@ env: addons: apt: sources: - - mongodb-3.2-precise + - mongodb-3.4-precise packages: - mongodb-org-server - mongodb-org-shell diff --git a/pom.xml b/pom.xml index ea80a3cb74..ed9720988d 100644 --- a/pom.xml +++ b/pom.xml @@ -178,11 +178,20 @@ + + + mongo34 + + 3.4.0 + + + + mongo34-next - 3.4.0-SNAPSHOT + 3.4.1-SNAPSHOT From cab35759db42bda0ecb9c1c5ee7f44bc7c26afc4 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 7 Dec 2016 11:15:37 +0100 Subject: [PATCH 033/118] DATAMONGO-1549 - Add $count aggregation stage. We now support the $count stage in aggregation pipelines. newAggregation( match(where("hotelCode").is("0360")), count().as("documents")); Original Pull Request: #422 --- .../mongodb/core/aggregation/Aggregation.java | 11 +++ .../core/aggregation/CountOperation.java | 82 +++++++++++++++++++ .../core/aggregation/AggregationTests.java | 25 ++++++ .../aggregation/CountOperationUnitTests.java | 65 +++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java 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 ff9ec46d14..fe8a0f9680 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java @@ -23,6 +23,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.aggregation.CountOperation.CountOperationBuilder; import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; @@ -396,6 +397,16 @@ public static LookupOperation lookup(Field from, Field localField, Field foreign return new LookupOperation(from, localField, foreignField, as); } + /** + * Creates a new {@link CountOperationBuilder}. + * + * @return never {@literal null}. + * @since 1.10 + */ + public static CountOperationBuilder count() { + return new CountOperationBuilder(); + } + /** * Creates a new {@link Fields} instance for the given field names. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java new file mode 100644 index 0000000000..ed4c274c2f --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java @@ -0,0 +1,82 @@ +/* + * 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.ExposedField; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Encapsulates the aggregation framework {@code $count}-operation. + *

+ * We recommend to use the static factory method {@link Aggregation#count()} instead of creating instances of this + * class directly. + * + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/count/#pipe._S_count + * @author Mark Paluch + * @since 1.10 + */ +public class CountOperation implements FieldsExposingAggregationOperation { + + private final String fieldName; + + /** + * Creates a new {@link CountOperation} given the {@link fieldName} field name. + * + * @param asFieldName must not be {@literal null} or empty. + */ + public CountOperation(String fieldName) { + + Assert.hasText(fieldName, "Field name must not be null or empty!"); + this.fieldName = fieldName; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + return new BasicDBObject("$count", fieldName); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return ExposedFields.from(new ExposedField(fieldName, true)); + } + + /** + * Builder for {@link CountOperation}. + * + * @author Mark Paluch + */ + public static class CountOperationBuilder { + + /** + * Returns the finally to be applied {@link CountOperation} with the given alias. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + public CountOperation as(String fieldName) { + return new CountOperation(fieldName); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index d89d1a782a..fffe2d304a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -100,6 +100,7 @@ public class AggregationTests { private static final Version TWO_DOT_FOUR = new Version(2, 4); private static final Version TWO_DOT_SIX = new Version(2, 6); private static final Version THREE_DOT_TWO = new Version(3, 2); + private static final Version THREE_DOT_FOUR = new Version(3, 4); private static boolean initialized = false; @@ -1298,6 +1299,30 @@ public void shouldSupportReturningCurrentAggregationRootInReference() { assertThat(result.getMappedResults(), hasSize(2)); } + /** + * @see DATAMONGO-1549 + */ + @Test + public void shouldApplyCountCorrectly() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + mongoTemplate.save(new Reservation("0123", "42", 100)); + mongoTemplate.save(new Reservation("0360", "43", 200)); + mongoTemplate.save(new Reservation("0360", "44", 300)); + + Aggregation agg = newAggregation( // + count().as("documents"), // + project("documents") // + .andExpression("documents * 2").as("twice")); + AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); + + assertThat(result.getMappedResults(), hasSize(1)); + + DBObject dbObject = result.getMappedResults().get(0); + assertThat(dbObject, isBsonObject().containing("documents", 3).containing("twice", 6)); + } + /** * @see DATAMONGO-975 */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java new file mode 100644 index 0000000000..2b60442f18 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link CountOperation}. + * + * @author Mark Paluch + */ +public class CountOperationUnitTests { + + /** + * @see DATAMONGO-1549 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsEmptyFieldName() { + new CountOperation(""); + } + + /** + * @see DATAMONGO-1549 + */ + @Test + public void shouldRenderCorrectly() { + + CountOperation countOperation = new CountOperation("field"); + DBObject dbObject = countOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{$count : \"field\" }"))); + } + + /** + * @see DATAMONGO-1549 + */ + @Test + public void countExposesFields() { + + CountOperation countOperation = new CountOperation("field"); + + assertThat(countOperation.getFields().exposesNoFields(), is(false)); + assertThat(countOperation.getFields().exposesSingleFieldOnly(), is(true)); + assertThat(countOperation.getFields().getField("field"), notNullValue()); + } +} From 4e56d9c57529883ba6ba0572716e4cbb3f47000c Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 12 Dec 2016 09:27:45 +0100 Subject: [PATCH 034/118] DATAMONGO-1549 - Polishing $count (aggregation stage). Original Pull Request: #422 --- .../mongodb/core/aggregation/CountOperation.java | 12 ++++++------ .../core/aggregation/CountOperationUnitTests.java | 7 ++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java index ed4c274c2f..c99c1521e0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java @@ -22,12 +22,12 @@ import com.mongodb.DBObject; /** - * Encapsulates the aggregation framework {@code $count}-operation. - *

- * We recommend to use the static factory method {@link Aggregation#count()} instead of creating instances of this - * class directly. - * - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/count/#pipe._S_count + * Encapsulates the aggregation framework {@code $count}-operation.
+ * We recommend to use the static factory method {@link Aggregation#count()} instead of creating instances of this class + * directly. + * + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/count/ * @author Mark Paluch * @since 1.10 */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java index 2b60442f18..a91441558d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java @@ -20,12 +20,11 @@ import org.junit.Test; -import com.mongodb.DBObject; import com.mongodb.util.JSON; /** * Unit tests for {@link CountOperation}. - * + * * @author Mark Paluch */ public class CountOperationUnitTests { @@ -45,9 +44,7 @@ public void rejectsEmptyFieldName() { public void shouldRenderCorrectly() { CountOperation countOperation = new CountOperation("field"); - DBObject dbObject = countOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); - - assertThat(dbObject, is(JSON.parse("{$count : \"field\" }"))); + assertThat(countOperation.toDBObject(Aggregation.DEFAULT_CONTEXT), is(JSON.parse("{$count : \"field\" }"))); } /** From 14678ce7a9185f10262dd0058d666c0f620ee259 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 7 Dec 2016 16:18:57 +0100 Subject: [PATCH 035/118] DATAMONGO-1550 - Add $replaceRoot aggregation stage. We now support the $replaceRoot stage in aggregation pipelines. $replaceRoot can reference either a field, an aggregation expression or it can be used to compose a replacement document. newAggregation( replaceRoot().withDocument() .andValue("value").as("field") .and(MULTIPLY.of(field("total"), field("discounted"))) ); newAggregation( replaceRoot("item"))); Original Pull Request: #422 --- .../mongodb/core/aggregation/Aggregation.java | 42 +- .../aggregation/ReplaceRootOperation.java | 564 ++++++++++++++++++ .../core/aggregation/AggregationTests.java | 26 + .../aggregation/AggregationUnitTests.java | 15 + .../ReplaceRootOperationUnitTests.java | 126 ++++ src/main/asciidoc/reference/mongodb.adoc | 2 +- 6 files changed, 772 insertions(+), 3 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java 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 fe8a0f9680..cd85574bfb 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 @@ -28,6 +28,8 @@ 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.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; +import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootDocumentOperationBuilder; +import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootOperationBuilder; import org.springframework.data.mongodb.core.aggregation.Fields.*; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.CriteriaDefinition; @@ -218,6 +220,40 @@ public static UnwindOperation unwind(String field) { return new UnwindOperation(field(field)); } + /** + * Factory method to create a new {@link ReplaceRootOperation} for the field with the given name. + * + * @param fieldName must not be {@literal null} or empty. + * @return + * @since 1.10 + */ + public static ReplaceRootOperation replaceRoot(String fieldName) { + return ReplaceRootOperation.builder().withValueOf(fieldName); + } + + /** + * Factory method to create a new {@link ReplaceRootOperation} for the field with the given + * {@link AggregationExpression}. + * + * @param aggregationExpression must not be {@literal null}. + * @return + * @since 1.10 + */ + public static ReplaceRootOperation replaceRoot(AggregationExpression aggregationExpression) { + return ReplaceRootOperation.builder().withValueOf(aggregationExpression); + } + + /** + * Factory method to create a new {@link ReplaceRootDocumentOperationBuilder} to configure a + * {@link ReplaceRootOperation}. + * + * @return the {@literal ReplaceRootDocumentOperationBuilder}. + * @since 1.10 + */ + public static ReplaceRootOperationBuilder replaceRoot() { + return ReplaceRootOperation.builder(); + } + /** * Factory method to create a new {@link UnwindOperation} for the field with the given name and * {@code preserveNullAndEmptyArrays}. Note that extended unwind is supported in MongoDB version 3.2+. @@ -470,11 +506,13 @@ public DBObject toDbObject(String inputCollectionName, AggregationOperationConte if (operation instanceof FieldsExposingAggregationOperation) { FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation; + ExposedFields fields = exposedFieldsOperation.getFields(); if (operation instanceof InheritsFieldsAggregationOperation) { - context = new InheritingExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), context); + context = new InheritingExposedFieldsAggregationOperationContext(fields, context); } else { - context = new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), context); + context = fields.exposesNoFields() ? DEFAULT_CONTEXT + : new ExposedFieldsAggregationOperationContext(fields, context); } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java new file mode 100644 index 0000000000..9d0958fbce --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java @@ -0,0 +1,564 @@ +/* + * 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.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.expression.spel.ast.Projection; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Encapsulates the aggregation framework {@code $replaceRoot}-operation. + *

+ * We recommend to use the static factory method {@link Aggregation#replaceRoot(String)} instead of creating instances + * of this class directly. + * + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/#pipe._S_replaceRoot + * @author Mark Paluch + * @since 1.10 + */ +public class ReplaceRootOperation implements FieldsExposingAggregationOperation { + + private final Replacement replacement; + + /** + * Creates a new {@link ReplaceRootOperation} given the {@link as} field name. + * + * @param field must not be {@literal null} or empty. + */ + public ReplaceRootOperation(Field field) { + this.replacement = new FieldReplacement(field); + } + + /** + * Creates a new {@link ReplaceRootOperation} given the {@link as} field name. + * + * @param aggregationExpression must not be {@literal null}. + */ + public ReplaceRootOperation(AggregationExpression aggregationExpression) { + + Assert.notNull(aggregationExpression, "AggregationExpression must not be null!"); + this.replacement = new AggregationExpressionReplacement(aggregationExpression); + } + + protected ReplaceRootOperation(Replacement replacement) { + this.replacement = replacement; + } + + /** + * Creates a new {@link ReplaceRootDocumentOperationBuilder}. + * + * @return a new {@link ReplaceRootDocumentOperationBuilder}. + */ + public static ReplaceRootOperationBuilder builder() { + return new ReplaceRootOperationBuilder(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + return new BasicDBObject("$replaceRoot", new BasicDBObject("newRoot", replacement.toObject(context))); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return ExposedFields.from(); + } + + /** + * Builder for {@link ReplaceRootOperation}. + * + * @author Mark Paluch + */ + public static class ReplaceRootOperationBuilder { + + /** + * Defines a root document replacement based on a {@literal fieldName} that resolves to a document. + * + * @param fieldName must not be {@literal null} or empty. + * @return the final {@link ReplaceRootOperation}. + */ + public ReplaceRootOperation withValueOf(String fieldName) { + return new ReplaceRootOperation(Fields.field(fieldName)); + } + + /** + * Defines a root document replacement based on a {@link AggregationExpression} that resolves to a document. + * + * @param aggregationExpression must not be {@literal null}. + * @return the final {@link ReplaceRootOperation}. + */ + public ReplaceRootOperation withValueOf(AggregationExpression aggregationExpression) { + return new ReplaceRootOperation(aggregationExpression); + } + + /** + * Defines a root document replacement based on a composable document that is empty initially. + *

+ * {@link ReplaceRootOperation} can be populated with individual entries and derive its values from other, existing + * documents. + * + * @return the {@link ReplaceRootDocumentOperation}. + */ + public ReplaceRootDocumentOperation withDocument() { + return new ReplaceRootDocumentOperation(); + } + + /** + * Defines a root document replacement based on a composable document given {@literal dbObject} + *

+ * {@link ReplaceRootOperation} can be populated with individual entries and derive its values from other, existing + * documents. + * + * @param dbObject must not be {@literal null}. + * @return the final {@link ReplaceRootOperation}. + */ + public ReplaceRootOperation withDocument(DBObject dbObject) { + + Assert.notNull(dbObject, "DBObject must not be null!"); + + return new ReplaceRootDocumentOperation().andValuesOf(dbObject); + } + } + + /** + * Encapsulates the aggregation framework {@code $replaceRoot}-operation to result in a composable replacement + * document. + *

+ * Instances of {@link ReplaceRootDocumentOperation} yield empty upon construction and can be populated with single + * values and documents. + * + * @author Mark Paluch + */ + static class ReplaceRootDocumentOperation extends ReplaceRootOperation { + + private final static ReplacementDocument EMPTY = new ReplacementDocument(); + private final ReplacementDocument current; + + /** + * Creates an empty {@link ReplaceRootDocumentOperation}. + */ + public ReplaceRootDocumentOperation() { + this(EMPTY); + } + + private ReplaceRootDocumentOperation(ReplacementDocument replacementDocument) { + super(replacementDocument); + current = replacementDocument; + } + + /** + * Creates an extended {@link ReplaceRootDocumentOperation} that combines {@link ReplacementDocument}s from the + * {@literal currentOperation} and {@literal extension} operation. + * + * @param currentOperation must not be {@literal null}. + * @param extension must not be {@literal null}. + */ + protected ReplaceRootDocumentOperation(ReplaceRootDocumentOperation currentOperation, + ReplacementDocument extension) { + this(currentOperation.current.extendWith(extension)); + } + + /** + * Creates a new {@link ReplaceRootDocumentOperationBuilder} to define a field for the {@link AggregationExpression} + * . + * + * @param aggregationExpression must not be {@literal null}. + * @return the {@link ReplaceRootDocumentOperationBuilder}. + */ + public ReplaceRootDocumentOperationBuilder and(AggregationExpression aggregationExpression) { + return new ReplaceRootDocumentOperationBuilder(this, aggregationExpression); + } + + /** + * Creates a new {@link ReplaceRootDocumentOperationBuilder} to define a field for the {@literal value}. + * + * @param value must not be {@literal null}. + * @return the {@link ReplaceRootDocumentOperationBuilder}. + */ + public ReplaceRootDocumentOperationBuilder andValue(Object value) { + return new ReplaceRootDocumentOperationBuilder(this, value); + } + + /** + * Creates a new {@link ReplaceRootDocumentOperation} that merges all existing replacement values with values from + * {@literal value}. Existing replacement values are overwritten. + * + * @param value must not be {@literal null}. + * @return the {@link ReplaceRootDocumentOperation}. + */ + public ReplaceRootDocumentOperation andValuesOf(Object value) { + return new ReplaceRootDocumentOperation(this, ReplacementDocument.valueOf(value)); + } + } + + /** + * Builder for {@link ReplaceRootDocumentOperation} to populate {@link ReplacementDocument} + * + * @author Mark Paluch + */ + public static class ReplaceRootDocumentOperationBuilder { + + private final ReplaceRootDocumentOperation currentOperation; + private final Object value; + + protected ReplaceRootDocumentOperationBuilder(ReplaceRootDocumentOperation currentOperation, Object value) { + + Assert.notNull(currentOperation, "Current ReplaceRootDocumentOperation must not be null!"); + Assert.notNull(value, "Value must not be null!"); + + this.currentOperation = currentOperation; + this.value = value; + } + + public ReplaceRootDocumentOperation as(String fieldName) { + + if (value instanceof AggregationExpression) { + return new ReplaceRootDocumentOperation(currentOperation, + ReplacementDocument.forExpression(fieldName, (AggregationExpression) value)); + } + + return new ReplaceRootDocumentOperation(currentOperation, ReplacementDocument.forSingleValue(fieldName, value)); + } + } + + /** + * Replacement object that results in a replacement document or an expression that results in a document. + * + * @author Mark Paluch + */ + private abstract static class Replacement { + + /** + * Renders the current {@link Replacement} into a {@link DBObject} based on the given + * {@link AggregationOperationContext}. + * + * @param context will never be {@literal null}. + * @return a replacement document or an expression that results in a document. + */ + public abstract Object toObject(AggregationOperationContext context); + } + + /** + * {@link Replacement} that uses a {@link AggregationExpression} that results in a replacement document. + * + * @author Mark Paluch + */ + private static class AggregationExpressionReplacement extends Replacement { + + private final AggregationExpression aggregationExpression; + + protected AggregationExpressionReplacement(AggregationExpression aggregationExpression) { + this.aggregationExpression = aggregationExpression; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.Replacement#toObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toObject(AggregationOperationContext context) { + return aggregationExpression.toDbObject(context); + } + } + + /** + * {@link Replacement that references a {@link Field} inside the current aggregation pipeline. + * + * @author Mark Paluch + */ + private static class FieldReplacement extends Replacement { + + private final Field field; + + /** + * Creates {@link FieldReplacement} given {@link Field}. + */ + protected FieldReplacement(Field field) { + + Assert.notNull(field, "Field must not be null!"); + this.field = field; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.Replacement#toObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public Object toObject(AggregationOperationContext context) { + return context.getReference(field).toString(); + } + } + + /** + * Replacement document consisting of multiple {@link ReplacementContributor}s. + * + * @author Mark Paluch + */ + private static class ReplacementDocument extends Replacement { + + private final Collection replacements; + + /** + * Creates an empty {@link ReplacementDocument}. + */ + protected ReplacementDocument() { + replacements = new ArrayList(); + } + + /** + * Creates a {@link ReplacementDocument} given {@link ReplacementContributor}. + * + * @param contributor + */ + protected ReplacementDocument(ReplacementContributor contributor) { + + Assert.notNull(contributor, "ReplacementContributor must not be null!"); + replacements = Collections.singleton(contributor); + } + + private ReplacementDocument(Collection replacements) { + this.replacements = replacements; + } + + /** + * Creates a {@link ReplacementDocument} given a {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static ReplacementDocument valueOf(Object value) { + return new ReplacementDocument(new DocumentContributor(value)); + } + + /** + * Creates a {@link ReplacementDocument} given a single {@literal field} and {@link AggregationExpression}. + * + * @param aggregationExpression must not be {@literal null}. + * @return + */ + public static ReplacementDocument forExpression(String field, AggregationExpression aggregationExpression) { + return new ReplacementDocument(new ExpressionFieldContributor(Fields.field(field), aggregationExpression)); + } + + /** + * Creates a {@link ReplacementDocument} given a single {@literal field} and {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static ReplacementDocument forSingleValue(String field, Object value) { + return new ReplacementDocument(new ValueFieldContributor(Fields.field(field), value)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.Replacement#toObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toObject(AggregationOperationContext context) { + + DBObject dbObject = new BasicDBObject(); + + for (ReplacementContributor replacement : replacements) { + dbObject.putAll(replacement.toDBObject(context)); + } + + return dbObject; + } + + /** + * Extend a replacement document that merges {@code this} and {@literal replacement} {@link ReplacementContributor}s + * in a new {@link ReplacementDocument}. + * + * @param extension must not be {@literal null}. + * @return the new, extended {@link ReplacementDocument} + */ + public ReplacementDocument extendWith(ReplacementDocument extension) { + + Assert.notNull(extension, "ReplacementDocument must not be null"); + + ReplacementDocument replacementDocument = new ReplacementDocument(); + + List replacements = new ArrayList( + this.replacements.size() + replacementDocument.replacements.size()); + + replacements.addAll(this.replacements); + replacements.addAll(extension.replacements); + + return new ReplacementDocument(replacements); + } + } + + /** + * Partial {@link DBObject} contributor for document replacement. + * + * @author Mark Paluch + */ + private abstract static class ReplacementContributor { + + /** + * Renders the current {@link ReplacementContributor} into a {@link DBObject} based on the given + * {@link AggregationOperationContext}. + * + * @param context will never be {@literal null}. + * @return + */ + public abstract DBObject toDBObject(AggregationOperationContext context); + } + + /** + * {@link ReplacementContributor} to contribute multiple fields based on the input {@literal value}. + *

+ * The value object is mapped into a MongoDB {@link DBObject}. + * + * @author Mark Paluch + */ + private static class DocumentContributor extends ReplacementContributor { + + private final Object value; + + /** + * Creates new {@link Projection} for the given {@link Field}. + * + * @param value must not be {@literal null}. + */ + public DocumentContributor(Object value) { + + Assert.notNull(value, "Value must not be null!"); + this.value = value; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplacementContributor#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + + DBObject dbObject = new BasicDBObject("$set", value); + + return (DBObject) context.getMappedObject(dbObject).get("$set"); + } + } + + /** + * Base class for {@link ReplacementContributor} implementations to contribute a single {@literal field} Typically + * used to construct a composite document that should contain the resulting key-value pair. + * + * @author Mark Paluch + */ + private abstract static class FieldContributorSupport extends ReplacementContributor { + + private final ExposedField field; + + /** + * Creates new {@link FieldContributorSupport} for the given {@link Field}. + * + * @param field must not be {@literal null}. + */ + public FieldContributorSupport(Field field) { + + Assert.notNull(field, "Field must not be null!"); + this.field = new ExposedField(field, true); + } + + /** + * @return the {@link ExposedField}. + */ + public ExposedField getField() { + return field; + } + } + + /** + * {@link ReplacementContributor} to contribute a single {@literal field} and {@literal value}. The {@literal value} + * is mapped to a MongoDB {@link DBObject} and can be a singular value, a list or subdocument. + * + * @author Mark Paluch + */ + private static class ValueFieldContributor extends FieldContributorSupport { + + private final Object value; + + /** + * Creates new {@link Projection} for the given {@link Field}. + * + * @param field must not be {@literal null}. + * @param value must not be {@literal null}. + */ + public ValueFieldContributor(Field field, Object value) { + + super(field); + + Assert.notNull(value, "Value must not be null!"); + + this.value = value; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplacementContributor#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + + DBObject dbObject = new BasicDBObject("$set", value); + return new BasicDBObject(getField().getTarget(), context.getMappedObject(dbObject).get("$set")); + } + } + + /** + * {@link ReplacementContributor} to contribute a single {@literal field} and value based on a + * {@link AggregationExpression}. + * + * @author Mark Paluch + */ + private static class ExpressionFieldContributor extends FieldContributorSupport { + + private final AggregationExpression aggregationExpression; + + /** + * Creates new {@link Projection} for the given {@link Field}. + * + * @param field must not be {@literal null}. + * @param aggregationExpression must not be {@literal null}. + */ + public ExpressionFieldContributor(Field field, AggregationExpression aggregationExpression) { + + super(field); + + Assert.notNull(aggregationExpression, "AggregationExpression must not be null!"); + + this.aggregationExpression = aggregationExpression; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplacementContributor#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + return new BasicDBObject(getField().getTarget(), aggregationExpression.toDbObject(context)); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index fffe2d304a..6cfc04b9f0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -1057,6 +1057,32 @@ public void shouldPerformStringProjectionOperatorsCorrectly() throws ParseExcept assertThat((Integer) dbo.get("millisecond"), is(789)); } + /** + * @see DATAMONGO-1550 + */ + @Test + public void shouldPerformReplaceRootOperatorCorrectly() throws ParseException { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Data data = new Data(); + DataItem dataItem = new DataItem(); + dataItem.primitiveIntValue = 42; + data.item = dataItem; + mongoTemplate.insert(data); + + TypedAggregation agg = newAggregation(Data.class, project("item"), // + replaceRoot("item"), // + project().and("primitiveIntValue").as("my_primitiveIntValue")); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + DBObject dbo = results.getUniqueMappedResult(); + + assertThat(dbo, is(notNullValue())); + assertThat((Integer) dbo.get("my_primitiveIntValue"), is(42)); + assertThat((Integer) dbo.keySet().size(), is(1)); + } + /** * @see DATAMONGO-788 */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index 684bda8326..275d35c3b4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -158,6 +158,21 @@ public void unwindOperationWithPreserveNullShouldBuildCorrectClause() { isBsonObject().notContaining("includeArrayIndex").containing("preserveNullAndEmptyArrays", true)); } + /** + * @see DATAMONGO-1550 + */ + @Test + public void replaceRootOperationShouldBuildCorrectClause() { + + DBObject agg = newAggregation( // + replaceRoot().withDocument().andValue("value").as("field")) // + .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + @SuppressWarnings("unchecked") + DBObject unwind = ((List) agg.get("pipeline")).get(0); + assertThat(unwind, isBsonObject().containing("$replaceRoot.newRoot", new BasicDBObject("field", "value"))); + } + /** * @see DATAMONGO-753 */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java new file mode 100644 index 0000000000..3753720400 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java @@ -0,0 +1,126 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.VariableOperators; +import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootDocumentOperation; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link ReplaceRootOperation}. + * + * @author Mark Paluch + */ +public class ReplaceRootOperationUnitTests { + + /** + * @see DATAMONGO-1550 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullField() { + new ReplaceRootOperation((Field) null); + } + + /** + * @see DATAMONGO-1550 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullExpression() { + new ReplaceRootOperation((AggregationExpression) null); + } + + /** + * @see DATAMONGO-1550 + */ + @Test + public void shouldRenderCorrectly() { + + ReplaceRootOperation operation = ReplaceRootDocumentOperation.builder() + .withDocument(new BasicDBObject("hello", "world")); + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $replaceRoot : { newRoot: { hello: \"world\" } } }"))); + } + + /** + * @see DATAMONGO-1550 + */ + @Test + public void shouldRenderExpressionCorrectly() { + + ReplaceRootOperation operation = new ReplaceRootOperation(VariableOperators // + .mapItemsOf("array") // + .as("element") // + .andApply(AggregationFunctionExpressions.MULTIPLY.of("$$element", 10))); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $replaceRoot : { newRoot : { " + + "$map : { input : \"$array\" , as : \"element\" , in : { $multiply : [ \"$$element\" , 10]} } " + "} } }"))); + } + + /** + * @see DATAMONGO-1550 + */ + @Test + public void shouldComposeDocument() { + + ReplaceRootOperation operation = ReplaceRootDocumentOperation.builder().withDocument() // + .andValue("value").as("key") // + .and(AggregationFunctionExpressions.MULTIPLY.of("$$element", 10)).as("multiply"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON + .parse("{ $replaceRoot : { newRoot: { key: \"value\", multiply: { $multiply : [ \"$$element\" , 10]} } } }"))); + } + + /** + * @see DATAMONGO-1550 + */ + @Test + public void shouldComposeSubDocument() { + + DBObject partialReplacement = new BasicDBObject("key", "override").append("key2", "value2"); + + ReplaceRootOperation operation = ReplaceRootDocumentOperation.builder().withDocument() // + .andValue("value").as("key") // + .andValuesOf(partialReplacement); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $replaceRoot : { newRoot: { key: \"override\", key2: \"value2\"} } } }"))); + } + + /** + * @see DATAMONGO-1550 + */ + @Test + public void shouldNotExposeFields() { + + ReplaceRootOperation operation = new ReplaceRootOperation(Fields.field("field")); + + assertThat(operation.getFields().exposesNoFields(), is(true)); + assertThat(operation.getFields().exposesSingleFieldOnly(), is(false)); + } +} diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index d6ff11f630..db8cf104dc 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1674,7 +1674,7 @@ At the time of this writing we provide support for the following Aggregation Ope [cols="2*"] |=== | Pipeline Aggregation Operators -| project, skip, limit, lookup, unwind, group, sort, geoNear +| count, geoNear, group, limit, lookup, match, project, replaceRoot, skip, sort, unwind | Set Aggregation Operators | setEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTrue From c1c7daf0ed5b78b824b50a3fcceb36e2195814a2 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 12 Dec 2016 11:09:46 +0100 Subject: [PATCH 036/118] DATAMONGO-1550 - Polishing $replaceRoot (aggregation stage). Original Pull Request: #422. --- .../aggregation/ReplaceRootOperation.java | 112 ++++++++++-------- .../ReplaceRootOperationUnitTests.java | 2 +- 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java index 9d0958fbce..95648b67b3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java @@ -28,13 +28,14 @@ import com.mongodb.DBObject; /** - * Encapsulates the aggregation framework {@code $replaceRoot}-operation. - *

+ * Encapsulates the aggregation framework {@code $replaceRoot}-operation.
* We recommend to use the static factory method {@link Aggregation#replaceRoot(String)} instead of creating instances * of this class directly. - * - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/#pipe._S_replaceRoot + * + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/ * @author Mark Paluch + * @author Christoph Strobl * @since 1.10 */ public class ReplaceRootOperation implements FieldsExposingAggregationOperation { @@ -42,32 +43,37 @@ public class ReplaceRootOperation implements FieldsExposingAggregationOperation private final Replacement replacement; /** - * Creates a new {@link ReplaceRootOperation} given the {@link as} field name. + * Creates a new {@link ReplaceRootOperation} given the {@link Field} field name. * * @param field must not be {@literal null} or empty. */ public ReplaceRootOperation(Field field) { - this.replacement = new FieldReplacement(field); + this(new FieldReplacement(field)); } /** - * Creates a new {@link ReplaceRootOperation} given the {@link as} field name. + * Creates a new {@link ReplaceRootOperation} given the {@link AggregationExpression} pointing to a document. * * @param aggregationExpression must not be {@literal null}. */ public ReplaceRootOperation(AggregationExpression aggregationExpression) { - - Assert.notNull(aggregationExpression, "AggregationExpression must not be null!"); - this.replacement = new AggregationExpressionReplacement(aggregationExpression); + this(new AggregationExpressionReplacement(aggregationExpression)); } - protected ReplaceRootOperation(Replacement replacement) { + /** + * Creates a new {@link ReplaceRootOperation} given the {@link Replacement}. + * + * @param replacement must not be {@literal null}. + */ + public ReplaceRootOperation(Replacement replacement) { + + Assert.notNull(replacement, "Replacement must not be null!"); this.replacement = replacement; } /** * Creates a new {@link ReplaceRootDocumentOperationBuilder}. - * + * * @return a new {@link ReplaceRootDocumentOperationBuilder}. */ public static ReplaceRootOperationBuilder builder() { @@ -79,7 +85,7 @@ public static ReplaceRootOperationBuilder builder() { */ @Override public DBObject toDBObject(AggregationOperationContext context) { - return new BasicDBObject("$replaceRoot", new BasicDBObject("newRoot", replacement.toObject(context))); + return new BasicDBObject("$replaceRoot", new BasicDBObject("newRoot", replacement.toDocumentExpression(context))); } /* (non-Javadoc) @@ -99,7 +105,7 @@ public static class ReplaceRootOperationBuilder { /** * Defines a root document replacement based on a {@literal fieldName} that resolves to a document. - * + * * @param fieldName must not be {@literal null} or empty. * @return the final {@link ReplaceRootOperation}. */ @@ -118,8 +124,7 @@ public ReplaceRootOperation withValueOf(AggregationExpression aggregationExpress } /** - * Defines a root document replacement based on a composable document that is empty initially. - *

+ * Defines a root document replacement based on a composable document that is empty initially.
* {@link ReplaceRootOperation} can be populated with individual entries and derive its values from other, existing * documents. * @@ -130,8 +135,7 @@ public ReplaceRootDocumentOperation withDocument() { } /** - * Defines a root document replacement based on a composable document given {@literal dbObject} - *

+ * Defines a root document replacement based on a composable document given {@literal dbObject}.
* {@link ReplaceRootOperation} can be populated with individual entries and derive its values from other, existing * documents. * @@ -148,8 +152,7 @@ public ReplaceRootOperation withDocument(DBObject dbObject) { /** * Encapsulates the aggregation framework {@code $replaceRoot}-operation to result in a composable replacement - * document. - *

+ * document.
* Instances of {@link ReplaceRootDocumentOperation} yield empty upon construction and can be populated with single * values and documents. * @@ -175,7 +178,7 @@ private ReplaceRootDocumentOperation(ReplacementDocument replacementDocument) { /** * Creates an extended {@link ReplaceRootDocumentOperation} that combines {@link ReplacementDocument}s from the * {@literal currentOperation} and {@literal extension} operation. - * + * * @param currentOperation must not be {@literal null}. * @param extension must not be {@literal null}. */ @@ -185,9 +188,9 @@ protected ReplaceRootDocumentOperation(ReplaceRootDocumentOperation currentOpera } /** - * Creates a new {@link ReplaceRootDocumentOperationBuilder} to define a field for the {@link AggregationExpression} - * . - * + * Creates a new {@link ReplaceRootDocumentOperationBuilder} to define a field for the + * {@link AggregationExpression}. + * * @param aggregationExpression must not be {@literal null}. * @return the {@link ReplaceRootDocumentOperationBuilder}. */ @@ -251,17 +254,18 @@ public ReplaceRootDocumentOperation as(String fieldName) { * Replacement object that results in a replacement document or an expression that results in a document. * * @author Mark Paluch + * @author Christoph Strobl */ - private abstract static class Replacement { + public interface Replacement { /** - * Renders the current {@link Replacement} into a {@link DBObject} based on the given + * Renders the current {@link Replacement} into a its MongoDB representation based on the given * {@link AggregationOperationContext}. * * @param context will never be {@literal null}. * @return a replacement document or an expression that results in a document. */ - public abstract Object toObject(AggregationOperationContext context); + Object toDocumentExpression(AggregationOperationContext context); } /** @@ -269,11 +273,13 @@ private abstract static class Replacement { * * @author Mark Paluch */ - private static class AggregationExpressionReplacement extends Replacement { + private static class AggregationExpressionReplacement implements Replacement { private final AggregationExpression aggregationExpression; protected AggregationExpressionReplacement(AggregationExpression aggregationExpression) { + + Assert.notNull(aggregationExpression, "AggregationExpression must not be null!"); this.aggregationExpression = aggregationExpression; } @@ -281,17 +287,17 @@ protected AggregationExpressionReplacement(AggregationExpression aggregationExpr * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.Replacement#toObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override - public DBObject toObject(AggregationOperationContext context) { + public DBObject toDocumentExpression(AggregationOperationContext context) { return aggregationExpression.toDbObject(context); } } /** * {@link Replacement that references a {@link Field} inside the current aggregation pipeline. - * + * * @author Mark Paluch */ - private static class FieldReplacement extends Replacement { + private static class FieldReplacement implements Replacement { private final Field field; @@ -308,7 +314,7 @@ protected FieldReplacement(Field field) { * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.Replacement#toObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override - public Object toObject(AggregationOperationContext context) { + public Object toDocumentExpression(AggregationOperationContext context) { return context.getReference(field).toString(); } } @@ -318,7 +324,7 @@ public Object toObject(AggregationOperationContext context) { * * @author Mark Paluch */ - private static class ReplacementDocument extends Replacement { + private static class ReplacementDocument implements Replacement { private final Collection replacements; @@ -378,12 +384,12 @@ public static ReplacementDocument forSingleValue(String field, Object value) { * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.Replacement#toObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override - public DBObject toObject(AggregationOperationContext context) { + public DBObject toDocumentExpression(AggregationOperationContext context) { DBObject dbObject = new BasicDBObject(); for (ReplacementContributor replacement : replacements) { - dbObject.putAll(replacement.toDBObject(context)); + dbObject.putAll(replacement.toDbObject(context)); } return dbObject; @@ -403,7 +409,7 @@ public ReplacementDocument extendWith(ReplacementDocument extension) { ReplacementDocument replacementDocument = new ReplacementDocument(); List replacements = new ArrayList( - this.replacements.size() + replacementDocument.replacements.size()); + this.replacements.size() + extension.replacements.size()); replacements.addAll(this.replacements); replacements.addAll(extension.replacements); @@ -414,10 +420,10 @@ public ReplacementDocument extendWith(ReplacementDocument extension) { /** * Partial {@link DBObject} contributor for document replacement. - * + * * @author Mark Paluch */ - private abstract static class ReplacementContributor { + private interface ReplacementContributor extends AggregationExpression { /** * Renders the current {@link ReplacementContributor} into a {@link DBObject} based on the given @@ -426,17 +432,17 @@ private abstract static class ReplacementContributor { * @param context will never be {@literal null}. * @return */ - public abstract DBObject toDBObject(AggregationOperationContext context); + DBObject toDbObject(AggregationOperationContext context); } /** - * {@link ReplacementContributor} to contribute multiple fields based on the input {@literal value}. - *

+ * {@link ReplacementContributor} to contribute multiple fields based on the input {@literal value}.
* The value object is mapped into a MongoDB {@link DBObject}. - * + * * @author Mark Paluch + * @author Christoph Strobl */ - private static class DocumentContributor extends ReplacementContributor { + private static class DocumentContributor implements ReplacementContributor { private final Object value; @@ -455,11 +461,13 @@ public DocumentContributor(Object value) { * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplacementContributor#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override - public DBObject toDBObject(AggregationOperationContext context) { + public DBObject toDbObject(AggregationOperationContext context) { - DBObject dbObject = new BasicDBObject("$set", value); + if (value instanceof DBObject) { + return (DBObject) value; + } - return (DBObject) context.getMappedObject(dbObject).get("$set"); + return (DBObject) context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); } } @@ -469,7 +477,7 @@ public DBObject toDBObject(AggregationOperationContext context) { * * @author Mark Paluch */ - private abstract static class FieldContributorSupport extends ReplacementContributor { + private abstract static class FieldContributorSupport implements ReplacementContributor { private final ExposedField field; @@ -521,10 +529,12 @@ public ValueFieldContributor(Field field, Object value) { * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplacementContributor#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override - public DBObject toDBObject(AggregationOperationContext context) { + public DBObject toDbObject(AggregationOperationContext context) { + + Object mappedValue = value instanceof BasicDBObject ? value + : context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); - DBObject dbObject = new BasicDBObject("$set", value); - return new BasicDBObject(getField().getTarget(), context.getMappedObject(dbObject).get("$set")); + return new BasicDBObject(getField().getTarget(), mappedValue); } } @@ -557,7 +567,7 @@ public ExpressionFieldContributor(Field field, AggregationExpression aggregation * @see org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplacementContributor#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override - public DBObject toDBObject(AggregationOperationContext context) { + public DBObject toDbObject(AggregationOperationContext context) { return new BasicDBObject(getField().getTarget(), aggregationExpression.toDbObject(context)); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java index 3753720400..5a68303364 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java @@ -28,7 +28,7 @@ /** * Unit tests for {@link ReplaceRootOperation}. - * + * * @author Mark Paluch */ public class ReplaceRootOperationUnitTests { From c6dae3c4446d0853107e54cb2d0ca7b96d473cb4 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 8 Dec 2016 10:20:07 +0100 Subject: [PATCH 037/118] DATAMONGO-1551 - Add $graphLookup aggregation stage. We now support the $graphLookup aggregation pipeline stage via Aggregation to perform recursive lookup adding the lookup result as array to documents entering $graphLookup. TypedAggregation agg = Aggregation.newAggregation(Employee.class, graphLookup("employee") .startWith("reportsTo") .connectFrom("reportsTo") .connectTo("name") .depthField("depth") .maxDepth(5) .as("reportingHierarchy")); Original Pull Request: #424 --- .../mongodb/core/aggregation/Aggregation.java | 12 + .../aggregation/GraphLookupOperation.java | 365 ++++++++++++++++++ .../core/aggregation/AggregationTests.java | 54 ++- .../GraphLookupOperationUnitTests.java | 122 ++++++ 4 files changed, 550 insertions(+), 3 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java 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 cd85574bfb..f34c227153 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 @@ -28,6 +28,7 @@ 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.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; +import org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder; import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootDocumentOperationBuilder; import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootOperationBuilder; import org.springframework.data.mongodb.core.aggregation.Fields.*; @@ -318,6 +319,17 @@ public static GroupOperation group(Fields fields) { return new GroupOperation(fields); } + /** + * Creates a new {@link GraphLookupOperation.FromBuilder} to construct a {@link GraphLookupOperation} given + * {@literal fromCollection}. + * + * @param fromCollection must not be {@literal null} or empty. + * @return + */ + public static StartWithBuilder graphLookup(String fromCollection) { + return GraphLookupOperation.builder().from(fromCollection); + } + /** * Factory method to create a new {@link SortOperation} for the given {@link Sort}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java new file mode 100644 index 0000000000..1f7d24b487 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java @@ -0,0 +1,365 @@ +/* + * 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.Arrays; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Encapsulates the aggregation framework {@code $graphLookup}-operation. + *

+ * Performs a recursive search on a collection, with options for restricting the search by recursion depth and query + * filter. + *

+ * We recommend to use the static factory method {@link Aggregation#graphLookup(String)} instead of creating instances + * of this class directly. + * + * @see http://docs.mongodb.org/manual/reference/aggregation/graphLookup/ + * @author Mark Paluch + * @since 1.10 + */ +public class GraphLookupOperation implements InheritsFieldsAggregationOperation { + + private final String from; + private final List startWith; + private final Field connectFrom; + private final Field connectTo; + private final Field as; + private final Long maxDepth; + private final Field depthField; + private final CriteriaDefinition restrictSearchWithMatch; + + private GraphLookupOperation(String from, List startWith, Field connectFrom, Field connectTo, Field as, + Long maxDepth, Field depthField, CriteriaDefinition restrictSearchWithMatch) { + + this.from = from; + this.startWith = startWith; + this.connectFrom = connectFrom; + this.connectTo = connectTo; + this.as = as; + this.maxDepth = maxDepth; + this.depthField = depthField; + this.restrictSearchWithMatch = restrictSearchWithMatch; + } + + /** + * Creates a new {@link FromBuilder} to build {@link GraphLookupOperation}. + * + * @return a new {@link FromBuilder}. + */ + public static FromBuilder builder() { + return new GraphLookupOperationFromBuilder(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + + DBObject graphLookup = new BasicDBObject(); + + graphLookup.put("from", from); + + BasicDBList list = new BasicDBList(); + + for (Object startWithElement : startWith) { + + if (startWithElement instanceof AggregationExpression) { + list.add(((AggregationExpression) startWithElement).toDbObject(context)); + } + + if (startWithElement instanceof Field) { + list.add(context.getReference((Field) startWithElement).toString()); + } + } + + if (list.size() == 1) { + graphLookup.put("startWith", list.get(0)); + } else { + graphLookup.put("startWith", list); + } + + graphLookup.put("connectFromField", connectFrom.getName()); + graphLookup.put("connectToField", connectTo.getName()); + graphLookup.put("as", as.getName()); + + if (maxDepth != null) { + graphLookup.put("maxDepth", maxDepth); + } + + if (depthField != null) { + graphLookup.put("depthField", depthField.getName()); + } + + if (restrictSearchWithMatch != null) { + graphLookup.put("restrictSearchWithMatch", context.getMappedObject(restrictSearchWithMatch.getCriteriaObject())); + } + + return new BasicDBObject("$graphLookup", graphLookup); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return ExposedFields.from(new ExposedField(as, true)); + } + + /** + * @author Mark Paluch + */ + public interface FromBuilder { + + /** + * Set the {@literal collectionName} to apply the {@code $graphLookup} to. + * + * @param collectionName must not be {@literal null} or empty. + * @return + */ + StartWithBuilder from(String collectionName); + } + + /** + * @author Mark Paluch + */ + public interface StartWithBuilder { + + /** + * Set the startWith {@literal fieldReferences} to apply the {@code $graphLookup} to. + * + * @param fieldReferences must not be {@literal null}. + * @return + */ + ConnectFromBuilder startWith(String... fieldReferences); + + /** + * Set the startWith {@literal expressions} to apply the {@code $graphLookup} to. + * + * @param expressions must not be {@literal null}. + * @return + */ + ConnectFromBuilder startWith(AggregationExpression... expressions); + } + + /** + * @author Mark Paluch + */ + public interface ConnectFromBuilder { + + /** + * Set the connectFrom {@literal fieldName} to apply the {@code $graphLookup} to. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + ConnectToBuilder connectFrom(String fieldName); + } + + /** + * @author Mark Paluch + */ + public interface ConnectToBuilder { + + /** + * Set the connectTo {@literal fieldName} to apply the {@code $graphLookup} to. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + GraphLookupOperationBuilder connectTo(String fieldName); + } + + /** + * Builder to build the initial {@link GraphLookupOperationBuilder} that configures the initial mandatory set of + * {@link GraphLookupOperation} properties. + * + * @author Mark Paluch + */ + static final class GraphLookupOperationFromBuilder + implements FromBuilder, StartWithBuilder, ConnectFromBuilder, ConnectToBuilder { + + private String from; + private List startWith; + private String connectFrom; + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.FromBuilder#from(java.lang.String) + */ + @Override + public StartWithBuilder from(String collectionName) { + + Assert.hasText(collectionName, "CollectionName must not be null or empty!"); + + this.from = collectionName; + + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder#startWith(java.lang.String[]) + */ + @Override + public ConnectFromBuilder startWith(String... fieldReferences) { + + Assert.notNull(fieldReferences, "FieldReferences must not be null!"); + Assert.noNullElements(fieldReferences, "FieldReferences must not contain null elements!"); + + List fields = new ArrayList(fieldReferences.length); + + for (String fieldReference : fieldReferences) { + fields.add(Fields.field(fieldReference)); + } + + this.startWith = fields; + + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder#startWith(org.springframework.data.mongodb.core.aggregation.AggregationExpression[]) + */ + @Override + public ConnectFromBuilder startWith(AggregationExpression... expressions) { + + Assert.notNull(expressions, "AggregationExpressions must not be null!"); + Assert.noNullElements(expressions, "AggregationExpressions must not contain null elements!"); + + this.startWith = Arrays.asList(expressions); + + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.ConnectFromBuilder#connectFrom(java.lang.String) + */ + @Override + public ConnectToBuilder connectFrom(String fieldName) { + + Assert.hasText(fieldName, "ConnectFrom must not be null or empty!"); + + this.connectFrom = fieldName; + + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.ConnectToBuilder#connectTo(java.lang.String) + */ + @Override + public GraphLookupOperationBuilder connectTo(String fieldName) { + + Assert.hasText(fieldName, "ConnectTo must not be null or empty!"); + + return new GraphLookupOperationBuilder(from, startWith, connectFrom, fieldName); + } + } + + /** + * @author Mark Paluch + */ + static final class GraphLookupOperationBuilder { + + private final String from; + private final List startWith; + private final Field connectFrom; + private final Field connectTo; + private Long maxDepth; + private Field depthField; + private CriteriaDefinition restrictSearchWithMatch; + + protected GraphLookupOperationBuilder(String from, List startWith, String connectFrom, + String connectTo) { + + this.from = from; + this.startWith = new ArrayList(startWith); + this.connectFrom = Fields.field(connectFrom); + this.connectTo = Fields.field(connectTo); + } + + /** + * Limit the number of recursions. + * + * @param numberOfRecursions must be greater or equal to zero. + * @return + */ + public GraphLookupOperationBuilder maxDepth(long numberOfRecursions) { + + Assert.isTrue(numberOfRecursions >= 0, "Max depth must be >= 0!"); + + this.maxDepth = numberOfRecursions; + + return this; + } + + /** + * Add a depth field {@literal fieldName} to each traversed document in the search path. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + public GraphLookupOperationBuilder depthField(String fieldName) { + + Assert.hasText(fieldName, "Depth field name must not be null or empty!"); + + this.depthField = Fields.field(fieldName); + + return this; + } + + /** + * Add a query specifying conditions to the recursive search. + * + * @param criteriaDefinition must not be {@literal null}. + * @return + */ + public GraphLookupOperationBuilder restrict(CriteriaDefinition criteriaDefinition) { + + Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!"); + + this.restrictSearchWithMatch = criteriaDefinition; + + return this; + } + + /** + * Set the name of the array field added to each output document and return the final {@link GraphLookupOperation}. + * Contains the documents traversed in the {@literal $graphLookup} stage to reach the document. + * + * @param fieldName must not be {@literal null} or empty. + * @return the final {@link GraphLookupOperation}. + */ + public GraphLookupOperation as(String fieldName) { + + Assert.hasText(fieldName, "As field name must not be null or empty!"); + + return new GraphLookupOperation(from, startWith, connectFrom, connectTo, Fields.field(fieldName), maxDepth, + depthField, restrictSearchWithMatch); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 6cfc04b9f0..2d0bb77508 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -72,6 +72,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.CommandResult; @@ -146,6 +147,7 @@ private void cleanDb() { mongoTemplate.dropCollection(InventoryItem.class); mongoTemplate.dropCollection(Sales.class); mongoTemplate.dropCollection(Sales2.class); + mongoTemplate.dropCollection(Employee.class); } /** @@ -1631,6 +1633,40 @@ public void letShouldBeAppliedCorrectly() { new BasicDBObjectBuilder().add("_id", "2").add("finalTotal", 10.25D).get())); } + /** + * @see DATAMONGO-1551 + */ + @Test + public void graphLookupShouldBeAppliedCorrectly() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Employee em1 = Employee.builder().id(1).name("Dev").build(); + Employee em2 = Employee.builder().id(2).name("Eliot").reportsTo("Dev").build(); + Employee em4 = Employee.builder().id(4).name("Andrew").reportsTo("Eliot").build(); + + mongoTemplate.insert(Arrays.asList(em1, em2, em4), Employee.class); + + TypedAggregation agg = Aggregation.newAggregation(Employee.class, + match(Criteria.where("name").is("Andrew")), // + Aggregation.graphLookup("employee") // + .startWith("reportsTo") // + .connectFrom("reportsTo") // + .connectTo("name") // + .depthField("depth") // + .maxDepth(5) // + .as("reportingHierarchy")); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + + DBObject object = result.getUniqueMappedResult(); + BasicDBList list = (BasicDBList) object.get("reportingHierarchy"); + + assertThat(object, isBsonObject().containing("reportingHierarchy", List.class)); + assertThat((DBObject) list.get(0), isBsonObject().containing("name", "Dev").containing("depth", 1L)); + assertThat((DBObject) list.get(1), isBsonObject().containing("name", "Eliot").containing("depth", 0L)); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); @@ -1873,7 +1909,7 @@ public InventoryItem(int id, String item, String description, int qty) { } /** - * @DATAMONGO-1491 + * @see DATAMONGO-1491 */ @lombok.Data @Builder @@ -1884,7 +1920,7 @@ static class Sales { } /** - * @DATAMONGO-1491 + * @see DATAMONGO-1491 */ @lombok.Data @Builder @@ -1897,7 +1933,7 @@ static class Item { } /** - * @DATAMONGO-1538 + * @see DATAMONGO-1538 */ @lombok.Data @Builder @@ -1908,4 +1944,16 @@ static class Sales2 { Float tax; boolean applyDiscount; } + + /** + * @see DATAMONGO-1551 + */ + @lombok.Data + @Builder + static class Employee { + + int id; + String name; + String reportsTo; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java new file mode 100644 index 0000000000..091e20106b --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import static org.hamcrest.core.Is.*; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.test.util.IsBsonObject.*; + +import org.junit.Test; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Literal; +import org.springframework.data.mongodb.core.query.Criteria; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link GraphLookupOperation}. + * + * @author Mark Paluch + */ +public class GraphLookupOperationUnitTests { + + /** + * @see DATAMONGO-1551 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullFromCollection() { + GraphLookupOperation.builder().from(null); + } + + /** + * @see DATAMONGO-1551 + */ + @Test + public void shouldRenderCorrectly() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith("reportsTo") // + .connectFrom("reportsTo") // + .connectTo("name") // + .depthField("depth") // + .maxDepth(42) // + .as("reportingHierarchy"); + + DBObject dbObject = graphLookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(dbObject, + isBsonObject().containing("$graphLookup.depthField", "depth").containing("$graphLookup.maxDepth", 42L)); + } + + /** + * @see DATAMONGO-1551 + */ + @Test + public void shouldRenderCriteriaCorrectly() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith("reportsTo") // + .connectFrom("reportsTo") // + .connectTo("name") // + .restrict(Criteria.where("key").is("value")) // + .as("reportingHierarchy"); + + DBObject dbObject = graphLookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(dbObject, + isBsonObject().containing("$graphLookup.restrictSearchWithMatch", new BasicDBObject("key", "value"))); + } + + /** + * @see DATAMONGO-1551 + */ + @Test + public void shouldRenderArrayOfStartsWithCorrectly() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith("reportsTo", "boss") // + .connectFrom("reportsTo") // + .connectTo("name") // + .as("reportingHierarchy"); + + DBObject dbObject = graphLookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $graphLookup : { from: \"employees\", startWith: [\"$reportsTo\", \"$boss\"], " + + "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"))); + } + + /** + * @see DATAMONGO-1551 + */ + @Test + public void shouldRenderStartWithAggregationExpressions() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith(Literal.asLiteral("hello")) // + .connectFrom("reportsTo") // + .connectTo("name") // + .as("reportingHierarchy"); + + DBObject dbObject = graphLookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $graphLookup : { from: \"employees\", startWith: { $literal: \"hello\"}, " + + "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"))); + } +} From 204a0515c4968a0712597163cd8d4a7ea63183d4 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 13 Dec 2016 14:21:33 +0100 Subject: [PATCH 038/118] DATAMONGO-1551 - Polishing. Add startWith overload allowing to mix expressions, removed white spaces, updated doc. Original Pull Request: #424 --- .../mongodb/core/aggregation/Aggregation.java | 5 +- .../aggregation/GraphLookupOperation.java | 110 +++++++++++++----- .../GraphLookupOperationUnitTests.java | 38 +++++- src/main/asciidoc/reference/mongodb.adoc | 2 +- 4 files changed, 119 insertions(+), 36 deletions(-) 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 f34c227153..b1ee37b8ad 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 @@ -320,11 +320,12 @@ public static GroupOperation group(Fields fields) { } /** - * Creates a new {@link GraphLookupOperation.FromBuilder} to construct a {@link GraphLookupOperation} given - * {@literal fromCollection}. + * Creates a new {@link GraphLookupOperation.GraphLookupOperationFromBuilder} to construct a + * {@link GraphLookupOperation} given {@literal fromCollection}. * * @param fromCollection must not be {@literal null} or empty. * @return + * @since 1.10 */ public static StartWithBuilder graphLookup(String fromCollection) { return GraphLookupOperation.builder().from(fromCollection); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java index 1f7d24b487..2291fed33d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java @@ -17,32 +17,37 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; -import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** - * Encapsulates the aggregation framework {@code $graphLookup}-operation. - *

+ * Encapsulates the aggregation framework {@code $graphLookup}-operation.
* Performs a recursive search on a collection, with options for restricting the search by recursion depth and query - * filter. - *

+ * filter.
* We recommend to use the static factory method {@link Aggregation#graphLookup(String)} instead of creating instances * of this class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/graphLookup/ + * @see http://docs.mongodb.org/manual/reference/aggregation/graphLookup/ * @author Mark Paluch + * @author Christoph Strobl * @since 1.10 */ public class GraphLookupOperation implements InheritsFieldsAggregationOperation { + private static final Set> ALLOWED_START_TYPES = new HashSet>( + Arrays.> asList(AggregationExpression.class, String.class, Field.class, DBObject.class)); + private final String from; private final List startWith; private final Field connectFrom; @@ -67,7 +72,7 @@ private GraphLookupOperation(String from, List startWith, Field connectF /** * Creates a new {@link FromBuilder} to build {@link GraphLookupOperation}. - * + * * @return a new {@link FromBuilder}. */ public static FromBuilder builder() { @@ -84,24 +89,20 @@ public DBObject toDBObject(AggregationOperationContext context) { graphLookup.put("from", from); - BasicDBList list = new BasicDBList(); + List mappedStartWith = new ArrayList(startWith.size()); for (Object startWithElement : startWith) { if (startWithElement instanceof AggregationExpression) { - list.add(((AggregationExpression) startWithElement).toDbObject(context)); - } - - if (startWithElement instanceof Field) { - list.add(context.getReference((Field) startWithElement).toString()); + mappedStartWith.add(((AggregationExpression) startWithElement).toDbObject(context)); + } else if (startWithElement instanceof Field) { + mappedStartWith.add(context.getReference((Field) startWithElement).toString()); + } else { + mappedStartWith.add(startWithElement); } } - if (list.size() == 1) { - graphLookup.put("startWith", list.get(0)); - } else { - graphLookup.put("startWith", list); - } + graphLookup.put("startWith", mappedStartWith.size() == 1 ? mappedStartWith.iterator().next() : mappedStartWith); graphLookup.put("connectFromField", connectFrom.getName()); graphLookup.put("connectToField", connectTo.getName()); @@ -147,6 +148,7 @@ public interface FromBuilder { /** * @author Mark Paluch + * @author Christoph Strobl */ public interface StartWithBuilder { @@ -165,6 +167,16 @@ public interface StartWithBuilder { * @return */ ConnectFromBuilder startWith(AggregationExpression... expressions); + + /** + * Set the startWith as either {@literal fieldReferences}, {@link Fields}, {@link DBObject} or + * {@link AggregationExpression} to apply the {@code $graphLookup} to. + * + * @param expressions must not be {@literal null}. + * @return + * @throws IllegalArgumentException + */ + ConnectFromBuilder startWith(Object... expressions); } /** @@ -198,7 +210,7 @@ public interface ConnectToBuilder { /** * Builder to build the initial {@link GraphLookupOperationBuilder} that configures the initial mandatory set of * {@link GraphLookupOperation} properties. - * + * * @author Mark Paluch */ static final class GraphLookupOperationFromBuilder @@ -217,7 +229,6 @@ public StartWithBuilder from(String collectionName) { Assert.hasText(collectionName, "CollectionName must not be null or empty!"); this.from = collectionName; - return this; } @@ -237,7 +248,6 @@ public ConnectFromBuilder startWith(String... fieldReferences) { } this.startWith = fields; - return this; } @@ -251,10 +261,50 @@ public ConnectFromBuilder startWith(AggregationExpression... expressions) { Assert.noNullElements(expressions, "AggregationExpressions must not contain null elements!"); this.startWith = Arrays.asList(expressions); + return this; + } + + @Override + public ConnectFromBuilder startWith(Object... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + Assert.noNullElements(expressions, "Expressions must not contain null elements!"); + this.startWith = verifyAndPotentiallyTransformStartsWithTypes(expressions); return this; } + private List verifyAndPotentiallyTransformStartsWithTypes(Object... expressions) { + + List expressionsToUse = new ArrayList(expressions.length); + + for (Object expression : expressions) { + + assertStartWithType(expression); + + if (expression instanceof String) { + expressionsToUse.add(Fields.field((String) expression)); + } else { + expressionsToUse.add(expression); + } + + } + return expressionsToUse; + } + + private void assertStartWithType(Object expression) { + + for (Class type : ALLOWED_START_TYPES) { + + if (ClassUtils.isAssignable(type, expression.getClass())) { + return; + } + } + + throw new IllegalArgumentException( + String.format("Expression must be any of %s but was %s", ALLOWED_START_TYPES, expression.getClass())); + } + /* (non-Javadoc) * @see org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.ConnectFromBuilder#connectFrom(java.lang.String) */ @@ -264,7 +314,6 @@ public ConnectToBuilder connectFrom(String fieldName) { Assert.hasText(fieldName, "ConnectFrom must not be null or empty!"); this.connectFrom = fieldName; - return this; } @@ -303,8 +352,8 @@ protected GraphLookupOperationBuilder(String from, List startW } /** - * Limit the number of recursions. - * + * Optionally limit the number of recursions. + * * @param numberOfRecursions must be greater or equal to zero. * @return */ @@ -313,13 +362,12 @@ public GraphLookupOperationBuilder maxDepth(long numberOfRecursions) { Assert.isTrue(numberOfRecursions >= 0, "Max depth must be >= 0!"); this.maxDepth = numberOfRecursions; - return this; } /** - * Add a depth field {@literal fieldName} to each traversed document in the search path. - * + * Optionally add a depth field {@literal fieldName} to each traversed document in the search path. + * * @param fieldName must not be {@literal null} or empty. * @return */ @@ -328,13 +376,12 @@ public GraphLookupOperationBuilder depthField(String fieldName) { Assert.hasText(fieldName, "Depth field name must not be null or empty!"); this.depthField = Fields.field(fieldName); - return this; } /** - * Add a query specifying conditions to the recursive search. - * + * Optionally add a query specifying conditions to the recursive search. + * * @param criteriaDefinition must not be {@literal null}. * @return */ @@ -343,14 +390,13 @@ public GraphLookupOperationBuilder restrict(CriteriaDefinition criteriaDefinitio Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!"); this.restrictSearchWithMatch = criteriaDefinition; - return this; } /** * Set the name of the array field added to each output document and return the final {@link GraphLookupOperation}. * Contains the documents traversed in the {@literal $graphLookup} stage to reach the document. - * + * * @param fieldName must not be {@literal null} or empty. * @return the final {@link GraphLookupOperation}. */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java index 091e20106b..f91d141225 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java @@ -20,6 +20,7 @@ import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import org.junit.Test; +import org.springframework.data.mongodb.core.Person; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Literal; import org.springframework.data.mongodb.core.query.Criteria; @@ -29,8 +30,9 @@ /** * Unit tests for {@link GraphLookupOperation}. - * + * * @author Mark Paluch + * @author Christoph Strobl */ public class GraphLookupOperationUnitTests { @@ -101,6 +103,40 @@ public void shouldRenderArrayOfStartsWithCorrectly() { + "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"))); } + /** + * @see DATAMONGO-1551 + */ + @Test + public void shouldRenderMixedArrayOfStartsWithCorrectly() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith("reportsTo", Literal.asLiteral("$boss")) // + .connectFrom("reportsTo") // + .connectTo("name") // + .as("reportingHierarchy"); + + DBObject dbObject = graphLookupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $graphLookup : { from: \"employees\", startWith: [\"$reportsTo\", { $literal: \"$boss\"}], " + + "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"))); + } + + /** + * @see DATAMONGO-1551 + */ + @Test(expected = IllegalArgumentException.class) + public void shouldRejectUnknownTypeInMixedArrayOfStartsWithCorrectly() { + + GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // + .from("employees") // + .startWith("reportsTo", new Person()) // + .connectFrom("reportsTo") // + .connectTo("name") // + .as("reportingHierarchy"); + } + /** * @see DATAMONGO-1551 */ diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index db8cf104dc..4994ca5984 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1674,7 +1674,7 @@ At the time of this writing we provide support for the following Aggregation Ope [cols="2*"] |=== | Pipeline Aggregation Operators -| count, geoNear, group, limit, lookup, match, project, replaceRoot, skip, sort, unwind +| count, geoNear, graphLookup, group, limit, lookup, match, project, replaceRoot, skip, sort, unwind | Set Aggregation Operators | setEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTrue From 9737464f9a82ebfb67488af51de7770d07f23fdb Mon Sep 17 00:00:00 2001 From: Ricardo Espirito Santo Date: Thu, 1 Dec 2016 16:53:02 +0000 Subject: [PATCH 039/118] DATAMONGO-442 - Support username/password authentication with MongoLog4jAppender. Added optional username and password for authentication support on Log4jAppender. Original pull request: #419. --- .../mongodb/log4j/MongoLog4jAppender.java | 62 ++++++++++++------- .../MongoLog4jAppenderIntegrationTests.java | 54 ++++++++-------- 2 files changed, 69 insertions(+), 47 deletions(-) diff --git a/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java b/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java index 165061e6bd..9b25550194 100644 --- a/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java +++ b/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java @@ -15,30 +15,23 @@ */ package org.springframework.data.mongodb.log4j; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Map; - +import com.mongodb.*; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.MDC; import org.apache.log4j.PatternLayout; import org.apache.log4j.spi.LoggingEvent; -import com.mongodb.BasicDBList; -import com.mongodb.BasicDBObject; -import com.mongodb.DB; -import com.mongodb.Mongo; -import com.mongodb.MongoClient; -import com.mongodb.WriteConcern; +import java.net.UnknownHostException; +import java.util.*; /** * Log4j appender writing log entries into a MongoDB instance. - * + * * @author Jon Brisbin * @author Oliver Gierke - * @auhtor Christoph Strobl + * @author Christoph Strobl + * @author Ricardo Espirito Santo */ public class MongoLog4jAppender extends AppenderSkeleton { @@ -56,6 +49,8 @@ public class MongoLog4jAppender extends AppenderSkeleton { protected String host = "localhost"; protected int port = 27017; + protected String username; + protected String password; protected String database = "logs"; protected String collectionPattern = "%c"; protected PatternLayout collectionLayout = new PatternLayout(collectionPattern); @@ -88,6 +83,22 @@ public void setPort(int port) { this.port = port; } + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + public String getDatabase() { return database; } @@ -113,14 +124,14 @@ public void setApplicationId(String applicationId) { this.applicationId = applicationId; } - public void setWarnOrHigherWriteConcern(String wc) { - this.warnOrHigherWriteConcern = WriteConcern.valueOf(wc); - } - public String getWarnOrHigherWriteConcern() { return warnOrHigherWriteConcern.toString(); } + public void setWarnOrHigherWriteConcern(String wc) { + this.warnOrHigherWriteConcern = WriteConcern.valueOf(wc); + } + public String getInfoOrLowerWriteConcern() { return infoOrLowerWriteConcern.toString(); } @@ -130,17 +141,26 @@ public void setInfoOrLowerWriteConcern(String wc) { } protected void connectToMongo() throws UnknownHostException { - this.mongo = new MongoClient(host, port); + ServerAddress serverAddress = new ServerAddress(host, port); + connectToMongoHandlingCredentials(serverAddress); this.db = mongo.getDB(database); } + private void connectToMongoHandlingCredentials(ServerAddress serverAddress) { + if (null == password || null == username) { + this.mongo = new MongoClient(serverAddress); + } else { + MongoCredential mongoCredential = MongoCredential.createCredential(username, database, password.toCharArray()); + List credentials = Collections.singletonList(mongoCredential); + this.mongo = new MongoClient(serverAddress, credentials); + } + } + /* * (non-Javadoc) * @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent) */ - @Override - @SuppressWarnings({ "unchecked" }) - protected void append(final LoggingEvent event) { + @Override @SuppressWarnings({ "unchecked" }) protected void append(final LoggingEvent event) { if (null == db) { try { connectToMongo(); diff --git a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java index 6862b054dd..caf395c662 100644 --- a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java +++ b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java @@ -15,56 +15,50 @@ */ package org.springframework.data.mongodb.log4j; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.util.Calendar; - +import com.mongodb.*; import org.apache.log4j.Logger; import org.apache.log4j.MDC; import org.junit.After; import org.junit.Before; import org.junit.Test; -import com.mongodb.BasicDBObject; -import com.mongodb.DB; -import com.mongodb.DBCursor; -import com.mongodb.MongoClient; +import java.net.UnknownHostException; +import java.util.Calendar; +import java.util.Collections; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; /** * Integration tests for {@link MongoLog4jAppender}. - * + * * @author Jon Brisbin * @author Oliver Gierke * @author Christoph Strobl */ public class MongoLog4jAppenderIntegrationTests { - static final String NAME = MongoLog4jAppenderIntegrationTests.class.getName(); - - private static final Logger log = Logger.getLogger(NAME); - MongoClient mongo; - DB db; - String collection; + private static final Logger log = Logger.getLogger(MongoLog4jAppenderIntegrationTests.class.getName()); + private MongoClient mongo; + private DB db; + private String collection; + private ServerAddress serverLocation; - @Before - public void setUp() throws Exception { + @Before public void setUp() throws Exception { + serverLocation = new ServerAddress("localhost", 27017); - mongo = new MongoClient("localhost", 27017); + mongo = new MongoClient(serverLocation); db = mongo.getDB("logs"); Calendar now = Calendar.getInstance(); collection = String.valueOf(now.get(Calendar.YEAR)) + String.format("%1$02d", now.get(Calendar.MONTH) + 1); } - @After - public void tearDown() { + @After public void tearDown() { db.getCollection(collection).remove(new BasicDBObject()); } - @Test - public void testLogging() { - + @Test public void testLogging() { log.debug("DEBUG message"); log.info("INFO message"); log.warn("WARN message"); @@ -74,10 +68,18 @@ public void testLogging() { assertThat(msgs.count(), is(4)); } - @Test - public void testProperties() { + /** + * @see DATAMONGO-442 + */ + @Test public void testLoggingWithCredentials() throws UnknownHostException { + MongoCredential credential = MongoCredential.createCredential("username", "logs", "password".toCharArray()); + mongo = new MongoClient(serverLocation, Collections.singletonList(credential)); + testLogging(); + } + @Test public void testProperties() { MDC.put("property", "one"); log.debug("DEBUG message"); } + } From aa1e91c761270e6d02fd61c4ca6c8af13ef3056a Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 13 Dec 2016 15:53:19 +0100 Subject: [PATCH 040/118] DATAMONGO-442 - Polishing. Reformat code according to Spring Data style. Add test for authenticated use. Add JavaDoc to newly introduced methods. Allow configuration of an authentication database. Update reference documentation. Original pull request: #419. --- .../mongodb/log4j/MongoLog4jAppender.java | 86 ++++++++++--- ...ppenderAuthenticationIntegrationTests.java | 114 ++++++++++++++++++ .../MongoLog4jAppenderIntegrationTests.java | 54 +++++---- .../log4j-with-authentication.properties | 16 +++ .../src/test/resources/log4j.properties | 20 +-- src/main/asciidoc/reference/logging.adoc | 43 +++++-- 6 files changed, 266 insertions(+), 67 deletions(-) create mode 100644 spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderAuthenticationIntegrationTests.java create mode 100644 spring-data-mongodb-log4j/src/test/resources/log4j-with-authentication.properties diff --git a/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java b/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java index 9b25550194..0474c56dd0 100644 --- a/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java +++ b/spring-data-mongodb-log4j/src/main/java/org/springframework/data/mongodb/log4j/MongoLog4jAppender.java @@ -15,15 +15,27 @@ */ package org.springframework.data.mongodb.log4j; -import com.mongodb.*; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.MDC; import org.apache.log4j.PatternLayout; import org.apache.log4j.spi.LoggingEvent; -import java.net.UnknownHostException; -import java.util.*; +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DB; +import com.mongodb.Mongo; +import com.mongodb.MongoClient; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.WriteConcern; /** * Log4j appender writing log entries into a MongoDB instance. @@ -51,6 +63,7 @@ public class MongoLog4jAppender extends AppenderSkeleton { protected int port = 27017; protected String username; protected String password; + protected String authenticationDatabase; protected String database = "logs"; protected String collectionPattern = "%c"; protected PatternLayout collectionLayout = new PatternLayout(collectionPattern); @@ -60,8 +73,7 @@ public class MongoLog4jAppender extends AppenderSkeleton { protected Mongo mongo; protected DB db; - public MongoLog4jAppender() { - } + public MongoLog4jAppender() {} public MongoLog4jAppender(boolean isActive) { super(isActive); @@ -83,20 +95,51 @@ public void setPort(int port) { this.port = port; } + /** + * @return + * @since 1.10 + */ + public String getUsername() { + return username; + } + + /** + * @param username may be {@literal null} for unauthenticated access. + * @since 1.10 + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * @return + * @since 1.10 + */ public String getPassword() { return password; } + /** + * @param password may be {@literal null} for unauthenticated access. + * @since 1.10 + */ public void setPassword(String password) { this.password = password; } - public String getUsername() { - return username; + /** + * @return + */ + public String getAuthenticationDatabase() { + return authenticationDatabase; } - public void setUsername(String username) { - this.username = username; + /** + * @param authenticationDatabase may be {@literal null} to use {@link #getDatabase()} as authentication database. + * @since 1.10 + */ + public void setAuthenticationDatabase(String authenticationDatabase) { + this.authenticationDatabase = authenticationDatabase; } public String getDatabase() { @@ -141,26 +184,33 @@ public void setInfoOrLowerWriteConcern(String wc) { } protected void connectToMongo() throws UnknownHostException { - ServerAddress serverAddress = new ServerAddress(host, port); - connectToMongoHandlingCredentials(serverAddress); + + this.mongo = createMongoClient(); this.db = mongo.getDB(database); } - private void connectToMongoHandlingCredentials(ServerAddress serverAddress) { + private MongoClient createMongoClient() throws UnknownHostException { + + ServerAddress serverAddress = new ServerAddress(host, port); + if (null == password || null == username) { - this.mongo = new MongoClient(serverAddress); - } else { - MongoCredential mongoCredential = MongoCredential.createCredential(username, database, password.toCharArray()); - List credentials = Collections.singletonList(mongoCredential); - this.mongo = new MongoClient(serverAddress, credentials); + return new MongoClient(serverAddress); } + + String authenticationDatabaseToUse = authenticationDatabase == null ? this.database : authenticationDatabase; + MongoCredential mongoCredential = MongoCredential.createCredential(username, + authenticationDatabaseToUse, password.toCharArray()); + List credentials = Collections.singletonList(mongoCredential); + return new MongoClient(serverAddress, credentials); } /* * (non-Javadoc) * @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent) */ - @Override @SuppressWarnings({ "unchecked" }) protected void append(final LoggingEvent event) { + @Override + @SuppressWarnings({ "unchecked" }) + protected void append(final LoggingEvent event) { if (null == db) { try { connectToMongo(); diff --git a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderAuthenticationIntegrationTests.java b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderAuthenticationIntegrationTests.java new file mode 100644 index 0000000000..6cf2818dea --- /dev/null +++ b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderAuthenticationIntegrationTests.java @@ -0,0 +1,114 @@ +/* + * 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.log4j; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Calendar; +import java.util.Collections; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.MDC; +import org.apache.log4j.PropertyConfigurator; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.BasicDBObjectBuilder; +import com.mongodb.DB; +import com.mongodb.DBCursor; +import com.mongodb.MongoClient; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; + +/** + * Integration tests for {@link MongoLog4jAppender} using authentication. + * + * @author Mark Paluch + */ +public class MongoLog4jAppenderAuthenticationIntegrationTests { + + private final static String username = "admin"; + private final static String password = "test"; + private final static String authenticationDatabase = "logs"; + + MongoClient mongo; + DB db; + String collection; + ServerAddress serverLocation; + + Logger log; + + @Before + public void setUp() throws Exception { + + serverLocation = new ServerAddress("localhost", 27017); + + mongo = new MongoClient(serverLocation); + db = mongo.getDB("logs"); + + BasicDBList roles = new BasicDBList(); + roles.add("dbOwner"); + db.command(new BasicDBObjectBuilder().add("createUser", username).add("pwd", password).add("roles", roles).get()); + mongo.close(); + + mongo = new MongoClient(serverLocation, Collections + .singletonList(MongoCredential.createCredential(username, authenticationDatabase, password.toCharArray()))); + db = mongo.getDB("logs"); + + Calendar now = Calendar.getInstance(); + collection = String.valueOf(now.get(Calendar.YEAR)) + String.format("%1$02d", now.get(Calendar.MONTH) + 1); + + LogManager.resetConfiguration(); + PropertyConfigurator.configure(getClass().getResource("/log4j-with-authentication.properties")); + + log = Logger.getLogger(MongoLog4jAppenderIntegrationTests.class.getName()); + } + + @After + public void tearDown() { + + if (db != null) { + db.getCollection(collection).remove(new BasicDBObject()); + db.command(new BasicDBObject("dropUser", username)); + } + + LogManager.resetConfiguration(); + PropertyConfigurator.configure(getClass().getResource("/log4j.properties")); + } + + @Test + public void testLogging() { + + log.debug("DEBUG message"); + log.info("INFO message"); + log.warn("WARN message"); + log.error("ERROR message"); + + DBCursor msgs = db.getCollection(collection).find(); + assertThat(msgs.count(), is(4)); + } + + @Test + public void testProperties() { + MDC.put("property", "one"); + log.debug("DEBUG message"); + } +} diff --git a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java index caf395c662..0271d47cdc 100644 --- a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java +++ b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderIntegrationTests.java @@ -15,19 +15,24 @@ */ package org.springframework.data.mongodb.log4j; -import com.mongodb.*; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Calendar; + +import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.MDC; +import org.apache.log4j.PropertyConfigurator; import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.net.UnknownHostException; -import java.util.Calendar; -import java.util.Collections; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; +import com.mongodb.BasicDBObject; +import com.mongodb.DB; +import com.mongodb.DBCursor; +import com.mongodb.MongoClient; +import com.mongodb.ServerAddress; /** * Integration tests for {@link MongoLog4jAppender}. @@ -38,13 +43,14 @@ */ public class MongoLog4jAppenderIntegrationTests { - private static final Logger log = Logger.getLogger(MongoLog4jAppenderIntegrationTests.class.getName()); - private MongoClient mongo; - private DB db; - private String collection; - private ServerAddress serverLocation; + MongoClient mongo; + DB db; + String collection; + ServerAddress serverLocation; + Logger log; - @Before public void setUp() throws Exception { + @Before + public void setUp() throws Exception { serverLocation = new ServerAddress("localhost", 27017); mongo = new MongoClient(serverLocation); @@ -52,13 +58,18 @@ public class MongoLog4jAppenderIntegrationTests { Calendar now = Calendar.getInstance(); collection = String.valueOf(now.get(Calendar.YEAR)) + String.format("%1$02d", now.get(Calendar.MONTH) + 1); + + log = Logger.getLogger(MongoLog4jAppenderIntegrationTests.class.getName()); } - @After public void tearDown() { + @After + public void tearDown() { db.getCollection(collection).remove(new BasicDBObject()); } - @Test public void testLogging() { + @Test + public void testLogging() { + log.debug("DEBUG message"); log.info("INFO message"); log.warn("WARN message"); @@ -68,18 +79,9 @@ public class MongoLog4jAppenderIntegrationTests { assertThat(msgs.count(), is(4)); } - /** - * @see DATAMONGO-442 - */ - @Test public void testLoggingWithCredentials() throws UnknownHostException { - MongoCredential credential = MongoCredential.createCredential("username", "logs", "password".toCharArray()); - mongo = new MongoClient(serverLocation, Collections.singletonList(credential)); - testLogging(); - } - - @Test public void testProperties() { + @Test + public void testProperties() { MDC.put("property", "one"); log.debug("DEBUG message"); } - } diff --git a/spring-data-mongodb-log4j/src/test/resources/log4j-with-authentication.properties b/spring-data-mongodb-log4j/src/test/resources/log4j-with-authentication.properties new file mode 100644 index 0000000000..d83684463e --- /dev/null +++ b/spring-data-mongodb-log4j/src/test/resources/log4j-with-authentication.properties @@ -0,0 +1,16 @@ +log4j.rootCategory=INFO, mongo + +log4j.appender.mongo=org.springframework.data.mongodb.log4j.MongoLog4jAppender +log4j.appender.mongo.layout=org.apache.log4j.PatternLayout +log4j.appender.mongo.layout.ConversionPattern=%d %p [%c] - <%m>%n +log4j.appender.mongo.host = localhost +log4j.appender.mongo.port = 27017 +log4j.appender.mongo.database = logs +log4j.appender.mongo.username = admin +log4j.appender.mongo.password = test +log4j.appender.mongo.authenticationDatabase = logs +log4j.appender.mongo.collectionPattern = %X{year}%X{month} +log4j.appender.mongo.applicationId = my.application +log4j.appender.mongo.warnOrHigherWriteConcern = FSYNC_SAFE + +log4j.category.org.springframework.data.mongodb=DEBUG diff --git a/spring-data-mongodb-log4j/src/test/resources/log4j.properties b/spring-data-mongodb-log4j/src/test/resources/log4j.properties index 88459b3ffa..75136af0a8 100644 --- a/spring-data-mongodb-log4j/src/test/resources/log4j.properties +++ b/spring-data-mongodb-log4j/src/test/resources/log4j.properties @@ -1,13 +1,13 @@ -log4j.rootCategory=INFO, stdout +log4j.rootCategory=INFO, mongo -log4j.appender.stdout=org.springframework.data.mongodb.log4j.MongoLog4jAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n -log4j.appender.stdout.host = localhost -log4j.appender.stdout.port = 27017 -log4j.appender.stdout.database = logs -log4j.appender.stdout.collectionPattern = %X{year}%X{month} -log4j.appender.stdout.applicationId = my.application -log4j.appender.stdout.warnOrHigherWriteConcern = FSYNC_SAFE +log4j.appender.mongo=org.springframework.data.mongodb.log4j.MongoLog4jAppender +log4j.appender.mongo.layout=org.apache.log4j.PatternLayout +log4j.appender.mongo.layout.ConversionPattern=%d %p [%c] - <%m>%n +log4j.appender.mongo.host = localhost +log4j.appender.mongo.port = 27017 +log4j.appender.mongo.database = logs +log4j.appender.mongo.collectionPattern = %X{year}%X{month} +log4j.appender.mongo.applicationId = my.application +log4j.appender.mongo.warnOrHigherWriteConcern = FSYNC_SAFE log4j.category.org.springframework.data.mongodb=DEBUG diff --git a/src/main/asciidoc/reference/logging.adoc b/src/main/asciidoc/reference/logging.adoc index 5cd3c5d401..d1cdbe05ae 100644 --- a/src/main/asciidoc/reference/logging.adoc +++ b/src/main/asciidoc/reference/logging.adoc @@ -10,17 +10,17 @@ Here is an example configuration [source] ---- -log4j.rootCategory=INFO, stdout - -log4j.appender.stdout=org.springframework.data.document.mongodb.log4j.MongoLog4jAppender -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n -log4j.appender.stdout.host = localhost -log4j.appender.stdout.port = 27017 -log4j.appender.stdout.database = logs -log4j.appender.stdout.collectionPattern = %X{year}%X{month} -log4j.appender.stdout.applicationId = my.application -log4j.appender.stdout.warnOrHigherWriteConcern = FSYNC_SAFE +log4j.rootCategory=INFO, mongo + +log4j.appender.mongo=org.springframework.data.document.mongodb.log4j.MongoLog4jAppender +log4j.appender.mongo.layout=org.apache.log4j.PatternLayout +log4j.appender.mongo.layout.ConversionPattern=%d %p [%c] - <%m>%n +log4j.appender.mongo.host = localhost +log4j.appender.mongo.port = 27017 +log4j.appender.mongo.database = logs +log4j.appender.mongo.collectionPattern = %X{year}%X{month} +log4j.appender.mongo.applicationId = my.application +log4j.appender.mongo.warnOrHigherWriteConcern = FSYNC_SAFE log4j.category.org.apache.activemq=ERROR log4j.category.org.springframework.batch=DEBUG @@ -28,6 +28,23 @@ log4j.category.org.springframework.data.document.mongodb=DEBUG log4j.category.org.springframework.transaction=INFO ---- -The important configuration to look at aside from host and port is the database and collectionPattern. The variables year, month, day and hour are available for you to use in forming a collection name. This is to support the common convention of grouping log information in a collection that corresponds to a specific time period, for example a collection per day. +The important configuration to look at aside from host and port is the database and `collectionPattern`. The variables `year`, `month`, `day` and `hour` are available for you to use in forming a collection name. This is to support the common convention of grouping log information in a collection that corresponds to a specific time period, for example a collection per day. -There is also an applicationId which is put into the stored message. The document stored from logging as the following keys: level, name, applicationId, timestamp, properties, traceback, and message. +There is also an `applicationId` which is put into the stored message. The document stored from logging as the following keys: `level`, `name`, `applicationId`, `timestamp`, `properties`, `traceback`, and `message`. + +[[mongodb:logging-configuration:authentication]] +=== Using authentication + +The MongoDB Log4j appender can be configured to use username/password authentication. +Authentication is performed using the specified database. A different `authenticationDatabase` can be specified to override the default behavior. + +[source] +---- +# ... +log4j.appender.mongo.username = admin +log4j.appender.mongo.password = test +log4j.appender.mongo.authenticationDatabase = logs +# ... +---- + +NOTE: Authentication failures lead to exceptions during logging and are propagated to the caller of the logging method. From e992d813fb1fd3028895a721650d0de145ff6770 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 9 Dec 2016 09:23:59 +0100 Subject: [PATCH 041/118] DATAMONGO-1552 - Add $bucket aggregation stage. Original Pull Request: #426 --- .../mongodb/core/aggregation/Aggregation.java | 22 +- .../core/aggregation/BucketOperation.java | 226 ++++++ .../aggregation/BucketOperationSupport.java | 697 ++++++++++++++++++ .../core/aggregation/AggregationTests.java | 55 ++ .../aggregation/BucketOperationUnitTests.java | 254 +++++++ 5 files changed, 1253 insertions(+), 1 deletion(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java 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 b1ee37b8ad..1cdf93fb96 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 @@ -202,7 +202,7 @@ public static ProjectionOperation project(String... fields) { } /** - * Creates a new {@link ProjectionOperation} includeing the given {@link Fields}. + * Creates a new {@link ProjectionOperation} including the given {@link Fields}. * * @param fields must not be {@literal null}. * @return @@ -418,6 +418,26 @@ public static OutOperation out(String outCollectionName) { return new OutOperation(outCollectionName); } + /** + * Creates a new {@link BucketOperation} using given {@literal groupByField}. + * + * @param groupByField must not be {@literal null} or empty. + * @return + */ + public static BucketOperation bucket(String groupByField) { + return new BucketOperation(field(groupByField)); + } + + /** + * Creates a new {@link BucketOperation} using given {@link AggregationExpression group-by expression}. + * + * @param groupByExpression must not be {@literal null}. + * @return + */ + public static BucketOperation bucket(AggregationExpression groupByExpression) { + return new BucketOperation(groupByExpression); + } + /** * Creates a new {@link LookupOperation}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java new file mode 100644 index 0000000000..080ece369e --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java @@ -0,0 +1,226 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.BucketOperation.BucketOperationOutputBuilder; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Encapsulates the aggregation framework {@code $bucket}-operation. + *

+ * Bucket stage is typically used with {@link Aggregation} and {@code $facet}. Categorizes incoming documents into + * groups, called buckets, based on a specified expression and bucket boundaries. + *

+ * We recommend to use the static factory method {@link Aggregation#bucket(String)} instead of creating instances of + * this class directly. + * + * @see http://docs.mongodb.org/manual/reference/aggregation/bucket/ + * @see BucketOperationSupport + * @author Mark Paluch + * @since 1.10 + */ +public class BucketOperation extends BucketOperationSupport + implements FieldsExposingAggregationOperation { + + private final List boundaries; + private final Object defaultBucket; + + /** + * Creates a new {@link BucketOperation} given a {@link Field group-by field}. + * + * @param groupByField must not be {@literal null}. + */ + public BucketOperation(Field groupByField) { + + super(groupByField); + + this.boundaries = Collections.emptyList(); + this.defaultBucket = null; + } + + /** + * Creates a new {@link BucketOperation} given a {@link AggregationExpression group-by expression}. + * + * @param groupByExpression must not be {@literal null}. + */ + public BucketOperation(AggregationExpression groupByExpression) { + + super(groupByExpression); + + this.boundaries = Collections.emptyList(); + this.defaultBucket = null; + } + + private BucketOperation(BucketOperation bucketOperation, Outputs outputs) { + + super(bucketOperation, outputs); + + this.boundaries = bucketOperation.boundaries; + this.defaultBucket = bucketOperation.defaultBucket; + } + + private BucketOperation(BucketOperation bucketOperation, List boundaries, Object defaultBucket) { + + super(bucketOperation); + + this.boundaries = new ArrayList(boundaries); + this.defaultBucket = defaultBucket; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + + DBObject options = new BasicDBObject(); + + options.put("boundaries", context.getMappedObject(new BasicDBObject("$set", boundaries)).get("$set")); + + if (defaultBucket != null) { + options.put("default", context.getMappedObject(new BasicDBObject("$set", defaultBucket)).get("$set")); + } + + options.putAll(super.toDBObject(context)); + + return new BasicDBObject("$bucket", options); + } + + /** + * Configures a default bucket {@literal literal} and return a new {@link BucketOperation}. + * + * @param literal must not be {@literal null}. + * @return + */ + public BucketOperation withDefaultBucket(Object literal) { + + Assert.notNull(literal, "Default bucket literal must not be null!"); + return new BucketOperation(this, boundaries, literal); + } + + /** + * Configures {@literal boundaries} and return a new {@link BucketOperation}. Existing {@literal boundaries} are + * preserved and the new {@literal boundaries} are appended. + * + * @param boundaries must not be {@literal null}. + * @return + */ + public BucketOperation withBoundaries(Object... boundaries) { + + Assert.notNull(boundaries, "Boundaries must not be null!"); + + List newBoundaries = new ArrayList(this.boundaries.size() + boundaries.length); + newBoundaries.addAll(this.boundaries); + newBoundaries.addAll(Arrays.asList(boundaries)); + + return new BucketOperation(this, newBoundaries, defaultBucket); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#newBucketOperation(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Outputs) + */ + @Override + protected BucketOperation newBucketOperation(Outputs outputs) { + return new BucketOperation(this, outputs); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutputExpression(java.lang.String, java.lang.Object[]) + */ + @Override + public ExpressionBucketOperationBuilder andOutputExpression(String expression, Object... params) { + return new ExpressionBucketOperationBuilder(expression, this, params); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public BucketOperationOutputBuilder andOutput(AggregationExpression expression) { + return new BucketOperationOutputBuilder(expression, this); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(java.lang.String) + */ + @Override + public BucketOperationOutputBuilder andOutput(String fieldName) { + return new BucketOperationOutputBuilder(Fields.field(fieldName), this); + } + + /** + * {@link OutputBuilder} implementation for {@link BucketOperation}. + */ + public static class BucketOperationOutputBuilder + extends BucketOperationSupport.OutputBuilder { + + /** + * Creates a new {@link BucketOperationOutputBuilder} fot the given value and {@link BucketOperation}. + * + * @param value must not be {@literal null}. + * @param operation must not be {@literal null}. + */ + protected BucketOperationOutputBuilder(Object value, BucketOperation operation) { + super(value, operation); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput) + */ + @Override + protected BucketOperationOutputBuilder apply(OperationOutput operationOutput) { + return new BucketOperationOutputBuilder(operationOutput, this.operation); + } + } + + /** + * {@link ExpressionBucketOperationBuilderSupport} implementation for {@link BucketOperation} using SpEL expression + * based {@link Output}. + * + * @author Mark Paluch + */ + public static class ExpressionBucketOperationBuilder + extends ExpressionBucketOperationBuilderSupport { + + /** + * Creates a new {@link ExpressionBucketOperationBuilderSupport} for the given value, {@link BucketOperation} + * and parameters. + * + * @param expression must not be {@literal null}. + * @param operation must not be {@literal null}. + * @param parameters + */ + protected ExpressionBucketOperationBuilder(String expression, BucketOperation operation, Object[] parameters) { + super(expression, operation, parameters); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput) + */ + @Override + protected BucketOperationOutputBuilder apply(OperationOutput operationOutput) { + return new BucketOperationOutputBuilder(operationOutput, this.operation); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java new file mode 100644 index 0000000000..13a63e6bd1 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java @@ -0,0 +1,697 @@ +/* + * 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.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; +import org.springframework.expression.spel.ast.Projection; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Base class for bucket operations that support output expressions the aggregation framework. + *

+ * Bucket stages collect documents into buckets and can contribute output fields. + *

+ * Implementing classes are required to provide an {@link OutputBuilder}. + * + * @see http://docs.mongodb.org/manual/reference/aggregation/bucket/ + * @author Mark Paluch + * @since 1.10 + */ +public abstract class BucketOperationSupport, B extends OutputBuilder> + implements FieldsExposingAggregationOperation { + + private final Field groupByField; + private final AggregationExpression groupByExpression; + private final Outputs outputs; + + /** + * Creates a new {@link BucketOperationSupport} given a {@link Field group-by field}. + * + * @param groupByField must not be {@literal null}. + */ + protected BucketOperationSupport(Field groupByField) { + + Assert.notNull(groupByField, "Group by field must not be null!"); + + this.groupByField = groupByField; + this.groupByExpression = null; + this.outputs = Outputs.EMPTY; + + } + + /** + * Creates a new {@link BucketOperationSupport} given a {@link AggregationExpression group-by expression}. + * + * @param groupByExpression must not be {@literal null}. + */ + protected BucketOperationSupport(AggregationExpression groupByExpression) { + + Assert.notNull(groupByExpression, "Group by AggregationExpression must not be null!"); + + this.groupByExpression = groupByExpression; + this.groupByField = null; + this.outputs = Outputs.EMPTY; + } + + /** + * Creates a copy of {@link BucketOperationSupport}. + * + * @param operationSupport must not be {@literal null}. + */ + protected BucketOperationSupport(BucketOperationSupport operationSupport) { + this(operationSupport, operationSupport.outputs); + } + + /** + * Creates a copy of {@link BucketOperationSupport} and applies the new {@link Outputs}. + * + * @param operationSupport must not be {@literal null}. + * @param outputs must not be {@literal null}. + */ + protected BucketOperationSupport(BucketOperationSupport operationSupport, Outputs outputs) { + + Assert.notNull(operationSupport, "BucketOperationSupport must not be null!"); + Assert.notNull(outputs, "Outputs must not be null!"); + + this.groupByField = operationSupport.groupByField; + this.groupByExpression = operationSupport.groupByExpression; + this.outputs = outputs; + } + + /** + * Creates a new {@link ExpressionBucketOperationBuilderSupport} given a SpEL {@literal expression} and optional + * {@literal params} to add an output field to the resulting bucket documents. + * + * @param expression the SpEL expression, must not be {@literal null} or empty. + * @param params must not be {@literal null} + * @return + */ + public abstract ExpressionBucketOperationBuilderSupport andOutputExpression(String expression, + Object... params); + + /** + * Creates a new {@link BucketOperationSupport} given an {@link AggregationExpression} to add an output field to the + * resulting bucket documents. + * + * @param expression the SpEL expression, must not be {@literal null} or empty. + * @return + */ + public abstract B andOutput(AggregationExpression expression); + + /** + * Creates a new {@link BucketOperationSupport} given {@literal fieldName} to add an output field to the resulting + * bucket documents. {@link BucketOperationSupport} exposes accumulation operations that can be applied to + * {@literal fieldName}. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + public abstract B andOutput(String fieldName); + + /** + * Creates a new {@link BucketOperationSupport} given to add a count field to the resulting bucket documents. + * + * @return + */ + public B andOutputCount() { + return andOutput(new AggregationExpression() { + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return new BasicDBObject("$sum", 1); + } + }); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + + DBObject dbObject = new BasicDBObject(); + + dbObject.put("groupBy", groupByExpression == null ? context.getReference(groupByField).toString() + : groupByExpression.toDbObject(context)); + + if (!outputs.isEmpty()) { + dbObject.put("output", outputs.toDbObject(context)); + } + + return dbObject; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return outputs.asExposedFields(); + } + + /** + * Implementation hook to create a new bucket operation. + * + * @param outputs the outputs + * @return the new bucket operation. + */ + protected abstract T newBucketOperation(Outputs outputs); + + protected T andOutput(Output output) { + return newBucketOperation(outputs.and(output)); + } + + /** + * Builder for SpEL expression-based {@link Output}. + * + * @author Mark Paluch + */ + public abstract static class ExpressionBucketOperationBuilderSupport, T extends BucketOperationSupport> + extends OutputBuilder { + + /** + * Creates a new {@link ExpressionBucketOperationBuilderSupport} for the given value, {@link BucketOperationSupport} + * and parameters. + * + * @param expression must not be {@literal null}. + * @param operation must not be {@literal null}. + * @param parameters + */ + protected ExpressionBucketOperationBuilderSupport(String expression, T operation, Object[] parameters) { + super(new SpelExpressionOutput(expression, parameters), operation); + } + } + + /** + * Base class for {@link Output} builders that result in a {@link BucketOperationSupport} providing the built + * {@link Output}. + * + * @author Mark Paluch + */ + public abstract static class OutputBuilder, T extends BucketOperationSupport> { + + protected final Object value; + protected final T operation; + + /** + * Creates a new {@link OutputBuilder} for the given value and {@link BucketOperationSupport}. + * + * @param value must not be {@literal null}. + * @param operation must not be {@literal null}. + */ + public OutputBuilder(Object value, T operation) { + + Assert.notNull(value, "Value must not be null or empty!"); + Assert.notNull(operation, "ProjectionOperation must not be null!"); + + this.value = value; + this.operation = operation; + } + + /** + * Generates a builder for a {@code $sum}-expression. + *

+ * Count expressions are emulated via {@code $sum: 1}. + *

+ * + * @return + */ + public B count() { + return sum(1); + } + + /** + * Generates a builder for a {@code $sum}-expression for the current value. + * + * @return + */ + public B sum() { + return apply(Accumulators.SUM); + } + + /** + * Generates a builder for a {@code $sum}-expression for the given {@literal value}. + * + * @param value + * @return + */ + public B sum(Number value) { + return apply(new OperationOutput(Accumulators.SUM.toString(), Collections.singleton(value))); + } + + /** + * Generates a builder for an {@code $last}-expression for the current value.. + * + * @return + */ + public B last() { + return apply(Accumulators.LAST); + } + + /** + * Generates a builder for a {@code $first}-expression the current value. + * + * @return + */ + public B first() { + return apply(Accumulators.FIRST); + } + + /** + * Generates a builder for an {@code $avg}-expression for the current value. + * + * @param reference + * @return + */ + public B avg() { + return apply(Accumulators.AVG); + } + + /** + * Generates a builder for an {@code $min}-expression for the current value. + * + * @return + */ + public B min() { + return apply(Accumulators.MIN); + } + + /** + * Generates a builder for an {@code $max}-expression for the current value. + * + * @return + */ + public B max() { + return apply(Accumulators.MAX); + } + + /** + * Generates a builder for an {@code $push}-expression for the current value. + * + * @return + */ + public B push() { + return apply(Accumulators.PUSH); + } + + /** + * Generates a builder for an {@code $addToSet}-expression for the current value. + * + * @return + */ + public B addToSet() { + return apply(Accumulators.ADDTOSET); + } + + /** + * Apply an operator to the current value. + * + * @param operation the operation name, must not be {@literal null} or empty. + * @param values must not be {@literal null}. + * @return + */ + public B apply(String operation, Object... values) { + + Assert.hasText(operation, "Operation must not be empty or null!"); + Assert.notNull(value, "Values must not be null!"); + + List objects = new ArrayList(values.length + 1); + objects.add(value); + objects.addAll(Arrays.asList(values)); + return apply(new OperationOutput(operation, objects)); + } + + /** + * Apply an {@link OperationOutput} to this output. + * + * @param operationOutput must not be {@literal null}. + * @return + */ + protected abstract B apply(OperationOutput operationOutput); + + private B apply(Accumulators operation) { + return this.apply(operation.toString()); + } + + /** + * Returns the finally to be applied {@link BucketOperation} with the given alias. + * + * @param alias will never be {@literal null} or empty. + * @return + */ + public T as(String alias) { + + if (value instanceof OperationOutput) { + return this.operation.andOutput(((OperationOutput) this.value).withAlias(alias)); + } + + if (value instanceof Field) { + throw new IllegalStateException("Cannot add a field as top-level output. Use accumulator expressions."); + } + + return this.operation + .andOutput(new AggregationExpressionOutput(Fields.field(alias), (AggregationExpression) value)); + } + } + + private enum Accumulators { + + SUM("$sum"), AVG("$avg"), FIRST("$first"), LAST("$last"), MAX("$max"), MIN("$min"), PUSH("$push"), ADDTOSET( + "$addToSet"); + + private String mongoOperator; + + Accumulators(String mongoOperator) { + this.mongoOperator = mongoOperator; + } + + /* (non-Javadoc) + * @see java.lang.Enum#toString() + */ + @Override + public String toString() { + return mongoOperator; + } + } + + /** + * Encapsulates {@link Output}s. + * + * @author Mark Paluch + */ + protected static class Outputs implements AggregationExpression { + + protected static final Outputs EMPTY = new Outputs(); + + private List outputs; + + /** + * Creates a new, empty {@link Outputs}. + */ + private Outputs() { + this.outputs = new ArrayList(); + } + + /** + * Creates new {@link Outputs} containing all given {@link Output}s. + * + * @param current + * @param output + */ + private Outputs(Collection current, Output output) { + + this.outputs = new ArrayList(current.size() + 1); + this.outputs.addAll(current); + this.outputs.add(output); + } + + /** + * @return the {@link ExposedFields} derived from {@link Output}. + */ + protected ExposedFields asExposedFields() { + + ExposedFields fields = ExposedFields.from(); + + for (Output output : outputs) { + fields = fields.and(output.getExposedField()); + } + + return fields; + } + + /** + * Create a new {@link Outputs} that contains the new {@link Output}. + * + * @param output must not be {@literal null}. + * @return the new {@link Outputs} that contains the new {@link Output} + */ + protected Outputs and(Output output) { + + Assert.notNull(output, "BucketOutput must not be null!"); + return new Outputs(this.outputs, output); + } + + /** + * @return {@literal true} if {@link Outputs} contains no {@link Output}. + */ + protected boolean isEmpty() { + return outputs.isEmpty(); + } + + /* (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 dbObject = new BasicDBObject(); + + for (Output output : outputs) { + dbObject.put(output.getExposedField().getName(), output.toDbObject(context)); + } + + return dbObject; + } + + } + + /** + * Encapsulates an output field in a bucket aggregation stage. + *

+ * Output fields can be either top-level fields that define a valid field name or nested output fields using + * operators. + * + * @author Mark Paluch + */ + protected abstract static class Output implements AggregationExpression { + + private final ExposedField field; + + /** + * Creates new {@link Projection} for the given {@link Field}. + * + * @param field must not be {@literal null}. + */ + protected Output(Field field) { + + Assert.notNull(field, "Field must not be null!"); + this.field = new ExposedField(field, true); + } + + /** + * Returns the field exposed by the {@link Output}. + * + * @return will never be {@literal null}. + */ + protected ExposedField getExposedField() { + return field; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public abstract DBObject toDbObject(AggregationOperationContext context); + } + + /** + * Output field that uses a Mongo operation (expression object) to generate an output field value. + *

+ * {@link OperationOutput} is used either with a regular field name or an operation keyword (e.g. + * {@literal $sum, $count}). + * + * @author Mark Paluch + */ + protected static class OperationOutput extends Output { + + private final String operation; + private final List values; + + /** + * Creates a new {@link Output} for the given field. + * + * @param operation the actual operation key, must not be {@literal null} or empty. + * @param values the values to pass into the operation, must not be {@literal null}. + */ + public OperationOutput(String operation, Collection values) { + + super(Fields.field(operation)); + + Assert.hasText(operation, "Operation must not be null or empty!"); + Assert.notNull(values, "Values must not be null!"); + + this.operation = operation; + this.values = new ArrayList(values); + } + + private OperationOutput(Field field, OperationOutput operationOutput) { + + super(field); + + this.operation = operationOutput.operation; + this.values = operationOutput.values; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + List operationArguments = getOperationArguments(context); + return new BasicDBObject(operation, + operationArguments.size() == 1 ? operationArguments.get(0) : operationArguments); + } + + protected List getOperationArguments(AggregationOperationContext context) { + + List result = new ArrayList(values != null ? values.size() : 1); + + for (Object element : values) { + + if (element instanceof Field) { + result.add(context.getReference((Field) element).toString()); + } else if (element instanceof Fields) { + for (Field field : (Fields) element) { + result.add(context.getReference(field).toString()); + } + } else if (element instanceof AggregationExpression) { + result.add(((AggregationExpression) element).toDbObject(context)); + } else { + result.add(element); + } + } + + return result; + } + + /** + * Returns the field that holds the {@link ProjectionOperationBuilder.OperationProjection}. + * + * @return + */ + protected Field getField() { + return getExposedField(); + } + + /** + * Creates a new instance of this {@link OperationOutput} with the given alias. + * + * @param alias the alias to set + * @return + */ + public OperationOutput withAlias(String alias) { + + final Field aliasedField = Fields.field(alias); + return new OperationOutput(aliasedField, this) { + + @Override + protected Field getField() { + return aliasedField; + } + + @Override + protected List getOperationArguments(AggregationOperationContext context) { + + // We have to make sure that we use the arguments from the "previous" OperationOutput that we replace + // with this new instance. + + return OperationOutput.this.getOperationArguments(context); + } + }; + } + } + + /** + * A {@link Output} based on a SpEL expression. + */ + static class SpelExpressionOutput extends Output { + + private static final SpelExpressionTransformer TRANSFORMER = new SpelExpressionTransformer(); + + private final String expression; + private final Object[] params; + + /** + * Creates a new {@link SpelExpressionOutput} for the given field, SpEL expression and parameters. + * + * @param expression must not be {@literal null} or empty. + * @param parameters must not be {@literal null}. + */ + public SpelExpressionOutput(String expression, Object[] parameters) { + + super(Fields.field(expression)); + + Assert.hasText(expression, "Expression must not be null!"); + Assert.notNull(parameters, "Parameters must not be null!"); + + this.expression = expression; + this.params = parameters.clone(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Output#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return (DBObject) toMongoExpression(context, expression, params); + } + + protected static Object toMongoExpression(AggregationOperationContext context, String expression, Object[] params) { + return TRANSFORMER.transform(expression, context, params); + } + } + + /** + * @author Mark Paluch + */ + private static class AggregationExpressionOutput extends Output { + + private final AggregationExpression expression; + + /** + * Creates a new {@link AggregationExpressionOutput}. + * + * @param field + * @param expression + */ + protected AggregationExpressionOutput(Field field, AggregationExpression expression) { + + super(field); + + this.expression = expression; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Output#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return expression.toDbObject(context); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 2d0bb77508..7a7981fd90 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -148,6 +148,7 @@ private void cleanDb() { mongoTemplate.dropCollection(Sales.class); mongoTemplate.dropCollection(Sales2.class); mongoTemplate.dropCollection(Employee.class); + mongoTemplate.dropCollection(Art.class); } /** @@ -1667,6 +1668,46 @@ public void graphLookupShouldBeAppliedCorrectly() { assertThat((DBObject) list.get(1), isBsonObject().containing("name", "Eliot").containing("depth", 0L)); } + /** + * @see DATAMONGO-1552 + */ + @Test + public void bucketShouldCollectDocumentsIntoABucket() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + TypedAggregation aggregation = newAggregation(Art.class, // + bucket("price") // + .withBoundaries(0, 100, 200) // + .withDefaultBucket("other") // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10").sum().as("sum")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); + + // { "_id" : 0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + DBObject bound0 = result.getMappedResults().get(0); + assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer")); + assertThat((Double) bound0.get("sum"), is(closeTo(760.40, 0.1))); + + // { "_id" : 100 , "count" : 2 , "titles" : [ "The Pillars of Society" , "The Great Wave off Kanagawa"] , "sum" : + // 3672.9} + DBObject bound100 = result.getMappedResults().get(1); + assertThat(bound100, isBsonObject().containing("count", 2).containing("_id", 100)); + assertThat((List) bound100.get("titles"), + hasItems("The Pillars of Society", "The Great Wave off Kanagawa")); + assertThat((Double) bound100.get("sum"), is(closeTo(3672.9, 0.1))); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); @@ -1956,4 +1997,18 @@ static class Employee { String name; String reportsTo; } + + /** + * @see DATAMONGO-1552 + */ + @lombok.Data + @Builder + static class Art { + + int id; + String title; + String artist; + Integer year; + double price; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java new file mode 100644 index 0000000000..f2ff2ba125 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java @@ -0,0 +1,254 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import static org.hamcrest.core.Is.*; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; + +import org.junit.Test; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link BucketOperation}. + * + * @author Mark Paluch + */ +public class BucketOperationUnitTests { + + /** + * @see DATAMONGO-1552 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullFields() { + new BucketOperation((Field) null); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderBucketOutputExpressions() { + + BucketOperation operation = Aggregation.bucket("field") // + .andOutputExpression("(netPrice + surCharge) * taxrate * [0]", 2).as("grossSalesPrice") // + .andOutput("title").push().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse( + "{ \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"titles\" : { $push: \"$title\" } }}"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test(expected = IllegalStateException.class) + public void shouldRenderEmptyAggregationExpression() { + bucket("groupby").andOutput("field").as("alias"); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderBucketOutputOperators() { + + BucketOperation operation = Aggregation.bucket("field") // + .andOutputCount().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $sum: 1 } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderSumAggregationExpression() { + + DBObject agg = bucket("field") // + .andOutput(ArithmeticOperators.valueOf("quizzes").sum()).as("quizTotal") // + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse( + "{ $bucket: { groupBy: \"$field\", boundaries: [], output : { quizTotal: { $sum: \"$quizzes\"} } } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderDefault() { + + DBObject agg = bucket("field").withDefaultBucket("default bucket").toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $bucket: { groupBy: \"$field\", boundaries: [], default: \"default bucket\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderBoundaries() { + + DBObject agg = bucket("field") // + .withDefaultBucket("default bucket") // + .withBoundaries(0) // + .withBoundaries(10, 20).toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(JSON.parse("{ $bucket: { boundaries: [0, 10, 20], default: \"default bucket\", groupBy: \"$field\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderSumOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("score").sum().as("cummulated_score"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ cummulated_score : { $sum: \"$score\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderSumWithValueOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("score").sum(4).as("cummulated_score"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ cummulated_score : { $sum: 4 } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderAvgOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("score").avg().as("average"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ average : { $avg: \"$score\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderFirstOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("title").first().as("first_title"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ first_title : { $first: \"$title\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderLastOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("title").last().as("last_title"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ last_title : { $last: \"$title\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderMinOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("score").min().as("min_score"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ min_score : { $min: \"$score\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderPushOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("title").push().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $push: \"$title\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderAddToSetOperator() { + + BucketOperation operation = bucket("field") // + .andOutput("title").addToSet().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $addToSet: \"$title\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderSumWithExpression() { + + BucketOperation operation = bucket("field") // + .andOutputExpression("netPrice + tax").sum().as("total"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ total : { $sum: { $add : [\"$netPrice\", \"$tax\"]} } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderSumWithOwnOutputExpression() { + + BucketOperation operation = bucket("field") // + .andOutputExpression("netPrice + tax").apply("$multiply", 5).as("total"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), + is(JSON.parse("{ total : { $multiply: [ {$add : [\"$netPrice\", \"$tax\"]}, 5] } }"))); + } + + private static DBObject extractOutput(DBObject fromBucketClause) { + return (DBObject) ((DBObject) fromBucketClause.get("$bucket")).get("output"); + } +} From 2c4377c9a6837255237054cf18ad2f466b2adb6e Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 9 Dec 2016 09:55:40 +0100 Subject: [PATCH 042/118] DATAMONGO-1552 - Add $bucketAuto aggregation stage. Original Pull Request: #426 --- .../mongodb/core/aggregation/Aggregation.java | 26 +- .../core/aggregation/BucketAutoOperation.java | 273 ++++++++++++++++++ .../aggregation/BucketOperationSupport.java | 7 +- .../core/aggregation/AggregationTests.java | 39 +++ .../BucketAutoOperationUnitTests.java | 142 +++++++++ .../aggregation/BucketOperationUnitTests.java | 18 +- 6 files changed, 500 insertions(+), 5 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java 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 1cdf93fb96..25b816b385 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 @@ -419,7 +419,7 @@ public static OutOperation out(String outCollectionName) { } /** - * Creates a new {@link BucketOperation} using given {@literal groupByField}. + * Creates a new {@link BucketOperation} given {@literal groupByField}. * * @param groupByField must not be {@literal null} or empty. * @return @@ -429,7 +429,7 @@ public static BucketOperation bucket(String groupByField) { } /** - * Creates a new {@link BucketOperation} using given {@link AggregationExpression group-by expression}. + * Creates a new {@link BucketOperation} given {@link AggregationExpression group-by expression}. * * @param groupByExpression must not be {@literal null}. * @return @@ -438,6 +438,28 @@ public static BucketOperation bucket(AggregationExpression groupByExpression) { return new BucketOperation(groupByExpression); } + /** + * Creates a new {@link BucketAutoOperation} given {@literal groupByField}. + * + * @param groupByField must not be {@literal null} or empty. + * @param buckets number of buckets, must be a positive integer. + * @return + */ + public static BucketAutoOperation bucketAuto(String groupByField, int buckets) { + return new BucketAutoOperation(field(groupByField), buckets); + } + + /** + * Creates a new {@link BucketAutoOperation} given {@link AggregationExpression group-by expression}. + * + * @param groupByExpression must not be {@literal null}. + * @param buckets number of buckets, must be a positive integer. + * @return + */ + public static BucketAutoOperation bucketAuto(AggregationExpression groupByExpression, int buckets) { + return new BucketAutoOperation(groupByExpression, buckets); + } + /** * Creates a new {@link LookupOperation}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java new file mode 100644 index 0000000000..9ad0d6f660 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java @@ -0,0 +1,273 @@ +/* + * 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.BucketAutoOperation.BucketAutoOperationOutputBuilder; +import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Encapsulates the aggregation framework {@code $bucketAuto}-operation. + *

+ * Bucket stage is typically used with {@link Aggregation} and {@code $facet}. Categorizes incoming documents into a + * specific number of groups, called buckets, based on a specified expression. Bucket boundaries are automatically + * determined in an attempt to evenly distribute the documents into the specified number of buckets. + *

+ * We recommend to use the static factory method {@link Aggregation#bucketAuto(String, int)} instead of creating instances of + * this class directly. + * + * @see http://docs.mongodb.org/manual/reference/aggregation/bucketAuto/ + * @see BucketOperationSupport + * @author Mark Paluch + * @since 1.10 + */ +public class BucketAutoOperation extends BucketOperationSupport + implements FieldsExposingAggregationOperation { + + private final int buckets; + private final String granularity; + + /** + * Creates a new {@link BucketAutoOperation} given a {@link Field group-by field}. + * + * @param groupByField must not be {@literal null}. + * @param buckets number of buckets, must be a positive integer. + */ + public BucketAutoOperation(Field groupByField, int buckets) { + + super(groupByField); + + Assert.isTrue(buckets > 0, "Number of buckets must be greater 0!"); + + this.buckets = buckets; + this.granularity = null; + } + + /** + * Creates a new {@link BucketAutoOperation} given a {@link AggregationExpression group-by expression}. + * + * @param groupByExpression must not be {@literal null}. + * @param buckets number of buckets, must be a positive integer. + */ + public BucketAutoOperation(AggregationExpression groupByExpression, int buckets) { + + super(groupByExpression); + + Assert.isTrue(buckets > 0, "Number of buckets must be greater 0!"); + + this.buckets = buckets; + this.granularity = null; + } + + private BucketAutoOperation(BucketAutoOperation bucketOperation, Outputs outputs) { + + super(bucketOperation, outputs); + + this.buckets = bucketOperation.buckets; + this.granularity = bucketOperation.granularity; + } + + private BucketAutoOperation(BucketAutoOperation bucketOperation, int buckets, String granularity) { + + super(bucketOperation); + + this.buckets = buckets; + this.granularity = granularity; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + + DBObject options = new BasicDBObject(); + + options.put("buckets", buckets); + + if (granularity != null) { + options.put("granularity", granularity); + } + + options.putAll(super.toDBObject(context)); + + return new BasicDBObject("$bucketAuto", options); + } + + /** + * Configures a number of bucket {@literal buckets} and return a new {@link BucketAutoOperation}. + * + * @param buckets must be a positive number. + * @return + */ + public BucketAutoOperation withBuckets(int buckets) { + + Assert.isTrue(buckets > 0, "Number of buckets must be greater 0!"); + return new BucketAutoOperation(this, buckets, granularity); + } + + /** + * Configures {@literal granularity} that specifies the preferred number series to use to ensure that the calculated + * boundary edges end on preferred round numbers or their powers of 10 and return a new {@link BucketAutoOperation}. + * + * @param granularity must not be {@literal null}. + * @return + */ + public BucketAutoOperation withGranularity(Granularity granularity) { + + Assert.notNull(granularity, "Granularity must not be null!"); + + return new BucketAutoOperation(this, buckets, granularity.toMongoGranularity()); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#newBucketOperation(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Outputs) + */ + @Override + protected BucketAutoOperation newBucketOperation(Outputs outputs) { + return new BucketAutoOperation(this, outputs); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutputExpression(java.lang.String, java.lang.Object[]) + */ + @Override + public ExpressionBucketAutoOperationBuilder andOutputExpression(String expression, Object... params) { + return new ExpressionBucketAutoOperationBuilder(expression, this, params); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public BucketAutoOperationOutputBuilder andOutput(AggregationExpression expression) { + return new BucketAutoOperationOutputBuilder(expression, this); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport#andOutput(java.lang.String) + */ + @Override + public BucketAutoOperationOutputBuilder andOutput(String fieldName) { + return new BucketAutoOperationOutputBuilder(Fields.field(fieldName), this); + } + + /** + * {@link OutputBuilder} implementation for {@link BucketAutoOperation}. + */ + public static class BucketAutoOperationOutputBuilder + extends OutputBuilder { + + /** + * Creates a new {@link BucketAutoOperationOutputBuilder} fot the given value and {@link BucketAutoOperation}. + * + * @param value must not be {@literal null}. + * @param operation must not be {@literal null}. + */ + protected BucketAutoOperationOutputBuilder(Object value, BucketAutoOperation operation) { + super(value, operation); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput) + */ + @Override + protected BucketAutoOperationOutputBuilder apply(OperationOutput operationOutput) { + return new BucketAutoOperationOutputBuilder(operationOutput, this.operation); + } + } + + /** + * {@link ExpressionBucketOperationBuilderSupport} implementation for {@link BucketAutoOperation} using SpEL + * expression based {@link Output}. + * + * @author Mark Paluch + */ + public static class ExpressionBucketAutoOperationBuilder + extends ExpressionBucketOperationBuilderSupport { + + /** + * Creates a new {@link ExpressionBucketAutoOperationBuilder} for the given value, {@link BucketAutoOperation} and + * parameters. + * + * @param expression must not be {@literal null}. + * @param operation must not be {@literal null}. + * @param parameters + */ + protected ExpressionBucketAutoOperationBuilder(String expression, BucketAutoOperation operation, + Object[] parameters) { + super(expression, operation, parameters); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OutputBuilder#apply(org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.OperationOutput) + */ + @Override + protected BucketAutoOperationOutputBuilder apply(OperationOutput operationOutput) { + return new BucketAutoOperationOutputBuilder(operationOutput, this.operation); + } + } + + /** + * @author Mark Paluch + */ + public static interface Granularity { + + /** + * @return a String that represents a MongoDB granularity to be used with {@link BucketAutoOperation}. + */ + String toMongoGranularity(); + } + + /** + * Supported MongoDB granularities. + * + * @see https://en.wikipedia.org/wiki/Preferred_number + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity + * @author Mark Paluch + */ + public enum Granularities implements Granularity { + + R5, R10, R20, R40, R80, // + + SERIES_1_2_5("1-2-5"), // + + E6, E12, E24, E48, E96, E192, // + + POWERSOF2; + + final String granularity; + + Granularities() { + this.granularity = name(); + } + + Granularities(String granularity) { + this.granularity = granularity; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.GranularitytoMongoGranularity() + */ + @Override + public String toMongoGranularity() { + return granularity; + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java index 13a63e6bd1..05a5bdb4cf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java @@ -222,7 +222,7 @@ public abstract static class OutputBuilder, T exte * @param value must not be {@literal null}. * @param operation must not be {@literal null}. */ - public OutputBuilder(Object value, T operation) { + protected OutputBuilder(Object value, T operation) { Assert.notNull(value, "Value must not be null or empty!"); Assert.notNull(operation, "ProjectionOperation must not be null!"); @@ -433,6 +433,11 @@ private Outputs(Collection current, Output output) { */ protected ExposedFields asExposedFields() { + // The count field is included by default when the output is not specified. + if (isEmpty()) { + return ExposedFields.from(new ExposedField("count", true)); + } + ExposedFields fields = ExposedFields.from(); for (Output output : outputs) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 7a7981fd90..63243e1b84 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -61,7 +61,9 @@ import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let; import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable; +import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Multiply; import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry; +import org.springframework.data.mongodb.core.aggregation.BucketAutoOperation.Granularities; import org.springframework.data.mongodb.core.index.GeospatialIndex; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.query.Criteria; @@ -1708,6 +1710,43 @@ public void bucketShouldCollectDocumentsIntoABucket() { assertThat((Double) bound100.get("sum"), is(closeTo(3672.9, 0.1))); } + /** + * @see DATAMONGO-1552 + */ + @Test + public void bucketAutoShouldCollectDocumentsIntoABucket() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + TypedAggregation aggregation = newAggregation(Art.class, // + bucketAuto(Multiply.valueOf("price").multiplyBy(10), 3) // + .withGranularity(Granularities.E12) // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10").sum().as("sum")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); + + // { "min" : 680.0 , "max" : 820.0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + DBObject bound0 = result.getMappedResults().get(0); + assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer").containing("min", 680.0) + .containing("max")); + + // { "min" : 820.0 , "max" : 1800.0 , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : 1673.0} + DBObject bound1 = result.getMappedResults().get(1); + assertThat(bound1, isBsonObject().containing("count", 1).containing("min", 820.0)); + assertThat((List) bound1.get("titles"), hasItems("The Great Wave off Kanagawa")); + assertThat((Double) bound1.get("sum"), is(closeTo(1673.0, 0.1))); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java new file mode 100644 index 0000000000..77388af89c --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java @@ -0,0 +1,142 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import static org.hamcrest.core.Is.*; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.DBObjectTestUtils.getAsDBObject; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; + +import org.junit.Test; +import org.springframework.data.mongodb.core.aggregation.BucketAutoOperation.Granularities; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link BucketAutoOperation}. + * + * @author Mark Paluch + */ +public class BucketAutoOperationUnitTests { + + /** + * @see DATAMONGO-1552 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullFields() { + new BucketAutoOperation((Field) null, 0); + } + + /** + * @see DATAMONGO-1552 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNonPositiveIntegerNullFields() { + new BucketAutoOperation(Fields.field("field"), 0); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderBucketOutputExpressions() { + + BucketAutoOperation operation = Aggregation.bucketAuto("field", 5) // + .andOutputExpression("(netPrice + surCharge) * taxrate * [0]", 2).as("grossSalesPrice") // + .andOutput("title").push().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse( + "{ \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"titles\" : { $push: \"$title\" } }}"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test(expected = IllegalStateException.class) + public void shouldRenderEmptyAggregationExpression() { + bucket("groupby").andOutput("field").as("alias"); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderBucketOutputOperators() { + + BucketAutoOperation operation = Aggregation.bucketAuto("field", 5) // + .andOutputCount().as("titles"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $sum: 1 } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderCorrectly() { + + DBObject agg = bucketAuto("field", 1).withBuckets(5).toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $bucketAuto: { groupBy: \"$field\", buckets: 5 } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderGranulariy() { + + DBObject agg = bucketAuto("field", 1) // + .withGranularity(Granularities.E24) // + .toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(JSON.parse("{ $bucketAuto: { buckets: 1, granularity: \"E24\", groupBy: \"$field\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderSumOperator() { + + BucketAutoOperation operation = bucketAuto("field", 5) // + .andOutput("score").sum().as("cummulated_score"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), is(JSON.parse("{ cummulated_score : { $sum: \"$score\" } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderSumWithOwnOutputExpression() { + + BucketAutoOperation operation = bucketAuto("field", 5) // + .andOutputExpression("netPrice + tax").apply("$multiply", 5).as("total"); + + DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); + assertThat(extractOutput(dbObject), + is(JSON.parse("{ total : { $multiply: [ {$add : [\"$netPrice\", \"$tax\"]}, 5] } }"))); + } + + private static DBObject extractOutput(DBObject fromBucketClause) { + return getAsDBObject(getAsDBObject(fromBucketClause, "$bucketAuto"), "output"); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java index f2ff2ba125..5915c73b5d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java @@ -15,8 +15,10 @@ */ package org.springframework.data.mongodb.core.aggregation; -import static org.hamcrest.core.Is.*; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; import org.junit.Test; @@ -248,7 +250,19 @@ public void shouldRenderSumWithOwnOutputExpression() { is(JSON.parse("{ total : { $multiply: [ {$add : [\"$netPrice\", \"$tax\"]}, 5] } }"))); } + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldExposeDefaultCountField() { + + BucketOperation operation = bucket("field"); + + assertThat(operation.getFields().exposesSingleFieldOnly(), is(true)); + assertThat(operation.getFields().getField("count"), is(notNullValue())); + } + private static DBObject extractOutput(DBObject fromBucketClause) { - return (DBObject) ((DBObject) fromBucketClause.get("$bucket")).get("output"); + return getAsDBObject(getAsDBObject(fromBucketClause, "$bucket"), "output"); } } From bcb63b273251d4de644124cc829eefac6c0fadac Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 9 Dec 2016 15:08:40 +0100 Subject: [PATCH 043/118] DATAMONGO-1552 - Add $facet aggregation stage. Original Pull Request: #426 --- .../mongodb/core/aggregation/Aggregation.java | 83 ++----- .../AggregationOperationRenderer.java | 109 +++++++++ .../core/aggregation/FacetOperation.java | 231 ++++++++++++++++++ .../core/aggregation/AggregationTests.java | 48 ++++ .../aggregation/AggregationUnitTests.java | 17 ++ .../aggregation/FacetOperationUnitTests.java | 117 +++++++++ 6 files changed, 546 insertions(+), 59 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java 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 25b816b385..124dd6517c 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 @@ -27,6 +27,7 @@ import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; +import org.springframework.data.mongodb.core.aggregation.FacetOperation.FacetOperationBuilder; import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; import org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder; import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootDocumentOperationBuilder; @@ -68,7 +69,7 @@ public class Aggregation { */ public static final String CURRENT = SystemVariable.CURRENT.toString(); - public static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext(); + public static final AggregationOperationContext DEFAULT_CONTEXT = AggregationOperationRenderer.DEFAULT_CONTEXT; public static final AggregationOptions DEFAULT_OPTIONS = newAggregationOptions().build(); protected final List operations; @@ -460,6 +461,25 @@ public static BucketAutoOperation bucketAuto(AggregationExpression groupByExpres return new BucketAutoOperation(groupByExpression, buckets); } + /** + * Creates a new {@link FacetOperation}. + * + * @return + */ + public static FacetOperation facet() { + return FacetOperation.EMPTY; + } + + /** + * Creates a new {@link FacetOperationBuilder} given {@link Aggregation}. + * + * @param aggregationOperations the sub-pipeline, must not be {@literal null}. + * @return + */ + public static FacetOperationBuilder facet(AggregationOperation... aggregationOperations) { + return facet().and(aggregationOperations); + } + /** * Creates a new {@link LookupOperation}. * @@ -551,26 +571,7 @@ public static AggregationOptions.Builder newAggregationOptions() { */ public DBObject toDbObject(String inputCollectionName, AggregationOperationContext rootContext) { - AggregationOperationContext context = rootContext; - List operationDocuments = new ArrayList(operations.size()); - - for (AggregationOperation operation : operations) { - - operationDocuments.add(operation.toDBObject(context)); - - if (operation instanceof FieldsExposingAggregationOperation) { - - FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation; - ExposedFields fields = exposedFieldsOperation.getFields(); - - if (operation instanceof InheritsFieldsAggregationOperation) { - context = new InheritingExposedFieldsAggregationOperationContext(fields, context); - } else { - context = fields.exposesNoFields() ? DEFAULT_CONTEXT - : new ExposedFieldsAggregationOperationContext(fields, context); - } - } - } + List operationDocuments = AggregationOperationRenderer.toDBObject(operations, rootContext); DBObject command = new BasicDBObject("aggregate", inputCollectionName); command.put("pipeline", operationDocuments); @@ -586,43 +587,7 @@ public DBObject toDbObject(String inputCollectionName, AggregationOperationConte */ @Override public String toString() { - return SerializationUtils - .serializeToJsonSafely(toDbObject("__collection__", new NoOpAggregationOperationContext())); - } - - /** - * Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is. - * - * @author Oliver Gierke - */ - private static class NoOpAggregationOperationContext implements AggregationOperationContext { - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.DBObject) - */ - @Override - public DBObject getMappedObject(DBObject dbObject) { - return dbObject; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.ExposedFields.AvailableField) - */ - @Override - public FieldReference getReference(Field field) { - return new DirectFieldReference(new ExposedField(field, true)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String) - */ - @Override - public FieldReference getReference(String name) { - return new DirectFieldReference(new ExposedField(new AggregationField(name), true)); - } + return SerializationUtils.serializeToJsonSafely(toDbObject("__collection__", DEFAULT_CONTEXT)); } /** @@ -662,7 +627,7 @@ public static boolean isReferingToSystemVariable(String fieldRef) { return false; } - /* + /* * (non-Javadoc) * @see java.lang.Enum#toString() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java new file mode 100644 index 0000000000..20dcb58a7d --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java @@ -0,0 +1,109 @@ +/* + * 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.DirectFieldReference; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; +import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; +import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; + +import com.mongodb.DBObject; + +/** + * Rendering support for {@link AggregationOperation} into a {@link List} of {@link com.mongodb.DBObject}. + * + * @author Mark Paluch + * @author Christoph Strobl + * @since 1.10 + */ +class AggregationOperationRenderer { + + static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext(); + + /** + * Render a {@link List} of {@link AggregationOperation} given {@link AggregationOperationContext} into their + * {@link DBObject} representation. + * + * @param operations must not be {@literal null}. + * @param context must not be {@literal null}. + * @return the {@link List} of {@link DBObject}. + */ + static List toDBObject(List operations, AggregationOperationContext rootContext) { + + List operationDocuments = new ArrayList(operations.size()); + + AggregationOperationContext contextToUse = rootContext; + + for (AggregationOperation operation : operations) { + + operationDocuments.add(operation.toDBObject(contextToUse)); + + if (operation instanceof FieldsExposingAggregationOperation) { + + FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation; + ExposedFields fields = exposedFieldsOperation.getFields(); + + if (operation instanceof InheritsFieldsAggregationOperation) { + contextToUse = new InheritingExposedFieldsAggregationOperationContext(fields, contextToUse); + } else { + contextToUse = fields.exposesNoFields() ? DEFAULT_CONTEXT + : new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), contextToUse); + } + } + } + + return operationDocuments; + } + + /** + * Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is. + * + * @author Oliver Gierke + */ + private static class NoOpAggregationOperationContext implements AggregationOperationContext { + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.DBObject) + */ + @Override + public DBObject getMappedObject(DBObject dbObject) { + return dbObject; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.ExposedFields.AvailableField) + */ + @Override + public FieldReference getReference(Field field) { + return new DirectFieldReference(new ExposedField(field, true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String) + */ + @Override + public FieldReference getReference(String name) { + return new DirectFieldReference(new ExposedField(new AggregationField(name), true)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java new file mode 100644 index 0000000000..9082acedf0 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java @@ -0,0 +1,231 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Output; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Encapsulates the aggregation framework {@code $facet}-operation. + *

+ * Facet of {@link AggregationOperation}s to be used in an {@link Aggregation}. Processes multiple + * {@link AggregationOperation} pipelines within a single stage on the same set of input documents. Each sub-pipeline + * has its own field in the output document where its results are stored as an array of documents. + * {@link FacetOperation} enables various aggregations on the same set of input documents, without needing to retrieve + * the input documents multiple times. + *

+ * As of MongoDB 3.4, {@link FacetOperation} cannot be used with nested pipelines containing {@link GeoNearOperation}, + * {@link OutOperation} and {@link FacetOperation}. + *

+ * We recommend to use the static factory method {@link Aggregation#facet()} instead of creating instances of this class + * directly. + * + * @see http://docs.mongodb.org/manual/reference/aggregation/facet/ + * @author Mark Paluch + * @since 1.10 + */ +public class FacetOperation implements FieldsExposingAggregationOperation { + + /** + * Empty (initial) {@link FacetOperation}. + */ + public static final FacetOperation EMPTY = new FacetOperation(); + + private final Facets facets; + + /** + * Creates a new {@link FacetOperation}. + */ + public FacetOperation() { + this(Facets.EMPTY); + } + + private FacetOperation(Facets facets) { + this.facets = facets; + } + + /** + * Creates a new {@link FacetOperationBuilder} to append a new facet using {@literal operations}. + *

+ * {@link FacetOperationBuilder} takes a pipeline of {@link AggregationOperation} to categorize documents into a + * single facet. + * + * @param operations must not be {@literal null} or empty. + * @return + */ + public FacetOperationBuilder and(AggregationOperation... operations) { + + Assert.notNull(operations, "AggregationOperations must not be null!"); + Assert.notEmpty(operations, "AggregationOperations must not be empty!"); + + return new FacetOperationBuilder(facets, Arrays.asList(operations)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + return new BasicDBObject("$facet", facets.toDBObject(context)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return facets.asExposedFields(); + } + + /** + * Builder for {@link FacetOperation} by adding existing and the new pipeline of {@link AggregationOperation} to the + * new {@link FacetOperation}. + * + * @author Mark Paluch + */ + public static class FacetOperationBuilder { + + private final Facets current; + private final List operations; + + private FacetOperationBuilder(Facets current, List operations) { + this.current = current; + this.operations = operations; + } + + /** + * Creates a new {@link FacetOperation} that contains the configured pipeline of {@link AggregationOperation} + * exposed as {@literal fieldName} in the resulting facet document. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + public FacetOperation as(String fieldName) { + + Assert.hasText(fieldName, "FieldName must not be null or empty!"); + + return new FacetOperation(current.and(fieldName, operations)); + } + } + + /** + * Encapsulates multiple {@link Facet}s + * + * @author Mark Paluch + */ + private static class Facets { + + private static final Facets EMPTY = new Facets(Collections. emptyList()); + + private List facets; + + /** + * Creates a new {@link Facets} given {@link List} of {@link Facet}. + * + * @param facets + */ + private Facets(List facets) { + this.facets = facets; + } + + /** + * @return the {@link ExposedFields} derived from {@link Output}. + */ + protected ExposedFields asExposedFields() { + + ExposedFields fields = ExposedFields.from(); + + for (Facet facet : facets) { + fields = fields.and(facet.getExposedField()); + } + + return fields; + } + + protected DBObject toDBObject(AggregationOperationContext context) { + + DBObject dbObject = new BasicDBObject(facets.size()); + + for (Facet facet : facets) { + dbObject.put(facet.getExposedField().getName(), facet.toDBObjects(context)); + } + + return dbObject; + } + + /** + * Adds a facet to this {@link Facets}. + * + * @param fieldName must not be {@literal null}. + * @param operations must not be {@literal null}. + * @return the new {@link Facets}. + */ + public Facets and(String fieldName, List operations) { + + Assert.hasText(fieldName, "FieldName must not be null or empty!"); + Assert.notNull(operations, "AggregationOperations must not be null!"); + + List facets = new ArrayList(this.facets.size() + 1); + facets.addAll(this.facets); + facets.add(new Facet(new ExposedField(fieldName, true), operations)); + + return new Facets(facets); + } + } + + /** + * A single facet with a {@link ExposedField} and its {@link AggregationOperation} pipeline. + * + * @author Mark Paluch + */ + private static class Facet { + + private final ExposedField exposedField; + private final List operations; + + /** + * Creates a new {@link Facet} given {@link ExposedField} and {@link AggregationOperation} pipeline. + * + * @param exposedField must not be {@literal null}. + * @param operations must not be {@literal null}. + */ + protected Facet(ExposedField exposedField, List operations) { + + Assert.notNull(exposedField, "ExposedField must not be null!"); + Assert.notNull(operations, "AggregationOperations must not be null!"); + + this.exposedField = exposedField; + this.operations = operations; + } + + protected ExposedField getExposedField() { + return exposedField; + } + + protected List toDBObjects(AggregationOperationContext context) { + return AggregationOperationRenderer.toDBObject(operations, context); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 63243e1b84..6112c86a9c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -1747,6 +1747,54 @@ public void bucketAutoShouldCollectDocumentsIntoABucket() { assertThat((Double) bound1.get("sum"), is(closeTo(1673.0, 0.1))); } + /** + * @see DATAMONGO-1552 + */ + @Test + public void facetShouldCreateFacets() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + BucketAutoOperation bucketPrice = bucketAuto(Multiply.valueOf("price").multiplyBy(10), 3) // + .withGranularity(Granularities.E12) // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10") // + .sum().as("sum"); + + TypedAggregation aggregation = newAggregation(Art.class, // + project("title", "artist", "year", "price"), // + facet(bucketPrice).as("categorizeByPrice") // + .and(bucketAuto("year", 3)).as("categorizeByYear")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(1)); + + DBObject mappedResult = result.getUniqueMappedResult(); + + // [ { "_id" : { "min" : 680.0 , "max" : 820.0} , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + // , + // { "_id" : { "min" : 820.0 , "max" : 1800.0} , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : + // 1673.0} , + // { "_id" : { "min" : 1800.0 , "max" : 3300.0} , "count" : 2 , "titles" : [ "The Pillars of Society" , "Melancholy + // III"] , "sum" : 4799.9}] + BasicDBList categorizeByPrice = (BasicDBList) mappedResult.get("categorizeByPrice"); + assertThat(categorizeByPrice, hasSize(3)); + + // [ { "_id" : { "min" : null , "max" : 1902} , "count" : 1} , + // { "_id" : { "min" : 1902 , "max" : 1925} , "count" : 1} , + // { "_id" : { "min" : 1925 , "max" : 1926} , "count" : 2}] + BasicDBList categorizeByYear = (BasicDBList) mappedResult.get("categorizeByYear"); + assertThat(categorizeByYear, hasSize(3)); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index 275d35c3b4..c865f2a269 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -578,6 +578,23 @@ public void shouldRenderProjectionIfNullWithFallbackFieldReferenceCorrectly() { isBsonObject().containing("$ifNull", Arrays.asList("$chroma", "$fallback"))); } + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldHonorDefaultCountField() { + + DBObject agg = Aggregation + .newAggregation(// + bucket("year"), // + project("count")) // + .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + DBObject project = extractPipelineElement(agg, 1, "$project"); + + assertThat(project, isBsonObject().containing("count", 1)); + } + private DBObject extractPipelineElement(DBObject agg, int index, String operation) { List pipeline = (List) agg.get("pipeline"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java new file mode 100644 index 0000000000..ee40128d57 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java @@ -0,0 +1,117 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; + +import org.junit.Test; +import org.springframework.data.mongodb.core.query.Criteria; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link FacetOperation}. + * + * @author Mark Paluch + * @soundtrack Stanley Foort - You Make Me Believe In Magic (Extended Mix) + */ +public class FacetOperationUnitTests { + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderCorrectly() throws Exception { + + FacetOperation facetOperation = new FacetOperation() + .and(match(Criteria.where("price").exists(true)), // + bucket("price") // + .withBoundaries(0, 150, 200, 300, 400) // + .withDefaultBucket("Other") // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles")) // + .as("categorizedByPrice") // + .and(bucketAuto("year", 5)).as("categorizedByYears"); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $facet: { categorizedByPrice: [" + "{ $match: { price: { $exists: true } } }, " + + "{ $bucket: { boundaries: [ 0, 150, 200, 300, 400 ], groupBy: \"$price\", default: \"Other\", " + + "output: { count: { $sum: 1 }, titles: { $push: \"$title\" } } } } ]," + + "categorizedByYears: [ { $bucketAuto: { buckets: 5, groupBy: \"$year\" } } ] } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderEmpty() throws Exception { + + FacetOperation facetOperation = facet(); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $facet: { } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test(expected = IllegalArgumentException.class) + public void shouldRejectNonExistingFields() throws Exception { + + FacetOperation facetOperation = new FacetOperation() + .and(project("price"), // + bucket("price") // + .withBoundaries(0, 150, 200, 300, 400) // + .withDefaultBucket("Other") // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles")) // + .as("categorizedByPrice"); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $facet: { categorizedByPrice: [" + "{ $match: { price: { $exists: true } } }, " + + "{ $bucket: {boundaries: [ 0, 150, 200, 300, 400 ], groupBy: \"$price\", default: \"Other\", " + + "output: { count: { $sum: 1 }, titles: { $push: \"$title\" } } } } ]," + + "categorizedByYears: [ { $bucketAuto: { buckets: 5, groupBy: \"$year\" } } ] } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldHonorProjectedFields() { + + FacetOperation facetOperation = new FacetOperation() + .and(project("price").and("title").as("name"), // + bucketAuto("price", 5) // + .andOutput("name").push().as("titles")) // + .as("categorizedByPrice"); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $facet: { categorizedByPrice: [" + "{ $project: { price: 1, name: \"$title\" } }, " + + "{ $bucketAuto: { buckets: 5, groupBy: \"$price\", " + + "output: { titles: { $push: \"$name\" } } } } ] } }"))); + } +} From d250f88c3893b1c32f01de79750560685713b450 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 9 Dec 2016 15:08:56 +0100 Subject: [PATCH 044/118] DATAMONGO-1552 - Update Documentation. Original Pull Request: #426 --- src/main/asciidoc/new-features.adoc | 1 + src/main/asciidoc/reference/mongodb.adoc | 82 +++++++++++++++++++++++- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 8066c6a086..dad476b2eb 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -5,6 +5,7 @@ == What's new in Spring Data MongoDB 1.10 * Support for `$min`, `$max` and `$slice` operators via `Update`. * Support for `$cond` and `$ifNull` operators via `Aggregation`. +* Multi-faceted aggregations using `$facet`, `$bucket` and `$bucketAuto` via `Aggregation`. [[new-features.1-9-0]] == What's new in Spring Data MongoDB 1.9 diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 4994ca5984..6a49c3f114 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1674,7 +1674,7 @@ At the time of this writing we provide support for the following Aggregation Ope [cols="2*"] |=== | Pipeline Aggregation Operators -| count, geoNear, graphLookup, group, limit, lookup, match, project, replaceRoot, skip, sort, unwind +| bucket, bucketAuto, count, facet, geoNear, graphLookup, group, limit, lookup, match, project, replaceRoot, skip, sort, unwind | Set Aggregation Operators | setEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTrue @@ -1735,10 +1735,88 @@ Note that more examples for project operations can be found in the `AggregationT Note that further details regarding the projection expressions can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/project/#pipe._S_project[corresponding section] of the MongoDB Aggregation Framework reference documentation. +[[mongo.aggregation.facet]] +=== Faceted classification + +MongoDB supports as of Version 3.4 faceted classification using the Aggregation Framework. A faceted classification uses semantic categories, either general or subject-specific, that are combined to create the full classification entry. Documents flowing through the aggregation pipeline are classificated into buckets. A multi-faceted classification enables various aggregations on the same set of input documents, without needing to retrieve the input documents multiple times. + +==== Buckets + +Bucket operations categorize incoming documents into groups, called buckets, based on a specified expression and bucket boundaries. Bucket operations require a grouping field or grouping expression. They can be defined via the `bucket()`/`bucketAuto()` methods of the `Aggregate` class. `BucketOperation` and `BucketAutoOperation` can expose accumulations based on aggregation expressions for input documents. The bucket operation can be extended with additional parameters through a fluent API via the `with…()` methods, the `andOutput(String)` method and aliased via the `as(String)` method. Each bucket is represented as a document in the output. + +`BucketOperation` takes a defined set of boundaries to group incoming documents into these categories. Boundaries are required to be sorted. + +.Bucket operation examples +==== +[source,java] +---- +// will generate {$bucket: {groupBy: $price, boundaries: [0, 100, 400]}} +bucket("price").withBoundaries(0, 100, 400); + +// will generate {$bucket: {groupBy: $price, default: "Other" boundaries: [0, 100]}} +bucket("price").withBoundaries(0, 100).withDefault("Other"); + +// will generate {$bucket: {groupBy: $price, boundaries: [0, 100], output: { count: { $sum: 1}}}} +bucket("price").withBoundaries(0, 100).andOutputCount().as("count"); + +// will generate {$bucket: {groupBy: $price, boundaries: [0, 100], 5, output: { titles: { $push: "$title"}}} +bucket("price").withBoundaries(0, 100).andOutput("title").push().as("titles"); +---- +==== + +`BucketAutoOperation` determines boundaries itself in an attempt to evenly distribute documents into a specified number of buckets. `BucketAutoOperation` optionally takes a granularity specifies the https://en.wikipedia.org/wiki/Preferred_number[preferred number] series to use to ensure that the calculated boundary edges end on preferred round numbers or their powers of 10. + +.Bucket operation examples +==== +[source,java] +---- +// will generate {$bucketAuto: {groupBy: $price, buckets: 5}} +bucketAuto("price", 5) + +// will generate {$bucketAuto: {groupBy: $price, buckets: 5, granularity: "E24"}} +bucketAuto("price", 5).withGranularity(Granularities.E24).withDefault("Other"); + +// will generate {$bucketAuto: {groupBy: $price, buckets: 5, output: { titles: { $push: "$title"}}} +bucketAuto("price", 5).andOutput("title").push().as("titles"); +---- +==== + +Bucket operations can use `AggregationExpression` via `andOutput()` and <> via `andOutputExpression()` to create output fields in buckets. + +Note that further details regarding bucket expressions can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/bucket/[`$bucket` section] and +http://docs.mongodb.org/manual/reference/operator/aggregation/bucketAuto/[`$bucketAuto` section] of the MongoDB Aggregation Framework reference documentation. + +==== Multi-faceted aggregation + +Multiple aggregation pipelines can be used to create multi-faceted aggregations which characterize data across multiple dimensions, or facets, within a single aggregation stage. Multi-faceted aggregations provide multiple filters and categorizations to guide data browsing and analysis. A common implementation of faceting is how many online retailers provide ways to narrow down search results by applying filters on product price, manufacturer, size, etc. + +A `FacetOperation` can be defined via the `facet()` method of the `Aggregation` class. It can be customized with multiple aggregation pipelines via the `and()` method. Each sub-pipeline has its own field in the output document where its results are stored as an array of documents. + +Sub-pipelines can project and filter input documents prior grouping. Common cases are extraction of date parts or calculations before categorization. + +.Facet operation examples +==== +[source,java] +---- +// will generate {$facet: {categorizedByPrice: [ { $match: { price: {$exists : true}}}, { $bucketAuto: {groupBy: $price, buckets: 5}}]}} +facet(match(Criteria.where("price").exists(true)), bucketAuto("price", 5)).as("categorizedByPrice")) + +// will generate {$facet: {categorizedByYear: [ +// { $project: { title: 1, publicationYear: { $year: "publicationDate"}}}, +// { $bucketAuto: {groupBy: $price, buckets: 5, output: { titles: {$push:"$title"}}} +// ]}} +facet(project("title").and("publicationDate").extractYear().as("publicationYear"), + bucketAuto("publicationYear", 5).andOutput("title").push().as("titles")) + .as("categorizedByYear")) +---- +==== + +Note that further details regarding facet operation can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/facet/[`$facet` section] of the MongoDB Aggregation Framework reference documentation. + [[mongo.aggregation.projection.expressions]] ==== Spring Expression Support in Projection Expressions -As of Version 1.4.0 we support the use of SpEL expression in projection expressions via the `andExpression` method of the `ProjectionOperation` class. This allows you to define the desired expression as a SpEL expression which is translated into a corresponding MongoDB projection expression part on query execution. This makes it much easier to express complex calculations. +We support the use of SpEL expression in projection expressions via the `andExpression` method of the `ProjectionOperation` and `BucketOperation` classes. This allows you to define the desired expression as a SpEL expression which is translated into a corresponding MongoDB projection expression part on query execution. This makes it much easier to express complex calculations. ===== Complex calculations with SpEL expressions From c9c5fe62ca49fd5e2d6cb3a2c4c87adae092817e Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 14 Dec 2016 09:46:50 +0100 Subject: [PATCH 045/118] DATAMONGO-1552 - Polishing. Updated doc, removed whitespaces, minor method wording changes. Original Pull Request: #426 --- .../mongodb/core/aggregation/Aggregation.java | 6 ++ .../AggregationOperationRenderer.java | 4 +- .../core/aggregation/BucketAutoOperation.java | 42 +++++----- .../core/aggregation/BucketOperation.java | 17 ++-- .../aggregation/BucketOperationSupport.java | 78 +++++++------------ .../core/aggregation/FacetOperation.java | 39 +++++----- .../BucketAutoOperationUnitTests.java | 2 +- .../aggregation/BucketOperationUnitTests.java | 2 +- .../aggregation/FacetOperationUnitTests.java | 2 +- 9 files changed, 88 insertions(+), 104 deletions(-) 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 124dd6517c..efe60dcb3a 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 @@ -424,6 +424,7 @@ public static OutOperation out(String outCollectionName) { * * @param groupByField must not be {@literal null} or empty. * @return + * @since 1.10 */ public static BucketOperation bucket(String groupByField) { return new BucketOperation(field(groupByField)); @@ -434,6 +435,7 @@ public static BucketOperation bucket(String groupByField) { * * @param groupByExpression must not be {@literal null}. * @return + * @since 1.10 */ public static BucketOperation bucket(AggregationExpression groupByExpression) { return new BucketOperation(groupByExpression); @@ -445,6 +447,7 @@ public static BucketOperation bucket(AggregationExpression groupByExpression) { * @param groupByField must not be {@literal null} or empty. * @param buckets number of buckets, must be a positive integer. * @return + * @since 1.10 */ public static BucketAutoOperation bucketAuto(String groupByField, int buckets) { return new BucketAutoOperation(field(groupByField), buckets); @@ -456,6 +459,7 @@ public static BucketAutoOperation bucketAuto(String groupByField, int buckets) { * @param groupByExpression must not be {@literal null}. * @param buckets number of buckets, must be a positive integer. * @return + * @since 1.10 */ public static BucketAutoOperation bucketAuto(AggregationExpression groupByExpression, int buckets) { return new BucketAutoOperation(groupByExpression, buckets); @@ -465,6 +469,7 @@ public static BucketAutoOperation bucketAuto(AggregationExpression groupByExpres * Creates a new {@link FacetOperation}. * * @return + * @since 1.10 */ public static FacetOperation facet() { return FacetOperation.EMPTY; @@ -475,6 +480,7 @@ public static FacetOperation facet() { * * @param aggregationOperations the sub-pipeline, must not be {@literal null}. * @return + * @since 1.10 */ public static FacetOperationBuilder facet(AggregationOperation... aggregationOperations) { return facet().and(aggregationOperations); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java index 20dcb58a7d..0cb930df1d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java @@ -28,7 +28,7 @@ /** * Rendering support for {@link AggregationOperation} into a {@link List} of {@link com.mongodb.DBObject}. - * + * * @author Mark Paluch * @author Christoph Strobl * @since 1.10 @@ -40,7 +40,7 @@ class AggregationOperationRenderer { /** * Render a {@link List} of {@link AggregationOperation} given {@link AggregationOperationContext} into their * {@link DBObject} representation. - * + * * @param operations must not be {@literal null}. * @param context must not be {@literal null}. * @return the {@link List} of {@link DBObject}. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java index 9ad0d6f660..987d859167 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java @@ -23,18 +23,18 @@ import com.mongodb.DBObject; /** - * Encapsulates the aggregation framework {@code $bucketAuto}-operation. - *

+ * Encapsulates the aggregation framework {@code $bucketAuto}-operation.
* Bucket stage is typically used with {@link Aggregation} and {@code $facet}. Categorizes incoming documents into a * specific number of groups, called buckets, based on a specified expression. Bucket boundaries are automatically - * determined in an attempt to evenly distribute the documents into the specified number of buckets. - *

- * We recommend to use the static factory method {@link Aggregation#bucketAuto(String, int)} instead of creating instances of - * this class directly. + * determined in an attempt to evenly distribute the documents into the specified number of buckets.
+ * We recommend to use the static factory method {@link Aggregation#bucketAuto(String, int)} instead of creating + * instances of this class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/bucketAuto/ + * @see http://docs.mongodb.org/manual/reference/aggregation/bucketAuto/ * @see BucketOperationSupport * @author Mark Paluch + * @author Christoph Strobl * @since 1.10 */ public class BucketAutoOperation extends BucketOperationSupport @@ -100,13 +100,12 @@ public DBObject toDBObject(AggregationOperationContext context) { DBObject options = new BasicDBObject(); options.put("buckets", buckets); + options.putAll(super.toDBObject(context)); if (granularity != null) { options.put("granularity", granularity); } - options.putAll(super.toDBObject(context)); - return new BasicDBObject("$bucketAuto", options); } @@ -123,8 +122,10 @@ public BucketAutoOperation withBuckets(int buckets) { } /** - * Configures {@literal granularity} that specifies the preferred number series to use to ensure that the calculated - * boundary edges end on preferred round numbers or their powers of 10 and return a new {@link BucketAutoOperation}. + * Configures {@link Granularity granularity} that specifies the preferred number series to use to ensure that the + * calculated boundary edges end on preferred round numbers or their powers of 10 and return a new + * {@link BucketAutoOperation}.
+ * Use either predefined {@link Granularities} or provide a own one. * * @param granularity must not be {@literal null}. * @return @@ -133,7 +134,7 @@ public BucketAutoOperation withGranularity(Granularity granularity) { Assert.notNull(granularity, "Granularity must not be null!"); - return new BucketAutoOperation(this, buckets, granularity.toMongoGranularity()); + return new BucketAutoOperation(this, buckets, granularity.getMongoRepresentation()); } /* (non-Javadoc) @@ -196,7 +197,7 @@ protected BucketAutoOperationOutputBuilder apply(OperationOutput operationOutput /** * {@link ExpressionBucketOperationBuilderSupport} implementation for {@link BucketAutoOperation} using SpEL * expression based {@link Output}. - * + * * @author Mark Paluch */ public static class ExpressionBucketAutoOperationBuilder @@ -227,19 +228,20 @@ protected BucketAutoOperationOutputBuilder apply(OperationOutput operationOutput /** * @author Mark Paluch */ - public static interface Granularity { + public interface Granularity { /** - * @return a String that represents a MongoDB granularity to be used with {@link BucketAutoOperation}. + * @return a String that represents a MongoDB granularity to be used with {@link BucketAutoOperation}. Never + * {@literal null}. */ - String toMongoGranularity(); + String getMongoRepresentation(); } /** * Supported MongoDB granularities. * - * @see https://en.wikipedia.org/wiki/Preferred_number - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/bucketAuto/#granularity + * @see http://docs.mongodb.org/manual/reference/aggregation/bucket/ * @see BucketOperationSupport * @author Mark Paluch * @since 1.10 @@ -109,7 +109,7 @@ public DBObject toDBObject(AggregationOperationContext context) { /** * Configures a default bucket {@literal literal} and return a new {@link BucketOperation}. - * + * * @param literal must not be {@literal null}. * @return */ @@ -122,13 +122,14 @@ public BucketOperation withDefaultBucket(Object literal) { /** * Configures {@literal boundaries} and return a new {@link BucketOperation}. Existing {@literal boundaries} are * preserved and the new {@literal boundaries} are appended. - * + * * @param boundaries must not be {@literal null}. * @return */ public BucketOperation withBoundaries(Object... boundaries) { Assert.notNull(boundaries, "Boundaries must not be null!"); + Assert.noNullElements(boundaries, "Boundaries must not contain null values!"); List newBoundaries = new ArrayList(this.boundaries.size() + boundaries.length); newBoundaries.addAll(this.boundaries); @@ -197,7 +198,7 @@ protected BucketOperationOutputBuilder apply(OperationOutput operationOutput) { /** * {@link ExpressionBucketOperationBuilderSupport} implementation for {@link BucketOperation} using SpEL expression * based {@link Output}. - * + * * @author Mark Paluch */ public static class ExpressionBucketOperationBuilder diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java index 05a5bdb4cf..c78eddddd0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperationSupport.java @@ -31,14 +31,12 @@ import com.mongodb.DBObject; /** - * Base class for bucket operations that support output expressions the aggregation framework. - *

- * Bucket stages collect documents into buckets and can contribute output fields. - *

+ * Base class for bucket operations that support output expressions the aggregation framework.
+ * Bucket stages collect documents into buckets and can contribute output fields.
* Implementing classes are required to provide an {@link OutputBuilder}. * - * @see http://docs.mongodb.org/manual/reference/aggregation/bucket/ * @author Mark Paluch + * @author Christoph Strobl * @since 1.10 */ public abstract class BucketOperationSupport, B extends OutputBuilder> @@ -50,7 +48,7 @@ public abstract class BucketOperationSupport operationSupport) { @@ -88,7 +85,7 @@ protected BucketOperationSupport(BucketOperationSupport operationSupport) /** * Creates a copy of {@link BucketOperationSupport} and applies the new {@link Outputs}. - * + * * @param operationSupport must not be {@literal null}. * @param outputs must not be {@literal null}. */ @@ -105,7 +102,7 @@ protected BucketOperationSupport(BucketOperationSupport operationSupport, /** * Creates a new {@link ExpressionBucketOperationBuilderSupport} given a SpEL {@literal expression} and optional * {@literal params} to add an output field to the resulting bucket documents. - * + * * @param expression the SpEL expression, must not be {@literal null} or empty. * @param params must not be {@literal null} * @return @@ -186,7 +183,7 @@ protected T andOutput(Output output) { /** * Builder for SpEL expression-based {@link Output}. - * + * * @author Mark Paluch */ public abstract static class ExpressionBucketOperationBuilderSupport, T extends BucketOperationSupport> @@ -232,11 +229,9 @@ protected OutputBuilder(Object value, T operation) { } /** - * Generates a builder for a {@code $sum}-expression. - *

+ * Generates a builder for a {@code $sum}-expression.
* Count expressions are emulated via {@code $sum: 1}. - *

- * + * * @return */ public B count() { @@ -245,7 +240,7 @@ public B count() { /** * Generates a builder for a {@code $sum}-expression for the current value. - * + * * @return */ public B sum() { @@ -259,12 +254,12 @@ public B sum() { * @return */ public B sum(Number value) { - return apply(new OperationOutput(Accumulators.SUM.toString(), Collections.singleton(value))); + return apply(new OperationOutput(Accumulators.SUM.getMongoOperator(), Collections.singleton(value))); } /** * Generates a builder for an {@code $last}-expression for the current value.. - * + * * @return */ public B last() { @@ -273,7 +268,7 @@ public B last() { /** * Generates a builder for a {@code $first}-expression the current value. - * + * * @return */ public B first() { @@ -282,7 +277,7 @@ public B first() { /** * Generates a builder for an {@code $avg}-expression for the current value. - * + * * @param reference * @return */ @@ -292,7 +287,7 @@ public B avg() { /** * Generates a builder for an {@code $min}-expression for the current value. - * + * * @return */ public B min() { @@ -301,7 +296,7 @@ public B min() { /** * Generates a builder for an {@code $max}-expression for the current value. - * + * * @return */ public B max() { @@ -346,14 +341,14 @@ public B apply(String operation, Object... values) { /** * Apply an {@link OperationOutput} to this output. - * + * * @param operationOutput must not be {@literal null}. * @return */ protected abstract B apply(OperationOutput operationOutput); private B apply(Accumulators operation) { - return this.apply(operation.toString()); + return this.apply(operation.getMongoOperator()); } /** @@ -388,18 +383,14 @@ private enum Accumulators { this.mongoOperator = mongoOperator; } - /* (non-Javadoc) - * @see java.lang.Enum#toString() - */ - @Override - public String toString() { + public String getMongoOperator() { return mongoOperator; } } /** * Encapsulates {@link Output}s. - * + * * @author Mark Paluch */ protected static class Outputs implements AggregationExpression { @@ -417,7 +408,7 @@ private Outputs() { /** * Creates new {@link Outputs} containing all given {@link Output}s. - * + * * @param current * @param output */ @@ -449,7 +440,7 @@ protected ExposedFields asExposedFields() { /** * Create a new {@link Outputs} that contains the new {@link Output}. - * + * * @param output must not be {@literal null}. * @return the new {@link Outputs} that contains the new {@link Output} */ @@ -484,11 +475,10 @@ public DBObject toDbObject(AggregationOperationContext context) { } /** - * Encapsulates an output field in a bucket aggregation stage. - *

+ * Encapsulates an output field in a bucket aggregation stage.
* Output fields can be either top-level fields that define a valid field name or nested output fields using * operators. - * + * * @author Mark Paluch */ protected abstract static class Output implements AggregationExpression { @@ -514,17 +504,10 @@ protected Output(Field field) { protected ExposedField getExposedField() { return field; } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) - */ - @Override - public abstract DBObject toDbObject(AggregationOperationContext context); } /** - * Output field that uses a Mongo operation (expression object) to generate an output field value. - *

+ * Output field that uses a Mongo operation (expression object) to generate an output field value.
* {@link OperationOutput} is used either with a regular field name or an operation keyword (e.g. * {@literal $sum, $count}). * @@ -624,7 +607,6 @@ protected List getOperationArguments(AggregationOperationContext context // We have to make sure that we use the arguments from the "previous" OperationOutput that we replace // with this new instance. - return OperationOutput.this.getOperationArguments(context); } }; @@ -634,7 +616,7 @@ protected List getOperationArguments(AggregationOperationContext context /** * A {@link Output} based on a SpEL expression. */ - static class SpelExpressionOutput extends Output { + private static class SpelExpressionOutput extends Output { private static final SpelExpressionTransformer TRANSFORMER = new SpelExpressionTransformer(); @@ -663,11 +645,7 @@ public SpelExpressionOutput(String expression, Object[] parameters) { */ @Override public DBObject toDbObject(AggregationOperationContext context) { - return (DBObject) toMongoExpression(context, expression, params); - } - - protected static Object toMongoExpression(AggregationOperationContext context, String expression, Object[] params) { - return TRANSFORMER.transform(expression, context, params); + return (DBObject) TRANSFORMER.transform(expression, context, params); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java index 9082acedf0..bb1d6b8fe5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java @@ -28,22 +28,20 @@ import com.mongodb.DBObject; /** - * Encapsulates the aggregation framework {@code $facet}-operation. - *

+ * Encapsulates the aggregation framework {@code $facet}-operation.
* Facet of {@link AggregationOperation}s to be used in an {@link Aggregation}. Processes multiple * {@link AggregationOperation} pipelines within a single stage on the same set of input documents. Each sub-pipeline * has its own field in the output document where its results are stored as an array of documents. * {@link FacetOperation} enables various aggregations on the same set of input documents, without needing to retrieve - * the input documents multiple times. - *

+ * the input documents multiple times.
* As of MongoDB 3.4, {@link FacetOperation} cannot be used with nested pipelines containing {@link GeoNearOperation}, - * {@link OutOperation} and {@link FacetOperation}. - *

+ * {@link OutOperation} and {@link FacetOperation}.
* We recommend to use the static factory method {@link Aggregation#facet()} instead of creating instances of this class * directly. * * @see http://docs.mongodb.org/manual/reference/aggregation/facet/ * @author Mark Paluch + * @author Christoph Strobl * @since 1.10 */ public class FacetOperation implements FieldsExposingAggregationOperation { @@ -67,11 +65,10 @@ private FacetOperation(Facets facets) { } /** - * Creates a new {@link FacetOperationBuilder} to append a new facet using {@literal operations}. - *

+ * Creates a new {@link FacetOperationBuilder} to append a new facet using {@literal operations}.
* {@link FacetOperationBuilder} takes a pipeline of {@link AggregationOperation} to categorize documents into a * single facet. - * + * * @param operations must not be {@literal null} or empty. * @return */ @@ -118,7 +115,7 @@ private FacetOperationBuilder(Facets current, List operati /** * Creates a new {@link FacetOperation} that contains the configured pipeline of {@link AggregationOperation} * exposed as {@literal fieldName} in the resulting facet document. - * + * * @param fieldName must not be {@literal null} or empty. * @return */ @@ -132,7 +129,7 @@ public FacetOperation as(String fieldName) { /** * Encapsulates multiple {@link Facet}s - * + * * @author Mark Paluch */ private static class Facets { @@ -143,7 +140,7 @@ private static class Facets { /** * Creates a new {@link Facets} given {@link List} of {@link Facet}. - * + * * @param facets */ private Facets(List facets) { @@ -153,7 +150,7 @@ private Facets(List facets) { /** * @return the {@link ExposedFields} derived from {@link Output}. */ - protected ExposedFields asExposedFields() { + ExposedFields asExposedFields() { ExposedFields fields = ExposedFields.from(); @@ -164,7 +161,7 @@ protected ExposedFields asExposedFields() { return fields; } - protected DBObject toDBObject(AggregationOperationContext context) { + DBObject toDBObject(AggregationOperationContext context) { DBObject dbObject = new BasicDBObject(facets.size()); @@ -177,12 +174,12 @@ protected DBObject toDBObject(AggregationOperationContext context) { /** * Adds a facet to this {@link Facets}. - * + * * @param fieldName must not be {@literal null}. * @param operations must not be {@literal null}. * @return the new {@link Facets}. */ - public Facets and(String fieldName, List operations) { + Facets and(String fieldName, List operations) { Assert.hasText(fieldName, "FieldName must not be null or empty!"); Assert.notNull(operations, "AggregationOperations must not be null!"); @@ -197,7 +194,7 @@ public Facets and(String fieldName, List operations) { /** * A single facet with a {@link ExposedField} and its {@link AggregationOperation} pipeline. - * + * * @author Mark Paluch */ private static class Facet { @@ -207,11 +204,11 @@ private static class Facet { /** * Creates a new {@link Facet} given {@link ExposedField} and {@link AggregationOperation} pipeline. - * + * * @param exposedField must not be {@literal null}. * @param operations must not be {@literal null}. */ - protected Facet(ExposedField exposedField, List operations) { + Facet(ExposedField exposedField, List operations) { Assert.notNull(exposedField, "ExposedField must not be null!"); Assert.notNull(operations, "AggregationOperations must not be null!"); @@ -220,11 +217,11 @@ protected Facet(ExposedField exposedField, List operations this.operations = operations; } - protected ExposedField getExposedField() { + ExposedField getExposedField() { return exposedField; } - protected List toDBObjects(AggregationOperationContext context) { + List toDBObjects(AggregationOperationContext context) { return AggregationOperationRenderer.toDBObject(operations, context); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java index 77388af89c..19459dc093 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java @@ -28,7 +28,7 @@ /** * Unit tests for {@link BucketAutoOperation}. - * + * * @author Mark Paluch */ public class BucketAutoOperationUnitTests { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java index 5915c73b5d..c978f37c1c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java @@ -29,7 +29,7 @@ /** * Unit tests for {@link BucketOperation}. - * + * * @author Mark Paluch */ public class BucketOperationUnitTests { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java index ee40128d57..228102b7cd 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java @@ -27,7 +27,7 @@ /** * Unit tests for {@link FacetOperation}. - * + * * @author Mark Paluch * @soundtrack Stanley Foort - You Make Me Believe In Magic (Extended Mix) */ From 89a02bb8224a5997228fa5c250ecbcf589035f03 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 15 Dec 2016 14:57:10 +0100 Subject: [PATCH 046/118] DATAMONGO-1566 - Adapt API in MongoRepositoryFactoryBean. Related tickets: DATACMNS-891. --- .../support/MongoRepositoryFactoryBean.java | 13 +++++++++++-- .../mongodb/performance/PerformanceTests.java | 12 ++++++------ ...ngoRepositoriesRegistrarIntegrationTests.java | 16 ---------------- .../MongoRepositoryFactoryBeanUnitTests.java | 5 ++--- .../PersonRepositoryIntegrationTests-context.xml | 4 ++-- 5 files changed, 21 insertions(+), 29 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBean.java index abaee0270a..405c4e7f97 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBean.java @@ -30,13 +30,22 @@ * * @author Oliver Gierke */ -public class MongoRepositoryFactoryBean, S, ID extends Serializable> extends - RepositoryFactoryBeanSupport { +public class MongoRepositoryFactoryBean, S, ID extends Serializable> + extends RepositoryFactoryBeanSupport { private MongoOperations operations; private boolean createIndexesForQueryMethods = false; private boolean mappingContextConfigured = false; + /** + * Creates a new {@link MongoRepositoryFactoryBean} for the given repository interface. + * + * @param repositoryInterface must not be {@literal null}. + */ + public MongoRepositoryFactoryBean(Class repositoryInterface) { + super(repositoryInterface); + } + /** * Configures the {@link MongoOperations} to be used. * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java index 9155721216..a135a05ae4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java @@ -97,9 +97,9 @@ public void setUp() throws Exception { this.converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), context); this.operations = new MongoTemplate(new SimpleMongoDbFactory(this.mongo, DATABASE_NAME), converter); - MongoRepositoryFactoryBean factory = new MongoRepositoryFactoryBean(); + MongoRepositoryFactoryBean factory = new MongoRepositoryFactoryBean( + PersonRepository.class); factory.setMongoOperations(operations); - factory.setRepositoryInterface(PersonRepository.class); factory.afterPropertiesSet(); this.repository = factory.getObject(); @@ -125,8 +125,8 @@ public void doWithWriteConcern(String constantName, WriteConcern concern) { @Test public void plainConversion() throws InterruptedException { - Statistics statistics = new Statistics("Plain conversion of " + NUMBER_OF_PERSONS * 100 - + " persons - After %s iterations"); + Statistics statistics = new Statistics( + "Plain conversion of " + NUMBER_OF_PERSONS * 100 + " persons - After %s iterations"); List dbObjects = getPersonDBObjects(NUMBER_OF_PERSONS * 100); @@ -842,8 +842,8 @@ public String print(double referenceAverage, double referenceMedian) { */ @Override public String toString() { - return times.isEmpty() ? "" : String.format("%s, %s: %s", api, mode, - StringUtils.collectionToCommaDelimitedString(times)) + '\n'; + return times.isEmpty() ? "" + : String.format("%s, %s: %s", api, mode, StringUtils.collectionToCommaDelimitedString(times)) + '\n'; } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoriesRegistrarIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoriesRegistrarIntegrationTests.java index 32d49c7b8a..3d1a0f11d7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoriesRegistrarIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoriesRegistrarIntegrationTests.java @@ -15,11 +15,6 @@ */ package org.springframework.data.mongodb.repository.config; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - -import java.util.Arrays; - import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -59,15 +54,4 @@ public MongoOperations mongoTemplate() throws Exception { @Test public void testConfiguration() {} - - /** - * @see DATAMONGO-901 - */ - @Test - public void registersTypePredictingPostProcessor() { - - Iterable beanNames = Arrays.asList(context.getBeanDefinitionNames()); - - assertThat(beanNames, hasItem(containsString("RepositoryFactoryBeanSupport_Predictor"))); - } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBeanUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBeanUnitTests.java index 0d0365e557..76f8185183 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBeanUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryBeanUnitTests.java @@ -48,7 +48,7 @@ public class MongoRepositoryFactoryBeanUnitTests { @SuppressWarnings("rawtypes") public void addsIndexEnsuringQueryCreationListenerIfConfigured() { - MongoRepositoryFactoryBean factory = new MongoRepositoryFactoryBean(); + MongoRepositoryFactoryBean factory = new MongoRepositoryFactoryBean(ContactRepository.class); factory.setCreateIndexesForQueryMethods(true); List listeners = getListenersFromFactory(factory); @@ -60,7 +60,7 @@ public void addsIndexEnsuringQueryCreationListenerIfConfigured() { @SuppressWarnings("rawtypes") public void doesNotAddIndexEnsuringQueryCreationListenerByDefault() { - List listeners = getListenersFromFactory(new MongoRepositoryFactoryBean()); + List listeners = getListenersFromFactory(new MongoRepositoryFactoryBean(ContactRepository.class)); assertThat(listeners.size(), is(1)); } @@ -72,7 +72,6 @@ private List getListenersFromFactory(MongoRepositoryFactoryBean factoryB factoryBean.setLazyInit(true); factoryBean.setMongoOperations(operations); - factoryBean.setRepositoryInterface(ContactRepository.class); factoryBean.afterPropertiesSet(); RepositoryFactorySupport factory = factoryBean.createRepositoryFactory(); diff --git a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml index 7e7271d855..119940c359 100644 --- a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml +++ b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml @@ -17,8 +17,8 @@ + - @@ -34,5 +34,5 @@ - + From 35bfb92ace981a19782a10cfbbcd53bd55a44e7a Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 14 Dec 2016 14:41:40 +0100 Subject: [PATCH 047/118] DATAMONGO-1533 - Add AggregationExpression derived from SpEL AST. We added an AggregationExpression that renders a MongoDB Aggregation Framework expression from the AST of a SpEL expression. This allows usage with various stages (eg. $project, $group) throughout the aggregation support. // { $and: [ { $gt: [ "$qty", 100 ] }, { $lt: [ "$qty", 250 ] } ] } expressionOf("qty > 100 && qty < 250); // { $cond : { if : { $gte : [ "$a", 42 ]}, then : "answer", else : "no-answer" } } expressionOf("cond(a >= 42, 'answer', 'no-answer')"); Original pull request: #428. --- .../AggregationSpELExpression.java | 70 +++++++++++++++++++ .../aggregation/AggregationUnitTests.java | 22 ++++++ 2 files changed, 92 insertions(+) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java new file mode 100644 index 0000000000..fedeb09834 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java @@ -0,0 +1,70 @@ +/* + * 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 com.mongodb.DBObject; +import org.springframework.util.Assert; + +/** + * An {@link AggregationExpression} that renders a MongoDB Aggregation Framework expression from the AST of a + * SpEL + * expression.
+ *
+ * Samples:
+ * + *
+ * // { $and: [ { $gt: [ "$qty", 100 ] }, { $lt: [ "$qty", 250 ] } ] }
+ * expressionOf("qty > 100 && qty < 250);
+ *
+ * // { $cond : { if : { $gte : [ "$a", 42 ]}, then : "answer", else : "no-answer" } }
+ * expressionOf("cond(a >= 42, 'answer', 'no-answer')");
+ * 
+ *
+ * + * @author Christoph Strobl + * @see SpelExpressionTransformer + * @since 1.10 + */ +public class AggregationSpELExpression implements AggregationExpression { + + private static final SpelExpressionTransformer TRANSFORMER = new SpelExpressionTransformer(); + private final String rawExpression; + private final Object[] parameters; + + private AggregationSpELExpression(String rawExpression, Object[] parameters) { + + this.rawExpression = rawExpression; + this.parameters = parameters; + } + + /** + * Creates new {@link AggregationSpELExpression} for the given {@literal expressionString} and {@literal parameters}. + * + * @param expression must not be {@literal null}. + * @param parameters can be empty. + * @return + */ + public static AggregationSpELExpression expressionOf(String expressionString, Object... parameters) { + + Assert.notNull(expressionString, "ExpressionString must not be null!"); + return new AggregationSpELExpression(expressionString, parameters); + } + + @Override + public DBObject toDbObject(AggregationOperationContext context) { + return (DBObject) TRANSFORMER.transform(rawExpression, context, parameters); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index c865f2a269..142e173526 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -37,6 +37,7 @@ import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; +import org.springframework.data.mongodb.test.util.BasicDbListBuilder; /** * Unit tests for {@link Aggregation}. @@ -595,6 +596,27 @@ public void shouldHonorDefaultCountField() { assertThat(project, isBsonObject().containing("count", 1)); } + /** + * @see DATAMONGO-1533 + */ + @Test + public void groupOperationShouldAllowUsageOfDerivedSpELAggregationExpression() { + + DBObject agg = newAggregation( // + project("a"), // + group("a").first(AggregationSpELExpression.expressionOf("cond(a >= 42, 'answer', 'no-answer')")).as("foosum") // + ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + @SuppressWarnings("unchecked") + DBObject secondProjection = ((List) agg.get("pipeline")).get(1); + DBObject fields = getAsDBObject(secondProjection, "$group"); + assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first")); + assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.if", + new BasicDBObject("$gte", new BasicDbListBuilder().add("$a").add(42).get()))); + assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.then", "answer")); + assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.else", "no-answer")); + } + private DBObject extractPipelineElement(DBObject agg, int index, String operation) { List pipeline = (List) agg.get("pipeline"); From 6993054d6ae2efe3bcb9e433d92b8b7bd15fc2cb Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 16 Dec 2016 08:55:21 +0100 Subject: [PATCH 048/118] DATAMONGO-1533 - Polishing. Enhance JavaDoc. Minor reformatting. Original pull request: #428. --- .../core/aggregation/AggregationSpELExpression.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java index fedeb09834..2773ee16f5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationSpELExpression.java @@ -15,9 +15,10 @@ */ package org.springframework.data.mongodb.core.aggregation; -import com.mongodb.DBObject; import org.springframework.util.Assert; +import com.mongodb.DBObject; + /** * An {@link AggregationExpression} that renders a MongoDB Aggregation Framework expression from the AST of a * SpEL @@ -53,7 +54,7 @@ private AggregationSpELExpression(String rawExpression, Object[] parameters) { /** * Creates new {@link AggregationSpELExpression} for the given {@literal expressionString} and {@literal parameters}. * - * @param expression must not be {@literal null}. + * @param expressionString must not be {@literal null}. * @param parameters can be empty. * @return */ @@ -63,6 +64,9 @@ public static AggregationSpELExpression expressionOf(String expressionString, Ob return new AggregationSpELExpression(expressionString, parameters); } + /* (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 (DBObject) TRANSFORMER.transform(rawExpression, context, parameters); From 6236384c1d4229df78e484347efba42007457a86 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 16 Dec 2016 10:31:22 +0100 Subject: [PATCH 049/118] DATAMONGO-1567 - Use newer Java 8 on Travis CI. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 62909f34f7..d078db8f05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,7 @@ addons: packages: - mongodb-org-server - mongodb-org-shell + - oracle-java8-installer sudo: false From 75139042e050e1ef853292b82ea12e6bf65ef3f9 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 15 Dec 2016 09:18:39 +0100 Subject: [PATCH 050/118] DATAMONGO-1564 - Split up AggregationExpressions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactored to multiple smaller Aggregation Operator classes reflecting the grouping (array operators, string operators,…) predefined by MongoDB. Original pull request: #429. --- .../AbstractAggregationExpression.java | 150 + .../aggregation/AccumulatorOperators.java | 642 ++ .../aggregation/AggregationExpressions.java | 8656 ----------------- .../core/aggregation/ArithmeticOperators.java | 1421 +++ .../core/aggregation/ArrayOperators.java | 1517 +++ .../core/aggregation/BooleanOperators.java | 353 + .../core/aggregation/ComparisonOperators.java | 879 ++ .../aggregation/ConditionalOperators.java | 975 ++ .../core/aggregation/DataTypeOperators.java | 67 + .../core/aggregation/DateOperators.java | 838 ++ .../core/aggregation/LiteralOperators.java | 96 + .../core/aggregation/ProjectionOperation.java | 50 +- .../core/aggregation/SetOperators.java | 666 ++ .../core/aggregation/StringOperators.java | 1089 +++ .../core/aggregation/VariableOperators.java | 391 + .../core/aggregation/AggregationTests.java | 16 +- .../aggregation/AggregationUnitTests.java | 10 +- .../aggregation/BucketOperationUnitTests.java | 1 - .../aggregation/CondExpressionUnitTests.java | 5 +- .../FilterExpressionUnitTests.java | 2 +- .../GraphLookupOperationUnitTests.java | 5 +- .../ProjectionOperationUnitTests.java | 43 +- .../ReplaceRootOperationUnitTests.java | 1 - ...dAggregationOperationContextUnitTests.java | 1 - 24 files changed, 9137 insertions(+), 8737 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java new file mode 100644 index 0000000000..45348ea188 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java @@ -0,0 +1,150 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import org.springframework.util.ObjectUtils; + +/** + * @author Christoph Strobl + * @since 1.10 + */ +abstract class AbstractAggregationExpression implements AggregationExpression { + + private final Object value; + + 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); + } + + public DBObject toDbObject(Object value, AggregationOperationContext context) { + + Object valueToUse; + if (value instanceof List) { + + List arguments = (List) value; + List args = new ArrayList(arguments.size()); + + for (Object val : arguments) { + args.add(unpack(val, context)); + } + valueToUse = args; + } else if (value instanceof java.util.Map) { + + DBObject dbo = new BasicDBObject(); + for (java.util.Map.Entry entry : ((java.util.Map) value).entrySet()) { + dbo.put(entry.getKey(), unpack(entry.getValue(), context)); + } + valueToUse = dbo; + } else { + valueToUse = unpack(value, context); + } + + return new BasicDBObject(getMongoMethod(), valueToUse); + } + + protected static List asFields(String... fieldRefs) { + + if (ObjectUtils.isEmpty(fieldRefs)) { + return Collections.emptyList(); + } + + return Fields.fields(fieldRefs).asList(); + } + + private Object unpack(Object value, AggregationOperationContext context) { + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + if (value instanceof Field) { + 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; + } + + protected List append(Object value) { + + if (this.value instanceof List) { + + List clone = new ArrayList((List) this.value); + + if (value instanceof List) { + for (Object val : (List) value) { + clone.add(val); + } + } else { + clone.add(value); + } + return clone; + } + + return Arrays.asList(this.value, value); + } + + protected java.util.Map append(String key, Object value) { + + if (!(this.value instanceof java.util.Map)) { + throw new IllegalArgumentException("o_O"); + } + 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(); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java new file mode 100644 index 0000000000..43cab0347c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java @@ -0,0 +1,642 @@ +/* + * 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.Collections; +import java.util.List; + +import org.springframework.util.Assert; + +import com.mongodb.DBObject; + +/** + * Gateway to {@literal accumulator} aggregation operations. + * + * @author Christoph Strobl + * @since 1.10 + * @soundtrack Rage Against The Machine - Killing In The Name + */ +public class AccumulatorOperators { + + /** + * Take the numeric value referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static AccumulatorOperatorFactory valueOf(String fieldReference) { + return new AccumulatorOperatorFactory(fieldReference); + } + + /** + * Take the numeric value referenced resulting from given {@link AggregationExpression}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static AccumulatorOperatorFactory valueOf(AggregationExpression expression) { + return new AccumulatorOperatorFactory(expression); + } + + /** + * @author Christoph Strobl + */ + public static class AccumulatorOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link AccumulatorOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public AccumulatorOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link ArrayOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public AccumulatorOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and calculates and + * returns the sum. + * + * @return + */ + public Sum sum() { + return usesFieldRef() ? Sum.sumOf(fieldReference) : Sum.sumOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and returns the + * average value. + * + * @return + */ + public Avg avg() { + return usesFieldRef() ? Avg.avgOf(fieldReference) : Avg.avgOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and returns the + * maximum value. + * + * @return + */ + public Max max() { + return usesFieldRef() ? Max.maxOf(fieldReference) : Max.maxOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and returns the + * minimum value. + * + * @return + */ + public Min min() { + return usesFieldRef() ? Min.minOf(fieldReference) : Min.minOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and calculates the + * population standard deviation of the input values. + * + * @return + */ + public StdDevPop stdDevPop() { + return usesFieldRef() ? StdDevPop.stdDevPopOf(fieldReference) : StdDevPop.stdDevPopOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and calculates the + * sample standard deviation of the input values. + * + * @return + */ + public StdDevSamp stdDevSamp() { + return usesFieldRef() ? StdDevSamp.stdDevSampOf(fieldReference) : StdDevSamp.stdDevSampOf(expression); + } + + private boolean usesFieldRef() { + return fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $sum}. + * + * @author Christoph Strobl + */ + public static class Sum extends AbstractAggregationExpression { + + private Sum(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$sum"; + } + + /** + * Creates new {@link Sum}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Sum sumOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Sum(asFields(fieldReference)); + } + + /** + * Creates new {@link Sum}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Sum sumOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Sum(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Sum and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Sum(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Sum and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + 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) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $avg}. + * + * @author Christoph Strobl + */ + public static class Avg extends AbstractAggregationExpression { + + private Avg(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$avg"; + } + + /** + * Creates new {@link Avg}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Avg avgOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Avg(asFields(fieldReference)); + } + + /** + * Creates new {@link Avg}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Avg avgOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Avg(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Avg} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Avg and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Avg(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Avg} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Avg and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + 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) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $max}. + * + * @author Christoph Strobl + */ + public static class Max extends AbstractAggregationExpression { + + private Max(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$max"; + } + + /** + * Creates new {@link Max}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Max maxOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Max(asFields(fieldReference)); + } + + /** + * Creates new {@link Max}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Max maxOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Max(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Max} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Max and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Max(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Max} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Max and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + 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) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $min}. + * + * @author Christoph Strobl + */ + public static class Min extends AbstractAggregationExpression { + + private Min(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$min"; + } + + /** + * Creates new {@link Min}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Min minOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Min(asFields(fieldReference)); + } + + /** + * Creates new {@link Min}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Min minOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Min(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Min} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Min and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Min(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Min} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public Min and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + 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) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $stdDevPop}. + * + * @author Christoph Strobl + */ + public static class StdDevPop extends AbstractAggregationExpression { + + private StdDevPop(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$stdDevPop"; + } + + /** + * Creates new {@link StdDevPop}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StdDevPop stdDevPopOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevPop(asFields(fieldReference)); + } + + /** + * Creates new {@link StdDevPop} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public static StdDevPop stdDevPopOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StdDevPop(Collections.singletonList(expression)); + } + + /** + * Creates new {@link StdDevPop} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public StdDevPop and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevPop(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public StdDevPop and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + 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) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } + + /** + * {@link AggregationExpression} for {@code $stdDevSamp}. + * + * @author Christoph Strobl + */ + public static class StdDevSamp extends AbstractAggregationExpression { + + private StdDevSamp(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$stdDevSamp"; + } + + /** + * Creates new {@link StdDevSamp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StdDevSamp stdDevSampOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevSamp(asFields(fieldReference)); + } + + /** + * Creates new {@link StdDevSamp}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static StdDevSamp stdDevSampOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StdDevSamp(Collections.singletonList(expression)); + } + + /** + * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public StdDevSamp and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StdDevSamp(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
+ * NOTE: Only possible in {@code $project} stage. + * + * @param expression must not be {@literal null}. + * @return + */ + public StdDevSamp and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + 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) { + + if (value instanceof List) { + if (((List) value).size() == 1) { + return super.toDbObject(((List) value).iterator().next(), context); + } + } + + return super.toDbObject(value, context); + } + } +} 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 deleted file mode 100644 index 0c86caf1e7..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java +++ /dev/null @@ -1,8656 +0,0 @@ -/* - * Copyright 2016. the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.mongodb.core.aggregation; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; - -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; -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.query.CriteriaDefinition; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; - -import com.mongodb.BasicDBObject; -import com.mongodb.DBObject; - -/** - * @author Christoph Strobl - * @author Mark Paluch - * @since 1.10 - */ -public interface AggregationExpressions { - - /** - * Gateway to {@literal boolean expressions} that evaluate their argument expressions as booleans and return a boolean - * as the result. - * - * @author Christoph Strobl - */ - class BooleanOperators { - - /** - * Take the array referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static BooleanOperatorFactory valueOf(String fieldReference) { - return new BooleanOperatorFactory(fieldReference); - } - - /** - * Take the array referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static BooleanOperatorFactory valueOf(AggregationExpression fieldReference) { - return new BooleanOperatorFactory(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that evaluates the boolean value of the referenced field and returns - * the opposite boolean value. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Not not(String fieldReference) { - return Not.not(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that evaluates the boolean value of {@link AggregationExpression} - * result and returns the opposite boolean value. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Not not(AggregationExpression expression) { - return Not.not(expression); - } - - public static class BooleanOperatorFactory { - - private final String fieldReference; - private final AggregationExpression expression; - - /** - * Creates new {@link BooleanOperatorFactory} for given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - */ - public BooleanOperatorFactory(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - this.fieldReference = fieldReference; - this.expression = null; - } - - /** - * Creates new {@link BooleanOperatorFactory} for given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - */ - public BooleanOperatorFactory(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - this.fieldReference = null; - this.expression = expression; - } - - /** - * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} - * if all of the expressions are {@literal true}. - * - * @param expression must not be {@literal null}. - * @return - */ - public And and(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createAnd().andExpression(expression); - } - - /** - * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} - * if all of the expressions are {@literal true}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public And and(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createAnd().andField(fieldReference); - } - - private And createAnd() { - return usesFieldRef() ? And.and(Fields.field(fieldReference)) : And.and(expression); - } - - /** - * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} - * if any of the expressions are {@literal true}. - * - * @param expression must not be {@literal null}. - * @return - */ - public Or or(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createOr().orExpression(expression); - } - - /** - * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} - * if any of the expressions are {@literal true}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Or or(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createOr().orField(fieldReference); - } - - private Or createOr() { - return usesFieldRef() ? Or.or(Fields.field(fieldReference)) : Or.or(expression); - } - - /** - * Creates new {@link AggregationExpression} that evaluates a boolean and returns the opposite boolean value. - * - * @return - */ - public Not not() { - return usesFieldRef() ? Not.not(fieldReference) : Not.not(expression); - } - - private boolean usesFieldRef() { - return this.fieldReference != null; - } - } - } - - /** - * Gateway to {@literal conditional expressions} that evaluate their argument expressions as booleans to a value. - * - * @author Mark Paluch - */ - class ConditionalOperators { - - /** - * Take the field referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static ConditionalOperatorFactory when(String fieldReference) { - return new ConditionalOperatorFactory(fieldReference); - } - - /** - * Take the value resulting from the given {@literal expression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static ConditionalOperatorFactory when(AggregationExpression expression) { - return new ConditionalOperatorFactory(expression); - } - - /** - * Take the value resulting from the given {@literal criteriaDefinition}. - * - * @param criteriaDefinition must not be {@literal null}. - * @return - */ - public static ConditionalOperatorFactory when(CriteriaDefinition criteriaDefinition) { - return new ConditionalOperatorFactory(criteriaDefinition); - } - - /** - * Creates new {@link AggregationExpressions} that evaluates an expression and returns the value of the expression - * if the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, - * including instances of undefined values or missing fields, returns the value of the replacement expression. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static IfNull.ThenBuilder ifNull(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return IfNull.ifNull(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that evaluates an expression and returns the value of the expression - * if the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, - * including instances of undefined values or missing fields, returns the value of the replacement expression. - * - * @param expression must not be {@literal null}. - * @return - */ - public static IfNull.ThenBuilder ifNull(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return IfNull.ifNull(expression); - } - - /** - * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it - * 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 - */ - 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 {@literal 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; - private final AggregationExpression expression; - private final CriteriaDefinition criteriaDefinition; - - /** - * Creates new {@link ConditionalOperatorFactory} for given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - */ - public ConditionalOperatorFactory(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - - this.fieldReference = fieldReference; - this.expression = null; - this.criteriaDefinition = null; - } - - /** - * Creates new {@link ConditionalOperatorFactory} for given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - */ - public ConditionalOperatorFactory(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - - this.fieldReference = null; - this.expression = expression; - this.criteriaDefinition = null; - } - - /** - * Creates new {@link ConditionalOperatorFactory} for given {@link CriteriaDefinition}. - * - * @param criteriaDefinition must not be {@literal null}. - */ - public ConditionalOperatorFactory(CriteriaDefinition criteriaDefinition) { - - Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!"); - - this.fieldReference = null; - this.expression = null; - this.criteriaDefinition = criteriaDefinition; - } - - /** - * Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two - * specified return expressions. - * - * @param value must not be {@literal null}. - * @return - */ - public OtherwiseBuilder then(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return createThenBuilder().then(value); - } - - /** - * Creates new {@link AggregationExpressions} that evaluates a boolean expression to return one of the two - * specified return expressions. - * - * @param expression must not be {@literal null}. - * @return - */ - public OtherwiseBuilder thenValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createThenBuilder().then(expression); - } - - /** - * Creates new {@link AggregationExpressions} that evaluates a boolean expression to return one of the two - * specified return expressions. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public OtherwiseBuilder thenValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createThenBuilder().then(fieldReference); - } - - private ThenBuilder createThenBuilder() { - - if (usesFieldRef()) { - return Cond.newBuilder().when(fieldReference); - } - - return usesCriteriaDefinition() ? Cond.newBuilder().when(criteriaDefinition) - : Cond.newBuilder().when(expression); - } - - private boolean usesFieldRef() { - return this.fieldReference != null; - } - - private boolean usesCriteriaDefinition() { - return this.criteriaDefinition != null; - } - } - } - - /** - * Gateway to {@literal Set expressions} which perform {@literal set} operation on arrays, treating arrays as sets. - * - * @author Christoph Strobl - */ - class SetOperators { - - /** - * Take the array referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static SetOperatorFactory arrayAsSet(String fieldReference) { - return new SetOperatorFactory(fieldReference); - } - - /** - * Take the array resulting from the given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static SetOperatorFactory arrayAsSet(AggregationExpression expression) { - return new SetOperatorFactory(expression); - } - - public static class SetOperatorFactory { - - private final String fieldReference; - private final AggregationExpression expression; - - /** - * Creates new {@link SetOperatorFactory} for given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - */ - public SetOperatorFactory(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - this.fieldReference = fieldReference; - this.expression = null; - } - - /** - * Creates new {@link SetOperatorFactory} for given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - */ - public SetOperatorFactory(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - this.fieldReference = null; - this.expression = expression; - } - - /** - * Creates new {@link AggregationExpressions} that compares the previously mentioned field to one or more arrays - * and returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. - * - * @param arrayReferences must not be {@literal null}. - * @return - */ - public SetEquals isEqualTo(String... arrayReferences) { - return createSetEquals().isEqualTo(arrayReferences); - } - - /** - * Creates new {@link AggregationExpressions} that compares the previously mentioned field to one or more arrays - * and returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. - * - * @param expressions must not be {@literal null}. - * @return - */ - public SetEquals isEqualTo(AggregationExpression... expressions) { - return createSetEquals().isEqualTo(expressions); - } - - private SetEquals createSetEquals() { - return usesFieldRef() ? SetEquals.arrayAsSet(fieldReference) : SetEquals.arrayAsSet(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more - * arrays and returns an array that contains the elements that appear in every of those. - * - * @param arrayReferences must not be {@literal null}. - * @return - */ - public SetIntersection intersects(String... arrayReferences) { - return createSetIntersection().intersects(arrayReferences); - } - - /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more - * arrays and returns an array that contains the elements that appear in every of those. - * - * @param expressions must not be {@literal null}. - * @return - */ - public SetIntersection intersects(AggregationExpression... expressions) { - return createSetIntersection().intersects(expressions); - } - - private SetIntersection createSetIntersection() { - return usesFieldRef() ? SetIntersection.arrayAsSet(fieldReference) : SetIntersection.arrayAsSet(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more - * arrays and returns an array that contains the elements that appear in any of those. - * - * @param arrayReferences must not be {@literal null}. - * @return - */ - public SetUnion union(String... arrayReferences) { - return createSetUnion().union(arrayReferences); - } - - /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more - * arrays and returns an array that contains the elements that appear in any of those. - * - * @param expressions must not be {@literal null}. - * @return - */ - public SetUnion union(AggregationExpression... expressions) { - return createSetUnion().union(expressions); - } - - private SetUnion createSetUnion() { - return usesFieldRef() ? SetUnion.arrayAsSet(fieldReference) : SetUnion.arrayAsSet(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns an - * array containing the elements that do not exist in the given {@literal arrayReference}. - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public SetDifference differenceTo(String arrayReference) { - return createSetDifference().differenceTo(arrayReference); - } - - /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns an - * array containing the elements that do not exist in the given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public SetDifference differenceTo(AggregationExpression expression) { - return createSetDifference().differenceTo(expression); - } - - private SetDifference createSetDifference() { - return usesFieldRef() ? SetDifference.arrayAsSet(fieldReference) : SetDifference.arrayAsSet(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns - * {@literal true} if it is a subset of the given {@literal arrayReference}. - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public SetIsSubset isSubsetOf(String arrayReference) { - return createSetIsSubset().isSubsetOf(arrayReference); - } - - /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns - * {@literal true} if it is a subset of the given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public SetIsSubset isSubsetOf(AggregationExpression expression) { - return createSetIsSubset().isSubsetOf(expression); - } - - private SetIsSubset createSetIsSubset() { - return usesFieldRef() ? SetIsSubset.arrayAsSet(fieldReference) : SetIsSubset.arrayAsSet(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns - * {@literal true} if any of the elements are {@literal true} and {@literal false} otherwise. - * - * @return - */ - public AnyElementTrue anyElementTrue() { - return usesFieldRef() ? AnyElementTrue.arrayAsSet(fieldReference) : AnyElementTrue.arrayAsSet(expression); - } - - /** - * Creates new {@link AggregationExpressions} that tkes array of the previously mentioned field and returns - * {@literal true} if no elements is {@literal false}. - * - * @return - */ - public AllElementsTrue allElementsTrue() { - return usesFieldRef() ? AllElementsTrue.arrayAsSet(fieldReference) : AllElementsTrue.arrayAsSet(expression); - } - - private boolean usesFieldRef() { - return this.fieldReference != null; - } - } - } - - /** - * Gateway to {@literal comparison expressions}. - * - * @author Christoph Strobl - */ - class ComparisonOperators { - - /** - * Take the field referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static ComparisonOperatorFactory valueOf(String fieldReference) { - return new ComparisonOperatorFactory(fieldReference); - } - - /** - * Take the value resulting from the given {@literal expression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static ComparisonOperatorFactory valueOf(AggregationExpression expression) { - return new ComparisonOperatorFactory(expression); - } - - public static class ComparisonOperatorFactory { - - private final String fieldReference; - private final AggregationExpression expression; - - /** - * Creates new {@link ComparisonOperatorFactory} for given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - */ - public ComparisonOperatorFactory(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - this.fieldReference = fieldReference; - this.expression = null; - } - - /** - * Creates new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - */ - public ComparisonOperatorFactory(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - this.fieldReference = null; - this.expression = expression; - } - - /** - * Creates new {@link AggregationExpressions} that compares two values. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Cmp compareTo(String fieldReference) { - return createCmp().compareTo(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values. - * - * @param expression must not be {@literal null}. - * @return - */ - public Cmp compareTo(AggregationExpression expression) { - return createCmp().compareTo(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values. - * - * @param value must not be {@literal null}. - * @return - */ - public Cmp compareToValue(Object value) { - return createCmp().compareToValue(value); - } - - private Cmp createCmp() { - return usesFieldRef() ? Cmp.valueOf(fieldReference) : Cmp.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is equal to the value of the referenced field. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Eq equalTo(String fieldReference) { - return createEq().equalTo(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is equal to the expression result. - * - * @param expression must not be {@literal null}. - * @return - */ - public Eq equalTo(AggregationExpression expression) { - return createEq().equalTo(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is equal to the given value. - * - * @param value must not be {@literal null}. - * @return - */ - public Eq equalToValue(Object value) { - return createEq().equalToValue(value); - } - - private Eq createEq() { - return usesFieldRef() ? Eq.valueOf(fieldReference) : Eq.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is greater than the value of the referenced field. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Gt greaterThan(String fieldReference) { - return createGt().greaterThan(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is greater than the expression result. - * - * @param expression must not be {@literal null}. - * @return - */ - public Gt greaterThan(AggregationExpression expression) { - return createGt().greaterThan(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is greater than the given value. - * - * @param value must not be {@literal null}. - * @return - */ - public Gt greaterThanValue(Object value) { - return createGt().greaterThanValue(value); - } - - private Gt createGt() { - return usesFieldRef() ? Gt.valueOf(fieldReference) : Gt.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is greater than or equivalent to the value of the referenced field. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Gte greaterThanEqualTo(String fieldReference) { - return createGte().greaterThanEqualTo(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is greater than or equivalent to the expression result. - * - * @param expression must not be {@literal null}. - * @return - */ - public Gte greaterThanEqualTo(AggregationExpression expression) { - return createGte().greaterThanEqualTo(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is greater than or equivalent to the given value. - * - * @param value must not be {@literal null}. - * @return - */ - public Gte greaterThanEqualToValue(Object value) { - return createGte().greaterThanEqualToValue(value); - } - - private Gte createGte() { - return usesFieldRef() ? Gte.valueOf(fieldReference) : Gte.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is less than the value of the referenced field. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Lt lessThan(String fieldReference) { - return createLt().lessThan(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is less than the expression result. - * - * @param expression must not be {@literal null}. - * @return - */ - public Lt lessThan(AggregationExpression expression) { - return createLt().lessThan(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is less than to the given value. - * - * @param value must not be {@literal null}. - * @return - */ - public Lt lessThanValue(Object value) { - return createLt().lessThanValue(value); - } - - private Lt createLt() { - return usesFieldRef() ? Lt.valueOf(fieldReference) : Lt.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is less than or equivalent to the value of the referenced field. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Lte lessThanEqualTo(String fieldReference) { - return createLte().lessThanEqualTo(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is less than or equivalent to the expression result. - * - * @param expression must not be {@literal null}. - * @return - */ - public Lte lessThanEqualTo(AggregationExpression expression) { - return createLte().lessThanEqualTo(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first - * value is less than or equivalent to the given value. - * - * @param value - * @return - */ - public Lte lessThanEqualToValue(Object value) { - return createLte().lessThanEqualToValue(value); - } - - private Lte createLte() { - return usesFieldRef() ? Lte.valueOf(fieldReference) : Lte.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values - * are not equivalent. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Ne notEqualTo(String fieldReference) { - return createNe().notEqualTo(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values - * are not equivalent. - * - * @param expression must not be {@literal null}. - * @return - */ - public Ne notEqualTo(AggregationExpression expression) { - return createNe().notEqualTo(expression); - } - - /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values - * are not equivalent. - * - * @param value must not be {@literal null}. - * @return - */ - public Ne notEqualToValue(Object value) { - return createNe().notEqualToValue(value); - } - - private Ne createNe() { - return usesFieldRef() ? Ne.valueOf(fieldReference) : Ne.valueOf(expression); - } - - private boolean usesFieldRef() { - return fieldReference != null; - } - } - - } - - /** - * Gateway to {@literal Arithmetic} aggregation operations that perform mathematic operations on numbers. - * - * @author Christoph Strobl - */ - class ArithmeticOperators { - - /** - * Take the field referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static ArithmeticOperatorFactory valueOf(String fieldReference) { - return new ArithmeticOperatorFactory(fieldReference); - } - - /** - * Take the value resulting from the given {@literal expression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static ArithmeticOperatorFactory valueOf(AggregationExpression expression) { - return new ArithmeticOperatorFactory(expression); - } - - public static class ArithmeticOperatorFactory { - - private final String fieldReference; - private final AggregationExpression expression; - - /** - * Creates new {@link ArithmeticOperatorFactory} for given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - */ - public ArithmeticOperatorFactory(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - this.fieldReference = fieldReference; - this.expression = null; - } - - /** - * Creates new {@link ArithmeticOperatorFactory} for given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - */ - public ArithmeticOperatorFactory(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - this.fieldReference = null; - this.expression = expression; - } - - /** - * Creates new {@link AggregationExpressions} that returns the absolute value of the associated number. - * - * @return - */ - public Abs abs() { - return fieldReference != null ? Abs.absoluteValueOf(fieldReference) : Abs.absoluteValueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that adds the value of {@literal fieldReference} to the associated - * number. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Add add(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createAdd().add(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that adds the resulting value of the given - * {@link AggregationExpression} to the associated number. - * - * @param expression must not be {@literal null}. - * @return - */ - public Add add(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createAdd().add(expression); - } - - /** - * Creates new {@link AggregationExpressions} that adds the given {@literal value} to the associated number. - * - * @param value must not be {@literal null}. - * @return - */ - public Add add(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return createAdd().add(value); - } - - private Add createAdd() { - return fieldReference != null ? Add.valueOf(fieldReference) : Add.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the smallest integer greater than or equal to the - * assoicated number. - * - * @return - */ - public Ceil ceil() { - return fieldReference != null ? Ceil.ceilValueOf(fieldReference) : Ceil.ceilValueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that ivides the associated number by number referenced via - * {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Divide divideBy(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createDivide().divideBy(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that divides the associated number by number extracted via - * {@literal expression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public Divide divideBy(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createDivide().divideBy(expression); - } - - /** - * Creates new {@link AggregationExpressions} that divides the associated number by given {@literal value}. - * - * @param value - * @return - */ - public Divide divideBy(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return createDivide().divideBy(value); - } - - private Divide createDivide() { - return fieldReference != null ? Divide.valueOf(fieldReference) : Divide.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that raises Euler’s number (i.e. e ) on the associated number. - * - * @return - */ - public Exp exp() { - return fieldReference != null ? Exp.expValueOf(fieldReference) : Exp.expValueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the largest integer less than or equal to the - * associated number. - * - * @return - */ - public Floor floor() { - return fieldReference != null ? Floor.floorValueOf(fieldReference) : Floor.floorValueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that calculates the natural logarithm ln (i.e loge) of the - * assoicated number. - * - * @return - */ - public Ln ln() { - return fieldReference != null ? Ln.lnValueOf(fieldReference) : Ln.lnValueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that calculates the log of the associated number in the specified - * base referenced via {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Log log(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createLog().log(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that calculates the log of the associated number in the specified - * base extracted by given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public Log log(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createLog().log(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that calculates the log of a the associated number in the specified - * {@literal base}. - * - * @param base must not be {@literal null}. - * @return - */ - public Log log(Number base) { - - Assert.notNull(base, "Base must not be null!"); - return createLog().log(base); - } - - private Log createLog() { - return fieldReference != null ? Log.valueOf(fieldReference) : Log.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that calculates the log base 10 for the associated number. - * - * @return - */ - public Log10 log10() { - return fieldReference != null ? Log10.log10ValueOf(fieldReference) : Log10.log10ValueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the - * remainder. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Mod mod(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createMod().mod(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the - * remainder. - * - * @param expression must not be {@literal null}. - * @return - */ - public Mod mod(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createMod().mod(expression); - } - - /** - * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the - * remainder. - * - * @param value must not be {@literal null}. - * @return - */ - public Mod mod(Number value) { - - Assert.notNull(value, "Base must not be null!"); - return createMod().mod(value); - } - - private Mod createMod() { - return fieldReference != null ? Mod.valueOf(fieldReference) : Mod.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that multiplies the associated number with another. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Multiply multiplyBy(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createMultiply().multiplyBy(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that multiplies the associated number with another. - * - * @param expression must not be {@literal null}. - * @return - */ - public Multiply multiplyBy(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createMultiply().multiplyBy(expression); - } - - /** - * Creates new {@link AggregationExpressions} that multiplies the associated number with another. - * - * @param value must not be {@literal null}. - * @return - */ - public Multiply multiplyBy(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return createMultiply().multiplyBy(value); - } - - private Multiply createMultiply() { - return fieldReference != null ? Multiply.valueOf(fieldReference) : Multiply.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Pow pow(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createPow().pow(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. - * - * @param expression must not be {@literal null}. - * @return - */ - public Pow pow(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createPow().pow(expression); - } - - /** - * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. - * - * @param value must not be {@literal null}. - * @return - */ - public Pow pow(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return createPow().pow(value); - } - - private Pow createPow() { - return fieldReference != null ? Pow.valueOf(fieldReference) : Pow.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that calculates the square root of the associated number. - * - * @return - */ - public Sqrt sqrt() { - return fieldReference != null ? Sqrt.sqrtOf(fieldReference) : Sqrt.sqrtOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that subtracts value of given from the associated number. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Subtract subtract(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createSubtract().subtract(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that subtracts value of given from the associated number. - * - * @param expression must not be {@literal null}. - * @return - */ - public Subtract subtract(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createSubtract().subtract(expression); - } - - /** - * Creates new {@link AggregationExpressions} that subtracts value from the associated number. - * - * @param value - * @return - */ - public Subtract subtract(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return createSubtract().subtract(value); - } - - private Subtract createSubtract() { - return fieldReference != null ? Subtract.valueOf(fieldReference) : Subtract.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that truncates a number to its integer. - * - * @return - */ - public Trunc trunc() { - return fieldReference != null ? Trunc.truncValueOf(fieldReference) : Trunc.truncValueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that calculates and returns the sum of numeric values. - * - * @return - */ - public Sum sum() { - return fieldReference != null ? Sum.sumOf(fieldReference) : Sum.sumOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the average value of the numeric values. - * - * @return - */ - public Avg avg() { - return fieldReference != null ? Avg.avgOf(fieldReference) : Avg.avgOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the maximum value. - * - * @return - */ - public Max max() { - return fieldReference != null ? Max.maxOf(fieldReference) : Max.maxOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the minimum value. - * - * @return - */ - public Min min() { - return fieldReference != null ? Min.minOf(fieldReference) : Min.minOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that calculates the population standard deviation of the input - * values. - * - * @return - */ - public StdDevPop stdDevPop() { - return fieldReference != null ? StdDevPop.stdDevPopOf(fieldReference) : StdDevPop.stdDevPopOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that calculates the sample standard deviation of the input values. - * - * @return - */ - public StdDevSamp stdDevSamp() { - return fieldReference != null ? StdDevSamp.stdDevSampOf(fieldReference) : StdDevSamp.stdDevSampOf(expression); - } - } - } - - /** - * Gateway to {@literal String} aggregation operations. - * - * @author Christoph Strobl - */ - class StringOperators { - - /** - * Take the array referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static StringOperatorFactory valueOf(String fieldReference) { - return new StringOperatorFactory(fieldReference); - } - - /** - * Take the array referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static StringOperatorFactory valueOf(AggregationExpression fieldReference) { - return new StringOperatorFactory(fieldReference); - } - - public static class StringOperatorFactory { - - private final String fieldReference; - private final AggregationExpression expression; - - /** - * Creates new {@link StringOperatorFactory} for given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - */ - public StringOperatorFactory(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - this.fieldReference = fieldReference; - this.expression = null; - } - - /** - * Creates new {@link StringOperatorFactory} for given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - */ - public StringOperatorFactory(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - this.fieldReference = null; - this.expression = expression; - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and concats the - * value of the referenced field to it. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Concat concatValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createConcat().concatValueOf(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and concats the - * result of the given {@link AggregationExpression} to it. - * - * @param expression must not be {@literal null}. - * @return - */ - public Concat concatValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createConcat().concatValueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and concats given - * {@literal value} to it. - * - * @param value must not be {@literal null}. - * @return - */ - public Concat concat(String value) { - - Assert.notNull(value, "Value must not be null!"); - return createConcat().concat(value); - } - - private Concat createConcat() { - return fieldReference != null ? Concat.valueOf(fieldReference) : Concat.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a - * substring starting at a specified index position. - * - * @param start - * @return - */ - public Substr substring(int start) { - return substring(start, -1); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a - * substring starting at a specified index position including the specified number of characters. - * - * @param start - * @param nrOfChars - * @return - */ - public Substr substring(int start, int nrOfChars) { - return createSubstr().substring(start, nrOfChars); - } - - private Substr createSubstr() { - return fieldReference != null ? Substr.valueOf(fieldReference) : Substr.valueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and lowers it. - * - * @return - */ - public ToLower toLower() { - return fieldReference != null ? ToLower.lowerValueOf(fieldReference) : ToLower.lowerValueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and uppers it. - * - * @return - */ - public ToUpper toUpper() { - return fieldReference != null ? ToUpper.upperValueOf(fieldReference) : ToUpper.upperValueOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and performs - * case-insensitive comparison to the given {@literal value}. - * - * @param value must not be {@literal null}. - * @return - */ - public StrCaseCmp strCaseCmp(String value) { - - Assert.notNull(value, "Value must not be null!"); - return createStrCaseCmp().strcasecmp(value); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and performs - * case-insensitive comparison to the referenced {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public StrCaseCmp strCaseCmpValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createStrCaseCmp().strcasecmpValueOf(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and performs - * case-insensitive comparison to the result of the given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public StrCaseCmp strCaseCmpValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createStrCaseCmp().strcasecmpValueOf(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 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 - */ - 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 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 - */ - 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 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 - */ - 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 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 - */ - 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 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 - */ - 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 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 - */ - 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); - } - } - } - - /** - * Gateway to {@literal array} aggregation operations. - * - * @author Christoph Strobl - */ - class ArrayOperators { - - /** - * Take the array referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static ArrayOperatorFactory arrayOf(String fieldReference) { - return new ArrayOperatorFactory(fieldReference); - } - - /** - * Take the array referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static ArrayOperatorFactory arrayOf(AggregationExpression expression) { - return new ArrayOperatorFactory(expression); - } - - public static class ArrayOperatorFactory { - - private final String fieldReference; - private final AggregationExpression expression; - - /** - * Creates new {@link ArrayOperatorFactory} for given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - */ - public ArrayOperatorFactory(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - this.fieldReference = fieldReference; - this.expression = null; - } - - /** - * Creates new {@link ArrayOperatorFactory} for given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - */ - public ArrayOperatorFactory(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - this.fieldReference = null; - this.expression = expression; - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the - * specified array {@literal position}. - * - * @param position - * @return - */ - public ArrayElemAt elementAt(int position) { - return createArrayElemAt().elementAt(position); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the - * position resulting form the given {@literal expression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public ArrayElemAt elementAt(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createArrayElemAt().elementAt(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the - * position defined by the referenced {@literal field}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public ArrayElemAt elementAt(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return createArrayElemAt().elementAt(fieldReference); - } - - private ArrayElemAt createArrayElemAt() { - return usesFieldRef() ? ArrayElemAt.arrayOf(fieldReference) : ArrayElemAt.arrayOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated array and concats the given - * {@literal arrayFieldReference} to it. - * - * @param arrayFieldReference must not be {@literal null}. - * @return - */ - public ConcatArrays concat(String arrayFieldReference) { - - Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!"); - return createConcatArrays().concat(arrayFieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated array and concats the array resulting form - * the given {@literal expression} to it. - * - * @param expression must not be {@literal null}. - * @return - */ - public ConcatArrays concat(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return createConcatArrays().concat(expression); - } - - private ConcatArrays createConcatArrays() { - return usesFieldRef() ? ConcatArrays.arrayOf(fieldReference) : ConcatArrays.arrayOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated array and selects a subset of the array to - * return based on the specified condition. - * - * @return - */ - public AsBuilder filter() { - return Filter.filter(fieldReference); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated array and an check if its an array. - * - * @return - */ - public IsArray isArray() { - return usesFieldRef() ? IsArray.isArray(fieldReference) : IsArray.isArray(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated array and retrieves its length. - * - * @return - */ - public Size length() { - return usesFieldRef() ? Size.lengthOfArray(fieldReference) : Size.lengthOfArray(expression); - } - - /** - * Creates new {@link AggregationExpressions} that takes the associated array and selects a subset from it. - * - * @return - */ - public Slice slice() { - return usesFieldRef() ? Slice.sliceArrayOf(fieldReference) : Slice.sliceArrayOf(expression); - } - - /** - * 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 - */ - 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 associated array. - * - * @param value must not be {@literal null}. - * @return - */ - 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); - } - - private boolean usesFieldRef() { - return fieldReference != null; - } - } - } - - /** - * Gateway to {@literal literal} aggregation operations. - * - * @author Christoph Strobl - */ - class LiteralOperators { - - /** - * Take the value referenced by given {@literal value}. - * - * @param value must not be {@literal null}. - * @return - */ - public static LiteralOperatorFactory valueOf(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new LiteralOperatorFactory(value); - } - - public static class LiteralOperatorFactory { - - private final Object value; - - /** - * Creates new {@link LiteralOperatorFactory} for given {@literal value}. - * - * @param value must not be {@literal null}. - */ - public LiteralOperatorFactory(Object value) { - - Assert.notNull(value, "Value must not be null!"); - this.value = value; - } - - /** - * Creates new {@link AggregationExpressions} that returns the associated value without parsing. - * - * @return - */ - public Literal asLiteral() { - return Literal.asLiteral(value); - } - } - } - - /** - * Gateway to {@literal Date} aggregation operations. - * - * @author Christoph Strobl - */ - class DateOperators { - - /** - * Take the date referenced by given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static DateOperatorFactory dateOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new DateOperatorFactory(fieldReference); - } - - /** - * Take the date resulting from the given {@literal expression}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static DateOperatorFactory dateOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new DateOperatorFactory(expression); - } - - public static class DateOperatorFactory { - - private final String fieldReference; - private final AggregationExpression expression; - - /** - * Creates new {@link ArithmeticOperatorFactory} for given {@literal fieldReference}. - * - * @param fieldReference must not be {@literal null}. - */ - public DateOperatorFactory(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - this.fieldReference = fieldReference; - this.expression = null; - } - - /** - * Creates new {@link ArithmeticOperatorFactory} for given {@link AggregationExpression}. - * - * @param expression must not be {@literal null}. - */ - public DateOperatorFactory(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - this.fieldReference = null; - this.expression = expression; - } - - /** - * Creates new {@link AggregationExpressions} that returns the day of the year for a date as a number between 1 - * and 366. - * - * @return - */ - public DayOfYear dayOfYear() { - return usesFieldRef() ? DayOfYear.dayOfYear(fieldReference) : DayOfYear.dayOfYear(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the day of the month for a date as a number between 1 - * and 31. - * - * @return - */ - public DayOfMonth dayOfMonth() { - return usesFieldRef() ? DayOfMonth.dayOfMonth(fieldReference) : DayOfMonth.dayOfMonth(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the day of the week for a date as a number between 1 - * (Sunday) and 7 (Saturday). - * - * @return - */ - public DayOfWeek dayOfWeek() { - return usesFieldRef() ? DayOfWeek.dayOfWeek(fieldReference) : DayOfWeek.dayOfWeek(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the year portion of a date. - * - * @return - */ - public Year year() { - return usesFieldRef() ? Year.yearOf(fieldReference) : Year.yearOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the month of a date as a number between 1 and 12. - * - * @return - */ - public Month month() { - return usesFieldRef() ? Month.monthOf(fieldReference) : Month.monthOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the week of the year for a date as a number between 0 - * and 53. - * - * @return - */ - public Week week() { - return usesFieldRef() ? Week.weekOf(fieldReference) : Week.weekOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the hour portion of a date as a number between 0 and - * 23. - * - * @return - */ - public Hour hour() { - return usesFieldRef() ? Hour.hourOf(fieldReference) : Hour.hourOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the minute portion of a date as a number between 0 and - * 59. - * - * @return - */ - public Minute minute() { - return usesFieldRef() ? Minute.minuteOf(fieldReference) : Minute.minuteOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the second portion of a date as a number between 0 and - * 59, but can be 60 to account for leap seconds. - * - * @return - */ - public Second second() { - return usesFieldRef() ? Second.secondOf(fieldReference) : Second.secondOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that returns the millisecond portion of a date as an integer between - * 0 and 999. - * - * @return - */ - public Millisecond millisecond() { - return usesFieldRef() ? Millisecond.millisecondOf(fieldReference) : Millisecond.millisecondOf(expression); - } - - /** - * Creates new {@link AggregationExpressions} that converts a date object to a string according to a - * user-specified {@literal format}. - * - * @param format must not be {@literal null}. - * @return - */ - public DateToString toString(String format) { - return (usesFieldRef() ? DateToString.dateOf(fieldReference) : DateToString.dateOf(expression)) - .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; - } - } - } - - /** - * Gateway to {@literal variable} aggregation operations. - * - * @author Christoph Strobl - * @author Mark Paluch - */ - class VariableOperators { - - /** - * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array - * and returns an array with the applied results. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Map.AsBuilder mapItemsOf(String fieldReference) { - return Map.itemsOf(fieldReference); - } - - /** - * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array - * and returns an array with the applied results. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Map.AsBuilder mapItemsOf(AggregationExpression expression) { - return Map.itemsOf(expression); - } - - /** - * Start creating new {@link Let} that allows definition of {@link ExpressionVariable} that can be used within a - * nested {@link AggregationExpression}. - * - * @param variables must not be {@literal null}. - * @return - */ - public static Let.LetBuilder define(ExpressionVariable... variables) { - return Let.define(variables); - } - - /** - * Start creating new {@link Let} that allows definition of {@link ExpressionVariable} that can be used within a - * nested {@link AggregationExpression}. - * - * @param variables must not be {@literal null}. - * @return - */ - public static Let.LetBuilder define(Collection variables) { - return Let.define(variables); - } - } - - /** - * @author Christoph Strobl - */ - abstract class AbstractAggregationExpression implements AggregationExpression { - - private final Object value; - - 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); - } - - public DBObject toDbObject(Object value, AggregationOperationContext context) { - - Object valueToUse; - if (value instanceof List) { - - List arguments = (List) value; - List args = new ArrayList(arguments.size()); - - for (Object val : arguments) { - args.add(unpack(val, context)); - } - valueToUse = args; - } else if (value instanceof java.util.Map) { - - DBObject dbo = new BasicDBObject(); - for (java.util.Map.Entry entry : ((java.util.Map) value).entrySet()) { - dbo.put(entry.getKey(), unpack(entry.getValue(), context)); - } - valueToUse = dbo; - } else { - valueToUse = unpack(value, context); - } - - return new BasicDBObject(getMongoMethod(), valueToUse); - } - - protected static List asFields(String... fieldRefs) { - - if (ObjectUtils.isEmpty(fieldRefs)) { - return Collections.emptyList(); - } - - return Fields.fields(fieldRefs).asList(); - } - - private Object unpack(Object value, AggregationOperationContext context) { - - if (value instanceof AggregationExpression) { - return ((AggregationExpression) value).toDbObject(context); - } - - if (value instanceof Field) { - 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; - } - - protected List append(Object value) { - - if (this.value instanceof List) { - - List clone = new ArrayList((List) this.value); - - if (value instanceof List) { - for (Object val : (List) value) { - clone.add(val); - } - } else { - clone.add(value); - } - return clone; - } - - return Arrays.asList(this.value, value); - } - - protected java.util.Map append(String key, Object value) { - - if (!(this.value instanceof java.util.Map)) { - throw new IllegalArgumentException("o_O"); - } - 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(); - } - - /** - * {@link AggregationExpression} for {@code $setEquals}. - * - * @author Christoph Strobl - */ - class SetEquals extends AbstractAggregationExpression { - - private SetEquals(List arrays) { - super(arrays); - } - - @Override - protected String getMongoMethod() { - return "$setEquals"; - } - - /** - * Create new {@link SetEquals}. - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public static SetEquals arrayAsSet(String arrayReference) { - - Assert.notNull(arrayReference, "ArrayReference must not be null!"); - return new SetEquals(asFields(arrayReference)); - } - - /** - * Create new {@link SetEquals}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static SetEquals arrayAsSet(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new SetEquals(Collections.singletonList(expression)); - } - - /** - * Creates new {@link java.util.Set} with all previously added arguments appending the given one. - * - * @param arrayReferences must not be {@literal null}. - * @return - */ - public SetEquals isEqualTo(String... arrayReferences) { - - Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); - return new SetEquals(append(Fields.fields(arrayReferences).asList())); - } - - /** - * Creates new {@link Sum} with all previously added arguments appending the given one. - * - * @param expressions must not be {@literal null}. - * @return - */ - public SetEquals isEqualTo(AggregationExpression... expressions) { - - Assert.notNull(expressions, "Expressions must not be null!"); - return new SetEquals(append(Arrays.asList(expressions))); - } - - /** - * Creates new {@link Sum} with all previously added arguments appending the given one. - * - * @param array must not be {@literal null}. - * @return - */ - public SetEquals isEqualTo(Object[] array) { - - Assert.notNull(array, "Array must not be null!"); - return new SetEquals(append(array)); - } - } - - /** - * {@link AggregationExpression} for {@code $setIntersection}. - * - * @author Christoph Strobl - */ - class SetIntersection extends AbstractAggregationExpression { - - private SetIntersection(List arrays) { - super(arrays); - } - - @Override - protected String getMongoMethod() { - return "$setIntersection"; - } - - /** - * Creates new {@link SetIntersection} - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public static SetIntersection arrayAsSet(String arrayReference) { - - Assert.notNull(arrayReference, "ArrayReference must not be null!"); - return new SetIntersection(asFields(arrayReference)); - } - - /** - * Creates new {@link SetIntersection}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static SetIntersection arrayAsSet(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new SetIntersection(Collections.singletonList(expression)); - } - - /** - * Creates new {@link SetIntersection} with all previously added arguments appending the given one. - * - * @param arrayReferences must not be {@literal null}. - * @return - */ - public SetIntersection intersects(String... arrayReferences) { - - Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); - return new SetIntersection(append(asFields(arrayReferences))); - } - - /** - * Creates new {@link SetIntersection} with all previously added arguments appending the given one. - * - * @param expressions must not be {@literal null}. - * @return - */ - public SetIntersection intersects(AggregationExpression... expressions) { - - Assert.notNull(expressions, "Expressions must not be null!"); - return new SetIntersection(append(Arrays.asList(expressions))); - } - } - - /** - * {@link AggregationExpression} for {@code $setUnion}. - * - * @author Christoph Strobl - */ - class SetUnion extends AbstractAggregationExpression { - - private SetUnion(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$setUnion"; - } - - /** - * Creates new {@link SetUnion}. - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public static SetUnion arrayAsSet(String arrayReference) { - - Assert.notNull(arrayReference, "ArrayReference must not be null!"); - return new SetUnion(asFields(arrayReference)); - } - - /** - * Creates new {@link SetUnion}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static SetUnion arrayAsSet(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new SetUnion(Collections.singletonList(expression)); - } - - /** - * Creates new {@link SetUnion} with all previously added arguments appending the given one. - * - * @param arrayReferences must not be {@literal null}. - * @return - */ - public SetUnion union(String... arrayReferences) { - - Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); - return new SetUnion(append(asFields(arrayReferences))); - } - - /** - * Creates new {@link SetUnion} with all previously added arguments appending the given one. - * - * @param expressions must not be {@literal null}. - * @return - */ - public SetUnion union(AggregationExpression... expressions) { - - Assert.notNull(expressions, "Expressions must not be null!"); - return new SetUnion(append(Arrays.asList(expressions))); - } - } - - /** - * {@link AggregationExpression} for {@code $setDifference}. - * - * @author Christoph Strobl - */ - class SetDifference extends AbstractAggregationExpression { - - private SetDifference(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$setDifference"; - } - - /** - * Creates new {@link SetDifference}. - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public static SetDifference arrayAsSet(String arrayReference) { - - Assert.notNull(arrayReference, "ArrayReference must not be null!"); - return new SetDifference(asFields(arrayReference)); - } - - /** - * Creates new {@link SetDifference}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static SetDifference arrayAsSet(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new SetDifference(Collections.singletonList(expression)); - } - - /** - * Creates new {@link SetDifference} with all previously added arguments appending the given one. - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public SetDifference differenceTo(String arrayReference) { - - Assert.notNull(arrayReference, "ArrayReference must not be null!"); - return new SetDifference(append(Fields.field(arrayReference))); - } - - /** - * Creates new {@link SetDifference} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public SetDifference differenceTo(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new SetDifference(append(expression)); - } - } - - /** - * {@link AggregationExpression} for {@code $setIsSubset}. - * - * @author Christoph Strobl - */ - class SetIsSubset extends AbstractAggregationExpression { - - private SetIsSubset(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$setIsSubset"; - } - - /** - * Creates new {@link SetIsSubset}. - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public static SetIsSubset arrayAsSet(String arrayReference) { - - Assert.notNull(arrayReference, "ArrayReference must not be null!"); - return new SetIsSubset(asFields(arrayReference)); - } - - /** - * Creates new {@link SetIsSubset}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static SetIsSubset arrayAsSet(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new SetIsSubset(Collections.singletonList(expression)); - } - - /** - * Creates new {@link SetIsSubset} with all previously added arguments appending the given one. - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public SetIsSubset isSubsetOf(String arrayReference) { - - Assert.notNull(arrayReference, "ArrayReference must not be null!"); - return new SetIsSubset(append(Fields.field(arrayReference))); - } - - /** - * Creates new {@link SetIsSubset} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public SetIsSubset isSubsetOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new SetIsSubset(append(expression)); - } - } - - /** - * {@link AggregationExpression} for {@code $anyElementTrue}. - * - * @author Christoph Strobl - */ - class AnyElementTrue extends AbstractAggregationExpression { - - private AnyElementTrue(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$anyElementTrue"; - } - - /** - * Creates new {@link AnyElementTrue}. - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public static AnyElementTrue arrayAsSet(String arrayReference) { - - Assert.notNull(arrayReference, "ArrayReference must not be null!"); - return new AnyElementTrue(asFields(arrayReference)); - } - - /** - * Creates new {@link AnyElementTrue}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static AnyElementTrue arrayAsSet(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new AnyElementTrue(Collections.singletonList(expression)); - } - - public AnyElementTrue anyElementTrue() { - return this; - } - } - - /** - * {@link AggregationExpression} for {@code $allElementsTrue}. - * - * @author Christoph Strobl - */ - class AllElementsTrue extends AbstractAggregationExpression { - - private AllElementsTrue(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$allElementsTrue"; - } - - /** - * Creates new {@link AllElementsTrue}. - * - * @param arrayReference must not be {@literal null}. - * @return - */ - public static AllElementsTrue arrayAsSet(String arrayReference) { - - Assert.notNull(arrayReference, "ArrayReference must not be null!"); - return new AllElementsTrue(asFields(arrayReference)); - } - - /** - * Creates new {@link AllElementsTrue}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static AllElementsTrue arrayAsSet(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new AllElementsTrue(Collections.singletonList(expression)); - } - - public AllElementsTrue allElementsTrue() { - return this; - } - } - - /** - * {@link AggregationExpression} for {@code $abs}. - * - * @author Christoph Strobl - */ - class Abs extends AbstractAggregationExpression { - - private Abs(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$abs"; - } - - /** - * Creates new {@link Abs}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Abs absoluteValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Abs(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Abs}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Abs absoluteValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Abs(expression); - } - - /** - * Creates new {@link Abs}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Abs absoluteValueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Abs(value); - } - } - - /** - * {@link AggregationExpression} for {@code $add}. - * - * @author Christoph Strobl - */ - class Add extends AbstractAggregationExpression { - - protected Add(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$add"; - } - - /** - * Creates new {@link Add}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Add valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Add(asFields(fieldReference)); - } - - /** - * Creates new {@link Add}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Add valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Add(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Add}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Add valueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Add(Collections.singletonList(value)); - } - - public Add add(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Add(append(Fields.field(fieldReference))); - } - - public Add add(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Add(append(expression)); - } - - public Add add(Number value) { - return new Add(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $ceil}. - * - * @author Christoph Strobl - */ - class Ceil extends AbstractAggregationExpression { - - private Ceil(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$ceil"; - } - - /** - * Creates new {@link Ceil}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Ceil ceilValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Ceil(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Ceil}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Ceil ceilValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Ceil(expression); - } - - /** - * Creates new {@link Ceil}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Ceil ceilValueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Ceil(value); - } - } - - /** - * {@link AggregationExpression} for {@code $divide}. - * - * @author Christoph Strobl - */ - class Divide extends AbstractAggregationExpression { - - private Divide(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$divide"; - } - - /** - * Creates new {@link Divide}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Divide valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Divide(asFields(fieldReference)); - } - - /** - * Creates new {@link Divide}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Divide valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Divide(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Divide}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Divide valueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Divide(Collections.singletonList(value)); - } - - public Divide divideBy(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Divide(append(Fields.field(fieldReference))); - } - - public Divide divideBy(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Divide(append(expression)); - } - - public Divide divideBy(Number value) { - return new Divide(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $exp}. - * - * @author Christoph Strobl - */ - class Exp extends AbstractAggregationExpression { - - private Exp(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$exp"; - } - - /** - * Creates new {@link Exp}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Exp expValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Exp(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Exp}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Exp expValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Exp(expression); - } - - /** - * Creates new {@link Exp}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Exp expValueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Exp(value); - } - } - - /** - * {@link AggregationExpression} for {@code $floor}. - * - * @author Christoph Strobl - */ - class Floor extends AbstractAggregationExpression { - - private Floor(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$floor"; - } - - /** - * Creates new {@link Floor}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Floor floorValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Floor(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Floor}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Floor floorValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Floor(expression); - } - - /** - * Creates new {@link Floor}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Floor floorValueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Floor(value); - } - } - - /** - * {@link AggregationExpression} for {@code $ln}. - * - * @author Christoph Strobl - */ - class Ln extends AbstractAggregationExpression { - - private Ln(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$ln"; - } - - /** - * Creates new {@link Ln}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Ln lnValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Ln(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Ln}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Ln lnValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Ln(expression); - } - - /** - * Creates new {@link Ln}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Ln lnValueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Ln(value); - } - } - - /** - * {@link AggregationExpression} for {@code $log}. - * - * @author Christoph Strobl - */ - class Log extends AbstractAggregationExpression { - - private Log(List values) { - super(values); - } - - @Override - protected String getMongoMethod() { - return "$log"; - } - - /** - * Creates new {@link Min}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Log valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Log(asFields(fieldReference)); - } - - /** - * Creates new {@link Log}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Log valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Log(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Log}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Log valueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Log(Collections.singletonList(value)); - } - - public Log log(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Log(append(Fields.field(fieldReference))); - } - - public Log log(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Log(append(expression)); - } - - public Log log(Number base) { - return new Log(append(base)); - } - } - - /** - * {@link AggregationExpression} for {@code $log10}. - * - * @author Christoph Strobl - */ - class Log10 extends AbstractAggregationExpression { - - private Log10(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$log10"; - } - - /** - * Creates new {@link Log10}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Log10 log10ValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Log10(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Log10}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Log10 log10ValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Log10(expression); - } - - /** - * Creates new {@link Log10}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Log10 log10ValueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Log10(value); - } - } - - /** - * {@link AggregationExpression} for {@code $mod}. - * - * @author Christoph Strobl - */ - class Mod extends AbstractAggregationExpression { - - private Mod(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$mod"; - } - - /** - * Creates new {@link Mod}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Mod valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Mod(asFields(fieldReference)); - } - - /** - * Creates new {@link Mod}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Mod valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Mod(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Mod}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Mod valueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Mod(Collections.singletonList(value)); - } - - public Mod mod(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Mod(append(Fields.field(fieldReference))); - } - - public Mod mod(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Mod(append(expression)); - } - - public Mod mod(Number base) { - return new Mod(append(base)); - } - } - - /** - * {@link AggregationExpression} for {@code $multiply}. - * - * @author Christoph Strobl - */ - class Multiply extends AbstractAggregationExpression { - - private Multiply(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$multiply"; - } - - /** - * Creates new {@link Multiply}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Multiply valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Multiply(asFields(fieldReference)); - } - - /** - * Creates new {@link Multiply}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Multiply valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Multiply(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Multiply}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Multiply valueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Multiply(Collections.singletonList(value)); - } - - public Multiply multiplyBy(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Multiply(append(Fields.field(fieldReference))); - } - - public Multiply multiplyBy(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Multiply(append(expression)); - } - - public Multiply multiplyBy(Number value) { - return new Multiply(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $pow}. - * - * @author Christoph Strobl - */ - class Pow extends AbstractAggregationExpression { - - private Pow(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$pow"; - } - - /** - * Creates new {@link Pow}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Pow valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Pow(asFields(fieldReference)); - } - - /** - * Creates new {@link Pow}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Pow valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Pow(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Pow}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Pow valueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Pow(Collections.singletonList(value)); - } - - public Pow pow(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Pow(append(Fields.field(fieldReference))); - } - - public Pow pow(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Pow(append(expression)); - } - - public Pow pow(Number value) { - return new Pow(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $sqrt}. - * - * @author Christoph Strobl - */ - class Sqrt extends AbstractAggregationExpression { - - private Sqrt(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$sqrt"; - } - - /** - * Creates new {@link Sqrt}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Sqrt sqrtOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Sqrt(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Sqrt}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Sqrt sqrtOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Sqrt(expression); - } - - /** - * Creates new {@link Sqrt}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Sqrt sqrtOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Sqrt(value); - } - } - - /** - * {@link AggregationExpression} for {@code $subtract}. - * - * @author Christoph Strobl - */ - class Subtract extends AbstractAggregationExpression { - - private Subtract(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$subtract"; - } - - /** - * Creates new {@link Subtract}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Subtract valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Subtract(asFields(fieldReference)); - } - - /** - * Creates new {@link Subtract}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Subtract valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Subtract(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Subtract}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Subtract valueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Subtract(Collections.singletonList(value)); - } - - public Subtract subtract(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Subtract(append(Fields.field(fieldReference))); - } - - public Subtract subtract(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Subtract(append(expression)); - } - - public Subtract subtract(Number value) { - return new Subtract(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $trunc}. - * - * @author Christoph Strobl - */ - class Trunc extends AbstractAggregationExpression { - - private Trunc(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$trunc"; - } - - /** - * Creates new {@link Trunc}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Trunc truncValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Trunc(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Trunc}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Trunc truncValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Trunc(expression); - } - - /** - * Creates new {@link Trunc}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Trunc truncValueOf(Number value) { - - Assert.notNull(value, "Value must not be null!"); - return new Trunc(value); - } - } - - // ######################################### - // STRING OPERATORS - // ######################################### - - /** - * {@link AggregationExpression} for {@code $concat}. - * - * @author Christoph Strobl - */ - class Concat extends AbstractAggregationExpression { - - private Concat(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$concat"; - } - - /** - * Creates new {@link Concat}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Concat valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Concat(asFields(fieldReference)); - } - - /** - * Creates new {@link Concat}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Concat valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Concat(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Concat}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Concat stringValue(String value) { - - Assert.notNull(value, "Value must not be null!"); - return new Concat(Collections.singletonList(value)); - } - - public Concat concatValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Concat(append(Fields.field(fieldReference))); - } - - public Concat concatValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Concat(append(expression)); - } - - public Concat concat(String value) { - return new Concat(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $substr}. - * - * @author Christoph Strobl - */ - class Substr extends AbstractAggregationExpression { - - private Substr(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$substr"; - } - - /** - * Creates new {@link Substr}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Substr valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Substr(asFields(fieldReference)); - } - - /** - * Creates new {@link Substr}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Substr valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Substr(Collections.singletonList(expression)); - } - - public Substr substring(int start) { - return substring(start, -1); - } - - public Substr substring(int start, int nrOfChars) { - return new Substr(append(Arrays.asList(start, nrOfChars))); - } - } - - /** - * {@link AggregationExpression} for {@code $toLower}. - * - * @author Christoph Strobl - */ - class ToLower extends AbstractAggregationExpression { - - private ToLower(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$toLower"; - } - - /** - * Creates new {@link ToLower}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static ToLower lowerValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new ToLower(Fields.field(fieldReference)); - } - - /** - * Creates new {@link ToLower}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static ToLower lowerValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new ToLower(Collections.singletonList(expression)); - } - - /** - * Creates new {@link ToLower}. - * - * @param value must not be {@literal null}. - * @return - */ - public static ToLower lower(String value) { - - Assert.notNull(value, "Value must not be null!"); - return new ToLower(value); - } - } - - /** - * {@link AggregationExpression} for {@code $toUpper}. - * - * @author Christoph Strobl - */ - class ToUpper extends AbstractAggregationExpression { - - private ToUpper(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$toUpper"; - } - - /** - * Creates new {@link ToUpper}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static ToUpper upperValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new ToUpper(Fields.field(fieldReference)); - } - - /** - * Creates new {@link ToUpper}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static ToUpper upperValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new ToUpper(Collections.singletonList(expression)); - } - - /** - * Creates new {@link ToUpper}. - * - * @param value must not be {@literal null}. - * @return - */ - public static ToUpper upper(String value) { - - Assert.notNull(value, "Value must not be null!"); - return new ToUpper(value); - } - } - - /** - * {@link AggregationExpression} for {@code $strcasecmp}. - * - * @author Christoph Strobl - */ - class StrCaseCmp extends AbstractAggregationExpression { - - private StrCaseCmp(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$strcasecmp"; - } - - /** - * Creates new {@link StrCaseCmp}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static StrCaseCmp valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new StrCaseCmp(asFields(fieldReference)); - } - - /** - * Creates new {@link StrCaseCmp}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static StrCaseCmp valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new StrCaseCmp(Collections.singletonList(expression)); - } - - /** - * Creates new {@link StrCaseCmp}. - * - * @param value must not be {@literal null}. - * @return - */ - public static StrCaseCmp stringValue(String value) { - - Assert.notNull(value, "Value must not be null!"); - return new StrCaseCmp(Collections.singletonList(value)); - } - - public StrCaseCmp strcasecmp(String value) { - return new StrCaseCmp(append(value)); - } - - public StrCaseCmp strcasecmpValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new StrCaseCmp(append(Fields.field(fieldReference))); - } - - public StrCaseCmp strcasecmpValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new StrCaseCmp(append(expression)); - } - } - - /** - * {@link AggregationExpression} for {@code $indexOfBytes}. - * - * @author Christoph Strobl - */ - class IndexOfBytes extends AbstractAggregationExpression { - - private IndexOfBytes(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$indexOfBytes"; - } - - /** - * Start creating a new {@link IndexOfBytes}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static SubstringBuilder valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new SubstringBuilder(Fields.field(fieldReference)); - } - - /** - * Start creating a new {@link IndexOfBytes}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static SubstringBuilder valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new SubstringBuilder(expression); - } - - /** - * Optionally define the substring search start and end position. - * - * @param range must not be {@literal null}. - * @return - */ - public IndexOfBytes 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 IndexOfBytes(append(rangeValues)); - } - - public static class SubstringBuilder { - - private final Object stringExpression; - - 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)); - } - } - } - - /** - * {@link AggregationExpression} for {@code $indexOfCP}. - * - * @author Christoph Strobl - */ - class IndexOfCP extends AbstractAggregationExpression { - - private IndexOfCP(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$indexOfCP"; - } - - /** - * Start creating a new {@link IndexOfCP}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static SubstringBuilder valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new SubstringBuilder(Fields.field(fieldReference)); - } - - /** - * Start creating a new {@link IndexOfCP}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static SubstringBuilder valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new SubstringBuilder(expression); - } - - /** - * Optionally define the substring search start and end position. - * - * @param range must not be {@literal null}. - * @return - */ - public IndexOfCP 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 IndexOfCP(append(rangeValues)); - } - - public static class SubstringBuilder { - - private final Object stringExpression; - - 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)); - } - } - } - - /** - * {@link AggregationExpression} for {@code $split}. - * - * @author Christoph Strobl - */ - 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 delimiter. - * - * @param delimiter must not be {@literal null}. - * @return - */ - public Split split(String delimiter) { - - Assert.notNull(delimiter, "Delimiter must not be null!"); - return new Split(append(delimiter)); - } - - /** - * Use value of referenced field as delimiter. - * - * @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 delimiter. - * - * @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}. - * - * @author Christoph Strobl - */ - class StrLenBytes extends AbstractAggregationExpression { - - private StrLenBytes(Object value) { - super(value); - } - - @Override - 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 { - - private StrLenCP(Object value) { - super(value); - } - - @Override - 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); - } - } - - /** - * {@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 - * @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(ExposedFields.from(as), context); - } - - private DBObject toFilter(ExposedFields exposedFields, AggregationOperationContext context) { - - DBObject filterExpression = new BasicDBObject(); - InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( - exposedFields, context); - - filterExpression.putAll(context.getMappedObject(new BasicDBObject("input", getMappedInput(context)))); - filterExpression.put("as", as.getTarget()); - - filterExpression.putAll(context.getMappedObject(new BasicDBObject("cond", getMappedCondition(operationContext)))); - - 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); - return ((AggregationExpression) condition).toDbObject(nea); - } - - /** - * @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; - } - } - } - - /** - * {@link AggregationExpression} for {@code $isArray}. - * - * @author Christoph Strobl - */ - class IsArray extends AbstractAggregationExpression { - - private IsArray(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$isArray"; - } - - /** - * Creates new {@link IsArray}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static IsArray isArray(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new IsArray(Fields.field(fieldReference)); - } - - /** - * Creates new {@link IsArray}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static IsArray isArray(AggregationExpression 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); - } - }; - } - - /** - * @author Christoph Strobl - */ - public interface SliceElementsBuilder { - - /** - * Set the number of elements given {@literal nrElements}. - * - * @param nrElements - * @return - */ - 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)); - } - - /** - * @author Christoph Strobl - */ - public static class IndexOfArrayBuilder { - - private final Object targetArray; - - 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!"); - 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"; - } - - /** - * 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); - } - - /** - * Start creating new {@link RangeOperator}. - * - * @param value - * @return - */ - 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; - } - - /** - * 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 { - - private ReverseArray(Object value) { - super(value); - } - - @Override - 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); - } - } - - /** - * {@link AggregationExpression} for {@code $reduce}. - * - * @author Christoph Strobl - */ - 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; - } - - /* (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(); - - 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###"); - } - } - - /** - * 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)); - } - }; - } - }; - } - - /** - * 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)); - } - }; - } - }; - } - - /** - * @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 initialValue must not be {@literal null}. - * @return - */ - 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 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 - */ - 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 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 - */ - Reduce reduce(PropertyExpression... expressions); - } - - /** - * @author Christoph Strobl - */ - public static class PropertyExpression implements AggregationExpression { - - private final String propertyName; - private final 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; - } - - /** - * 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); - } - }; - } - - /* (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 { - - /** - * 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 referringTo(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 $zip}. - * - * @author Christoph Strobl - */ - class Zip extends AbstractAggregationExpression { - - protected Zip(java.util.Map value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$zip"; - } - - /** - * Start creating new {@link Zip}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static ZipBuilder arrayOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new ZipBuilder(Fields.field(fieldReference)); - } - - /** - * Start creating new {@link Zip}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static ZipBuilder arrayOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new ZipBuilder(expression); - } - - /** - * Create new {@link Zip} and set the {@code useLongestLength} property to {@literal true}. - * - * @return - */ - public Zip useLongestLength() { - return new Zip(append("useLongestLength", true)); - } - - /** - * Optionally provide a default value. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Zip defaultTo(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Zip(append("defaults", Fields.field(fieldReference))); - } - - /** - * Optionally provide a default value. - * - * @param expression must not be {@literal null}. - * @return - */ - public Zip defaultTo(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Zip(append("defaults", expression)); - } - - /** - * 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 static class ZipBuilder { - - private final List sourceArrays; - - private 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}. - * - * @author Christoph Strobl - */ - class In extends AbstractAggregationExpression { - - private In(List values) { - super(values); - } - - @Override - 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) { - - Assert.notNull(value, "Value must not be null!"); - return new In(Arrays.asList(value, Fields.field(fieldReference))); - } - }; - } - - /** - * 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) { - - Assert.notNull(value, "Value must not be null!"); - return new In(Arrays.asList(value, expression)); - } - }; - } - - /** - * @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); - } - } - - // ############ - // LITERAL OPERATORS - // ############ - - /** - * {@link AggregationExpression} for {@code $literal}. - * - * @author Christoph Strobl - */ - class Literal extends AbstractAggregationExpression { - - private Literal(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$literal"; - } - - /** - * Creates new {@link Literal}. - * - * @param value must not be {@literal null}. - * @return - */ - public static Literal asLiteral(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new Literal(value); - } - } - - /** - * {@link AggregationExpression} for {@code $dayOfYear}. - * - * @author Christoph Strobl - */ - class DayOfYear extends AbstractAggregationExpression { - - private DayOfYear(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$dayOfYear"; - } - - /** - * Creates new {@link DayOfYear}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static DayOfYear dayOfYear(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new DayOfYear(Fields.field(fieldReference)); - } - - /** - * Creates new {@link DayOfYear}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static DayOfYear dayOfYear(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new DayOfYear(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $dayOfMonth}. - * - * @author Christoph Strobl - */ - class DayOfMonth extends AbstractAggregationExpression { - - private DayOfMonth(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$dayOfMonth"; - } - - /** - * Creates new {@link DayOfMonth}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static DayOfMonth dayOfMonth(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new DayOfMonth(Fields.field(fieldReference)); - } - - /** - * Creates new {@link DayOfMonth}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static DayOfMonth dayOfMonth(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new DayOfMonth(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $dayOfWeek}. - * - * @author Christoph Strobl - */ - class DayOfWeek extends AbstractAggregationExpression { - - private DayOfWeek(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$dayOfWeek"; - } - - /** - * Creates new {@link DayOfWeek}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static DayOfWeek dayOfWeek(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new DayOfWeek(Fields.field(fieldReference)); - } - - /** - * Creates new {@link DayOfWeek}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static DayOfWeek dayOfWeek(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new DayOfWeek(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $year}. - * - * @author Christoph Strobl - */ - class Year extends AbstractAggregationExpression { - - private Year(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$year"; - } - - /** - * Creates new {@link Year}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Year yearOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Year(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Year}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Year yearOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Year(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $month}. - * - * @author Christoph Strobl - */ - class Month extends AbstractAggregationExpression { - - private Month(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$month"; - } - - /** - * Creates new {@link Month}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Month monthOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Month(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Month}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Month monthOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Month(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $week}. - * - * @author Christoph Strobl - */ - class Week extends AbstractAggregationExpression { - - private Week(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$week"; - } - - /** - * Creates new {@link Week}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Week weekOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Week(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Week}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Week weekOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Week(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $hour}. - * - * @author Christoph Strobl - */ - class Hour extends AbstractAggregationExpression { - - private Hour(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$hour"; - } - - /** - * Creates new {@link Hour}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Hour hourOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Hour(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Hour}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Hour hourOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Hour(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $minute}. - * - * @author Christoph Strobl - */ - class Minute extends AbstractAggregationExpression { - - private Minute(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$minute"; - } - - /** - * Creates new {@link Minute}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Minute minuteOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Minute(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Minute}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Minute minuteOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Minute(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $second}. - * - * @author Christoph Strobl - */ - class Second extends AbstractAggregationExpression { - - private Second(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$second"; - } - - /** - * Creates new {@link Second}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Second secondOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Second(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Second}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Second secondOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Second(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $millisecond}. - * - * @author Christoph Strobl - */ - class Millisecond extends AbstractAggregationExpression { - - private Millisecond(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$millisecond"; - } - - /** - * Creates new {@link Millisecond}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Millisecond millisecondOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Millisecond(Fields.field(fieldReference)); - } - - /** - * Creates new {@link Millisecond}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Millisecond millisecondOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Millisecond(expression); - } - } - - /** - * {@link AggregationExpression} for {@code $dateToString}. - * - * @author Christoph Strobl - */ - class DateToString extends AbstractAggregationExpression { - - private DateToString(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$dateToString"; - } - - /** - * Creates new {@link FormatBuilder} allowing to define the date format to apply. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static FormatBuilder dateOf(final String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference 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(Fields.field(fieldReference), format)); - } - }; - } - - /** - * Creates new {@link FormatBuilder} allowing to define the date format to apply. - * - * @param expression must not be {@literal null}. - * @return - */ - 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)); - } - }; - } - - private static java.util.Map argumentMap(Object date, String format) { - - java.util.Map args = new LinkedHashMap(2); - args.put("format", format); - args.put("date", date); - return args; - } - - public interface FormatBuilder { - - /** - * Creates new {@link DateToString} with all previously added arguments appending the given one. - * - * @param format must not be {@literal null}. - * @return - */ - DateToString toString(String format); - } - } - - /** - * {@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}. - * - * @author Christoph Strobl - */ - class Sum extends AbstractAggregationExpression { - - private Sum(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$sum"; - } - - /** - * Creates new {@link Sum}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Sum sumOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Sum(asFields(fieldReference)); - } - - /** - * Creates new {@link Sum}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Sum sumOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Sum(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Sum} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Sum and(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Sum(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Sum} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param expression must not be {@literal null}. - * @return - */ - public Sum and(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - 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) { - - if (value instanceof List) { - if (((List) value).size() == 1) { - return super.toDbObject(((List) value).iterator().next(), context); - } - } - - return super.toDbObject(value, context); - } - } - - /** - * {@link AggregationExpression} for {@code $avg}. - * - * @author Christoph Strobl - */ - class Avg extends AbstractAggregationExpression { - - private Avg(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$avg"; - } - - /** - * Creates new {@link Avg}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Avg avgOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Avg(asFields(fieldReference)); - } - - /** - * Creates new {@link Avg}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Avg avgOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Avg(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Avg} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Avg and(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Avg(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Avg} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param expression must not be {@literal null}. - * @return - */ - public Avg and(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - 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) { - - if (value instanceof List) { - if (((List) value).size() == 1) { - return super.toDbObject(((List) value).iterator().next(), context); - } - } - - return super.toDbObject(value, context); - } - } - - /** - * {@link AggregationExpression} for {@code $max}. - * - * @author Christoph Strobl - */ - class Max extends AbstractAggregationExpression { - - private Max(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$max"; - } - - /** - * Creates new {@link Max}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Max maxOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Max(asFields(fieldReference)); - } - - /** - * Creates new {@link Max}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Max maxOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Max(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Max} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Max and(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Max(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Max} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param expression must not be {@literal null}. - * @return - */ - public Max and(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - 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) { - - if (value instanceof List) { - if (((List) value).size() == 1) { - return super.toDbObject(((List) value).iterator().next(), context); - } - } - - return super.toDbObject(value, context); - } - } - - /** - * {@link AggregationExpression} for {@code $min}. - * - * @author Christoph Strobl - */ - class Min extends AbstractAggregationExpression { - - private Min(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$min"; - } - - /** - * Creates new {@link Min}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Min minOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Min(asFields(fieldReference)); - } - - /** - * Creates new {@link Min}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Min minOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Min(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Min} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Min and(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Min(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Min} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param expression must not be {@literal null}. - * @return - */ - public Min and(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - 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) { - - if (value instanceof List) { - if (((List) value).size() == 1) { - return super.toDbObject(((List) value).iterator().next(), context); - } - } - - return super.toDbObject(value, context); - } - } - - /** - * {@link AggregationExpression} for {@code $stdDevPop}. - * - * @author Christoph Strobl - */ - class StdDevPop extends AbstractAggregationExpression { - - private StdDevPop(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$stdDevPop"; - } - - /** - * Creates new {@link StdDevPop}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static StdDevPop stdDevPopOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new StdDevPop(asFields(fieldReference)); - } - - /** - * Creates new {@link StdDevPop} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public static StdDevPop stdDevPopOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new StdDevPop(Collections.singletonList(expression)); - } - - /** - * Creates new {@link StdDevPop} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public StdDevPop and(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new StdDevPop(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param expression must not be {@literal null}. - * @return - */ - public StdDevPop and(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - 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) { - - if (value instanceof List) { - if (((List) value).size() == 1) { - return super.toDbObject(((List) value).iterator().next(), context); - } - } - - return super.toDbObject(value, context); - } - } - - /** - * {@link AggregationExpression} for {@code $stdDevSamp}. - * - * @author Christoph Strobl - */ - class StdDevSamp extends AbstractAggregationExpression { - - private StdDevSamp(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$stdDevSamp"; - } - - /** - * Creates new {@link StdDevSamp}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static StdDevSamp stdDevSampOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new StdDevSamp(asFields(fieldReference)); - } - - /** - * Creates new {@link StdDevSamp}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static StdDevSamp stdDevSampOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new StdDevSamp(Collections.singletonList(expression)); - } - - /** - * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public StdDevSamp and(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new StdDevSamp(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link StdDevSamp} with all previously added arguments appending the given one.
- * NOTE: Only possible in {@code $project} stage. - * - * @param expression must not be {@literal null}. - * @return - */ - public StdDevSamp and(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - 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) { - - if (value instanceof List) { - if (((List) value).size() == 1) { - return super.toDbObject(((List) value).iterator().next(), context); - } - } - - return super.toDbObject(value, context); - } - } - - /** - * {@link AggregationExpression} for {@code $cmp}. - * - * @author Christoph Strobl - */ - class Cmp extends AbstractAggregationExpression { - - private Cmp(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$cmp"; - } - - /** - * Creates new {@link Cmp}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Cmp valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Cmp(asFields(fieldReference)); - } - - /** - * Creates new {@link Cmp}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Cmp valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Cmp(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Cmp} with all previously added arguments appending the given one. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Cmp compareTo(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Cmp(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Cmp} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public Cmp compareTo(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Cmp(append(expression)); - } - - /** - * Creates new {@link Cmp} with all previously added arguments appending the given one. - * - * @param value must not be {@literal null}. - * @return - */ - public Cmp compareToValue(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new Cmp(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $eq}. - * - * @author Christoph Strobl - */ - class Eq extends AbstractAggregationExpression { - - private Eq(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$eq"; - } - - /** - * Creates new {@link Eq}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Eq valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Eq(asFields(fieldReference)); - } - - /** - * Creates new {@link Eq}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Eq valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Eq(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Eq} with all previously added arguments appending the given one. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Eq equalTo(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Eq(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Eq} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public Eq equalTo(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Eq(append(expression)); - } - - /** - * Creates new {@link Eq} with all previously added arguments appending the given one. - * - * @param value must not be {@literal null}. - * @return - */ - public Eq equalToValue(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new Eq(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $gt}. - * - * @author Christoph Strobl - */ - class Gt extends AbstractAggregationExpression { - - private Gt(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$gt"; - } - - /** - * Creates new {@link Gt}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Gt valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Gt(asFields(fieldReference)); - } - - /** - * Creates new {@link Gt}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Gt valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Gt(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Gt} with all previously added arguments appending the given one. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Gt greaterThan(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Gt(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Gt} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public Gt greaterThan(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Gt(append(expression)); - } - - /** - * Creates new {@link Gt} with all previously added arguments appending the given one. - * - * @param value must not be {@literal null}. - * @return - */ - public Gt greaterThanValue(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new Gt(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $lt}. - * - * @author Christoph Strobl - */ - class Lt extends AbstractAggregationExpression { - - private Lt(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$lt"; - } - - /** - * Creates new {@link Lt}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Lt valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Lt(asFields(fieldReference)); - } - - /** - * Creates new {@link Lt}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Lt valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Lt(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Lt} with all previously added arguments appending the given one. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Lt lessThan(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Lt(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Lt} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public Lt lessThan(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Lt(append(expression)); - } - - /** - * Creates new {@link Lt} with all previously added arguments appending the given one. - * - * @param value must not be {@literal null}. - * @return - */ - public Lt lessThanValue(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new Lt(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $gte}. - * - * @author Christoph Strobl - */ - class Gte extends AbstractAggregationExpression { - - private Gte(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$gte"; - } - - /** - * Creates new {@link Gte}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Gte valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Gte(asFields(fieldReference)); - } - - /** - * Creates new {@link Gte}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Gte valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Gte(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Gte} with all previously added arguments appending the given one. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Gte greaterThanEqualTo(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Gte(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Gte} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public Gte greaterThanEqualTo(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Gte(append(expression)); - } - - /** - * Creates new {@link Gte} with all previously added arguments appending the given one. - * - * @param value must not be {@literal null}. - * @return - */ - public Gte greaterThanEqualToValue(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new Gte(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $lte}. - * - * @author Christoph Strobl - */ - class Lte extends AbstractAggregationExpression { - - private Lte(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$lte"; - } - - /** - * Creates new {@link Lte}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Lte valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Lte(asFields(fieldReference)); - } - - /** - * Creates new {@link Lte}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Lte valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Lte(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Lte} with all previously added arguments appending the given one. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Lte lessThanEqualTo(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Lte(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Lte} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public Lte lessThanEqualTo(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Lte(append(expression)); - } - - /** - * Creates new {@link Lte} with all previously added arguments appending the given one. - * - * @param value must not be {@literal null}. - * @return - */ - public Lte lessThanEqualToValue(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new Lte(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $ne}. - * - * @author Christoph Strobl - */ - class Ne extends AbstractAggregationExpression { - - private Ne(List value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$ne"; - } - - /** - * Creates new {@link Ne}. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Ne valueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Ne(asFields(fieldReference)); - } - - /** - * Creates new {@link Ne}. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Ne valueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Ne(Collections.singletonList(expression)); - } - - /** - * Creates new {@link Ne} with all previously added arguments appending the given one. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Ne notEqualTo(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Ne(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Ne} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public Ne notEqualTo(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Ne(append(expression)); - } - - /** - * Creates new {@link Eq} with all previously added arguments appending the given one. - * - * @param value must not be {@literal null}. - * @return - */ - public Ne notEqualToValue(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new Ne(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $and}. - * - * @author Christoph Strobl - */ - class And extends AbstractAggregationExpression { - - private And(List values) { - super(values); - } - - @Override - protected String getMongoMethod() { - return "$and"; - } - - /** - * Creates new {@link And} that evaluates one or more expressions and returns {@literal true} if all of the - * expressions are {@literal true}. - * - * @param expressions - * @return - */ - public static And and(Object... expressions) { - return new And(Arrays.asList(expressions)); - } - - /** - * Creates new {@link And} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public And andExpression(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new And(append(expression)); - } - - /** - * Creates new {@link And} with all previously added arguments appending the given one. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public And andField(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new And(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link And} with all previously added arguments appending the given one. - * - * @param value must not be {@literal null}. - * @return - */ - public And andValue(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new And(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $or}. - * - * @author Christoph Strobl - */ - class Or extends AbstractAggregationExpression { - - private Or(List values) { - super(values); - } - - @Override - protected String getMongoMethod() { - return "$or"; - } - - /** - * Creates new {@link Or} that evaluates one or more expressions and returns {@literal true} if any of the - * expressions are {@literal true}. - * - * @param expressions must not be {@literal null}. - * @return - */ - public static Or or(Object... expressions) { - - Assert.notNull(expressions, "Expressions must not be null!"); - return new Or(Arrays.asList(expressions)); - } - - /** - * Creates new {@link Or} with all previously added arguments appending the given one. - * - * @param expression must not be {@literal null}. - * @return - */ - public Or orExpression(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Or(append(expression)); - } - - /** - * Creates new {@link Or} with all previously added arguments appending the given one. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public Or orField(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Or(append(Fields.field(fieldReference))); - } - - /** - * Creates new {@link Or} with all previously added arguments appending the given one. - * - * @param value must not be {@literal null}. - * @return - */ - public Or orValue(Object value) { - - Assert.notNull(value, "Value must not be null!"); - return new Or(append(value)); - } - } - - /** - * {@link AggregationExpression} for {@code $not}. - * - * @author Christoph Strobl - */ - class Not extends AbstractAggregationExpression { - - private Not(Object value) { - super(value); - } - - @Override - protected String getMongoMethod() { - return "$not"; - } - - /** - * Creates new {@link Not} that evaluates the boolean value of the referenced field and returns the opposite boolean - * value. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - public static Not not(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Not(asFields(fieldReference)); - } - - /** - * Creates new {@link Not} that evaluates the resulting boolean value of the given {@link AggregationExpression} and - * returns the opposite boolean value. - * - * @param expression must not be {@literal null}. - * @return - */ - public static Not not(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Not(Collections.singletonList(expression)); - } - } - - /** - * {@link AggregationExpression} for {@code $map}. - */ - class Map implements AggregationExpression { - - private Object sourceArray; - private String itemVariableName; - private AggregationExpression functionToApply; - - private Map(Object sourceArray, String itemVariableName, AggregationExpression functionToApply) { - - Assert.notNull(sourceArray, "SourceArray must not be null!"); - Assert.notNull(itemVariableName, "ItemVariableName must not be null!"); - Assert.notNull(functionToApply, "FunctionToApply must not be null!"); - - this.sourceArray = sourceArray; - this.itemVariableName = itemVariableName; - this.functionToApply = functionToApply; - } - - /** - * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array - * and returns an array with the applied results. - * - * @param fieldReference must not be {@literal null}. - * @return - */ - 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); - } - }; - } - - }; - }; - - /** - * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array - * and returns an array with the applied results. - * - * @param source must not be {@literal null}. - * @return - */ - 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); - } - }; - } - }; - } - - /* (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); - } - - private DBObject toMap(ExposedFields exposedFields, AggregationOperationContext context) { - - BasicDBObject map = new BasicDBObject(); - InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( - exposedFields, context); - - BasicDBObject input; - if (sourceArray instanceof Field) { - input = new BasicDBObject("input", context.getReference((Field) sourceArray).toString()); - } else { - input = new BasicDBObject("input", ((AggregationExpression) sourceArray).toDbObject(context)); - } - - map.putAll(context.getMappedObject(input)); - map.put("as", itemVariableName); - map.put("in", - functionToApply.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(operationContext))); - - return new BasicDBObject("$map", map); - } - - interface AsBuilder { - - /** - * Define the {@literal variableName} for addressing items within the array. - * - * @param variableName must not be {@literal null}. - * @return - */ - FunctionBuilder as(String variableName); - } - - interface FunctionBuilder { - - /** - * Creates new {@link Map} that applies the given {@link AggregationExpression} to each item of the referenced - * array and returns an array with the applied results. - * - * @param expression must not be {@literal null}. - * @return - */ - Map andApply(AggregationExpression expression); - } - } - - /** - * Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field - * field references}, {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be - * converted to a simple MongoDB type. - * - * @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ - * @author Mark Paluch - */ - class IfNull implements AggregationExpression { - - private final Object condition; - private final Object value; - - private IfNull(Object condition, Object value) { - - this.condition = condition; - this.value = value; - } - - /** - * Creates new {@link IfNull}. - * - * @param fieldReference the field to check for a {@literal null} value, field reference must not be {@literal null} - * . - * @return - */ - public static ThenBuilder ifNull(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new IfNullOperatorBuilder().ifNull(fieldReference); - } - - /** - * Creates new {@link IfNull}. - * - * @param expression the expression to check for a {@literal null} value, field reference must not be - * {@literal null}. - * @return - */ - public static ThenBuilder ifNull(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new IfNullOperatorBuilder().ifNull(expression); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) - */ - @Override - public DBObject toDbObject(AggregationOperationContext context) { - - List list = new ArrayList(); - - if (condition instanceof Field) { - list.add(context.getReference((Field) condition).toString()); - } else if (condition instanceof AggregationExpression) { - list.add(((AggregationExpression) condition).toDbObject(context)); - } else { - list.add(condition); - } - - list.add(resolve(value, context)); - - return new BasicDBObject("$ifNull", list); - } - - private Object resolve(Object value, AggregationOperationContext context) { - - if (value instanceof Field) { - return context.getReference((Field) value).toString(); - } else if (value instanceof AggregationExpression) { - return ((AggregationExpression) value).toDbObject(context); - } else if (value instanceof DBObject) { - return value; - } - - return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); - } - - /** - * @author Mark Paluch - */ - public static interface IfNullBuilder { - - /** - * @param fieldReference the field to check for a {@literal null} value, field reference must not be - * {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder ifNull(String fieldReference); - - /** - * @param expression the expression to check for a {@literal null} value, field name must not be {@literal null} - * or empty. - * @return the {@link ThenBuilder} - */ - ThenBuilder ifNull(AggregationExpression expression); - } - - /** - * @author Mark Paluch - */ - public static interface ThenBuilder { - - /** - * @param value the value to be used if the {@code $ifNull} condition evaluates {@literal true}. Can be a - * {@link DBObject}, a value that is supported by MongoDB or a value that can be converted to a MongoDB - * representation but must not be {@literal null}. - * @return - */ - IfNull then(Object value); - - /** - * @param fieldReference the field holding the replacement value, must not be {@literal null}. - * @return - */ - IfNull thenValueOf(String fieldReference); - - /** - * @param expression the expression yielding to the replacement value, must not be {@literal null}. - * @return - */ - public IfNull thenValueOf(AggregationExpression expression); - } - - /** - * Builder for fluent {@link IfNullOperator} creation. - * - * @author Mark Paluch - */ - static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder { - - private Object condition; - - private IfNullOperatorBuilder() {} - - /** - * Creates a new builder for {@link IfNullOperator}. - * - * @return never {@literal null}. - */ - public static IfNullOperatorBuilder newBuilder() { - return new IfNullOperatorBuilder(); - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.IfNullBuilder#ifNull(java.lang.String) - */ - public ThenBuilder ifNull(String fieldReference) { - - Assert.hasText(fieldReference, "FieldReference name must not be null or empty!"); - this.condition = Fields.field(fieldReference); - return this; - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.AggregationExpression) - */ - @Override - public ThenBuilder ifNull(AggregationExpression expression) { - - Assert.notNull(expression, "AggregationExpression name must not be null or empty!"); - this.condition = expression; - return this; - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#then(java.lang.Object) - */ - public IfNull then(Object value) { - return new IfNull(condition, value); - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#thenValueOf(java.lang.String) - */ - public IfNull thenValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new IfNull(condition, Fields.field(fieldReference)); - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) - */ - public IfNull thenValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new IfNull(condition, expression); - } - } - } - - /** - * Encapsulates the aggregation framework {@code $cond} operator. A {@link Cond} allows nested conditions - * {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition}, {@link AggregationExpression} - * or a {@link DBObject custom} condition. Replacement values can be either {@link Field field references}, - * {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be converted to a - * simple MongoDB type. - * - * @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/ - * @author Mark Paluch - * @author Christoph Strobl - */ - class Cond implements AggregationExpression { - - private final Object condition; - private final Object thenValue; - private final Object otherwiseValue; - - /** - * Creates a new {@link Cond} for a given {@link Field} and {@code then}/{@code otherwise} values. - * - * @param condition must not be {@literal null}. - * @param thenValue must not be {@literal null}. - * @param otherwiseValue must not be {@literal null}. - */ - private Cond(Field condition, Object thenValue, Object otherwiseValue) { - this((Object) condition, thenValue, otherwiseValue); - } - - /** - * Creates a new {@link Cond} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise} values. - * - * @param condition must not be {@literal null}. - * @param thenValue must not be {@literal null}. - * @param otherwiseValue must not be {@literal null}. - */ - private Cond(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) { - this((Object) condition, thenValue, otherwiseValue); - } - - private Cond(Object condition, Object thenValue, Object otherwiseValue) { - - Assert.notNull(condition, "Condition must not be null!"); - Assert.notNull(thenValue, "Then value must not be null!"); - Assert.notNull(otherwiseValue, "Otherwise value must not be null!"); - - assertNotBuilder(condition, "Condition"); - assertNotBuilder(thenValue, "Then value"); - assertNotBuilder(otherwiseValue, "Otherwise value"); - - this.condition = condition; - this.thenValue = thenValue; - this.otherwiseValue = otherwiseValue; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) - */ - @Override - public DBObject toDbObject(AggregationOperationContext context) { - - BasicDBObject condObject = new BasicDBObject(); - - condObject.append("if", resolveCriteria(context, condition)); - condObject.append("then", resolveValue(context, thenValue)); - condObject.append("else", resolveValue(context, otherwiseValue)); - - return new BasicDBObject("$cond", condObject); - } - - private Object resolveValue(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject || value instanceof Field) { - return resolve(context, value); - } - - if (value instanceof AggregationExpression) { - return ((AggregationExpression) value).toDbObject(context); - } - - return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); - } - - private Object resolveCriteria(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject || value instanceof Field) { - return resolve(context, value); - } - - if (value instanceof AggregationExpression) { - return ((AggregationExpression) value).toDbObject(context); - } - - if (value instanceof CriteriaDefinition) { - - DBObject mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject()); - List clauses = new ArrayList(); - - clauses.addAll(getClauses(context, mappedObject)); - - return clauses.size() == 1 ? clauses.get(0) : clauses; - } - - throw new InvalidDataAccessApiUsageException( - String.format("Invalid value in condition. Supported: DBObject, Field references, Criteria, got: %s", value)); - } - - private List getClauses(AggregationOperationContext context, DBObject mappedObject) { - - List clauses = new ArrayList(); - - for (String key : mappedObject.keySet()) { - - Object predicate = mappedObject.get(key); - clauses.addAll(getClauses(context, key, predicate)); - } - - return clauses; - } - - private List getClauses(AggregationOperationContext context, String key, Object predicate) { - - List clauses = new ArrayList(); - - if (predicate instanceof List) { - - List args = new ArrayList(); - for (Object clause : (List) predicate) { - if (clause instanceof DBObject) { - args.addAll(getClauses(context, (DBObject) clause)); - } - } - - clauses.add(new BasicDBObject(key, args)); - - } else if (predicate instanceof DBObject) { - - DBObject nested = (DBObject) predicate; - - for (String s : nested.keySet()) { - - if (!isKeyword(s)) { - continue; - } - - List args = new ArrayList(); - args.add("$" + key); - args.add(nested.get(s)); - clauses.add(new BasicDBObject(s, args)); - } - - } else if (!isKeyword(key)) { - - List args = new ArrayList(); - args.add("$" + key); - args.add(predicate); - clauses.add(new BasicDBObject("$eq", args)); - } - - return clauses; - } - - /** - * Returns whether the given {@link String} is a MongoDB keyword. - * - * @param candidate - * @return - */ - private boolean isKeyword(String candidate) { - return candidate.startsWith("$"); - } - - private Object resolve(AggregationOperationContext context, Object value) { - - if (value instanceof DBObject) { - return context.getMappedObject((DBObject) value); - } - - return context.getReference((Field) value).toString(); - } - - private void assertNotBuilder(Object toCheck, String name) { - Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck), - String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName())); - } - - /** - * Get a builder that allows fluent creation of {@link Cond}. - * - * @return never {@literal null}. - */ - public static WhenBuilder newBuilder() { - return ConditionalExpressionBuilder.newBuilder(); - } - - /** - * Start creating new {@link Cond} by providing the boolean expression used in {@code if}. - * - * @param booleanExpression must not be {@literal null}. - * @return never {@literal null}. - */ - public static ThenBuilder when(DBObject booleanExpression) { - return ConditionalExpressionBuilder.newBuilder().when(booleanExpression); - } - - /** - * Start creating new {@link Cond} by providing the {@link AggregationExpression} used in {@code if}. - * - * @param expression expression that yields in a boolean result, must not be {@literal null}. - * @return never {@literal null}. - */ - public static ThenBuilder when(AggregationExpression expression) { - return ConditionalExpressionBuilder.newBuilder().when(expression); - } - - /** - * Start creating new {@link Cond} by providing the field reference used in {@code if}. - * - * @param booleanField name of a field holding a boolean value, must not be {@literal null}. - * @return never {@literal null}. - */ - public static ThenBuilder when(String booleanField) { - return ConditionalExpressionBuilder.newBuilder().when(booleanField); - } - - /** - * Start creating new {@link Cond} by providing the {@link CriteriaDefinition} used in {@code if}. - * - * @param criteria criteria to evaluate, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - public static ThenBuilder when(CriteriaDefinition criteria) { - return ConditionalExpressionBuilder.newBuilder().when(criteria); - } - - /** - * @author Mark Paluch - */ - public static interface WhenBuilder { - - /** - * @param booleanExpression expression that yields in a boolean result, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(DBObject booleanExpression); - - /** - * @param expression expression that yields in a boolean result, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(AggregationExpression expression); - - /** - * @param booleanField name of a field holding a boolean value, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(String booleanField); - - /** - * @param criteria criteria to evaluate, must not be {@literal null}. - * @return the {@link ThenBuilder} - */ - ThenBuilder when(CriteriaDefinition criteria); - } - - /** - * @author Mark Paluch - */ - public static interface ThenBuilder { - - /** - * @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link DBObject}, a - * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but - * must not be {@literal null}. - * @return the {@link OtherwiseBuilder} - */ - OtherwiseBuilder then(Object value); - - /** - * @param fieldReference must not be {@literal null}. - * @return the {@link OtherwiseBuilder} - */ - OtherwiseBuilder thenValueOf(String fieldReference); - - /** - * @param expression must not be {@literal null}. - * @return the {@link OtherwiseBuilder} - */ - OtherwiseBuilder thenValueOf(AggregationExpression expression); - } - - /** - * @author Mark Paluch - */ - public static interface OtherwiseBuilder { - - /** - * @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link DBObject}, a - * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but - * must not be {@literal null}. - * @return the {@link Cond} - */ - Cond otherwise(Object value); - - /** - * @param fieldReference must not be {@literal null}. - * @return the {@link Cond} - */ - Cond otherwiseValueOf(String fieldReference); - - /** - * @param expression must not be {@literal null}. - * @return the {@link Cond} - */ - Cond otherwiseValueOf(AggregationExpression expression); - } - - /** - * Builder for fluent {@link Cond} creation. - * - * @author Mark Paluch - */ - static class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder { - - private Object condition; - private Object thenValue; - - private ConditionalExpressionBuilder() {} - - /** - * Creates a new builder for {@link Cond}. - * - * @return never {@literal null}. - */ - public static ConditionalExpressionBuilder newBuilder() { - return new ConditionalExpressionBuilder(); - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(com.mongodb.DBObject) - */ - @Override - public ConditionalExpressionBuilder when(DBObject booleanExpression) { - - Assert.notNull(booleanExpression, "'Boolean expression' must not be null!"); - - this.condition = booleanExpression; - return this; - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition) - */ - @Override - public ThenBuilder when(CriteriaDefinition criteria) { - - Assert.notNull(criteria, "Criteria must not be null!"); - this.condition = criteria; - return this; - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.AggregationExpression) - */ - @Override - public ThenBuilder when(AggregationExpression expression) { - - Assert.notNull(expression, "AggregationExpression field must not be null!"); - this.condition = expression; - return this; - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(java.lang.String) - */ - @Override - public ThenBuilder when(String booleanField) { - - Assert.hasText(booleanField, "Boolean field name must not be null or empty!"); - this.condition = Fields.field(booleanField); - return this; - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#then(java.lang.Object) - */ - @Override - public OtherwiseBuilder then(Object thenValue) { - - Assert.notNull(thenValue, "Then-value must not be null!"); - this.thenValue = thenValue; - return this; - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#thenValueOf(java.lang.String) - */ - @Override - public OtherwiseBuilder thenValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - this.thenValue = Fields.field(fieldReference); - return this; - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) - */ - @Override - public OtherwiseBuilder thenValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "AggregationExpression must not be null!"); - this.thenValue = expression; - return this; - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwise(java.lang.Object) - */ - @Override - public Cond otherwise(Object otherwiseValue) { - - Assert.notNull(otherwiseValue, "Value must not be null!"); - return new Cond(condition, thenValue, otherwiseValue); - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwiseValueOf(java.lang.String) - */ - @Override - public Cond otherwiseValueOf(String fieldReference) { - - Assert.notNull(fieldReference, "FieldReference must not be null!"); - return new Cond(condition, thenValue, Fields.field(fieldReference)); - } - - /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwiseValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) - */ - @Override - public Cond otherwiseValueOf(AggregationExpression expression) { - - Assert.notNull(expression, "AggregationExpression must not be null!"); - return new Cond(condition, thenValue, expression); - } - } - } - - /** - * {@link AggregationExpression} for {@code $let} that binds {@link AggregationExpression} to variables for use in the - * specified {@code in} expression, and returns the result of the expression. - * - * @author Christoph Strobl - * @since 1.10 - */ - class Let implements AggregationExpression { - - private final List vars; - private final AggregationExpression expression; - - private Let(List vars, AggregationExpression expression) { - - this.vars = vars; - this.expression = expression; - } - - /** - * Start creating new {@link Let} by defining the variables for {@code $vars}. - * - * @param variables must not be {@literal null}. - * @return - */ - 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) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Let(new ArrayList(variables), expression); - } - }; - } - - /** - * Start creating new {@link Let} by defining the variables for {@code $vars}. - * - * @param variables must not be {@literal null}. - * @return - */ - 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) { - - Assert.notNull(expression, "Expression must not be null!"); - return new Let(Arrays.asList(variables), expression); - } - }; - } - - public interface LetBuilder { - - /** - * Define the {@link AggregationExpression} to evaluate. - * - * @param expression must not be {@literal null}. - * @return - */ - 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); - } - - private String[] getVariableNames() { - - String[] varNames = new String[this.vars.size()]; - for (int i = 0; i < this.vars.size(); i++) { - varNames[i] = this.vars.get(i).variableName; - } - - return varNames; - } - - private DBObject toLet(ExposedFields exposedFields, AggregationOperationContext context) { - - DBObject letExpression = new BasicDBObject(); - DBObject mappedVars = new BasicDBObject(); - InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( - exposedFields, context); - - for (ExpressionVariable var : this.vars) { - mappedVars.putAll(getMappedVariable(var, context)); - } - - letExpression.put("vars", mappedVars); - letExpression.put("in", getMappedIn(operationContext)); - - return new BasicDBObject("$let", letExpression); - } - - private DBObject getMappedVariable(ExpressionVariable var, AggregationOperationContext context) { - - return new BasicDBObject(var.variableName, var.expression instanceof AggregationExpression - ? ((AggregationExpression) var.expression).toDbObject(context) : var.expression); - } - - private Object getMappedIn(AggregationOperationContext context) { - return expression.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(context)); - } - - /** - * @author Christoph Strobl - */ - public static class ExpressionVariable { - - private final String variableName; - private final Object expression; - - /** - * Creates new {@link ExpressionVariable}. - * - * @param variableName can be {@literal null}. - * @param expression can be {@literal null}. - */ - private ExpressionVariable(String variableName, Object expression) { - - this.variableName = variableName; - this.expression = expression; - } - - /** - * Create a new {@link ExpressionVariable} with given name. - * - * @param variableName must not be {@literal null}. - * @return never {@literal null}. - */ - public static ExpressionVariable newVariable(String variableName) { - - Assert.notNull(variableName, "VariableName must not be null!"); - return new ExpressionVariable(variableName, null); - } - - /** - * Create a new {@link ExpressionVariable} with current name and given {@literal expression}. - * - * @param expression must not be {@literal null}. - * @return never {@literal null}. - */ - public ExpressionVariable forExpression(AggregationExpression expression) { - - Assert.notNull(expression, "Expression must not be null!"); - return new ExpressionVariable(variableName, expression); - } - - /** - * Create a new {@link ExpressionVariable} with current name and given {@literal expressionObject}. - * - * @param expressionObject must not be {@literal null}. - * @return never {@literal null}. - */ - public ExpressionVariable forExpression(DBObject expressionObject) { - - Assert.notNull(expressionObject, "Expression must not be null!"); - return new ExpressionVariable(variableName, 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"; - } - - /** - * 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!"); - return new Switch(Collections. singletonMap("branches", new ArrayList(conditions))); - } - - 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; - 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); - } - }; - } - - /* (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) { - 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; - } - - /** - * @author Christoph Strobl - */ - public interface ThenBuilder { - - /** - * Set the then {@literal value}. - * - * @param value must not be {@literal null}. - * @return - */ - 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/ArithmeticOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java new file mode 100644 index 0000000000..12124f0a57 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java @@ -0,0 +1,1421 @@ +/* + * 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.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Avg; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Max; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Min; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.StdDevPop; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.StdDevSamp; +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Sum; +import org.springframework.util.Assert; + +/** + * Gateway to {@literal Arithmetic} aggregation operations that perform math operations on numbers. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class ArithmeticOperators { + + /** + * Take the field referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArithmeticOperatorFactory valueOf(String fieldReference) { + return new ArithmeticOperatorFactory(fieldReference); + } + + /** + * Take the value resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ArithmeticOperatorFactory valueOf(AggregationExpression expression) { + return new ArithmeticOperatorFactory(expression); + } + + /** + * @author Christoph Strobl + */ + public static class ArithmeticOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ArithmeticOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ArithmeticOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that returns the absolute value of the associated number. + * + * @return + */ + public Abs abs() { + return fieldReference != null ? Abs.absoluteValueOf(fieldReference) : Abs.absoluteValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that adds the value of {@literal fieldReference} to the associated + * number. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Add add(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createAdd().add(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that adds the resulting value of the given + * {@link AggregationExpression} to the associated number. + * + * @param expression must not be {@literal null}. + * @return + */ + public Add add(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createAdd().add(expression); + } + + /** + * Creates new {@link AggregationExpressions} that adds the given {@literal value} to the associated number. + * + * @param value must not be {@literal null}. + * @return + */ + public Add add(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createAdd().add(value); + } + + private Add createAdd() { + return fieldReference != null ? Add.valueOf(fieldReference) : Add.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the smallest integer greater than or equal to the + * assoicated number. + * + * @return + */ + public Ceil ceil() { + return fieldReference != null ? Ceil.ceilValueOf(fieldReference) : Ceil.ceilValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that ivides the associated number by number referenced via + * {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Divide divideBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createDivide().divideBy(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that divides the associated number by number extracted via + * {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Divide divideBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createDivide().divideBy(expression); + } + + /** + * Creates new {@link AggregationExpressions} that divides the associated number by given {@literal value}. + * + * @param value + * @return + */ + public Divide divideBy(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createDivide().divideBy(value); + } + + private Divide createDivide() { + return fieldReference != null ? Divide.valueOf(fieldReference) : Divide.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that raises Euler’s number (i.e. e ) on the associated number. + * + * @return + */ + public Exp exp() { + return fieldReference != null ? Exp.expValueOf(fieldReference) : Exp.expValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the largest integer less than or equal to the associated + * number. + * + * @return + */ + public Floor floor() { + return fieldReference != null ? Floor.floorValueOf(fieldReference) : Floor.floorValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the natural logarithm ln (i.e loge) of the assoicated + * number. + * + * @return + */ + public Ln ln() { + return fieldReference != null ? Ln.lnValueOf(fieldReference) : Ln.lnValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the log of the associated number in the specified base + * referenced via {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Log log(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createLog().log(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the log of the associated number in the specified base + * extracted by given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Log log(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createLog().log(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the log of a the associated number in the specified + * {@literal base}. + * + * @param base must not be {@literal null}. + * @return + */ + public Log log(Number base) { + + Assert.notNull(base, "Base must not be null!"); + return createLog().log(base); + } + + private Log createLog() { + return fieldReference != null ? Log.valueOf(fieldReference) : Log.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the log base 10 for the associated number. + * + * @return + */ + public Log10 log10() { + return fieldReference != null ? Log10.log10ValueOf(fieldReference) : Log10.log10ValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the + * remainder. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Mod mod(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createMod().mod(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the + * remainder. + * + * @param expression must not be {@literal null}. + * @return + */ + public Mod mod(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createMod().mod(expression); + } + + /** + * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the + * remainder. + * + * @param value must not be {@literal null}. + * @return + */ + public Mod mod(Number value) { + + Assert.notNull(value, "Base must not be null!"); + return createMod().mod(value); + } + + private Mod createMod() { + return fieldReference != null ? Mod.valueOf(fieldReference) : Mod.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that multiplies the associated number with another. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Multiply multiplyBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createMultiply().multiplyBy(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that multiplies the associated number with another. + * + * @param expression must not be {@literal null}. + * @return + */ + public Multiply multiplyBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createMultiply().multiplyBy(expression); + } + + /** + * Creates new {@link AggregationExpressions} that multiplies the associated number with another. + * + * @param value must not be {@literal null}. + * @return + */ + public Multiply multiplyBy(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createMultiply().multiplyBy(value); + } + + private Multiply createMultiply() { + return fieldReference != null ? Multiply.valueOf(fieldReference) : Multiply.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Pow pow(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createPow().pow(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. + * + * @param expression must not be {@literal null}. + * @return + */ + public Pow pow(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createPow().pow(expression); + } + + /** + * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. + * + * @param value must not be {@literal null}. + * @return + */ + public Pow pow(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createPow().pow(value); + } + + private Pow createPow() { + return fieldReference != null ? Pow.valueOf(fieldReference) : Pow.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the square root of the associated number. + * + * @return + */ + public Sqrt sqrt() { + return fieldReference != null ? Sqrt.sqrtOf(fieldReference) : Sqrt.sqrtOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that subtracts value of given from the associated number. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Subtract subtract(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createSubtract().subtract(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that subtracts value of given from the associated number. + * + * @param expression must not be {@literal null}. + * @return + */ + public Subtract subtract(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createSubtract().subtract(expression); + } + + /** + * Creates new {@link AggregationExpressions} that subtracts value from the associated number. + * + * @param value + * @return + */ + public Subtract subtract(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return createSubtract().subtract(value); + } + + private Subtract createSubtract() { + return fieldReference != null ? Subtract.valueOf(fieldReference) : Subtract.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that truncates a number to its integer. + * + * @return + */ + public Trunc trunc() { + return fieldReference != null ? Trunc.truncValueOf(fieldReference) : Trunc.truncValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates and returns the sum of numeric values. + * + * @return + */ + public Sum sum() { + return fieldReference != null ? AccumulatorOperators.Sum.sumOf(fieldReference) + : AccumulatorOperators.Sum.sumOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the average value of the numeric values. + * + * @return + */ + public Avg avg() { + return fieldReference != null ? AccumulatorOperators.Avg.avgOf(fieldReference) + : AccumulatorOperators.Avg.avgOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the maximum value. + * + * @return + */ + public Max max() { + return fieldReference != null ? AccumulatorOperators.Max.maxOf(fieldReference) + : AccumulatorOperators.Max.maxOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the minimum value. + * + * @return + */ + public Min min() { + return fieldReference != null ? AccumulatorOperators.Min.minOf(fieldReference) + : AccumulatorOperators.Min.minOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the population standard deviation of the input values. + * + * @return + */ + public StdDevPop stdDevPop() { + return fieldReference != null ? AccumulatorOperators.StdDevPop.stdDevPopOf(fieldReference) + : AccumulatorOperators.StdDevPop.stdDevPopOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that calculates the sample standard deviation of the input values. + * + * @return + */ + public StdDevSamp stdDevSamp() { + return fieldReference != null ? AccumulatorOperators.StdDevSamp.stdDevSampOf(fieldReference) + : AccumulatorOperators.StdDevSamp.stdDevSampOf(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $abs}. + * + * @author Christoph Strobl + */ + public static class Abs extends AbstractAggregationExpression { + + private Abs(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$abs"; + } + + /** + * Creates new {@link Abs}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Abs absoluteValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Abs(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Abs}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Abs absoluteValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Abs(expression); + } + + /** + * Creates new {@link Abs}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Abs absoluteValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Abs(value); + } + } + + /** + * {@link AggregationExpression} for {@code $add}. + * + * @author Christoph Strobl + */ + public static class Add extends AbstractAggregationExpression { + + protected Add(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$add"; + } + + /** + * Creates new {@link Add}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Add valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Add(asFields(fieldReference)); + } + + /** + * Creates new {@link Add}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Add valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Add(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Add}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Add valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Add(Collections.singletonList(value)); + } + + public Add add(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Add(append(Fields.field(fieldReference))); + } + + public Add add(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Add(append(expression)); + } + + public Add add(Number value) { + return new Add(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $ceil}. + * + * @author Christoph Strobl + */ + public static class Ceil extends AbstractAggregationExpression { + + private Ceil(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$ceil"; + } + + /** + * Creates new {@link Ceil}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Ceil ceilValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ceil(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Ceil}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Ceil ceilValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ceil(expression); + } + + /** + * Creates new {@link Ceil}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Ceil ceilValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Ceil(value); + } + } + + /** + * {@link AggregationExpression} for {@code $divide}. + * + * @author Christoph Strobl + */ + public static class Divide extends AbstractAggregationExpression { + + private Divide(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$divide"; + } + + /** + * Creates new {@link Divide}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Divide valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Divide(asFields(fieldReference)); + } + + /** + * Creates new {@link Divide}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Divide valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Divide(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Divide}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Divide valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Divide(Collections.singletonList(value)); + } + + public Divide divideBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Divide(append(Fields.field(fieldReference))); + } + + public Divide divideBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Divide(append(expression)); + } + + public Divide divideBy(Number value) { + return new Divide(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $exp}. + * + * @author Christoph Strobl + */ + public static class Exp extends AbstractAggregationExpression { + + private Exp(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$exp"; + } + + /** + * Creates new {@link Exp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Exp expValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Exp(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Exp}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Exp expValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Exp(expression); + } + + /** + * Creates new {@link Exp}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Exp expValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Exp(value); + } + } + + /** + * {@link AggregationExpression} for {@code $floor}. + * + * @author Christoph Strobl + */ + public static class Floor extends AbstractAggregationExpression { + + private Floor(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$floor"; + } + + /** + * Creates new {@link Floor}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Floor floorValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Floor(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Floor}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Floor floorValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Floor(expression); + } + + /** + * Creates new {@link Floor}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Floor floorValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Floor(value); + } + } + + /** + * {@link AggregationExpression} for {@code $ln}. + * + * @author Christoph Strobl + */ + public static class Ln extends AbstractAggregationExpression { + + private Ln(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$ln"; + } + + /** + * Creates new {@link Ln}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Ln lnValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ln(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Ln}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Ln lnValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ln(expression); + } + + /** + * Creates new {@link Ln}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Ln lnValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Ln(value); + } + } + + /** + * {@link AggregationExpression} for {@code $log}. + * + * @author Christoph Strobl + */ + public static class Log extends AbstractAggregationExpression { + + private Log(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$log"; + } + + /** + * Creates new {@link Min}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Log valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Log(asFields(fieldReference)); + } + + /** + * Creates new {@link Log}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Log valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Log(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Log}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Log valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Log(Collections.singletonList(value)); + } + + public Log log(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Log(append(Fields.field(fieldReference))); + } + + public Log log(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Log(append(expression)); + } + + public Log log(Number base) { + return new Log(append(base)); + } + } + + /** + * {@link AggregationExpression} for {@code $log10}. + * + * @author Christoph Strobl + */ + public static class Log10 extends AbstractAggregationExpression { + + private Log10(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$log10"; + } + + /** + * Creates new {@link Log10}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Log10 log10ValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Log10(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Log10}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Log10 log10ValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Log10(expression); + } + + /** + * Creates new {@link Log10}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Log10 log10ValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Log10(value); + } + } + + /** + * {@link AggregationExpression} for {@code $mod}. + * + * @author Christoph Strobl + */ + public static class Mod extends AbstractAggregationExpression { + + private Mod(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$mod"; + } + + /** + * Creates new {@link Mod}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Mod valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Mod(asFields(fieldReference)); + } + + /** + * Creates new {@link Mod}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Mod valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Mod(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Mod}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Mod valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Mod(Collections.singletonList(value)); + } + + public Mod mod(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Mod(append(Fields.field(fieldReference))); + } + + public Mod mod(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Mod(append(expression)); + } + + public Mod mod(Number base) { + return new Mod(append(base)); + } + } + + /** + * {@link AggregationExpression} for {@code $multiply}. + * + * @author Christoph Strobl + */ + public static class Multiply extends AbstractAggregationExpression { + + private Multiply(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$multiply"; + } + + /** + * Creates new {@link Multiply}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Multiply valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Multiply(asFields(fieldReference)); + } + + /** + * Creates new {@link Multiply}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Multiply valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Multiply(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Multiply}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Multiply valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Multiply(Collections.singletonList(value)); + } + + public Multiply multiplyBy(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Multiply(append(Fields.field(fieldReference))); + } + + public Multiply multiplyBy(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Multiply(append(expression)); + } + + public Multiply multiplyBy(Number value) { + return new Multiply(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $pow}. + * + * @author Christoph Strobl + */ + public static class Pow extends AbstractAggregationExpression { + + private Pow(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$pow"; + } + + /** + * Creates new {@link Pow}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Pow valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Pow(asFields(fieldReference)); + } + + /** + * Creates new {@link Pow}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Pow valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Pow(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Pow}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Pow valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Pow(Collections.singletonList(value)); + } + + public Pow pow(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Pow(append(Fields.field(fieldReference))); + } + + public Pow pow(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Pow(append(expression)); + } + + public Pow pow(Number value) { + return new Pow(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $sqrt}. + * + * @author Christoph Strobl + */ + public static class Sqrt extends AbstractAggregationExpression { + + private Sqrt(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$sqrt"; + } + + /** + * Creates new {@link Sqrt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Sqrt sqrtOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Sqrt(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Sqrt}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Sqrt sqrtOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Sqrt(expression); + } + + /** + * Creates new {@link Sqrt}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Sqrt sqrtOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Sqrt(value); + } + } + + /** + * {@link AggregationExpression} for {@code $subtract}. + * + * @author Christoph Strobl + */ + public static class Subtract extends AbstractAggregationExpression { + + private Subtract(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$subtract"; + } + + /** + * Creates new {@link Subtract}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Subtract valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Subtract(asFields(fieldReference)); + } + + /** + * Creates new {@link Subtract}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Subtract valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Subtract(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Subtract}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Subtract valueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Subtract(Collections.singletonList(value)); + } + + public Subtract subtract(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Subtract(append(Fields.field(fieldReference))); + } + + public Subtract subtract(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Subtract(append(expression)); + } + + public Subtract subtract(Number value) { + return new Subtract(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $trunc}. + * + * @author Christoph Strobl + */ + public static class Trunc extends AbstractAggregationExpression { + + private Trunc(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$trunc"; + } + + /** + * Creates new {@link Trunc}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Trunc truncValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Trunc(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Trunc}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Trunc truncValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Trunc(expression); + } + + /** + * Creates new {@link Trunc}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Trunc truncValueOf(Number value) { + + Assert.notNull(value, "Value must not be null!"); + return new Trunc(value); + } + } +} 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 new file mode 100644 index 0000000000..b549269ac0 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArrayOperators.java @@ -0,0 +1,1517 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.domain.Range; +import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.AsBuilder; +import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.PropertyExpression; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Gateway to {@literal array} aggregation operations. + * + * @author Christoph Strobl + * @since 1.0 + */ +public class ArrayOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArrayOperatorFactory arrayOf(String fieldReference) { + return new ArrayOperatorFactory(fieldReference); + } + + /** + * Take the array referenced resulting from the given {@link AggregationExpression}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ArrayOperatorFactory arrayOf(AggregationExpression expression) { + return new ArrayOperatorFactory(expression); + } + + /** + * @author Christoph Strobl + */ + public static class ArrayOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link ArrayOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ArrayOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link ArrayOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ArrayOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the + * specified array {@literal position}. + * + * @param position + * @return + */ + public ArrayElemAt elementAt(int position) { + return createArrayElemAt().elementAt(position); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the + * position resulting form the given {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public ArrayElemAt elementAt(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createArrayElemAt().elementAt(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the + * position defined by the referenced {@literal field}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public ArrayElemAt elementAt(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createArrayElemAt().elementAt(fieldReference); + } + + private ArrayElemAt createArrayElemAt() { + return usesFieldRef() ? ArrayElemAt.arrayOf(fieldReference) : ArrayElemAt.arrayOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and concats the given + * {@literal arrayFieldReference} to it. + * + * @param arrayFieldReference must not be {@literal null}. + * @return + */ + public ConcatArrays concat(String arrayFieldReference) { + + Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!"); + return createConcatArrays().concat(arrayFieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and concats the array resulting form + * the given {@literal expression} to it. + * + * @param expression must not be {@literal null}. + * @return + */ + public ConcatArrays concat(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createConcatArrays().concat(expression); + } + + private ConcatArrays createConcatArrays() { + return usesFieldRef() ? ConcatArrays.arrayOf(fieldReference) : ConcatArrays.arrayOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and selects a subset of the array to + * return based on the specified condition. + * + * @return + */ + public AsBuilder filter() { + return Filter.filter(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and an check if its an array. + * + * @return + */ + public IsArray isArray() { + return usesFieldRef() ? IsArray.isArray(fieldReference) : IsArray.isArray(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and retrieves its length. + * + * @return + */ + public Size length() { + return usesFieldRef() ? Size.lengthOfArray(fieldReference) : Size.lengthOfArray(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated array and selects a subset from it. + * + * @return + */ + public Slice slice() { + return usesFieldRef() ? Slice.sliceArrayOf(fieldReference) : Slice.sliceArrayOf(expression); + } + + /** + * 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 + */ + 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 ArrayOperatorFactory.ReduceInitialValueBuilder reduce(final AggregationExpression expression) { + return new ArrayOperatorFactory.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 ArrayOperatorFactory.ReduceInitialValueBuilder reduce(final PropertyExpression... expressions) { + + return new ArrayOperatorFactory.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 associated array. + * + * @param value must not be {@literal null}. + * @return + */ + 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); + } + + private boolean usesFieldRef() { + return fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $arrayElementAt}. + * + * @author Christoph Strobl + */ + public static 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 + */ + public static 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 + * @since 1.10 + */ + public static 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(ExposedFields.from(as), context); + } + + private DBObject toFilter(ExposedFields exposedFields, AggregationOperationContext context) { + + DBObject filterExpression = new BasicDBObject(); + InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( + exposedFields, context); + + filterExpression.putAll(context.getMappedObject(new BasicDBObject("input", getMappedInput(context)))); + filterExpression.put("as", as.getTarget()); + + filterExpression.putAll(context.getMappedObject(new BasicDBObject("cond", getMappedCondition(operationContext)))); + + 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); + return ((AggregationExpression) condition).toDbObject(nea); + } + + /** + * @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; + } + } + } + + /** + * {@link AggregationExpression} for {@code $isArray}. + * + * @author Christoph Strobl + */ + public static class IsArray extends AbstractAggregationExpression { + + private IsArray(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$isArray"; + } + + /** + * Creates new {@link IsArray}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IsArray isArray(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IsArray(Fields.field(fieldReference)); + } + + /** + * Creates new {@link IsArray}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IsArray isArray(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IsArray(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $size}. + * + * @author Christoph Strobl + */ + public static 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 + */ + public static 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); + } + }; + } + + /** + * @author Christoph Strobl + */ + public interface SliceElementsBuilder { + + /** + * Set the number of elements given {@literal nrElements}. + * + * @param nrElements + * @return + */ + Slice itemCount(int nrElements); + } + } + + /** + * {@link AggregationExpression} for {@code $indexOfArray}. + * + * @author Christoph Strobl + */ + public static 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)); + } + + /** + * @author Christoph Strobl + */ + public static class IndexOfArrayBuilder { + + private final Object targetArray; + + 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!"); + return new IndexOfArray(Arrays.asList(targetArray, value)); + } + } + } + + /** + * {@link AggregationExpression} for {@code $range}. + * + * @author Christoph Strobl + */ + public static class RangeOperator extends AbstractAggregationExpression { + + private RangeOperator(List values) { + super(values); + } + + @Override + 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); + } + + /** + * Start creating new {@link RangeOperator}. + * + * @param value + * @return + */ + 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; + } + + /** + * 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 + */ + public static class ReverseArray extends AbstractAggregationExpression { + + private ReverseArray(Object value) { + super(value); + } + + @Override + 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); + } + } + + /** + * {@link AggregationExpression} for {@code $reduce}. + * + * @author Christoph Strobl + */ + public static 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; + } + + /* (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(); + + 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###"); + } + } + + /** + * 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)); + } + }; + } + }; + } + + /** + * 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)); + } + }; + } + }; + } + + /** + * @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 initialValue must not be {@literal null}. + * @return + */ + 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 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 + */ + 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 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 + */ + Reduce reduce(PropertyExpression... expressions); + } + + /** + * @author Christoph Strobl + */ + public static class PropertyExpression implements AggregationExpression { + + private final String propertyName; + private final 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; + } + + /** + * 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); + } + }; + } + + /* (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 + */ + public 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 referringTo(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 $zip}. + * + * @author Christoph Strobl + */ + public static class Zip extends AbstractAggregationExpression { + + protected Zip(java.util.Map value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$zip"; + } + + /** + * Start creating new {@link Zip}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ZipBuilder arrayOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ZipBuilder(Fields.field(fieldReference)); + } + + /** + * Start creating new {@link Zip}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ZipBuilder arrayOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ZipBuilder(expression); + } + + /** + * Create new {@link Zip} and set the {@code useLongestLength} property to {@literal true}. + * + * @return + */ + public Zip useLongestLength() { + return new Zip(append("useLongestLength", true)); + } + + /** + * Optionally provide a default value. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Zip defaultTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Zip(append("defaults", Fields.field(fieldReference))); + } + + /** + * Optionally provide a default value. + * + * @param expression must not be {@literal null}. + * @return + */ + public Zip defaultTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Zip(append("defaults", expression)); + } + + /** + * 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 static class ZipBuilder { + + private final List sourceArrays; + + private 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}. + * + * @author Christoph Strobl + */ + public static class In extends AbstractAggregationExpression { + + private In(List values) { + super(values); + } + + @Override + 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) { + + Assert.notNull(value, "Value must not be null!"); + return new In(Arrays.asList(value, Fields.field(fieldReference))); + } + }; + } + + /** + * 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) { + + Assert.notNull(value, "Value must not be null!"); + return new In(Arrays.asList(value, expression)); + } + }; + } + + /** + * @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); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java new file mode 100644 index 0000000000..7e6ef68728 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java @@ -0,0 +1,353 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * Gateway to {@literal boolean expressions} that evaluate their argument expressions as booleans and return a boolean + * as the result. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class BooleanOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static BooleanOperatorFactory valueOf(String fieldReference) { + return new BooleanOperatorFactory(fieldReference); + } + + /** + * Take the value resulting of the given {@link AggregationExpression}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static BooleanOperatorFactory valueOf(AggregationExpression fieldReference) { + return new BooleanOperatorFactory(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates the boolean value of the referenced field and returns the + * opposite boolean value. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Not not(String fieldReference) { + return Not.not(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates the boolean value of {@link AggregationExpression} result + * and returns the opposite boolean value. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Not not(AggregationExpression expression) { + return Not.not(expression); + } + + /** + * @author Christoph Strobl + */ + public static class BooleanOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link BooleanOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public BooleanOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link BooleanOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public BooleanOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} if + * all of the expressions are {@literal true}. + * + * @param expression must not be {@literal null}. + * @return + */ + public And and(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createAnd().andExpression(expression); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} if + * all of the expressions are {@literal true}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public And and(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createAnd().andField(fieldReference); + } + + private And createAnd() { + return usesFieldRef() ? And.and(Fields.field(fieldReference)) : And.and(expression); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} if + * any of the expressions are {@literal true}. + * + * @param expression must not be {@literal null}. + * @return + */ + public Or or(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createOr().orExpression(expression); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} if + * any of the expressions are {@literal true}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Or or(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createOr().orField(fieldReference); + } + + private Or createOr() { + return usesFieldRef() ? Or.or(Fields.field(fieldReference)) : Or.or(expression); + } + + /** + * Creates new {@link AggregationExpression} that evaluates a boolean and returns the opposite boolean value. + * + * @return + */ + public Not not() { + return usesFieldRef() ? Not.not(fieldReference) : Not.not(expression); + } + + private boolean usesFieldRef() { + return this.fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $and}. + * + * @author Christoph Strobl + */ + public static class And extends AbstractAggregationExpression { + + private And(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$and"; + } + + /** + * Creates new {@link And} that evaluates one or more expressions and returns {@literal true} if all of the + * expressions are {@literal true}. + * + * @param expressions + * @return + */ + public static And and(Object... expressions) { + return new And(Arrays.asList(expressions)); + } + + /** + * Creates new {@link And} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public And andExpression(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new And(append(expression)); + } + + /** + * Creates new {@link And} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public And andField(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new And(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link And} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public And andValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new And(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $or}. + * + * @author Christoph Strobl + */ + public static class Or extends AbstractAggregationExpression { + + private Or(List values) { + super(values); + } + + @Override + protected String getMongoMethod() { + return "$or"; + } + + /** + * Creates new {@link Or} that evaluates one or more expressions and returns {@literal true} if any of the + * expressions are {@literal true}. + * + * @param expressions must not be {@literal null}. + * @return + */ + public static Or or(Object... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new Or(Arrays.asList(expressions)); + } + + /** + * Creates new {@link Or} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Or orExpression(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Or(append(expression)); + } + + /** + * Creates new {@link Or} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Or orField(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Or(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Or} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Or orValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Or(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $not}. + * + * @author Christoph Strobl + */ + public static class Not extends AbstractAggregationExpression { + + private Not(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$not"; + } + + /** + * Creates new {@link Not} that evaluates the boolean value of the referenced field and returns the opposite boolean + * value. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Not not(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Not(asFields(fieldReference)); + } + + /** + * Creates new {@link Not} that evaluates the resulting boolean value of the given {@link AggregationExpression} and + * returns the opposite boolean value. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Not not(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Not(Collections.singletonList(expression)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java new file mode 100644 index 0000000000..8196b506ab --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java @@ -0,0 +1,879 @@ +/* + * 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.Collections; +import java.util.List; + +import org.springframework.util.Assert; + +/** + * Gateway to {@literal comparison expressions}. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class ComparisonOperators { + + /** + * Take the field referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ComparisonOperatorFactory valueOf(String fieldReference) { + return new ComparisonOperatorFactory(fieldReference); + } + + /** + * Take the value resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ComparisonOperatorFactory valueOf(AggregationExpression expression) { + return new ComparisonOperatorFactory(expression); + } + + public static class ComparisonOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link ComparisonOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ComparisonOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ComparisonOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that compares two values. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Cmp compareTo(String fieldReference) { + return createCmp().compareTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values. + * + * @param expression must not be {@literal null}. + * @return + */ + public Cmp compareTo(AggregationExpression expression) { + return createCmp().compareTo(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values. + * + * @param value must not be {@literal null}. + * @return + */ + public Cmp compareToValue(Object value) { + return createCmp().compareToValue(value); + } + + private Cmp createCmp() { + return usesFieldRef() ? Cmp.valueOf(fieldReference) : Cmp.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is equal to the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Eq equalTo(String fieldReference) { + return createEq().equalTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is equal to the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Eq equalTo(AggregationExpression expression) { + return createEq().equalTo(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is equal to the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Eq equalToValue(Object value) { + return createEq().equalToValue(value); + } + + private Eq createEq() { + return usesFieldRef() ? Eq.valueOf(fieldReference) : Eq.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gt greaterThan(String fieldReference) { + return createGt().greaterThan(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gt greaterThan(AggregationExpression expression) { + return createGt().greaterThan(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Gt greaterThanValue(Object value) { + return createGt().greaterThanValue(value); + } + + private Gt createGt() { + return usesFieldRef() ? Gt.valueOf(fieldReference) : Gt.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than or equivalent to the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(String fieldReference) { + return createGte().greaterThanEqualTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than or equivalent to the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(AggregationExpression expression) { + return createGte().greaterThanEqualTo(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is greater than or equivalent to the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualToValue(Object value) { + return createGte().greaterThanEqualToValue(value); + } + + private Gte createGte() { + return usesFieldRef() ? Gte.valueOf(fieldReference) : Gte.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lt lessThan(String fieldReference) { + return createLt().lessThan(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lt lessThan(AggregationExpression expression) { + return createLt().lessThan(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than to the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public Lt lessThanValue(Object value) { + return createLt().lessThanValue(value); + } + + private Lt createLt() { + return usesFieldRef() ? Lt.valueOf(fieldReference) : Lt.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than or equivalent to the value of the referenced field. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(String fieldReference) { + return createLte().lessThanEqualTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than or equivalent to the expression result. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(AggregationExpression expression) { + return createLte().lessThanEqualTo(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * value is less than or equivalent to the given value. + * + * @param value + * @return + */ + public Lte lessThanEqualToValue(Object value) { + return createLte().lessThanEqualToValue(value); + } + + private Lte createLte() { + return usesFieldRef() ? Lte.valueOf(fieldReference) : Lte.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values + * are not equivalent. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Ne notEqualTo(String fieldReference) { + return createNe().notEqualTo(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values + * are not equivalent. + * + * @param expression must not be {@literal null}. + * @return + */ + public Ne notEqualTo(AggregationExpression expression) { + return createNe().notEqualTo(expression); + } + + /** + * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values + * are not equivalent. + * + * @param value must not be {@literal null}. + * @return + */ + public Ne notEqualToValue(Object value) { + return createNe().notEqualToValue(value); + } + + private Ne createNe() { + return usesFieldRef() ? Ne.valueOf(fieldReference) : Ne.valueOf(expression); + } + + private boolean usesFieldRef() { + return fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $cmp}. + * + * @author Christoph Strobl + */ + public static class Cmp extends AbstractAggregationExpression { + + private Cmp(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$cmp"; + } + + /** + * Creates new {@link Cmp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Cmp valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Cmp(asFields(fieldReference)); + } + + /** + * Creates new {@link Cmp}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Cmp valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Cmp(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Cmp} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Cmp compareTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Cmp(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Cmp} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Cmp compareTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Cmp(append(expression)); + } + + /** + * Creates new {@link Cmp} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Cmp compareToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Cmp(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $eq}. + * + * @author Christoph Strobl + */ + public static class Eq extends AbstractAggregationExpression { + + private Eq(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$eq"; + } + + /** + * Creates new {@link Eq}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Eq valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Eq(asFields(fieldReference)); + } + + /** + * Creates new {@link Eq}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Eq valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Eq(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Eq equalTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Eq(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Eq equalTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Eq(append(expression)); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Eq equalToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Eq(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $gt}. + * + * @author Christoph Strobl + */ + public static class Gt extends AbstractAggregationExpression { + + private Gt(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$gt"; + } + + /** + * Creates new {@link Gt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Gt valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gt(asFields(fieldReference)); + } + + /** + * Creates new {@link Gt}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Gt valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gt(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Gt} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gt greaterThan(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gt(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Gt} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gt greaterThan(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gt(append(expression)); + } + + /** + * Creates new {@link Gt} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Gt greaterThanValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Gt(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $lt}. + * + * @author Christoph Strobl + */ + public static class Lt extends AbstractAggregationExpression { + + private Lt(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$lt"; + } + + /** + * Creates new {@link Lt}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Lt valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lt(asFields(fieldReference)); + } + + /** + * Creates new {@link Lt}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Lt valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lt(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Lt} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lt lessThan(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lt(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Lt} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lt lessThan(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lt(append(expression)); + } + + /** + * Creates new {@link Lt} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Lt lessThanValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Lt(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $gte}. + * + * @author Christoph Strobl + */ + public static class Gte extends AbstractAggregationExpression { + + private Gte(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$gte"; + } + + /** + * Creates new {@link Gte}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Gte valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gte(asFields(fieldReference)); + } + + /** + * Creates new {@link Gte}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Gte valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gte(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Gte} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Gte(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Gte} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Gte(append(expression)); + } + + /** + * Creates new {@link Gte} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Gte greaterThanEqualToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Gte(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $lte}. + * + * @author Christoph Strobl + */ + public static class Lte extends AbstractAggregationExpression { + + private Lte(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$lte"; + } + + /** + * Creates new {@link Lte}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Lte valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lte(asFields(fieldReference)); + } + + /** + * Creates new {@link Lte}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Lte valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lte(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Lte} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Lte(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Lte} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Lte lessThanEqualTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Lte(append(expression)); + } + + /** + * Creates new {@link Lte} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Lte lessThanEqualToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Lte(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $ne}. + * + * @author Christoph Strobl + */ + public static class Ne extends AbstractAggregationExpression { + + private Ne(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$ne"; + } + + /** + * Creates new {@link Ne}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Ne valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ne(asFields(fieldReference)); + } + + /** + * Creates new {@link Ne}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Ne valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ne(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Ne} with all previously added arguments appending the given one. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Ne notEqualTo(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Ne(append(Fields.field(fieldReference))); + } + + /** + * Creates new {@link Ne} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public Ne notEqualTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Ne(append(expression)); + } + + /** + * Creates new {@link Eq} with all previously added arguments appending the given one. + * + * @param value must not be {@literal null}. + * @return + */ + public Ne notEqualToValue(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Ne(append(value)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java new file mode 100644 index 0000000000..31d412f5a9 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java @@ -0,0 +1,975 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Gateway to {@literal conditional expressions} that evaluate their argument expressions as booleans to a value. + * + * @author Mark Paluch + */ +public class ConditionalOperators { + + /** + * Take the field referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ConditionalOperatorFactory when(String fieldReference) { + return new ConditionalOperatorFactory(fieldReference); + } + + /** + * Take the value resulting from the given {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ConditionalOperatorFactory when(AggregationExpression expression) { + return new ConditionalOperatorFactory(expression); + } + + /** + * Take the value resulting from the given {@literal criteriaDefinition}. + * + * @param criteriaDefinition must not be {@literal null}. + * @return + */ + public static ConditionalOperatorFactory when(CriteriaDefinition criteriaDefinition) { + return new ConditionalOperatorFactory(criteriaDefinition); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates an expression and returns the value of the expression if + * the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, including + * instances of undefined values or missing fields, returns the value of the replacement expression. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static IfNull.ThenBuilder ifNull(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return IfNull.ifNull(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates an expression and returns the value of the expression if + * the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, including + * instances of undefined values or missing fields, returns the value of the replacement expression. + * + * @param expression must not be {@literal null}. + * @return + */ + public static IfNull.ThenBuilder ifNull(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return IfNull.ifNull(expression); + } + + /** + * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it + * 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 + */ + 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 {@literal 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; + private final AggregationExpression expression; + private final CriteriaDefinition criteriaDefinition; + + /** + * Creates new {@link ConditionalOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public ConditionalOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + + this.fieldReference = fieldReference; + this.expression = null; + this.criteriaDefinition = null; + } + + /** + * Creates new {@link ConditionalOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public ConditionalOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + this.fieldReference = null; + this.expression = expression; + this.criteriaDefinition = null; + } + + /** + * Creates new {@link ConditionalOperatorFactory} for given {@link CriteriaDefinition}. + * + * @param criteriaDefinition must not be {@literal null}. + */ + public ConditionalOperatorFactory(CriteriaDefinition criteriaDefinition) { + + Assert.notNull(criteriaDefinition, "CriteriaDefinition must not be null!"); + + this.fieldReference = null; + this.expression = null; + this.criteriaDefinition = criteriaDefinition; + } + + /** + * Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two specified + * return expressions. + * + * @param value must not be {@literal null}. + * @return + */ + public OtherwiseBuilder then(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return createThenBuilder().then(value); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates a boolean expression to return one of the two specified + * return expressions. + * + * @param expression must not be {@literal null}. + * @return + */ + public OtherwiseBuilder thenValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createThenBuilder().then(expression); + } + + /** + * Creates new {@link AggregationExpressions} that evaluates a boolean expression to return one of the two specified + * return expressions. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public OtherwiseBuilder thenValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createThenBuilder().then(fieldReference); + } + + private ThenBuilder createThenBuilder() { + + if (usesFieldRef()) { + return Cond.newBuilder().when(fieldReference); + } + + return usesCriteriaDefinition() ? Cond.newBuilder().when(criteriaDefinition) : Cond.newBuilder().when(expression); + } + + private boolean usesFieldRef() { + return this.fieldReference != null; + } + + private boolean usesCriteriaDefinition() { + return this.criteriaDefinition != null; + } + } + + /** + * Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field + * field references}, {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be + * converted to a simple MongoDB type. + * + * @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ + * @author Mark Paluch + */ + public static class IfNull implements AggregationExpression { + + private final Object condition; + private final Object value; + + private IfNull(Object condition, Object value) { + + this.condition = condition; + this.value = value; + } + + /** + * Creates new {@link IfNull}. + * + * @param fieldReference the field to check for a {@literal null} value, field reference must not be {@literal null} + * . + * @return + */ + public static ThenBuilder ifNull(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IfNullOperatorBuilder().ifNull(fieldReference); + } + + /** + * Creates new {@link IfNull}. + * + * @param expression the expression to check for a {@literal null} value, field reference must not be + * {@literal null}. + * @return + */ + public static ThenBuilder ifNull(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IfNullOperatorBuilder().ifNull(expression); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + List list = new ArrayList(); + + if (condition instanceof Field) { + list.add(context.getReference((Field) condition).toString()); + } else if (condition instanceof AggregationExpression) { + list.add(((AggregationExpression) condition).toDbObject(context)); + } else { + list.add(condition); + } + + list.add(resolve(value, context)); + + return new BasicDBObject("$ifNull", list); + } + + private Object resolve(Object value, AggregationOperationContext context) { + + if (value instanceof Field) { + return context.getReference((Field) value).toString(); + } else if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } else if (value instanceof DBObject) { + return value; + } + + return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); + } + + /** + * @author Mark Paluch + */ + public static interface IfNullBuilder { + + /** + * @param fieldReference the field to check for a {@literal null} value, field reference must not be + * {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder ifNull(String fieldReference); + + /** + * @param expression the expression to check for a {@literal null} value, field name must not be {@literal null} + * or empty. + * @return the {@link ThenBuilder} + */ + ThenBuilder ifNull(AggregationExpression expression); + } + + /** + * @author Mark Paluch + */ + public static interface ThenBuilder { + + /** + * @param value the value to be used if the {@code $ifNull} condition evaluates {@literal true}. Can be a + * {@link DBObject}, a value that is supported by MongoDB or a value that can be converted to a MongoDB + * representation but must not be {@literal null}. + * @return + */ + IfNull then(Object value); + + /** + * @param fieldReference the field holding the replacement value, must not be {@literal null}. + * @return + */ + IfNull thenValueOf(String fieldReference); + + /** + * @param expression the expression yielding to the replacement value, must not be {@literal null}. + * @return + */ + public IfNull thenValueOf(AggregationExpression expression); + } + + /** + * Builder for fluent {@link IfNullOperator} creation. + * + * @author Mark Paluch + */ + static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder { + + private Object condition; + + private IfNullOperatorBuilder() {} + + /** + * Creates a new builder for {@link IfNullOperator}. + * + * @return never {@literal null}. + */ + public static IfNullOperatorBuilder newBuilder() { + return new IfNullOperatorBuilder(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.IfNullBuilder#ifNull(java.lang.String) + */ + public ThenBuilder ifNull(String fieldReference) { + + Assert.hasText(fieldReference, "FieldReference name must not be null or empty!"); + this.condition = Fields.field(fieldReference); + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public ThenBuilder ifNull(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression name must not be null or empty!"); + this.condition = expression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#then(java.lang.Object) + */ + public IfNull then(Object value) { + return new IfNull(condition, value); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#thenValueOf(java.lang.String) + */ + public IfNull thenValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new IfNull(condition, Fields.field(fieldReference)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + public IfNull thenValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new IfNull(condition, expression); + } + } + } + + /** + * {@link AggregationExpression} for {@code $switch}. + * + * @author Christoph Strobl + */ + public static class Switch extends AbstractAggregationExpression { + + private Switch(java.util.Map values) { + super(values); + } + + @Override + 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!"); + return new Switch(Collections. singletonMap("branches", new ArrayList(conditions))); + } + + 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; + 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); + } + }; + } + + /* (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) { + 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; + } + + /** + * @author Christoph Strobl + */ + public interface ThenBuilder { + + /** + * Set the then {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + CaseOperator then(Object value); + } + } + } + + /** + * Encapsulates the aggregation framework {@code $cond} operator. A {@link Cond} allows nested conditions + * {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition}, {@link AggregationExpression} + * or a {@link DBObject custom} condition. Replacement values can be either {@link Field field references}, + * {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be converted to a + * simple MongoDB type. + * + * @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/ + * @author Mark Paluch + * @author Christoph Strobl + */ + public static class Cond implements AggregationExpression { + + private final Object condition; + private final Object thenValue; + private final Object otherwiseValue; + + /** + * Creates a new {@link Cond} for a given {@link Field} and {@code then}/{@code otherwise} values. + * + * @param condition must not be {@literal null}. + * @param thenValue must not be {@literal null}. + * @param otherwiseValue must not be {@literal null}. + */ + private Cond(Field condition, Object thenValue, Object otherwiseValue) { + this((Object) condition, thenValue, otherwiseValue); + } + + /** + * Creates a new {@link Cond} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise} values. + * + * @param condition must not be {@literal null}. + * @param thenValue must not be {@literal null}. + * @param otherwiseValue must not be {@literal null}. + */ + private Cond(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) { + this((Object) condition, thenValue, otherwiseValue); + } + + private Cond(Object condition, Object thenValue, Object otherwiseValue) { + + Assert.notNull(condition, "Condition must not be null!"); + Assert.notNull(thenValue, "Then value must not be null!"); + Assert.notNull(otherwiseValue, "Otherwise value must not be null!"); + + assertNotBuilder(condition, "Condition"); + assertNotBuilder(thenValue, "Then value"); + assertNotBuilder(otherwiseValue, "Otherwise value"); + + this.condition = condition; + this.thenValue = thenValue; + this.otherwiseValue = otherwiseValue; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDbObject(AggregationOperationContext context) { + + BasicDBObject condObject = new BasicDBObject(); + + condObject.append("if", resolveCriteria(context, condition)); + condObject.append("then", resolveValue(context, thenValue)); + condObject.append("else", resolveValue(context, otherwiseValue)); + + return new BasicDBObject("$cond", condObject); + } + + private Object resolveValue(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject || value instanceof Field) { + return resolve(context, value); + } + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + return context.getMappedObject(new BasicDBObject("$set", value)).get("$set"); + } + + private Object resolveCriteria(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject || value instanceof Field) { + return resolve(context, value); + } + + if (value instanceof AggregationExpression) { + return ((AggregationExpression) value).toDbObject(context); + } + + if (value instanceof CriteriaDefinition) { + + DBObject mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject()); + List clauses = new ArrayList(); + + clauses.addAll(getClauses(context, mappedObject)); + + return clauses.size() == 1 ? clauses.get(0) : clauses; + } + + throw new InvalidDataAccessApiUsageException( + String.format("Invalid value in condition. Supported: DBObject, Field references, Criteria, got: %s", value)); + } + + private List getClauses(AggregationOperationContext context, DBObject mappedObject) { + + List clauses = new ArrayList(); + + for (String key : mappedObject.keySet()) { + + Object predicate = mappedObject.get(key); + clauses.addAll(getClauses(context, key, predicate)); + } + + return clauses; + } + + private List getClauses(AggregationOperationContext context, String key, Object predicate) { + + List clauses = new ArrayList(); + + if (predicate instanceof List) { + + List args = new ArrayList(); + for (Object clause : (List) predicate) { + if (clause instanceof DBObject) { + args.addAll(getClauses(context, (DBObject) clause)); + } + } + + clauses.add(new BasicDBObject(key, args)); + + } else if (predicate instanceof DBObject) { + + DBObject nested = (DBObject) predicate; + + for (String s : nested.keySet()) { + + if (!isKeyword(s)) { + continue; + } + + List args = new ArrayList(); + args.add("$" + key); + args.add(nested.get(s)); + clauses.add(new BasicDBObject(s, args)); + } + + } else if (!isKeyword(key)) { + + List args = new ArrayList(); + args.add("$" + key); + args.add(predicate); + clauses.add(new BasicDBObject("$eq", args)); + } + + return clauses; + } + + /** + * Returns whether the given {@link String} is a MongoDB keyword. + * + * @param candidate + * @return + */ + private boolean isKeyword(String candidate) { + return candidate.startsWith("$"); + } + + private Object resolve(AggregationOperationContext context, Object value) { + + if (value instanceof DBObject) { + return context.getMappedObject((DBObject) value); + } + + return context.getReference((Field) value).toString(); + } + + private void assertNotBuilder(Object toCheck, String name) { + Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck), + String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName())); + } + + /** + * Get a builder that allows fluent creation of {@link Cond}. + * + * @return never {@literal null}. + */ + public static WhenBuilder newBuilder() { + return ConditionalExpressionBuilder.newBuilder(); + } + + /** + * Start creating new {@link Cond} by providing the boolean expression used in {@code if}. + * + * @param booleanExpression must not be {@literal null}. + * @return never {@literal null}. + */ + public static ThenBuilder when(DBObject booleanExpression) { + return ConditionalExpressionBuilder.newBuilder().when(booleanExpression); + } + + /** + * Start creating new {@link Cond} by providing the {@link AggregationExpression} used in {@code if}. + * + * @param expression expression that yields in a boolean result, must not be {@literal null}. + * @return never {@literal null}. + */ + public static ThenBuilder when(AggregationExpression expression) { + return ConditionalExpressionBuilder.newBuilder().when(expression); + } + + /** + * Start creating new {@link Cond} by providing the field reference used in {@code if}. + * + * @param booleanField name of a field holding a boolean value, must not be {@literal null}. + * @return never {@literal null}. + */ + public static ThenBuilder when(String booleanField) { + return ConditionalExpressionBuilder.newBuilder().when(booleanField); + } + + /** + * Start creating new {@link Cond} by providing the {@link CriteriaDefinition} used in {@code if}. + * + * @param criteria criteria to evaluate, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + public static ThenBuilder when(CriteriaDefinition criteria) { + return ConditionalExpressionBuilder.newBuilder().when(criteria); + } + + /** + * @author Mark Paluch + */ + public static interface WhenBuilder { + + /** + * @param booleanExpression expression that yields in a boolean result, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(DBObject booleanExpression); + + /** + * @param expression expression that yields in a boolean result, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(AggregationExpression expression); + + /** + * @param booleanField name of a field holding a boolean value, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(String booleanField); + + /** + * @param criteria criteria to evaluate, must not be {@literal null}. + * @return the {@link ThenBuilder} + */ + ThenBuilder when(CriteriaDefinition criteria); + } + + /** + * @author Mark Paluch + */ + public static interface ThenBuilder { + + /** + * @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link DBObject}, a + * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but + * must not be {@literal null}. + * @return the {@link OtherwiseBuilder} + */ + OtherwiseBuilder then(Object value); + + /** + * @param fieldReference must not be {@literal null}. + * @return the {@link OtherwiseBuilder} + */ + OtherwiseBuilder thenValueOf(String fieldReference); + + /** + * @param expression must not be {@literal null}. + * @return the {@link OtherwiseBuilder} + */ + OtherwiseBuilder thenValueOf(AggregationExpression expression); + } + + /** + * @author Mark Paluch + */ + public static interface OtherwiseBuilder { + + /** + * @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link DBObject}, a + * value that is supported by MongoDB or a value that can be converted to a MongoDB representation but + * must not be {@literal null}. + * @return the {@link Cond} + */ + Cond otherwise(Object value); + + /** + * @param fieldReference must not be {@literal null}. + * @return the {@link Cond} + */ + Cond otherwiseValueOf(String fieldReference); + + /** + * @param expression must not be {@literal null}. + * @return the {@link Cond} + */ + Cond otherwiseValueOf(AggregationExpression expression); + } + + /** + * Builder for fluent {@link Cond} creation. + * + * @author Mark Paluch + */ + static class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder { + + private Object condition; + private Object thenValue; + + private ConditionalExpressionBuilder() {} + + /** + * Creates a new builder for {@link Cond}. + * + * @return never {@literal null}. + */ + public static ConditionalExpressionBuilder newBuilder() { + return new ConditionalExpressionBuilder(); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(com.mongodb.DBObject) + */ + @Override + public ConditionalExpressionBuilder when(DBObject booleanExpression) { + + Assert.notNull(booleanExpression, "'Boolean expression' must not be null!"); + + this.condition = booleanExpression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition) + */ + @Override + public ThenBuilder when(CriteriaDefinition criteria) { + + Assert.notNull(criteria, "Criteria must not be null!"); + this.condition = criteria; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public ThenBuilder when(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression field must not be null!"); + this.condition = expression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(java.lang.String) + */ + @Override + public ThenBuilder when(String booleanField) { + + Assert.hasText(booleanField, "Boolean field name must not be null or empty!"); + this.condition = Fields.field(booleanField); + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#then(java.lang.Object) + */ + @Override + public OtherwiseBuilder then(Object thenValue) { + + Assert.notNull(thenValue, "Then-value must not be null!"); + this.thenValue = thenValue; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#thenValueOf(java.lang.String) + */ + @Override + public OtherwiseBuilder thenValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.thenValue = Fields.field(fieldReference); + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public OtherwiseBuilder thenValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null!"); + this.thenValue = expression; + return this; + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwise(java.lang.Object) + */ + @Override + public Cond otherwise(Object otherwiseValue) { + + Assert.notNull(otherwiseValue, "Value must not be null!"); + return new Cond(condition, thenValue, otherwiseValue); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwiseValueOf(java.lang.String) + */ + @Override + public Cond otherwiseValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Cond(condition, thenValue, Fields.field(fieldReference)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwiseValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + */ + @Override + public Cond otherwiseValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "AggregationExpression must not be null!"); + return new Cond(condition, thenValue, expression); + } + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java new file mode 100644 index 0000000000..9a83753a17 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DataTypeOperators.java @@ -0,0 +1,67 @@ +/* + * 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.util.Assert; + +/** + * Gateway to {@literal data type} expressions. + * + * @author Christoph Strobl + * @since 1.10 + * @soundtrack Clawfinger - Catch Me + */ +public class DataTypeOperators { + + /** + * Return the BSON data type of the given {@literal field}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Type typeOf(String fieldReference) { + return Type.typeOf(fieldReference); + } + + /** + * {@link AggregationExpression} for {@code $type}. + * + * @author Christoph Strobl + */ + public static 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/DateOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java new file mode 100644 index 0000000000..88fca85f46 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java @@ -0,0 +1,838 @@ +/* + * 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.LinkedHashMap; + +import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators.ArithmeticOperatorFactory; +import org.springframework.util.Assert; + +/** + * Gateway to {@literal Date} aggregation operations. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class DateOperators { + + /** + * Take the date referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static DateOperatorFactory dateOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DateOperatorFactory(fieldReference); + } + + /** + * Take the date resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static DateOperatorFactory dateOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DateOperatorFactory(expression); + } + + /** + * @author Christoph Strobl + */ + public static class DateOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public DateOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link ArithmeticOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public DateOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that returns the day of the year for a date as a number between 1 and + * 366. + * + * @return + */ + public DayOfYear dayOfYear() { + return usesFieldRef() ? DayOfYear.dayOfYear(fieldReference) : DayOfYear.dayOfYear(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the day of the month for a date as a number between 1 and + * 31. + * + * @return + */ + public DayOfMonth dayOfMonth() { + return usesFieldRef() ? DayOfMonth.dayOfMonth(fieldReference) : DayOfMonth.dayOfMonth(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the day of the week for a date as a number between 1 + * (Sunday) and 7 (Saturday). + * + * @return + */ + public DayOfWeek dayOfWeek() { + return usesFieldRef() ? DayOfWeek.dayOfWeek(fieldReference) : DayOfWeek.dayOfWeek(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the year portion of a date. + * + * @return + */ + public Year year() { + return usesFieldRef() ? Year.yearOf(fieldReference) : Year.yearOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the month of a date as a number between 1 and 12. + * + * @return + */ + public Month month() { + return usesFieldRef() ? Month.monthOf(fieldReference) : Month.monthOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the week of the year for a date as a number between 0 and + * 53. + * + * @return + */ + public Week week() { + return usesFieldRef() ? Week.weekOf(fieldReference) : Week.weekOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the hour portion of a date as a number between 0 and 23. + * + * @return + */ + public Hour hour() { + return usesFieldRef() ? Hour.hourOf(fieldReference) : Hour.hourOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the minute portion of a date as a number between 0 and + * 59. + * + * @return + */ + public Minute minute() { + return usesFieldRef() ? Minute.minuteOf(fieldReference) : Minute.minuteOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the second portion of a date as a number between 0 and + * 59, but can be 60 to account for leap seconds. + * + * @return + */ + public Second second() { + return usesFieldRef() ? Second.secondOf(fieldReference) : Second.secondOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that returns the millisecond portion of a date as an integer between 0 + * and 999. + * + * @return + */ + public Millisecond millisecond() { + return usesFieldRef() ? Millisecond.millisecondOf(fieldReference) : Millisecond.millisecondOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that converts a date object to a string according to a user-specified + * {@literal format}. + * + * @param format must not be {@literal null}. + * @return + */ + public DateToString toString(String format) { + return (usesFieldRef() ? DateToString.dateOf(fieldReference) : DateToString.dateOf(expression)).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; + } + } + + /** + * {@link AggregationExpression} for {@code $dayOfYear}. + * + * @author Christoph Strobl + */ + public static class DayOfYear extends AbstractAggregationExpression { + + private DayOfYear(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$dayOfYear"; + } + + /** + * Creates new {@link DayOfYear}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static DayOfYear dayOfYear(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DayOfYear(Fields.field(fieldReference)); + } + + /** + * Creates new {@link DayOfYear}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static DayOfYear dayOfYear(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DayOfYear(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $dayOfMonth}. + * + * @author Christoph Strobl + */ + public static class DayOfMonth extends AbstractAggregationExpression { + + private DayOfMonth(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$dayOfMonth"; + } + + /** + * Creates new {@link DayOfMonth}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static DayOfMonth dayOfMonth(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DayOfMonth(Fields.field(fieldReference)); + } + + /** + * Creates new {@link DayOfMonth}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static DayOfMonth dayOfMonth(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DayOfMonth(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $dayOfWeek}. + * + * @author Christoph Strobl + */ + public static class DayOfWeek extends AbstractAggregationExpression { + + private DayOfWeek(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$dayOfWeek"; + } + + /** + * Creates new {@link DayOfWeek}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static DayOfWeek dayOfWeek(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new DayOfWeek(Fields.field(fieldReference)); + } + + /** + * Creates new {@link DayOfWeek}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static DayOfWeek dayOfWeek(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new DayOfWeek(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $year}. + * + * @author Christoph Strobl + */ + public static class Year extends AbstractAggregationExpression { + + private Year(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$year"; + } + + /** + * Creates new {@link Year}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Year yearOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Year(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Year}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Year yearOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Year(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $month}. + * + * @author Christoph Strobl + */ + public static class Month extends AbstractAggregationExpression { + + private Month(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$month"; + } + + /** + * Creates new {@link Month}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Month monthOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Month(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Month}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Month monthOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Month(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $week}. + * + * @author Christoph Strobl + */ + public static class Week extends AbstractAggregationExpression { + + private Week(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$week"; + } + + /** + * Creates new {@link Week}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Week weekOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Week(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Week}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Week weekOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Week(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $hour}. + * + * @author Christoph Strobl + */ + public static class Hour extends AbstractAggregationExpression { + + private Hour(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$hour"; + } + + /** + * Creates new {@link Hour}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Hour hourOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Hour(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Hour}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Hour hourOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Hour(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $minute}. + * + * @author Christoph Strobl + */ + public static class Minute extends AbstractAggregationExpression { + + private Minute(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$minute"; + } + + /** + * Creates new {@link Minute}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Minute minuteOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Minute(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Minute}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Minute minuteOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Minute(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $second}. + * + * @author Christoph Strobl + */ + public static class Second extends AbstractAggregationExpression { + + private Second(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$second"; + } + + /** + * Creates new {@link Second}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Second secondOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Second(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Second}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Second secondOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Second(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $millisecond}. + * + * @author Christoph Strobl + */ + public static class Millisecond extends AbstractAggregationExpression { + + private Millisecond(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$millisecond"; + } + + /** + * Creates new {@link Millisecond}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Millisecond millisecondOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Millisecond(Fields.field(fieldReference)); + } + + /** + * Creates new {@link Millisecond}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Millisecond millisecondOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Millisecond(expression); + } + } + + /** + * {@link AggregationExpression} for {@code $dateToString}. + * + * @author Christoph Strobl + */ + public static class DateToString extends AbstractAggregationExpression { + + private DateToString(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$dateToString"; + } + + /** + * Creates new {@link FormatBuilder} allowing to define the date format to apply. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static FormatBuilder dateOf(final String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference 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(Fields.field(fieldReference), format)); + } + }; + } + + /** + * Creates new {@link FormatBuilder} allowing to define the date format to apply. + * + * @param expression must not be {@literal null}. + * @return + */ + 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)); + } + }; + } + + private static java.util.Map argumentMap(Object date, String format) { + + java.util.Map args = new LinkedHashMap(2); + args.put("format", format); + args.put("date", date); + return args; + } + + public interface FormatBuilder { + + /** + * Creates new {@link DateToString} with all previously added arguments appending the given one. + * + * @param format must not be {@literal null}. + * @return + */ + DateToString toString(String format); + } + } + + /** + * {@link AggregationExpression} for {@code $isoDayOfWeek}. + * + * @author Christoph Strobl + */ + public static 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 + */ + public static 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 + */ + public static 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); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java new file mode 100644 index 0000000000..a91b4e935c --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java @@ -0,0 +1,96 @@ +/* + * 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.util.Assert; + +/** + * Gateway to {@literal literal} aggregation operations. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class LiteralOperators { + + /** + * Take the value referenced by given {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static LiteralOperatorFactory valueOf(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new LiteralOperatorFactory(value); + } + + /** + * @author Christoph Strobl + */ + public static class LiteralOperatorFactory { + + private final Object value; + + /** + * Creates new {@link LiteralOperatorFactory} for given {@literal value}. + * + * @param value must not be {@literal null}. + */ + public LiteralOperatorFactory(Object value) { + + Assert.notNull(value, "Value must not be null!"); + this.value = value; + } + + /** + * Creates new {@link AggregationExpressions} that returns the associated value without parsing. + * + * @return + */ + public Literal asLiteral() { + return Literal.asLiteral(value); + } + } + + /** + * {@link AggregationExpression} for {@code $literal}. + * + * @author Christoph Strobl + */ + public static class Literal extends AbstractAggregationExpression { + + private Literal(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$literal"; + } + + /** + * Creates new {@link Literal}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Literal asLiteral(Object value) { + + Assert.notNull(value, "Value must not be null!"); + return new Literal(value); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java index f1d67b02c4..bc18aed8ad 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 @@ -21,9 +21,9 @@ import java.util.Collections; import java.util.List; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull; +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection; @@ -790,7 +790,7 @@ public ProjectionOperationBuilder slice(int count, int offset) { * @since 1.10 */ public ProjectionOperationBuilder filter(String as, AggregationExpression condition) { - return this.operation.and(AggregationExpressions.Filter.filter(name).as(as).by(condition)); + return this.operation.and(ArrayOperators.Filter.filter(name).as(as).by(condition)); } // SET OPERATORS @@ -895,7 +895,7 @@ public ProjectionOperationBuilder allElementsInArrayTrue() { * @since 1.10 */ public ProjectionOperationBuilder absoluteValue() { - return this.operation.and(AggregationExpressions.Abs.absoluteValueOf(name)); + return this.operation.and(ArithmeticOperators.Abs.absoluteValueOf(name)); } /** @@ -906,7 +906,7 @@ public ProjectionOperationBuilder absoluteValue() { * @since 1.10 */ public ProjectionOperationBuilder ceil() { - return this.operation.and(AggregationExpressions.Ceil.ceilValueOf(name)); + return this.operation.and(ArithmeticOperators.Ceil.ceilValueOf(name)); } /** @@ -917,7 +917,7 @@ public ProjectionOperationBuilder ceil() { * @since 1.10 */ public ProjectionOperationBuilder exp() { - return this.operation.and(AggregationExpressions.Exp.expValueOf(name)); + return this.operation.and(ArithmeticOperators.Exp.expValueOf(name)); } /** @@ -928,7 +928,7 @@ public ProjectionOperationBuilder exp() { * @since 1.10 */ public ProjectionOperationBuilder floor() { - return this.operation.and(AggregationExpressions.Floor.floorValueOf(name)); + return this.operation.and(ArithmeticOperators.Floor.floorValueOf(name)); } /** @@ -939,7 +939,7 @@ public ProjectionOperationBuilder floor() { * @since 1.10 */ public ProjectionOperationBuilder ln() { - return this.operation.and(AggregationExpressions.Ln.lnValueOf(name)); + return this.operation.and(ArithmeticOperators.Ln.lnValueOf(name)); } /** @@ -951,7 +951,7 @@ public ProjectionOperationBuilder ln() { * @since 1.10 */ public ProjectionOperationBuilder log(String baseFieldRef) { - return this.operation.and(AggregationExpressions.Log.valueOf(name).log(baseFieldRef)); + return this.operation.and(ArithmeticOperators.Log.valueOf(name).log(baseFieldRef)); } /** @@ -963,7 +963,7 @@ public ProjectionOperationBuilder log(String baseFieldRef) { * @since 1.10 */ public ProjectionOperationBuilder log(Number base) { - return this.operation.and(AggregationExpressions.Log.valueOf(name).log(base)); + return this.operation.and(ArithmeticOperators.Log.valueOf(name).log(base)); } /** @@ -975,7 +975,7 @@ public ProjectionOperationBuilder log(Number base) { * @since 1.10 */ public ProjectionOperationBuilder log(AggregationExpression base) { - return this.operation.and(AggregationExpressions.Log.valueOf(name).log(base)); + return this.operation.and(ArithmeticOperators.Log.valueOf(name).log(base)); } /** @@ -986,7 +986,7 @@ public ProjectionOperationBuilder log(AggregationExpression base) { * @since 1.10 */ public ProjectionOperationBuilder log10() { - return this.operation.and(AggregationExpressions.Log10.log10ValueOf(name)); + return this.operation.and(ArithmeticOperators.Log10.log10ValueOf(name)); } /** @@ -998,7 +998,7 @@ public ProjectionOperationBuilder log10() { * @since 1.10 */ public ProjectionOperationBuilder pow(String exponentFieldRef) { - return this.operation.and(AggregationExpressions.Pow.valueOf(name).pow(exponentFieldRef)); + return this.operation.and(ArithmeticOperators.Pow.valueOf(name).pow(exponentFieldRef)); } /** @@ -1010,7 +1010,7 @@ public ProjectionOperationBuilder pow(String exponentFieldRef) { * @since 1.10 */ public ProjectionOperationBuilder pow(Number exponent) { - return this.operation.and(AggregationExpressions.Pow.valueOf(name).pow(exponent)); + return this.operation.and(ArithmeticOperators.Pow.valueOf(name).pow(exponent)); } /** @@ -1022,7 +1022,7 @@ public ProjectionOperationBuilder pow(Number exponent) { * @since 1.10 */ public ProjectionOperationBuilder pow(AggregationExpression exponentExpression) { - return this.operation.and(AggregationExpressions.Pow.valueOf(name).pow(exponentExpression)); + return this.operation.and(ArithmeticOperators.Pow.valueOf(name).pow(exponentExpression)); } /** @@ -1033,7 +1033,7 @@ public ProjectionOperationBuilder pow(AggregationExpression exponentExpression) * @since 1.10 */ public ProjectionOperationBuilder sqrt() { - return this.operation.and(AggregationExpressions.Sqrt.sqrtOf(name)); + return this.operation.and(ArithmeticOperators.Sqrt.sqrtOf(name)); } /** @@ -1043,7 +1043,7 @@ public ProjectionOperationBuilder sqrt() { * @since 1.10 */ public ProjectionOperationBuilder trunc() { - return this.operation.and(AggregationExpressions.Trunc.truncValueOf(name)); + return this.operation.and(ArithmeticOperators.Trunc.truncValueOf(name)); } /** @@ -1090,7 +1090,7 @@ public ProjectionOperationBuilder substring(int start, int nrOfChars) { * @since 1.10 */ public ProjectionOperationBuilder toLower() { - return this.operation.and(AggregationExpressions.ToLower.lowerValueOf(name)); + return this.operation.and(StringOperators.ToLower.lowerValueOf(name)); } /** @@ -1101,7 +1101,7 @@ public ProjectionOperationBuilder toLower() { * @since 1.10 */ public ProjectionOperationBuilder toUpper() { - return this.operation.and(AggregationExpressions.ToUpper.upperValueOf(name)); + return this.operation.and(StringOperators.ToUpper.upperValueOf(name)); } /** @@ -1172,7 +1172,7 @@ public ProjectionOperationBuilder concatArrays(String... fields) { * @since 1.10 */ public ProjectionOperationBuilder isArray() { - return this.operation.and(AggregationExpressions.IsArray.isArray(name)); + return this.operation.and(ArrayOperators.IsArray.isArray(name)); } /** @@ -1182,7 +1182,7 @@ public ProjectionOperationBuilder isArray() { * @since 1.10 */ public ProjectionOperationBuilder asLiteral() { - return this.operation.and(AggregationExpressions.Literal.asLiteral(name)); + return this.operation.and(LiteralOperators.Literal.asLiteral(name)); } /** @@ -1194,7 +1194,7 @@ public ProjectionOperationBuilder asLiteral() { * @since 1.10 */ public ProjectionOperationBuilder dateAsFormattedString(String format) { - return this.operation.and(AggregationExpressions.DateToString.dateOf(name).toString(format)); + return this.operation.and(DateOperators.DateToString.dateOf(name).toString(format)); } /** @@ -1209,7 +1209,7 @@ public ProjectionOperationBuilder dateAsFormattedString(String format) { */ public ProjectionOperationBuilder let(AggregationExpression valueExpression, String variableName, AggregationExpression in) { - return this.operation.and(AggregationExpressions.Let.define(ExpressionVariable.newVariable(variableName).forExpression(valueExpression)).andApply(in)); + return this.operation.and(VariableOperators.Let.define(ExpressionVariable.newVariable(variableName).forExpression(valueExpression)).andApply(in)); } /** @@ -1222,7 +1222,7 @@ public ProjectionOperationBuilder let(AggregationExpression valueExpression, Str * @since 1.10 */ public ProjectionOperationBuilder let(Collection variables, AggregationExpression in) { - return this.operation.and(AggregationExpressions.Let.define(variables).andApply(in)); + return this.operation.and(VariableOperators.Let.define(variables).andApply(in)); } /* diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java new file mode 100644 index 0000000000..598c1e35ba --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java @@ -0,0 +1,666 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Sum; +import org.springframework.util.Assert; + +/** + * Gateway to {@literal Set expressions} which perform {@literal set} operation on arrays, treating arrays as sets. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class SetOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static SetOperatorFactory arrayAsSet(String fieldReference) { + return new SetOperatorFactory(fieldReference); + } + + /** + * Take the array resulting from the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetOperatorFactory arrayAsSet(AggregationExpression expression) { + return new SetOperatorFactory(expression); + } + + /** + * @author Christoph Strobl + */ + public static class SetOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link SetOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public SetOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link SetOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public SetOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that compares the previously mentioned field to one or more arrays and + * returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(String... arrayReferences) { + return createSetEquals().isEqualTo(arrayReferences); + } + + /** + * Creates new {@link AggregationExpressions} that compares the previously mentioned field to one or more arrays and + * returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(AggregationExpression... expressions) { + return createSetEquals().isEqualTo(expressions); + } + + private SetEquals createSetEquals() { + return usesFieldRef() ? SetEquals.arrayAsSet(fieldReference) : SetEquals.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in every of those. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetIntersection intersects(String... arrayReferences) { + return createSetIntersection().intersects(arrayReferences); + } + + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in every of those. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetIntersection intersects(AggregationExpression... expressions) { + return createSetIntersection().intersects(expressions); + } + + private SetIntersection createSetIntersection() { + return usesFieldRef() ? SetIntersection.arrayAsSet(fieldReference) : SetIntersection.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in any of those. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetUnion union(String... arrayReferences) { + return createSetUnion().union(arrayReferences); + } + + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * arrays and returns an array that contains the elements that appear in any of those. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetUnion union(AggregationExpression... expressions) { + return createSetUnion().union(expressions); + } + + private SetUnion createSetUnion() { + return usesFieldRef() ? SetUnion.arrayAsSet(fieldReference) : SetUnion.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns an + * array containing the elements that do not exist in the given {@literal arrayReference}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public SetDifference differenceTo(String arrayReference) { + return createSetDifference().differenceTo(arrayReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns an + * array containing the elements that do not exist in the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public SetDifference differenceTo(AggregationExpression expression) { + return createSetDifference().differenceTo(expression); + } + + private SetDifference createSetDifference() { + return usesFieldRef() ? SetDifference.arrayAsSet(fieldReference) : SetDifference.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns + * {@literal true} if it is a subset of the given {@literal arrayReference}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public SetIsSubset isSubsetOf(String arrayReference) { + return createSetIsSubset().isSubsetOf(arrayReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns + * {@literal true} if it is a subset of the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public SetIsSubset isSubsetOf(AggregationExpression expression) { + return createSetIsSubset().isSubsetOf(expression); + } + + private SetIsSubset createSetIsSubset() { + return usesFieldRef() ? SetIsSubset.arrayAsSet(fieldReference) : SetIsSubset.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns + * {@literal true} if any of the elements are {@literal true} and {@literal false} otherwise. + * + * @return + */ + public AnyElementTrue anyElementTrue() { + return usesFieldRef() ? AnyElementTrue.arrayAsSet(fieldReference) : AnyElementTrue.arrayAsSet(expression); + } + + /** + * Creates new {@link AggregationExpressions} that tkes array of the previously mentioned field and returns + * {@literal true} if no elements is {@literal false}. + * + * @return + */ + public AllElementsTrue allElementsTrue() { + return usesFieldRef() ? AllElementsTrue.arrayAsSet(fieldReference) : AllElementsTrue.arrayAsSet(expression); + } + + private boolean usesFieldRef() { + return this.fieldReference != null; + } + } + + /** + * {@link AggregationExpression} for {@code $setEquals}. + * + * @author Christoph Strobl + */ + public static class SetEquals extends AbstractAggregationExpression { + + private SetEquals(List arrays) { + super(arrays); + } + + @Override + protected String getMongoMethod() { + return "$setEquals"; + } + + /** + * Create new {@link SetEquals}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetEquals arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetEquals(asFields(arrayReference)); + } + + /** + * Create new {@link SetEquals}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetEquals arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetEquals(Collections.singletonList(expression)); + } + + /** + * Creates new {@link java.util.Set} with all previously added arguments appending the given one. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(String... arrayReferences) { + + Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); + return new SetEquals(append(Fields.fields(arrayReferences).asList())); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(AggregationExpression... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new SetEquals(append(Arrays.asList(expressions))); + } + + /** + * Creates new {@link Sum} with all previously added arguments appending the given one. + * + * @param array must not be {@literal null}. + * @return + */ + public SetEquals isEqualTo(Object[] array) { + + Assert.notNull(array, "Array must not be null!"); + return new SetEquals(append(array)); + } + } + + /** + * {@link AggregationExpression} for {@code $setIntersection}. + * + * @author Christoph Strobl + */ + public static class SetIntersection extends AbstractAggregationExpression { + + private SetIntersection(List arrays) { + super(arrays); + } + + @Override + protected String getMongoMethod() { + return "$setIntersection"; + } + + /** + * Creates new {@link SetIntersection} + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetIntersection arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetIntersection(asFields(arrayReference)); + } + + /** + * Creates new {@link SetIntersection}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetIntersection arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetIntersection(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetIntersection} with all previously added arguments appending the given one. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetIntersection intersects(String... arrayReferences) { + + Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); + return new SetIntersection(append(asFields(arrayReferences))); + } + + /** + * Creates new {@link SetIntersection} with all previously added arguments appending the given one. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetIntersection intersects(AggregationExpression... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new SetIntersection(append(Arrays.asList(expressions))); + } + } + + /** + * {@link AggregationExpression} for {@code $setUnion}. + * + * @author Christoph Strobl + */ + public static class SetUnion extends AbstractAggregationExpression { + + private SetUnion(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$setUnion"; + } + + /** + * Creates new {@link SetUnion}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetUnion arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetUnion(asFields(arrayReference)); + } + + /** + * Creates new {@link SetUnion}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetUnion arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetUnion(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetUnion} with all previously added arguments appending the given one. + * + * @param arrayReferences must not be {@literal null}. + * @return + */ + public SetUnion union(String... arrayReferences) { + + Assert.notNull(arrayReferences, "ArrayReferences must not be null!"); + return new SetUnion(append(asFields(arrayReferences))); + } + + /** + * Creates new {@link SetUnion} with all previously added arguments appending the given one. + * + * @param expressions must not be {@literal null}. + * @return + */ + public SetUnion union(AggregationExpression... expressions) { + + Assert.notNull(expressions, "Expressions must not be null!"); + return new SetUnion(append(Arrays.asList(expressions))); + } + } + + /** + * {@link AggregationExpression} for {@code $setDifference}. + * + * @author Christoph Strobl + */ + public static class SetDifference extends AbstractAggregationExpression { + + private SetDifference(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$setDifference"; + } + + /** + * Creates new {@link SetDifference}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetDifference arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetDifference(asFields(arrayReference)); + } + + /** + * Creates new {@link SetDifference}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetDifference arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetDifference(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetDifference} with all previously added arguments appending the given one. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public SetDifference differenceTo(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetDifference(append(Fields.field(arrayReference))); + } + + /** + * Creates new {@link SetDifference} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public SetDifference differenceTo(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetDifference(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $setIsSubset}. + * + * @author Christoph Strobl + */ + public static class SetIsSubset extends AbstractAggregationExpression { + + private SetIsSubset(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$setIsSubset"; + } + + /** + * Creates new {@link SetIsSubset}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static SetIsSubset arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetIsSubset(asFields(arrayReference)); + } + + /** + * Creates new {@link SetIsSubset}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SetIsSubset arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetIsSubset(Collections.singletonList(expression)); + } + + /** + * Creates new {@link SetIsSubset} with all previously added arguments appending the given one. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public SetIsSubset isSubsetOf(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new SetIsSubset(append(Fields.field(arrayReference))); + } + + /** + * Creates new {@link SetIsSubset} with all previously added arguments appending the given one. + * + * @param expression must not be {@literal null}. + * @return + */ + public SetIsSubset isSubsetOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SetIsSubset(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $anyElementTrue}. + * + * @author Christoph Strobl + */ + public static class AnyElementTrue extends AbstractAggregationExpression { + + private AnyElementTrue(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$anyElementTrue"; + } + + /** + * Creates new {@link AnyElementTrue}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static AnyElementTrue arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new AnyElementTrue(asFields(arrayReference)); + } + + /** + * Creates new {@link AnyElementTrue}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static AnyElementTrue arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new AnyElementTrue(Collections.singletonList(expression)); + } + + public AnyElementTrue anyElementTrue() { + return this; + } + } + + /** + * {@link AggregationExpression} for {@code $allElementsTrue}. + * + * @author Christoph Strobl + */ + public static class AllElementsTrue extends AbstractAggregationExpression { + + private AllElementsTrue(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$allElementsTrue"; + } + + /** + * Creates new {@link AllElementsTrue}. + * + * @param arrayReference must not be {@literal null}. + * @return + */ + public static AllElementsTrue arrayAsSet(String arrayReference) { + + Assert.notNull(arrayReference, "ArrayReference must not be null!"); + return new AllElementsTrue(asFields(arrayReference)); + } + + /** + * Creates new {@link AllElementsTrue}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static AllElementsTrue arrayAsSet(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new AllElementsTrue(Collections.singletonList(expression)); + } + + public AllElementsTrue allElementsTrue() { + return this; + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java new file mode 100644 index 0000000000..e79f25b231 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java @@ -0,0 +1,1089 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.domain.Range; +import org.springframework.util.Assert; + +/** + * Gateway to {@literal String} aggregation operations. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class StringOperators { + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StringOperatorFactory valueOf(String fieldReference) { + return new StringOperatorFactory(fieldReference); + } + + /** + * Take the array referenced by given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StringOperatorFactory valueOf(AggregationExpression fieldReference) { + return new StringOperatorFactory(fieldReference); + } + + /** + * @author Christoph Strobl + */ + public static class StringOperatorFactory { + + private final String fieldReference; + private final AggregationExpression expression; + + /** + * Creates new {@link StringOperatorFactory} for given {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + */ + public StringOperatorFactory(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + this.fieldReference = fieldReference; + this.expression = null; + } + + /** + * Creates new {@link StringOperatorFactory} for given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + */ + public StringOperatorFactory(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + this.fieldReference = null; + this.expression = expression; + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and concats the value + * of the referenced field to it. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public Concat concatValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createConcat().concatValueOf(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and concats the result + * of the given {@link AggregationExpression} to it. + * + * @param expression must not be {@literal null}. + * @return + */ + public Concat concatValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createConcat().concatValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and concats given + * {@literal value} to it. + * + * @param value must not be {@literal null}. + * @return + */ + public Concat concat(String value) { + + Assert.notNull(value, "Value must not be null!"); + return createConcat().concat(value); + } + + private Concat createConcat() { + return fieldReference != null ? Concat.valueOf(fieldReference) : Concat.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a + * substring starting at a specified index position. + * + * @param start + * @return + */ + public Substr substring(int start) { + return substring(start, -1); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a + * substring starting at a specified index position including the specified number of characters. + * + * @param start + * @param nrOfChars + * @return + */ + public Substr substring(int start, int nrOfChars) { + return createSubstr().substring(start, nrOfChars); + } + + private Substr createSubstr() { + return fieldReference != null ? Substr.valueOf(fieldReference) : Substr.valueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and lowers it. + * + * @return + */ + public ToLower toLower() { + return fieldReference != null ? ToLower.lowerValueOf(fieldReference) : ToLower.lowerValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and uppers it. + * + * @return + */ + public ToUpper toUpper() { + return fieldReference != null ? ToUpper.upperValueOf(fieldReference) : ToUpper.upperValueOf(expression); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and performs + * case-insensitive comparison to the given {@literal value}. + * + * @param value must not be {@literal null}. + * @return + */ + public StrCaseCmp strCaseCmp(String value) { + + Assert.notNull(value, "Value must not be null!"); + return createStrCaseCmp().strcasecmp(value); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and performs + * case-insensitive comparison to the referenced {@literal fieldReference}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public StrCaseCmp strCaseCmpValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return createStrCaseCmp().strcasecmpValueOf(fieldReference); + } + + /** + * Creates new {@link AggregationExpressions} that takes the associated string representation and performs + * case-insensitive comparison to the result of the given {@link AggregationExpression}. + * + * @param expression must not be {@literal null}. + * @return + */ + public StrCaseCmp strCaseCmpValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return createStrCaseCmp().strcasecmpValueOf(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 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 + */ + 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 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 + */ + 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 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 + */ + 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 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 + */ + 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 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 + */ + 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 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 + */ + 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); + } + } + + /** + * {@link AggregationExpression} for {@code $concat}. + * + * @author Christoph Strobl + */ + public static class Concat extends AbstractAggregationExpression { + + private Concat(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$concat"; + } + + /** + * Creates new {@link Concat}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Concat valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Concat(asFields(fieldReference)); + } + + /** + * Creates new {@link Concat}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Concat valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Concat(Collections.singletonList(expression)); + } + + /** + * Creates new {@link Concat}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Concat stringValue(String value) { + + Assert.notNull(value, "Value must not be null!"); + return new Concat(Collections.singletonList(value)); + } + + public Concat concatValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Concat(append(Fields.field(fieldReference))); + } + + public Concat concatValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Concat(append(expression)); + } + + public Concat concat(String value) { + return new Concat(append(value)); + } + } + + /** + * {@link AggregationExpression} for {@code $substr}. + * + * @author Christoph Strobl + */ + public static class Substr extends AbstractAggregationExpression { + + private Substr(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$substr"; + } + + /** + * Creates new {@link Substr}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Substr valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new Substr(asFields(fieldReference)); + } + + /** + * Creates new {@link Substr}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Substr valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Substr(Collections.singletonList(expression)); + } + + public Substr substring(int start) { + return substring(start, -1); + } + + public Substr substring(int start, int nrOfChars) { + return new Substr(append(Arrays.asList(start, nrOfChars))); + } + } + + /** + * {@link AggregationExpression} for {@code $toLower}. + * + * @author Christoph Strobl + */ + public static class ToLower extends AbstractAggregationExpression { + + private ToLower(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$toLower"; + } + + /** + * Creates new {@link ToLower}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ToLower lowerValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ToLower(Fields.field(fieldReference)); + } + + /** + * Creates new {@link ToLower}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ToLower lowerValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ToLower(Collections.singletonList(expression)); + } + + /** + * Creates new {@link ToLower}. + * + * @param value must not be {@literal null}. + * @return + */ + public static ToLower lower(String value) { + + Assert.notNull(value, "Value must not be null!"); + return new ToLower(value); + } + } + + /** + * {@link AggregationExpression} for {@code $toUpper}. + * + * @author Christoph Strobl + */ + public static class ToUpper extends AbstractAggregationExpression { + + private ToUpper(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$toUpper"; + } + + /** + * Creates new {@link ToUpper}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static ToUpper upperValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new ToUpper(Fields.field(fieldReference)); + } + + /** + * Creates new {@link ToUpper}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static ToUpper upperValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ToUpper(Collections.singletonList(expression)); + } + + /** + * Creates new {@link ToUpper}. + * + * @param value must not be {@literal null}. + * @return + */ + public static ToUpper upper(String value) { + + Assert.notNull(value, "Value must not be null!"); + return new ToUpper(value); + } + } + + /** + * {@link AggregationExpression} for {@code $strcasecmp}. + * + * @author Christoph Strobl + */ + public static class StrCaseCmp extends AbstractAggregationExpression { + + private StrCaseCmp(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$strcasecmp"; + } + + /** + * Creates new {@link StrCaseCmp}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static StrCaseCmp valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StrCaseCmp(asFields(fieldReference)); + } + + /** + * Creates new {@link StrCaseCmp}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static StrCaseCmp valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StrCaseCmp(Collections.singletonList(expression)); + } + + /** + * Creates new {@link StrCaseCmp}. + * + * @param value must not be {@literal null}. + * @return + */ + public static StrCaseCmp stringValue(String value) { + + Assert.notNull(value, "Value must not be null!"); + return new StrCaseCmp(Collections.singletonList(value)); + } + + public StrCaseCmp strcasecmp(String value) { + return new StrCaseCmp(append(value)); + } + + public StrCaseCmp strcasecmpValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new StrCaseCmp(append(Fields.field(fieldReference))); + } + + public StrCaseCmp strcasecmpValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new StrCaseCmp(append(expression)); + } + } + + /** + * {@link AggregationExpression} for {@code $indexOfBytes}. + * + * @author Christoph Strobl + */ + public static class IndexOfBytes extends AbstractAggregationExpression { + + private IndexOfBytes(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$indexOfBytes"; + } + + /** + * Start creating a new {@link IndexOfBytes}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static SubstringBuilder valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new SubstringBuilder(Fields.field(fieldReference)); + } + + /** + * Start creating a new {@link IndexOfBytes}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SubstringBuilder valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SubstringBuilder(expression); + } + + /** + * Optionally define the substring search start and end position. + * + * @param range must not be {@literal null}. + * @return + */ + public IndexOfBytes 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 IndexOfBytes(append(rangeValues)); + } + + public static class SubstringBuilder { + + private final Object stringExpression; + + 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)); + } + } + } + + /** + * {@link AggregationExpression} for {@code $indexOfCP}. + * + * @author Christoph Strobl + */ + public static class IndexOfCP extends AbstractAggregationExpression { + + private IndexOfCP(List value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$indexOfCP"; + } + + /** + * Start creating a new {@link IndexOfCP}. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static SubstringBuilder valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + return new SubstringBuilder(Fields.field(fieldReference)); + } + + /** + * Start creating a new {@link IndexOfCP}. + * + * @param expression must not be {@literal null}. + * @return + */ + public static SubstringBuilder valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new SubstringBuilder(expression); + } + + /** + * Optionally define the substring search start and end position. + * + * @param range must not be {@literal null}. + * @return + */ + public IndexOfCP 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 IndexOfCP(append(rangeValues)); + } + + public static class SubstringBuilder { + + private final Object stringExpression; + + 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)); + } + } + } + + /** + * {@link AggregationExpression} for {@code $split}. + * + * @author Christoph Strobl + */ + public static 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 delimiter. + * + * @param delimiter must not be {@literal null}. + * @return + */ + public Split split(String delimiter) { + + Assert.notNull(delimiter, "Delimiter must not be null!"); + return new Split(append(delimiter)); + } + + /** + * Use value of referenced field as delimiter. + * + * @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 delimiter. + * + * @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}. + * + * @author Christoph Strobl + */ + public static class StrLenBytes extends AbstractAggregationExpression { + + private StrLenBytes(Object value) { + super(value); + } + + @Override + 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 + */ + public static class StrLenCP extends AbstractAggregationExpression { + + private StrLenCP(Object value) { + super(value); + } + + @Override + 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); + } + } + + /** + * {@link AggregationExpression} for {@code $substrCP}. + * + * @author Christoph Strobl + */ + public static 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))); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java new file mode 100644 index 0000000000..79e8a5289a --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java @@ -0,0 +1,391 @@ +/* + * 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.Arrays; +import java.util.Collection; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Gateway to {@literal variable} aggregation operations. + * + * @author Christoph Strobl + * @author Mark Paluch + * @since 1.10 + */ +public class VariableOperators { + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public static Map.AsBuilder mapItemsOf(String fieldReference) { + return Map.itemsOf(fieldReference); + } + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @param expression must not be {@literal null}. + * @return + */ + public static Map.AsBuilder mapItemsOf(AggregationExpression expression) { + return Map.itemsOf(expression); + } + + /** + * Start creating new {@link Let} that allows definition of {@link ExpressionVariable} that can be used within a + * nested {@link AggregationExpression}. + * + * @param variables must not be {@literal null}. + * @return + */ + public static Let.LetBuilder define(ExpressionVariable... variables) { + return Let.define(variables); + } + + /** + * Start creating new {@link Let} that allows definition of {@link ExpressionVariable} that can be used within a + * nested {@link AggregationExpression}. + * + * @param variables must not be {@literal null}. + * @return + */ + public static Let.LetBuilder define(Collection variables) { + return Let.define(variables); + } + + /** + * {@link AggregationExpression} for {@code $map}. + */ + public static class Map implements AggregationExpression { + + private Object sourceArray; + private String itemVariableName; + private AggregationExpression functionToApply; + + private Map(Object sourceArray, String itemVariableName, AggregationExpression functionToApply) { + + Assert.notNull(sourceArray, "SourceArray must not be null!"); + Assert.notNull(itemVariableName, "ItemVariableName must not be null!"); + Assert.notNull(functionToApply, "FunctionToApply must not be null!"); + + this.sourceArray = sourceArray; + this.itemVariableName = itemVariableName; + this.functionToApply = functionToApply; + } + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @param fieldReference must not be {@literal null}. + * @return + */ + public 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); + } + }; + } + + }; + }; + + /** + * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array + * and returns an array with the applied results. + * + * @param source must not be {@literal null}. + * @return + */ + 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); + } + }; + } + }; + } + + /* (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); + } + + private DBObject toMap(ExposedFields exposedFields, AggregationOperationContext context) { + + BasicDBObject map = new BasicDBObject(); + InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( + exposedFields, context); + + BasicDBObject input; + if (sourceArray instanceof Field) { + input = new BasicDBObject("input", context.getReference((Field) sourceArray).toString()); + } else { + input = new BasicDBObject("input", ((AggregationExpression) sourceArray).toDbObject(context)); + } + + map.putAll(context.getMappedObject(input)); + map.put("as", itemVariableName); + map.put("in", + functionToApply.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(operationContext))); + + return new BasicDBObject("$map", map); + } + + public interface AsBuilder { + + /** + * Define the {@literal variableName} for addressing items within the array. + * + * @param variableName must not be {@literal null}. + * @return + */ + FunctionBuilder as(String variableName); + } + + public interface FunctionBuilder { + + /** + * Creates new {@link Map} that applies the given {@link AggregationExpression} to each item of the referenced + * array and returns an array with the applied results. + * + * @param expression must not be {@literal null}. + * @return + */ + Map andApply(AggregationExpression expression); + } + } + + /** + * {@link AggregationExpression} for {@code $let} that binds {@link AggregationExpression} to variables for use in the + * specified {@code in} expression, and returns the result of the expression. + * + * @author Christoph Strobl + * @since 1.10 + */ + public static class Let implements AggregationExpression { + + private final List vars; + private final AggregationExpression expression; + + private Let(List vars, AggregationExpression expression) { + + this.vars = vars; + this.expression = expression; + } + + /** + * Start creating new {@link Let} by defining the variables for {@code $vars}. + * + * @param variables must not be {@literal null}. + * @return + */ + 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) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Let(new ArrayList(variables), expression); + } + }; + } + + /** + * Start creating new {@link Let} by defining the variables for {@code $vars}. + * + * @param variables must not be {@literal null}. + * @return + */ + 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) { + + Assert.notNull(expression, "Expression must not be null!"); + return new Let(Arrays.asList(variables), expression); + } + }; + } + + public interface LetBuilder { + + /** + * Define the {@link AggregationExpression} to evaluate. + * + * @param expression must not be {@literal null}. + * @return + */ + 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); + } + + private String[] getVariableNames() { + + String[] varNames = new String[this.vars.size()]; + for (int i = 0; i < this.vars.size(); i++) { + varNames[i] = this.vars.get(i).variableName; + } + + return varNames; + } + + private DBObject toLet(ExposedFields exposedFields, AggregationOperationContext context) { + + DBObject letExpression = new BasicDBObject(); + DBObject mappedVars = new BasicDBObject(); + InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext( + exposedFields, context); + + for (ExpressionVariable var : this.vars) { + mappedVars.putAll(getMappedVariable(var, context)); + } + + letExpression.put("vars", mappedVars); + letExpression.put("in", getMappedIn(operationContext)); + + return new BasicDBObject("$let", letExpression); + } + + private DBObject getMappedVariable(ExpressionVariable var, AggregationOperationContext context) { + + return new BasicDBObject(var.variableName, var.expression instanceof AggregationExpression + ? ((AggregationExpression) var.expression).toDbObject(context) : var.expression); + } + + private Object getMappedIn(AggregationOperationContext context) { + return expression.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(context)); + } + + /** + * @author Christoph Strobl + */ + public static class ExpressionVariable { + + private final String variableName; + private final Object expression; + + /** + * Creates new {@link ExpressionVariable}. + * + * @param variableName can be {@literal null}. + * @param expression can be {@literal null}. + */ + private ExpressionVariable(String variableName, Object expression) { + + this.variableName = variableName; + this.expression = expression; + } + + /** + * Create a new {@link ExpressionVariable} with given name. + * + * @param variableName must not be {@literal null}. + * @return never {@literal null}. + */ + public static ExpressionVariable newVariable(String variableName) { + + Assert.notNull(variableName, "VariableName must not be null!"); + return new ExpressionVariable(variableName, null); + } + + /** + * Create a new {@link ExpressionVariable} with current name and given {@literal expression}. + * + * @param expression must not be {@literal null}. + * @return never {@literal null}. + */ + public ExpressionVariable forExpression(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + return new ExpressionVariable(variableName, expression); + } + + /** + * Create a new {@link ExpressionVariable} with current name and given {@literal expressionObject}. + * + * @param expressionObject must not be {@literal null}. + * @return never {@literal null}. + */ + public ExpressionVariable forExpression(DBObject expressionObject) { + + Assert.notNull(expressionObject, "Expression must not be null!"); + return new ExpressionVariable(variableName, expressionObject); + } + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 6112c86a9c..9a46581696 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -57,11 +57,7 @@ import org.springframework.data.mongodb.core.CollectionCallback; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.Venue; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Multiply; +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry; import org.springframework.data.mongodb.core.aggregation.BucketAutoOperation.Granularities; import org.springframework.data.mongodb.core.index.GeospatialIndex; @@ -535,7 +531,7 @@ public void aggregationUsingConditionalProjectionToCalculateDiscount() { TypedAggregation aggregation = newAggregation(InventoryItem.class, // project("item") // .and("discount")// - .applyCondition(Cond.newBuilder().when(Criteria.where("qty").gte(250)) // + .applyCondition(ConditionalOperators.Cond.newBuilder().when(Criteria.where("qty").gte(250)) // .then(30) // .otherwise(20))); @@ -1622,11 +1618,11 @@ public void letShouldBeAppliedCorrectly() { ExpressionVariable total = ExpressionVariable.newVariable("total") .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))); ExpressionVariable discounted = ExpressionVariable.newVariable("discounted") - .forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); + .forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); TypedAggregation agg = Aggregation.newAggregation(Sales2.class, Aggregation.project() - .and(Let.define(total, discounted).andApply( + .and(VariableOperators.Let.define(total, discounted).andApply( AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) .as("finalTotal")); @@ -1726,7 +1722,7 @@ public void bucketAutoShouldCollectDocumentsIntoABucket() { mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); TypedAggregation aggregation = newAggregation(Art.class, // - bucketAuto(Multiply.valueOf("price").multiplyBy(10), 3) // + bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // .withGranularity(Granularities.E12) // .andOutputCount().as("count") // .andOutput("title").push().as("titles") // @@ -1762,7 +1758,7 @@ public void facetShouldCreateFacets() { mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); - BucketAutoOperation bucketPrice = bucketAuto(Multiply.valueOf("price").multiplyBy(10), 3) // + BucketAutoOperation bucketPrice = bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // .withGranularity(Granularities.E12) // .andOutputCount().as("count") // .andOutput("title").push().as("titles") // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index 142e173526..ae1c239d0f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -30,8 +30,6 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.data.domain.Sort.Direction; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; import org.springframework.data.mongodb.core.query.Criteria; import com.mongodb.BasicDBObject; @@ -428,7 +426,7 @@ public void conditionExpressionBasedFieldsShouldBeReferencableInFollowingOperati public void shouldRenderProjectionConditionalExpressionCorrectly() { DBObject agg = Aggregation.newAggregation(// - project().and(Cond.newBuilder() // + project().and(ConditionalOperators.Cond.newBuilder() // .when("isYellow") // .then("bright") // .otherwise("dark")).as("color")) @@ -451,7 +449,7 @@ public void shouldRenderProjectionConditionalCorrectly() { DBObject agg = Aggregation.newAggregation(// project().and("color") - .applyCondition(Cond.newBuilder() // + .applyCondition(ConditionalOperators.Cond.newBuilder() // .when("isYellow") // .then("bright") // .otherwise("dark"))) @@ -475,7 +473,7 @@ public void shouldRenderProjectionConditionalWithCriteriaCorrectly() { DBObject agg = Aggregation .newAggregation(project()// .and("color")// - .applyCondition(Cond.newBuilder().when(Criteria.where("key").gt(5)) // + .applyCondition(ConditionalOperators.Cond.newBuilder().when(Criteria.where("key").gt(5)) // .then("bright").otherwise("dark"))) // .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); @@ -523,7 +521,7 @@ public void referencingProjectionAliasesShouldRenderProjectionConditionalWithCri .newAggregation(// project().and("color").as("chroma"), project().and("luminosity") // - .applyCondition(Cond.newBuilder() + .applyCondition(ConditionalOperators.Cond.newBuilder() .when(Criteria.where("chroma") // .is(100)) // .then("bright").otherwise("dark"))) // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java index c978f37c1c..989267e7b9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java @@ -22,7 +22,6 @@ import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; import org.junit.Test; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators; import com.mongodb.DBObject; import com.mongodb.util.JSON; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java index b86fb27d87..009cf79e4d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java @@ -16,14 +16,13 @@ package org.springframework.data.mongodb.core.aggregation; import static org.junit.Assert.*; -import static org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.*; +import static org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import java.util.Arrays; import org.junit.Test; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond; import org.springframework.data.mongodb.core.query.Criteria; import com.mongodb.BasicDBObject; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java index 5cc4e799f8..6bb7219046 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java @@ -17,7 +17,7 @@ import static org.hamcrest.core.Is.*; import static org.junit.Assert.*; -import static org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.*; +import static org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.*; import java.util.Arrays; import java.util.List; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java index f91d141225..3400911a87 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java @@ -21,7 +21,6 @@ import org.junit.Test; import org.springframework.data.mongodb.core.Person; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Literal; import org.springframework.data.mongodb.core.query.Criteria; import com.mongodb.BasicDBObject; @@ -111,7 +110,7 @@ public void shouldRenderMixedArrayOfStartsWithCorrectly() { GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // .from("employees") // - .startWith("reportsTo", Literal.asLiteral("$boss")) // + .startWith("reportsTo", LiteralOperators.Literal.asLiteral("$boss")) // .connectFrom("reportsTo") // .connectTo("name") // .as("reportingHierarchy"); @@ -145,7 +144,7 @@ public void shouldRenderStartWithAggregationExpressions() { GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // .from("employees") // - .startWith(Literal.asLiteral("hello")) // + .startWith(LiteralOperators.Literal.asLiteral("hello")) // .connectFrom("reportsTo") // .connectTo("name") // .as("reportingHierarchy"); 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 63d825134c..e5b4a4857e 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 @@ -19,7 +19,7 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; -import static org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable.*; +import static org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable.*; import static org.springframework.data.mongodb.core.aggregation.AggregationFunctionExpressions.*; import static org.springframework.data.mongodb.core.aggregation.Fields.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; @@ -32,28 +32,11 @@ import org.junit.Test; import org.springframework.data.domain.Range; import org.springframework.data.mongodb.core.DBObjectTestUtils; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.And; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArrayOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Avg; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.BooleanOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ComparisonOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.DateOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Gte; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.LiteralOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Lt; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.RangeOperator; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Reduce.PropertyExpression; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Reduce.Variable; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.SetOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.StringOperators; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Switch.CaseOperator; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Type; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.VariableOperators; +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; +import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.PropertyExpression; +import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.Variable; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.*; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; @@ -1773,7 +1756,7 @@ public void shouldRenderLetExpressionCorrectly() { .define( newVariable("total") .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))), - newVariable("discounted").forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D))) + newVariable("discounted").forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D))) .andApply(AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) // .as("finalTotal").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1797,7 +1780,7 @@ public void shouldRenderLetExpressionCorrectlyWhenUsingLetOnProjectionBuilder() .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))); ExpressionVariable var2 = newVariable("discounted") - .forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); + .forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); DBObject agg = Aggregation.project().and("foo") .let(Arrays.asList(var1, var2), @@ -1930,7 +1913,7 @@ public void shouldRenderIndexOfArrayCorrectly() { @Test public void shouldRenderRangeCorrectly() { - DBObject agg = project().and(RangeOperator.rangeStartingAt(0L).to("distance").withStepSize(25L)).as("rest_stops") + DBObject agg = project().and(ArrayOperators.RangeOperator.rangeStartingAt(0L).to("distance").withStepSize(25L)).as("rest_stops") .toDBObject(Aggregation.DEFAULT_CONTEXT); assertThat(agg, isBsonObject().containing("$project.rest_stops.$range.[0]", 0L) @@ -2077,11 +2060,11 @@ public void shouldRenderSwitchCorrectly() { " }\n" + // "}"; - CaseOperator cond1 = CaseOperator.when(Gte.valueOf(Avg.avgOf("scores")).greaterThanEqualToValue(90)) + CaseOperator cond1 = CaseOperator.when(ComparisonOperators.Gte.valueOf(AccumulatorOperators.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)) + CaseOperator cond2 = CaseOperator.when(BooleanOperators.And.and(ComparisonOperators.Gte.valueOf(AccumulatorOperators.Avg.avgOf("scores")).greaterThanEqualToValue(80), + ComparisonOperators.Lt.valueOf(AccumulatorOperators.Avg.avgOf("scores")).lessThanValue(90))).then("Doing pretty well."); + CaseOperator cond3 = CaseOperator.when(ComparisonOperators.Lt.valueOf(AccumulatorOperators.Avg.avgOf("scores")).lessThanValue(80)) .then("Needs improvement."); DBObject agg = project().and(ConditionalOperators.switchCases(cond1, cond2, cond3).defaultTo("No scores found.")) @@ -2096,7 +2079,7 @@ public void shouldRenderSwitchCorrectly() { @Test public void shouldTypeCorrectly() { - DBObject agg = project().and(Type.typeOf("a")).as("a") + DBObject agg = project().and(DataTypeOperators.Type.typeOf("a")).as("a") .toDBObject(Aggregation.DEFAULT_CONTEXT); assertThat(agg, Matchers.is(JSON.parse("{ $project : { a: { $type: \"$a\" } } }"))); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java index 5a68303364..49223ccb7e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java @@ -19,7 +19,6 @@ import static org.junit.Assert.*; import org.junit.Test; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.VariableOperators; import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootDocumentOperation; import com.mongodb.BasicDBObject; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java index e80bb17815..b35cf59614 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java @@ -36,7 +36,6 @@ import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mapping.model.MappingException; -import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators; import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; From f026ab419d927c8accaadeeef802e5753aab54d0 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 16 Dec 2016 13:44:41 +0100 Subject: [PATCH 051/118] DATAMONGO-1564 - Polishing. Fix JavaDoc references and minor a import formatting issue. Original pull request: #429. --- .../AbstractAggregationExpression.java | 11 +-- .../aggregation/AccumulatorOperators.java | 34 +++++---- .../AggregationFunctionExpressions.java | 2 +- .../core/aggregation/ArithmeticOperators.java | 70 +++++++++---------- .../core/aggregation/ArrayOperators.java | 61 ++++++++-------- .../core/aggregation/BooleanOperators.java | 12 ++-- .../core/aggregation/ComparisonOperators.java | 42 +++++------ .../aggregation/ConditionalOperators.java | 61 ++++++++-------- .../core/aggregation/DateOperators.java | 33 +++++---- .../core/aggregation/LiteralOperators.java | 2 +- .../core/aggregation/SetOperators.java | 28 ++++---- .../core/aggregation/StringOperators.java | 44 ++++++------ .../core/aggregation/VariableOperators.java | 2 +- .../core/aggregation/AggregationTests.java | 2 +- 14 files changed, 210 insertions(+), 194 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java index 45348ea188..57abd72014 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java @@ -21,9 +21,10 @@ import java.util.LinkedHashMap; import java.util.List; +import org.springframework.util.ObjectUtils; + import com.mongodb.BasicDBObject; import com.mongodb.DBObject; -import org.springframework.util.ObjectUtils; /** * @author Christoph Strobl @@ -45,6 +46,7 @@ public DBObject toDbObject(AggregationOperationContext context) { return toDbObject(this.value, context); } + @SuppressWarnings("unchecked") public DBObject toDbObject(Object value, AggregationOperationContext context) { Object valueToUse; @@ -80,6 +82,7 @@ protected static List asFields(String... fieldRefs) { return Fields.fields(fieldRefs).asList(); } + @SuppressWarnings("unchecked") private Object unpack(Object value, AggregationOperationContext context) { if (value instanceof AggregationExpression) { @@ -123,13 +126,13 @@ protected List append(Object value) { return Arrays.asList(this.value, value); } + @SuppressWarnings("unchecked") protected java.util.Map append(String key, Object value) { if (!(this.value instanceof java.util.Map)) { throw new IllegalArgumentException("o_O"); } - java.util.Map clone = new LinkedHashMap( - (java.util.Map) this.value); + java.util.Map clone = new LinkedHashMap((java.util.Map) this.value); clone.put(key, value); return clone; @@ -143,7 +146,7 @@ protected List values() { if (value instanceof java.util.Map) { return new ArrayList(((java.util.Map) value).values()); } - return new ArrayList(Arrays.asList(value)); + return new ArrayList(Collections.singletonList(value)); } protected abstract String getMongoMethod(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java index 43cab0347c..945c211b98 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java @@ -44,7 +44,7 @@ public static AccumulatorOperatorFactory valueOf(String fieldReference) { /** * Take the numeric value referenced resulting from given {@link AggregationExpression}. * - * @param fieldReference must not be {@literal null}. + * @param expression must not be {@literal null}. * @return */ public static AccumulatorOperatorFactory valueOf(AggregationExpression expression) { @@ -72,7 +72,7 @@ public AccumulatorOperatorFactory(String fieldReference) { } /** - * Creates new {@link ArrayOperatorFactory} for given {@link AggregationExpression}. + * Creates new {@link AccumulatorOperatorFactory} for given {@link AggregationExpression}. * * @param expression must not be {@literal null}. */ @@ -84,7 +84,7 @@ public AccumulatorOperatorFactory(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and calculates and + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates and * returns the sum. * * @return @@ -94,7 +94,7 @@ public Sum sum() { } /** - * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and returns the + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the * average value. * * @return @@ -104,7 +104,7 @@ public Avg avg() { } /** - * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and returns the + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the * maximum value. * * @return @@ -114,7 +114,7 @@ public Max max() { } /** - * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and returns the + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the * minimum value. * * @return @@ -124,7 +124,7 @@ public Min min() { } /** - * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and calculates the + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates the * population standard deviation of the input values. * * @return @@ -134,7 +134,7 @@ public StdDevPop stdDevPop() { } /** - * Creates new {@link AggregationExpressions} that takes the associated numeric value expression and calculates the + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates the * sample standard deviation of the input values. * * @return @@ -215,9 +215,10 @@ public Sum and(AggregationExpression expression) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override + @SuppressWarnings("unchecked") public DBObject toDbObject(Object value, AggregationOperationContext context) { if (value instanceof List) { @@ -297,9 +298,10 @@ public Avg and(AggregationExpression expression) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override + @SuppressWarnings("unchecked") public DBObject toDbObject(Object value, AggregationOperationContext context) { if (value instanceof List) { @@ -379,9 +381,10 @@ public Max and(AggregationExpression expression) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override + @SuppressWarnings("unchecked") public DBObject toDbObject(Object value, AggregationOperationContext context) { if (value instanceof List) { @@ -461,9 +464,10 @@ public Min and(AggregationExpression expression) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override + @SuppressWarnings("unchecked") public DBObject toDbObject(Object value, AggregationOperationContext context) { if (value instanceof List) { @@ -543,9 +547,10 @@ public StdDevPop and(AggregationExpression expression) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override + @SuppressWarnings("unchecked") public DBObject toDbObject(Object value, AggregationOperationContext context) { if (value instanceof List) { @@ -625,9 +630,10 @@ public StdDevSamp and(AggregationExpression expression) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + * @see org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override + @SuppressWarnings("unchecked") public DBObject toDbObject(Object value, AggregationOperationContext context) { if (value instanceof List) { 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 219a552ac1..ff2769a491 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 @@ -31,7 +31,7 @@ * @author Oliver Gierke * @author Christoph Strobl * @since 1.7 - * @deprecated since 1.10. Please use {@link AggregationExpressions} instead. + * @deprecated since 1.10. Please use {@link ArithmeticOperators} and {@link ComparisonOperators} instead. */ @Deprecated public enum AggregationFunctionExpressions { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java index 12124f0a57..43f0c3d1a2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java @@ -87,7 +87,7 @@ public ArithmeticOperatorFactory(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that returns the absolute value of the associated number. + * Creates new {@link AggregationExpression} that returns the absolute value of the associated number. * * @return */ @@ -96,7 +96,7 @@ public Abs abs() { } /** - * Creates new {@link AggregationExpressions} that adds the value of {@literal fieldReference} to the associated + * Creates new {@link AggregationExpression} that adds the value of {@literal fieldReference} to the associated * number. * * @param fieldReference must not be {@literal null}. @@ -109,7 +109,7 @@ public Add add(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that adds the resulting value of the given + * Creates new {@link AggregationExpression} that adds the resulting value of the given * {@link AggregationExpression} to the associated number. * * @param expression must not be {@literal null}. @@ -122,7 +122,7 @@ public Add add(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that adds the given {@literal value} to the associated number. + * Creates new {@link AggregationExpression} that adds the given {@literal value} to the associated number. * * @param value must not be {@literal null}. * @return @@ -138,7 +138,7 @@ private Add createAdd() { } /** - * Creates new {@link AggregationExpressions} that returns the smallest integer greater than or equal to the + * Creates new {@link AggregationExpression} that returns the smallest integer greater than or equal to the * assoicated number. * * @return @@ -148,7 +148,7 @@ public Ceil ceil() { } /** - * Creates new {@link AggregationExpressions} that ivides the associated number by number referenced via + * Creates new {@link AggregationExpression} that ivides the associated number by number referenced via * {@literal fieldReference}. * * @param fieldReference must not be {@literal null}. @@ -161,7 +161,7 @@ public Divide divideBy(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that divides the associated number by number extracted via + * Creates new {@link AggregationExpression} that divides the associated number by number extracted via * {@literal expression}. * * @param expression must not be {@literal null}. @@ -174,7 +174,7 @@ public Divide divideBy(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that divides the associated number by given {@literal value}. + * Creates new {@link AggregationExpression} that divides the associated number by given {@literal value}. * * @param value * @return @@ -190,7 +190,7 @@ private Divide createDivide() { } /** - * Creates new {@link AggregationExpressions} that raises Euler’s number (i.e. e ) on the associated number. + * Creates new {@link AggregationExpression} that raises Euler’s number (i.e. e ) on the associated number. * * @return */ @@ -199,7 +199,7 @@ public Exp exp() { } /** - * Creates new {@link AggregationExpressions} that returns the largest integer less than or equal to the associated + * Creates new {@link AggregationExpression} that returns the largest integer less than or equal to the associated * number. * * @return @@ -209,7 +209,7 @@ public Floor floor() { } /** - * Creates new {@link AggregationExpressions} that calculates the natural logarithm ln (i.e loge) of the assoicated + * Creates new {@link AggregationExpression} that calculates the natural logarithm ln (i.e loge) of the assoicated * number. * * @return @@ -219,7 +219,7 @@ public Ln ln() { } /** - * Creates new {@link AggregationExpressions} that calculates the log of the associated number in the specified base + * Creates new {@link AggregationExpression} that calculates the log of the associated number in the specified base * referenced via {@literal fieldReference}. * * @param fieldReference must not be {@literal null}. @@ -232,7 +232,7 @@ public Log log(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that calculates the log of the associated number in the specified base + * Creates new {@link AggregationExpression} that calculates the log of the associated number in the specified base * extracted by given {@link AggregationExpression}. * * @param expression must not be {@literal null}. @@ -245,7 +245,7 @@ public Log log(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that calculates the log of a the associated number in the specified + * Creates new {@link AggregationExpression} that calculates the log of a the associated number in the specified * {@literal base}. * * @param base must not be {@literal null}. @@ -262,7 +262,7 @@ private Log createLog() { } /** - * Creates new {@link AggregationExpressions} that calculates the log base 10 for the associated number. + * Creates new {@link AggregationExpression} that calculates the log base 10 for the associated number. * * @return */ @@ -271,7 +271,7 @@ public Log10 log10() { } /** - * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the + * Creates new {@link AggregationExpression} that divides the associated number by another and returns the * remainder. * * @param fieldReference must not be {@literal null}. @@ -284,7 +284,7 @@ public Mod mod(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the + * Creates new {@link AggregationExpression} that divides the associated number by another and returns the * remainder. * * @param expression must not be {@literal null}. @@ -297,7 +297,7 @@ public Mod mod(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that divides the associated number by another and returns the + * Creates new {@link AggregationExpression} that divides the associated number by another and returns the * remainder. * * @param value must not be {@literal null}. @@ -314,7 +314,7 @@ private Mod createMod() { } /** - * Creates new {@link AggregationExpressions} that multiplies the associated number with another. + * Creates new {@link AggregationExpression} that multiplies the associated number with another. * * @param fieldReference must not be {@literal null}. * @return @@ -326,7 +326,7 @@ public Multiply multiplyBy(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that multiplies the associated number with another. + * Creates new {@link AggregationExpression} that multiplies the associated number with another. * * @param expression must not be {@literal null}. * @return @@ -338,7 +338,7 @@ public Multiply multiplyBy(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that multiplies the associated number with another. + * Creates new {@link AggregationExpression} that multiplies the associated number with another. * * @param value must not be {@literal null}. * @return @@ -354,7 +354,7 @@ private Multiply createMultiply() { } /** - * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. + * Creates new {@link AggregationExpression} that raises the associated number to the specified exponent. * * @param fieldReference must not be {@literal null}. * @return @@ -366,7 +366,7 @@ public Pow pow(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. + * Creates new {@link AggregationExpression} that raises the associated number to the specified exponent. * * @param expression must not be {@literal null}. * @return @@ -378,7 +378,7 @@ public Pow pow(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that raises the associated number to the specified exponent. + * Creates new {@link AggregationExpression} that raises the associated number to the specified exponent. * * @param value must not be {@literal null}. * @return @@ -394,7 +394,7 @@ private Pow createPow() { } /** - * Creates new {@link AggregationExpressions} that calculates the square root of the associated number. + * Creates new {@link AggregationExpression} that calculates the square root of the associated number. * * @return */ @@ -403,7 +403,7 @@ public Sqrt sqrt() { } /** - * Creates new {@link AggregationExpressions} that subtracts value of given from the associated number. + * Creates new {@link AggregationExpression} that subtracts value of given from the associated number. * * @param fieldReference must not be {@literal null}. * @return @@ -415,7 +415,7 @@ public Subtract subtract(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that subtracts value of given from the associated number. + * Creates new {@link AggregationExpression} that subtracts value of given from the associated number. * * @param expression must not be {@literal null}. * @return @@ -427,7 +427,7 @@ public Subtract subtract(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that subtracts value from the associated number. + * Creates new {@link AggregationExpression} that subtracts value from the associated number. * * @param value * @return @@ -443,7 +443,7 @@ private Subtract createSubtract() { } /** - * Creates new {@link AggregationExpressions} that truncates a number to its integer. + * Creates new {@link AggregationExpression} that truncates a number to its integer. * * @return */ @@ -452,7 +452,7 @@ public Trunc trunc() { } /** - * Creates new {@link AggregationExpressions} that calculates and returns the sum of numeric values. + * Creates new {@link AggregationExpression} that calculates and returns the sum of numeric values. * * @return */ @@ -462,7 +462,7 @@ public Sum sum() { } /** - * Creates new {@link AggregationExpressions} that returns the average value of the numeric values. + * Creates new {@link AggregationExpression} that returns the average value of the numeric values. * * @return */ @@ -472,7 +472,7 @@ public Avg avg() { } /** - * Creates new {@link AggregationExpressions} that returns the maximum value. + * Creates new {@link AggregationExpression} that returns the maximum value. * * @return */ @@ -482,7 +482,7 @@ public Max max() { } /** - * Creates new {@link AggregationExpressions} that returns the minimum value. + * Creates new {@link AggregationExpression} that returns the minimum value. * * @return */ @@ -492,7 +492,7 @@ public Min min() { } /** - * Creates new {@link AggregationExpressions} that calculates the population standard deviation of the input values. + * Creates new {@link AggregationExpression} that calculates the population standard deviation of the input values. * * @return */ @@ -502,7 +502,7 @@ public StdDevPop stdDevPop() { } /** - * Creates new {@link AggregationExpressions} that calculates the sample standard deviation of the input values. + * Creates new {@link AggregationExpression} that calculates the sample standard deviation of the input values. * * @return */ 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 b549269ac0..d6346baaa3 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 @@ -50,7 +50,7 @@ public static ArrayOperatorFactory arrayOf(String fieldReference) { /** * Take the array referenced resulting from the given {@link AggregationExpression}. * - * @param fieldReference must not be {@literal null}. + * @param expression must not be {@literal null}. * @return */ public static ArrayOperatorFactory arrayOf(AggregationExpression expression) { @@ -90,7 +90,7 @@ public ArrayOperatorFactory(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the + * Creates new {@link AggregationExpression} that takes the associated array and returns the element at the * specified array {@literal position}. * * @param position @@ -101,8 +101,8 @@ public ArrayElemAt elementAt(int position) { } /** - * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the - * position resulting form the given {@literal expression}. + * Creates new {@link AggregationExpression} that takes the associated array and returns the element at the position + * resulting form the given {@literal expression}. * * @param expression must not be {@literal null}. * @return @@ -114,8 +114,8 @@ public ArrayElemAt elementAt(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that takes the associated array and returns the element at the - * position defined by the referenced {@literal field}. + * Creates new {@link AggregationExpression} that takes the associated array and returns the element at the position + * defined by the referenced {@literal field}. * * @param fieldReference must not be {@literal null}. * @return @@ -131,7 +131,7 @@ private ArrayElemAt createArrayElemAt() { } /** - * Creates new {@link AggregationExpressions} that takes the associated array and concats the given + * Creates new {@link AggregationExpression} that takes the associated array and concats the given * {@literal arrayFieldReference} to it. * * @param arrayFieldReference must not be {@literal null}. @@ -144,7 +144,7 @@ public ConcatArrays concat(String arrayFieldReference) { } /** - * Creates new {@link AggregationExpressions} that takes the associated array and concats the array resulting form + * Creates new {@link AggregationExpression} that takes the associated array and concats the array resulting form * the given {@literal expression} to it. * * @param expression must not be {@literal null}. @@ -161,7 +161,7 @@ private ConcatArrays createConcatArrays() { } /** - * Creates new {@link AggregationExpressions} that takes the associated array and selects a subset of the array to + * Creates new {@link AggregationExpression} that takes the associated array and selects a subset of the array to * return based on the specified condition. * * @return @@ -171,7 +171,7 @@ public AsBuilder filter() { } /** - * Creates new {@link AggregationExpressions} that takes the associated array and an check if its an array. + * Creates new {@link AggregationExpression} that takes the associated array and an check if its an array. * * @return */ @@ -180,7 +180,7 @@ public IsArray isArray() { } /** - * Creates new {@link AggregationExpressions} that takes the associated array and retrieves its length. + * Creates new {@link AggregationExpression} that takes the associated array and retrieves its length. * * @return */ @@ -189,7 +189,7 @@ public Size length() { } /** - * Creates new {@link AggregationExpressions} that takes the associated array and selects a subset from it. + * Creates new {@link AggregationExpression} that takes the associated array and selects a subset from it. * * @return */ @@ -198,7 +198,7 @@ public Slice slice() { } /** - * Creates new {@link AggregationExpressions} that searches the associated array for an occurrence of a specified + * Creates new {@link AggregationExpression} 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}. @@ -210,7 +210,7 @@ public IndexOfArray indexOf(Object value) { } /** - * Creates new {@link AggregationExpressions} that returns an array with the elements in reverse order. + * Creates new {@link AggregationExpression} that returns an array with the elements in reverse order. * * @return */ @@ -219,7 +219,7 @@ public ReverseArray reverse() { } /** - * Start creating new {@link AggregationExpressions} that applies an {@link AggregationExpression} to each element + * Start creating new {@link AggregationExpression} 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}. @@ -237,7 +237,7 @@ public Reduce startingWith(Object initialValue) { } /** - * Start creating new {@link AggregationExpressions} that applies an {@link AggregationExpression} to each element + * Start creating new {@link AggregationExpression} that applies an {@link AggregationExpression} to each element * in an array and combines them into a single value. * * @param expressions @@ -256,9 +256,9 @@ 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. + * Creates new {@link AggregationExpression} 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 @@ -268,8 +268,8 @@ public Zip zipWith(Object... arrays) { } /** - * Creates new {@link AggregationExpressions} that returns a boolean indicating whether a specified value is in - * the associated array. + * Creates new {@link AggregationExpression} that returns a boolean indicating whether a specified value is in the + * associated array. * * @param value must not be {@literal null}. * @return @@ -575,13 +575,18 @@ static final class FilterExpressionBuilder implements InputBuilder, AsBuilder, C this.filter = new Filter(); } + /** + * Creates new {@link InputBuilder}. + * + * @return + */ public static InputBuilder newBuilder() { return new FilterExpressionBuilder(); } /* * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(java.util.List) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.InputBuilder#filter(java.util.List) */ @Override public AsBuilder filter(List array) { @@ -593,7 +598,7 @@ public AsBuilder filter(List array) { /* * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(org.springframework.data.mongodb.core.aggregation.Field) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.InputBuilder#filter(org.springframework.data.mongodb.core.aggregation.Field) */ @Override public AsBuilder filter(Field field) { @@ -605,7 +610,7 @@ public AsBuilder filter(Field field) { /* * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder#as(java.lang.String) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.AsBuilder#as(java.lang.String) */ @Override public ConditionBuilder as(String variableName) { @@ -617,7 +622,7 @@ public ConditionBuilder as(String variableName) { /* * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.ConditionBuilder#by(org.springframework.data.mongodb.core.aggregation.AggregationExpression) */ @Override public Filter by(AggregationExpression condition) { @@ -629,7 +634,7 @@ public Filter by(AggregationExpression condition) { /* * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(java.lang.String) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.ConditionBuilder#by(java.lang.String) */ @Override public Filter by(String expression) { @@ -641,7 +646,7 @@ public Filter by(String expression) { /* * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(com.mongodb.DBObject) + * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.ConditionBuilder#by(com.mongodb.DBObject) */ @Override public Filter by(DBObject expression) { @@ -1179,7 +1184,7 @@ public interface ReduceBuilder { * 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}. + * @param expressions must not be {@literal null}. * @return */ Reduce reduce(PropertyExpression... expressions); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java index 7e6ef68728..df93fd8919 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BooleanOperators.java @@ -51,7 +51,7 @@ public static BooleanOperatorFactory valueOf(AggregationExpression fieldReferenc } /** - * Creates new {@link AggregationExpressions} that evaluates the boolean value of the referenced field and returns the + * Creates new {@link AggregationExpression} that evaluates the boolean value of the referenced field and returns the * opposite boolean value. * * @param fieldReference must not be {@literal null}. @@ -62,7 +62,7 @@ public static Not not(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that evaluates the boolean value of {@link AggregationExpression} result + * Creates new {@link AggregationExpression} that evaluates the boolean value of {@link AggregationExpression} result * and returns the opposite boolean value. * * @param expression must not be {@literal null}. @@ -105,7 +105,7 @@ public BooleanOperatorFactory(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} if + * Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if * all of the expressions are {@literal true}. * * @param expression must not be {@literal null}. @@ -118,7 +118,7 @@ public And and(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} if + * Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if * all of the expressions are {@literal true}. * * @param fieldReference must not be {@literal null}. @@ -135,7 +135,7 @@ private And createAnd() { } /** - * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} if + * Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if * any of the expressions are {@literal true}. * * @param expression must not be {@literal null}. @@ -148,7 +148,7 @@ public Or or(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true} if + * Creates new {@link AggregationExpression} that evaluates one or more expressions and returns {@literal true} if * any of the expressions are {@literal true}. * * @param fieldReference must not be {@literal null}. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java index 8196b506ab..2824634237 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ComparisonOperators.java @@ -78,7 +78,7 @@ public ComparisonOperatorFactory(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that compares two values. + * Creates new {@link AggregationExpression} that compares two values. * * @param fieldReference must not be {@literal null}. * @return @@ -88,7 +88,7 @@ public Cmp compareTo(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that compares two values. + * Creates new {@link AggregationExpression} that compares two values. * * @param expression must not be {@literal null}. * @return @@ -98,7 +98,7 @@ public Cmp compareTo(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that compares two values. + * Creates new {@link AggregationExpression} that compares two values. * * @param value must not be {@literal null}. * @return @@ -112,7 +112,7 @@ private Cmp createCmp() { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is equal to the value of the referenced field. * * @param fieldReference must not be {@literal null}. @@ -123,7 +123,7 @@ public Eq equalTo(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is equal to the expression result. * * @param expression must not be {@literal null}. @@ -134,7 +134,7 @@ public Eq equalTo(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is equal to the given value. * * @param value must not be {@literal null}. @@ -149,7 +149,7 @@ private Eq createEq() { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is greater than the value of the referenced field. * * @param fieldReference must not be {@literal null}. @@ -160,7 +160,7 @@ public Gt greaterThan(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is greater than the expression result. * * @param expression must not be {@literal null}. @@ -171,7 +171,7 @@ public Gt greaterThan(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is greater than the given value. * * @param value must not be {@literal null}. @@ -186,7 +186,7 @@ private Gt createGt() { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is greater than or equivalent to the value of the referenced field. * * @param fieldReference must not be {@literal null}. @@ -197,7 +197,7 @@ public Gte greaterThanEqualTo(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is greater than or equivalent to the expression result. * * @param expression must not be {@literal null}. @@ -208,7 +208,7 @@ public Gte greaterThanEqualTo(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is greater than or equivalent to the given value. * * @param value must not be {@literal null}. @@ -223,7 +223,7 @@ private Gte createGte() { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is less than the value of the referenced field. * * @param fieldReference must not be {@literal null}. @@ -234,7 +234,7 @@ public Lt lessThan(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is less than the expression result. * * @param expression must not be {@literal null}. @@ -245,7 +245,7 @@ public Lt lessThan(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is less than to the given value. * * @param value must not be {@literal null}. @@ -260,7 +260,7 @@ private Lt createLt() { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is less than or equivalent to the value of the referenced field. * * @param fieldReference must not be {@literal null}. @@ -271,7 +271,7 @@ public Lte lessThanEqualTo(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is less than or equivalent to the expression result. * * @param expression must not be {@literal null}. @@ -282,7 +282,7 @@ public Lte lessThanEqualTo(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the first + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the first * value is less than or equivalent to the given value. * * @param value @@ -297,7 +297,7 @@ private Lte createLte() { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the values * are not equivalent. * * @param fieldReference must not be {@literal null}. @@ -308,7 +308,7 @@ public Ne notEqualTo(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the values * are not equivalent. * * @param expression must not be {@literal null}. @@ -319,7 +319,7 @@ public Ne notEqualTo(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that compares two values and returns {@literal true} when the values + * Creates new {@link AggregationExpression} that compares two values and returns {@literal true} when the values * are not equivalent. * * @param value must not be {@literal null}. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java index 31d412f5a9..4289add6b9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java @@ -35,6 +35,7 @@ * Gateway to {@literal conditional expressions} that evaluate their argument expressions as booleans to a value. * * @author Mark Paluch + * @since 1.10 */ public class ConditionalOperators { @@ -69,7 +70,7 @@ public static ConditionalOperatorFactory when(CriteriaDefinition criteriaDefinit } /** - * Creates new {@link AggregationExpressions} that evaluates an expression and returns the value of the expression if + * Creates new {@link AggregationExpression} that evaluates an expression and returns the value of the expression if * the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, including * instances of undefined values or missing fields, returns the value of the replacement expression. * @@ -83,7 +84,7 @@ public static IfNull.ThenBuilder ifNull(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that evaluates an expression and returns the value of the expression if + * Creates new {@link AggregationExpression} that evaluates an expression and returns the value of the expression if * the expression evaluates to a non-null value. If the expression evaluates to a {@literal null} value, including * instances of undefined values or missing fields, returns the value of the replacement expression. * @@ -182,7 +183,7 @@ public OtherwiseBuilder then(Object value) { } /** - * Creates new {@link AggregationExpressions} that evaluates a boolean expression to return one of the two specified + * Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two specified * return expressions. * * @param expression must not be {@literal null}. @@ -195,7 +196,7 @@ public OtherwiseBuilder thenValueOf(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that evaluates a boolean expression to return one of the two specified + * Creates new {@link AggregationExpression} that evaluates a boolean expression to return one of the two specified * return expressions. * * @param fieldReference must not be {@literal null}. @@ -230,7 +231,8 @@ private boolean usesCriteriaDefinition() { * field references}, {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be * converted to a simple MongoDB type. * - * @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ + * @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ * @author Mark Paluch */ public static class IfNull implements AggregationExpression { @@ -308,7 +310,7 @@ private Object resolve(Object value, AggregationOperationContext context) { /** * @author Mark Paluch */ - public static interface IfNullBuilder { + public interface IfNullBuilder { /** * @param fieldReference the field to check for a {@literal null} value, field reference must not be @@ -328,7 +330,7 @@ public static interface IfNullBuilder { /** * @author Mark Paluch */ - public static interface ThenBuilder { + public interface ThenBuilder { /** * @param value the value to be used if the {@code $ifNull} condition evaluates {@literal true}. Can be a @@ -348,11 +350,11 @@ public static interface ThenBuilder { * @param expression the expression yielding to the replacement value, must not be {@literal null}. * @return */ - public IfNull thenValueOf(AggregationExpression expression); + IfNull thenValueOf(AggregationExpression expression); } /** - * Builder for fluent {@link IfNullOperator} creation. + * Builder for fluent {@link IfNull} creation. * * @author Mark Paluch */ @@ -363,7 +365,7 @@ static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder { private IfNullOperatorBuilder() {} /** - * Creates a new builder for {@link IfNullOperator}. + * Creates a new builder for {@link IfNull}. * * @return never {@literal null}. */ @@ -372,7 +374,7 @@ public static IfNullOperatorBuilder newBuilder() { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.IfNullBuilder#ifNull(java.lang.String) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.IfNullBuilder#ifNull(java.lang.String) */ public ThenBuilder ifNull(String fieldReference) { @@ -382,7 +384,7 @@ public ThenBuilder ifNull(String fieldReference) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.AggregationExpression) */ @Override public ThenBuilder ifNull(AggregationExpression expression) { @@ -393,14 +395,14 @@ public ThenBuilder ifNull(AggregationExpression expression) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#then(java.lang.Object) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#then(java.lang.Object) */ public IfNull then(Object value) { return new IfNull(condition, value); } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#thenValueOf(java.lang.String) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#thenValueOf(java.lang.String) */ public IfNull thenValueOf(String fieldReference) { @@ -409,7 +411,7 @@ public IfNull thenValueOf(String fieldReference) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) */ public IfNull thenValueOf(AggregationExpression expression) { @@ -532,7 +534,8 @@ public interface ThenBuilder { * {@link AggregationExpression expressions}, values of simple MongoDB types or values that can be converted to a * simple MongoDB type. * - * @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/ + * @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/ * @author Mark Paluch * @author Christoph Strobl */ @@ -763,7 +766,7 @@ public static ThenBuilder when(CriteriaDefinition criteria) { /** * @author Mark Paluch */ - public static interface WhenBuilder { + public interface WhenBuilder { /** * @param booleanExpression expression that yields in a boolean result, must not be {@literal null}. @@ -793,7 +796,7 @@ public static interface WhenBuilder { /** * @author Mark Paluch */ - public static interface ThenBuilder { + public interface ThenBuilder { /** * @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link DBObject}, a @@ -819,7 +822,7 @@ public static interface ThenBuilder { /** * @author Mark Paluch */ - public static interface OtherwiseBuilder { + public interface OtherwiseBuilder { /** * @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link DBObject}, a @@ -864,7 +867,7 @@ public static ConditionalExpressionBuilder newBuilder() { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(com.mongodb.DBObject) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(com.mongodb.DBObject) */ @Override public ConditionalExpressionBuilder when(DBObject booleanExpression) { @@ -876,7 +879,7 @@ public ConditionalExpressionBuilder when(DBObject booleanExpression) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition) */ @Override public ThenBuilder when(CriteriaDefinition criteria) { @@ -887,7 +890,7 @@ public ThenBuilder when(CriteriaDefinition criteria) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.AggregationExpression) */ @Override public ThenBuilder when(AggregationExpression expression) { @@ -898,7 +901,7 @@ public ThenBuilder when(AggregationExpression expression) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.WhenBuilder#when(java.lang.String) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.WhenBuilder#when(java.lang.String) */ @Override public ThenBuilder when(String booleanField) { @@ -909,7 +912,7 @@ public ThenBuilder when(String booleanField) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#then(java.lang.Object) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder#then(java.lang.Object) */ @Override public OtherwiseBuilder then(Object thenValue) { @@ -920,7 +923,7 @@ public OtherwiseBuilder then(Object thenValue) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#thenValueOf(java.lang.String) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder#thenValueOf(java.lang.String) */ @Override public OtherwiseBuilder thenValueOf(String fieldReference) { @@ -931,7 +934,7 @@ public OtherwiseBuilder thenValueOf(String fieldReference) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.ThenBuilder#thenValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) */ @Override public OtherwiseBuilder thenValueOf(AggregationExpression expression) { @@ -942,7 +945,7 @@ public OtherwiseBuilder thenValueOf(AggregationExpression expression) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwise(java.lang.Object) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder#otherwise(java.lang.Object) */ @Override public Cond otherwise(Object otherwiseValue) { @@ -952,7 +955,7 @@ public Cond otherwise(Object otherwiseValue) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwiseValueOf(java.lang.String) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder#otherwiseValueOf(java.lang.String) */ @Override public Cond otherwiseValueOf(String fieldReference) { @@ -962,7 +965,7 @@ public Cond otherwiseValueOf(String fieldReference) { } /* (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder#otherwiseValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) + * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond.OtherwiseBuilder#otherwiseValueOf(org.springframework.data.mongodb.core.aggregation.AggregationExpression) */ @Override public Cond otherwiseValueOf(AggregationExpression expression) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java index 88fca85f46..4b501d782d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DateOperators.java @@ -85,7 +85,7 @@ public DateOperatorFactory(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that returns the day of the year for a date as a number between 1 and + * Creates new {@link AggregationExpression} that returns the day of the year for a date as a number between 1 and * 366. * * @return @@ -95,7 +95,7 @@ public DayOfYear dayOfYear() { } /** - * Creates new {@link AggregationExpressions} that returns the day of the month for a date as a number between 1 and + * Creates new {@link AggregationExpression} that returns the day of the month for a date as a number between 1 and * 31. * * @return @@ -105,7 +105,7 @@ public DayOfMonth dayOfMonth() { } /** - * Creates new {@link AggregationExpressions} that returns the day of the week for a date as a number between 1 + * Creates new {@link AggregationExpression} that returns the day of the week for a date as a number between 1 * (Sunday) and 7 (Saturday). * * @return @@ -115,7 +115,7 @@ public DayOfWeek dayOfWeek() { } /** - * Creates new {@link AggregationExpressions} that returns the year portion of a date. + * Creates new {@link AggregationExpression} that returns the year portion of a date. * * @return */ @@ -124,7 +124,7 @@ public Year year() { } /** - * Creates new {@link AggregationExpressions} that returns the month of a date as a number between 1 and 12. + * Creates new {@link AggregationExpression} that returns the month of a date as a number between 1 and 12. * * @return */ @@ -133,7 +133,7 @@ public Month month() { } /** - * Creates new {@link AggregationExpressions} that returns the week of the year for a date as a number between 0 and + * Creates new {@link AggregationExpression} that returns the week of the year for a date as a number between 0 and * 53. * * @return @@ -143,7 +143,7 @@ public Week week() { } /** - * Creates new {@link AggregationExpressions} that returns the hour portion of a date as a number between 0 and 23. + * Creates new {@link AggregationExpression} that returns the hour portion of a date as a number between 0 and 23. * * @return */ @@ -152,8 +152,7 @@ public Hour hour() { } /** - * Creates new {@link AggregationExpressions} that returns the minute portion of a date as a number between 0 and - * 59. + * Creates new {@link AggregationExpression} that returns the minute portion of a date as a number between 0 and 59. * * @return */ @@ -162,8 +161,8 @@ public Minute minute() { } /** - * Creates new {@link AggregationExpressions} that returns the second portion of a date as a number between 0 and - * 59, but can be 60 to account for leap seconds. + * Creates new {@link AggregationExpression} that returns the second portion of a date as a number between 0 and 59, + * but can be 60 to account for leap seconds. * * @return */ @@ -172,7 +171,7 @@ public Second second() { } /** - * Creates new {@link AggregationExpressions} that returns the millisecond portion of a date as an integer between 0 + * Creates new {@link AggregationExpression} that returns the millisecond portion of a date as an integer between 0 * and 999. * * @return @@ -182,7 +181,7 @@ public Millisecond millisecond() { } /** - * Creates new {@link AggregationExpressions} that converts a date object to a string according to a user-specified + * Creates new {@link AggregationExpression} that converts a date object to a string according to a user-specified * {@literal format}. * * @param format must not be {@literal null}. @@ -193,8 +192,8 @@ public DateToString toString(String format) { } /** - * Creates new {@link AggregationExpressions} that returns the weekday number in ISO 8601 format, ranging from 1 - * (for Monday) to 7 (for Sunday). + * Creates new {@link AggregationExpression} that returns the weekday number in ISO 8601 format, ranging from 1 (for + * Monday) to 7 (for Sunday). * * @return */ @@ -203,7 +202,7 @@ public IsoDayOfWeek isoDayOfWeek() { } /** - * Creates new {@link AggregationExpressions} that returns the week number in ISO 8601 format, ranging from 1 to 53. + * Creates new {@link AggregationExpression} that returns the week number in ISO 8601 format, ranging from 1 to 53. * * @return */ @@ -212,7 +211,7 @@ public IsoWeek isoWeek() { } /** - * Creates new {@link AggregationExpressions} that returns the year number in ISO 8601 format. + * Creates new {@link AggregationExpression} that returns the year number in ISO 8601 format. * * @return */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java index a91b4e935c..e54ab14134 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LiteralOperators.java @@ -56,7 +56,7 @@ public LiteralOperatorFactory(Object value) { } /** - * Creates new {@link AggregationExpressions} that returns the associated value without parsing. + * Creates new {@link Literal} that returns the associated value without parsing. * * @return */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java index 598c1e35ba..f14ebd4315 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperators.java @@ -83,7 +83,7 @@ public SetOperatorFactory(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that compares the previously mentioned field to one or more arrays and + * Creates new {@link AggregationExpression} that compares the previously mentioned field to one or more arrays and * returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. * * @param arrayReferences must not be {@literal null}. @@ -94,7 +94,7 @@ public SetEquals isEqualTo(String... arrayReferences) { } /** - * Creates new {@link AggregationExpressions} that compares the previously mentioned field to one or more arrays and + * Creates new {@link AggregationExpression} that compares the previously mentioned field to one or more arrays and * returns {@literal true} if they have the same distinct elements and {@literal false} otherwise. * * @param expressions must not be {@literal null}. @@ -109,7 +109,7 @@ private SetEquals createSetEquals() { } /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and one or more * arrays and returns an array that contains the elements that appear in every of those. * * @param arrayReferences must not be {@literal null}. @@ -120,7 +120,7 @@ public SetIntersection intersects(String... arrayReferences) { } /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and one or more * arrays and returns an array that contains the elements that appear in every of those. * * @param expressions must not be {@literal null}. @@ -135,7 +135,7 @@ private SetIntersection createSetIntersection() { } /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and one or more * arrays and returns an array that contains the elements that appear in any of those. * * @param arrayReferences must not be {@literal null}. @@ -146,7 +146,7 @@ public SetUnion union(String... arrayReferences) { } /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and one or more + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and one or more * arrays and returns an array that contains the elements that appear in any of those. * * @param expressions must not be {@literal null}. @@ -161,8 +161,8 @@ private SetUnion createSetUnion() { } /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns an - * array containing the elements that do not exist in the given {@literal arrayReference}. + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and returns an array + * containing the elements that do not exist in the given {@literal arrayReference}. * * @param arrayReference must not be {@literal null}. * @return @@ -172,8 +172,8 @@ public SetDifference differenceTo(String arrayReference) { } /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns an - * array containing the elements that do not exist in the given {@link AggregationExpression}. + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and returns an array + * containing the elements that do not exist in the given {@link AggregationExpression}. * * @param expression must not be {@literal null}. * @return @@ -187,7 +187,7 @@ private SetDifference createSetDifference() { } /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and returns * {@literal true} if it is a subset of the given {@literal arrayReference}. * * @param arrayReference must not be {@literal null}. @@ -198,7 +198,7 @@ public SetIsSubset isSubsetOf(String arrayReference) { } /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and returns * {@literal true} if it is a subset of the given {@link AggregationExpression}. * * @param expression must not be {@literal null}. @@ -213,7 +213,7 @@ private SetIsSubset createSetIsSubset() { } /** - * Creates new {@link AggregationExpressions} that takes array of the previously mentioned field and returns + * Creates new {@link AggregationExpression} that takes array of the previously mentioned field and returns * {@literal true} if any of the elements are {@literal true} and {@literal false} otherwise. * * @return @@ -223,7 +223,7 @@ public AnyElementTrue anyElementTrue() { } /** - * Creates new {@link AggregationExpressions} that tkes array of the previously mentioned field and returns + * Creates new {@link AggregationExpression} that tkes array of the previously mentioned field and returns * {@literal true} if no elements is {@literal false}. * * @return diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java index e79f25b231..eed2ac8d30 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java @@ -84,7 +84,7 @@ public StringOperatorFactory(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and concats the value + * Creates new {@link AggregationExpression} that takes the associated string representation and concats the value * of the referenced field to it. * * @param fieldReference must not be {@literal null}. @@ -97,7 +97,7 @@ public Concat concatValueOf(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and concats the result + * Creates new {@link AggregationExpression} that takes the associated string representation and concats the result * of the given {@link AggregationExpression} to it. * * @param expression must not be {@literal null}. @@ -110,7 +110,7 @@ public Concat concatValueOf(AggregationExpression expression) { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and concats given + * Creates new {@link AggregationExpression} that takes the associated string representation and concats given * {@literal value} to it. * * @param value must not be {@literal null}. @@ -127,8 +127,8 @@ private Concat createConcat() { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a - * substring starting at a specified index position. + * Creates new {@link AggregationExpression} that takes the associated string representation and returns a substring + * starting at a specified index position. * * @param start * @return @@ -138,8 +138,8 @@ public Substr substring(int start) { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a - * substring starting at a specified index position including the specified number of characters. + * Creates new {@link AggregationExpression} that takes the associated string representation and returns a substring + * starting at a specified index position including the specified number of characters. * * @param start * @param nrOfChars @@ -154,7 +154,7 @@ private Substr createSubstr() { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and lowers it. + * Creates new {@link AggregationExpression} that takes the associated string representation and lowers it. * * @return */ @@ -163,7 +163,7 @@ public ToLower toLower() { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and uppers it. + * Creates new {@link AggregationExpression} that takes the associated string representation and uppers it. * * @return */ @@ -172,7 +172,7 @@ public ToUpper toUpper() { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and performs + * Creates new {@link AggregationExpression} that takes the associated string representation and performs * case-insensitive comparison to the given {@literal value}. * * @param value must not be {@literal null}. @@ -185,7 +185,7 @@ public StrCaseCmp strCaseCmp(String value) { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and performs + * Creates new {@link AggregationExpression} that takes the associated string representation and performs * case-insensitive comparison to the referenced {@literal fieldReference}. * * @param fieldReference must not be {@literal null}. @@ -198,7 +198,7 @@ public StrCaseCmp strCaseCmpValueOf(String fieldReference) { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and performs + * Creates new {@link AggregationExpression} that takes the associated string representation and performs * case-insensitive comparison to the result of the given {@link AggregationExpression}. * * @param expression must not be {@literal null}. @@ -215,7 +215,7 @@ private StrCaseCmp createStrCaseCmp() { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a string + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a string * for an occurrence of a given {@literal substring} and returns the UTF-8 byte index (zero-based) of the first * occurrence. * @@ -229,7 +229,7 @@ public IndexOfBytes indexOf(String substring) { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a string + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a 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. * @@ -243,7 +243,7 @@ public IndexOfBytes indexOf(Field fieldReference) { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a string + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a 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. * @@ -261,7 +261,7 @@ private IndexOfBytes.SubstringBuilder createIndexOfBytesSubstringBuilder() { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a string + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a string * for an occurrence of a given {@literal substring} and returns the UTF-8 code point index (zero-based) of the * first occurrence. * @@ -275,7 +275,7 @@ public IndexOfCP indexOfCP(String substring) { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a string + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a 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. * @@ -289,7 +289,7 @@ public IndexOfCP indexOfCP(Field fieldReference) { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a string + * Creates new {@link AggregationExpression} that takes the associated string representation and searches a 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. * @@ -365,8 +365,8 @@ public StrLenCP lengthCP() { } /** - * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a - * substring starting at a specified code point index position. + * Creates new {@link AggregationExpression} that takes the associated string representation and returns a substring + * starting at a specified code point index position. * * @param codePointStart * @return @@ -376,8 +376,8 @@ public SubstrCP substringCP(int codePointStart) { } /** - * 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. + * Creates new {@link AggregationExpression} 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 diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java index 79e8a5289a..101e65cfee 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/VariableOperators.java @@ -129,7 +129,7 @@ public Map andApply(final AggregationExpression expression) { } }; - }; + } /** * Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 9a46581696..8adb939dfa 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -57,9 +57,9 @@ import org.springframework.data.mongodb.core.CollectionCallback; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.Venue; -import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry; import org.springframework.data.mongodb.core.aggregation.BucketAutoOperation.Granularities; +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.data.mongodb.core.index.GeospatialIndex; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.query.Criteria; From 14e326dc09a1a2a268ea969483f7614f224bd2eb Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 15 Dec 2016 10:37:16 +0100 Subject: [PATCH 052/118] DATAMONGO-1565 - Ignore placeholder pattern in replacement values for annotated queries. We now make sure to quote single and double ticks in the replacement values before actually appending them to the query. We also replace single ticks around parameters in the actual raw annotated query by double quotes to make sure they are treated as a single string parameter. --- .../ExpressionEvaluatingParameterBinder.java | 150 ++++++++++++++---- .../query/StringBasedMongoQueryUnitTests.java | 97 ++++++++++- 2 files changed, 213 insertions(+), 34 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index 10c34bb391..e5f7faf797 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -15,8 +15,13 @@ */ package org.springframework.data.mongodb.repository.query; -import java.util.Collections; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.xml.bind.DatatypeConverter; @@ -94,47 +99,67 @@ private String replacePlaceholders(String input, MongoParameterAccessor accessor return input; } - boolean isCompletlyParameterizedQuery = input.matches("^\\?\\d+$"); - StringBuilder result = new StringBuilder(input); - - for (ParameterBinding binding : bindingContext.getBindings()) { + if (input.matches("^\\?\\d+$")) { + return getParameterValueForBinding(accessor, bindingContext.getParameters(), + bindingContext.getBindings().iterator().next()); + } - String parameter = binding.getParameter(); - int idx = result.indexOf(parameter); + Matcher matcher = createReplacementPattern(bindingContext.getBindings()).matcher(input); + StringBuffer buffer = new StringBuffer(); - if (idx == -1) { - continue; - } + while (matcher.find()) { + ParameterBinding binding = bindingContext.getBindingFor(extractPlaceholder(matcher.group())); String valueForBinding = getParameterValueForBinding(accessor, bindingContext.getParameters(), binding); - int start = idx; - int end = idx + parameter.length(); + // appendReplacement does not like unescaped $ sign and others, so we need to quote that stuff first + matcher.appendReplacement(buffer, Matcher.quoteReplacement(valueForBinding)); + + if (binding.isQuoted()) { + postProcessQuotedBinding(buffer, valueForBinding); + } + } + + matcher.appendTail(buffer); + return buffer.toString(); + } - // If the value to bind is an object literal we need to remove the quoting around the expression insertion point. - if (valueForBinding.startsWith("{") && !isCompletlyParameterizedQuery) { + /** + * Sanitize String binding by replacing single quoted values with double quotes which prevents potential single quotes + * contained in replacement to interfere with the Json parsing. Also take care of complex objects by removing the + * quotation entirely. + * + * @param buffer the {@link StringBuffer} to operate upon. + * @param valueForBinding the actual binding value. + */ + private void postProcessQuotedBinding(StringBuffer buffer, String valueForBinding) { - // Is the insertion point actually surrounded by quotes? - char beforeStart = result.charAt(start - 1); - char afterEnd = result.charAt(end); + int quotationMarkIndex = buffer.length() - valueForBinding.length() - 1; + char quotationMark = buffer.charAt(quotationMarkIndex); - if ((beforeStart == '\'' || beforeStart == '"') && (afterEnd == '\'' || afterEnd == '"')) { + while (quotationMark != '\'' && quotationMark != '"') { - // Skip preceding and following quote - start -= 1; - end += 1; - } + quotationMarkIndex--; + if (quotationMarkIndex < 0) { + throw new IllegalArgumentException("Could not find opening quotes for quoted parameter"); } - - result.replace(start, end, valueForBinding); + quotationMark = buffer.charAt(quotationMarkIndex); } - return result.toString(); + if (valueForBinding.startsWith("{")) { // remove quotation char before the complex object string + buffer.deleteCharAt(quotationMarkIndex); + } else { + + if (quotationMark == '\'') { + buffer.replace(quotationMarkIndex, quotationMarkIndex + 1, "\""); + } + buffer.append("\""); + } } /** * Returns the serialized value to be used for the given {@link ParameterBinding}. - * + * * @param accessor must not be {@literal null}. * @param parameters * @param binding must not be {@literal null}. @@ -148,7 +173,7 @@ private String getParameterValueForBinding(MongoParameterAccessor accessor, Mong : accessor.getBindableValue(binding.getParameterIndex()); if (value instanceof String && binding.isQuoted()) { - return (String) value; + return ((String) value).startsWith("{") ? (String) value : ((String) value).replace("\"", "\\\""); } if (value instanceof byte[]) { @@ -167,7 +192,7 @@ private String getParameterValueForBinding(MongoParameterAccessor accessor, Mong /** * Evaluates the given {@code expressionString}. - * + * * @param expressionString must not be {@literal null} or empty. * @param parameters must not be {@literal null}. * @param parameterValues must not be {@literal null}. @@ -181,6 +206,40 @@ private Object evaluateExpression(String expressionString, MongoParameters param return expression.getValue(evaluationContext, Object.class); } + /** + * Creates a replacement {@link Pattern} for all {@link ParameterBinding#getParameter() binding parameters} including + * a potentially trailing quotation mark. + * + * @param bindings + * @return + */ + private Pattern createReplacementPattern(List bindings) { + + StringBuilder regex = new StringBuilder(); + for (ParameterBinding binding : bindings) { + regex.append("|"); + regex.append(Pattern.quote(binding.getParameter())); + regex.append("['\"]?"); // potential quotation char (as in { foo : '?0' }). + } + + return Pattern.compile(regex.substring(1)); + } + + /** + * Extract the placeholder stripping any trailing trailing quotation mark that might have resulted from the + * {@link #createReplacementPattern(List) pattern} used. + * + * @param groupName The actual {@link Matcher#group() group}. + * @return + */ + private String extractPlaceholder(String groupName) { + + if (!groupName.endsWith("'") && !groupName.endsWith("\"")) { + return groupName; + } + return groupName.substring(0, groupName.length() - 1); + } + /** * @author Christoph Strobl * @since 1.9 @@ -188,18 +247,18 @@ private Object evaluateExpression(String expressionString, MongoParameters param static class BindingContext { final MongoParameters parameters; - final List bindings; + final Map bindings; /** * Creates new {@link BindingContext}. - * + * * @param parameters * @param bindings */ public BindingContext(MongoParameters parameters, List bindings) { this.parameters = parameters; - this.bindings = bindings; + this.bindings = mapBindings(bindings); } /** @@ -211,11 +270,28 @@ boolean hasBindings() { /** * Get unmodifiable list of {@link ParameterBinding}s. - * + * * @return never {@literal null}. */ public List getBindings() { - return Collections.unmodifiableList(bindings); + return new ArrayList(bindings.values()); + } + + /** + * Get the concrete {@link ParameterBinding} for a given {@literal placeholder}. + * + * @param placeholder must not be {@literal null}. + * @return + * @throws java.util.NoSuchElementException + * @since 1.10 + */ + ParameterBinding getBindingFor(String placeholder) { + + if (!bindings.containsKey(placeholder)) { + throw new NoSuchElementException(String.format("Could not to find binding for placeholder '%s'.", placeholder)); + } + + return bindings.get(placeholder); } /** @@ -227,5 +303,13 @@ public MongoParameters getParameters() { return parameters; } + private static Map mapBindings(List bindings) { + + Map map = new LinkedHashMap(bindings.size(), 1); + for (ParameterBinding binding : bindings) { + map.put(binding.getParameter(), binding); + } + return map; + } } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index f3b4fe1672..72520b9c15 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -54,6 +54,7 @@ import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; import com.mongodb.DBRef; +import com.mongodb.util.JSON; /** * Unit tests for {@link StringBasedMongoQuery}. @@ -179,7 +180,8 @@ public void preventsDeleteAndCountFlagAtTheSameTime() throws Exception { @Test public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception { - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new BasicDBObject("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1)); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, + new BasicDBObject("firstname", "first").append("lastname", "last"), Collections.singletonMap("lastname", 1)); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByParameterizedCriteriaAndFields", DBObject.class, Map.class); @@ -373,6 +375,96 @@ public void shouldSupportExistsProjection() throws Exception { assertThat(mongoQuery.isExistsQuery(), is(true)); } + /** + * @see DATAMONGO-1565 + */ + @Test + public void shouldIgnorePlaceholderPatternInReplacementValue() throws Exception { + + ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "argWith?1andText", + "nothing-special"); + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByStringWithWildcardChar", String.class, String.class); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + assertThat(query.getQueryObject(), + is(JSON.parse("{ \"arg0\" : \"argWith?1andText\" , \"arg1\" : \"nothing-special\"}"))); + } + + /** + * @see DATAMONGO-1565 + */ + @Test + public void shouldQuoteStringReplacementCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); + ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews', password: 'foo"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + assertThat(query.getQueryObject(), + is(not(new BasicDBObjectBuilder().add("lastname", "Matthews").add("password", "foo").get()))); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews', password: 'foo"))); + } + + /** + * @see DATAMONGO-1565 + */ + @Test + public void shouldQuoteStringReplacementContainingQuotesCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); + ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews\", password: \"foo"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + assertThat(query.getQueryObject(), + is(not(new BasicDBObjectBuilder().add("lastname", "Matthews").add("password", "foo").get()))); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews\", password: \"foo"))); + } + + /** + * @see DATAMONGO-1565 + */ + @Test + public void shouldQuoteStringReplacementWithQuotationsCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); + ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, + "\"Dave Matthews\", password: 'foo"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + assertThat(query.getQueryObject(), + is((DBObject) new BasicDBObject("lastname", "\"Dave Matthews\", password: 'foo"))); + } + + /** + * @see DATAMONGO-1565 + */ + @Test + public void shouldQuoteComplexQueryStringCorreclty() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); + ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"calamity\" }"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + assertThat(query.getQueryObject(), + is((DBObject) new BasicDBObject("lastname", new BasicDBObject("$ne", "calamity")))); + } + + /** + * @see DATAMONGO-1565 + */ + @Test + public void shouldQuotationInQuotedComplexQueryString() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); + ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, + "{ $ne : \"\\\"calamity\\\"\" }"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + assertThat(query.getQueryObject(), + is((DBObject) new BasicDBObject("lastname", new BasicDBObject("$ne", "\"calamity\"")))); + } + private StringBasedMongoQuery createQueryForMethod(String name, Class... parameters) throws Exception { Method method = SampleRepository.class.getMethod(name, parameters); @@ -434,5 +526,8 @@ private interface SampleRepository extends Repository { @Query(value = "{ 'lastname' : ?0 }", exists = true) boolean existsByLastname(String lastname); + + @Query("{ 'arg0' : ?0, 'arg1' : ?1 }") + List findByStringWithWildcardChar(String arg0, String arg1); } } From 1a105333aa084650b415fc91a2ed51d09efc595e Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 16 Dec 2016 16:18:32 +0100 Subject: [PATCH 053/118] DATAMONGO-1565 - Polishing. Consider quoted/unquoted parameter use with the same parameter reference. Extend date range in license headers. --- .../ExpressionEvaluatingParameterBinder.java | 52 ++++++++++---- .../query/StringBasedMongoQueryUnitTests.java | 70 +++++++++++++------ 2 files changed, 87 insertions(+), 35 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index e5f7faf797..c81afb6259 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -15,6 +15,9 @@ */ package org.springframework.data.mongodb.repository.query; +import lombok.Data; +import lombok.RequiredArgsConstructor; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -44,6 +47,7 @@ * @author Christoph Strobl * @author Thomas Darimont * @author Oliver Gierke + * @author Mark Paluch * @since 1.9 */ class ExpressionEvaluatingParameterBinder { @@ -90,7 +94,7 @@ public String bind(String raw, MongoParameterAccessor accessor, BindingContext b * * @param input must not be {@literal null} or empty. * @param accessor must not be {@literal null}. - * @param bindings must not be {@literal null}. + * @param bindingContext must not be {@literal null}. * @return */ private String replacePlaceholders(String input, MongoParameterAccessor accessor, BindingContext bindingContext) { @@ -232,22 +236,23 @@ private Pattern createReplacementPattern(List bindings) { * @param groupName The actual {@link Matcher#group() group}. * @return */ - private String extractPlaceholder(String groupName) { + private Placeholder extractPlaceholder(String groupName) { if (!groupName.endsWith("'") && !groupName.endsWith("\"")) { - return groupName; + return new Placeholder(groupName, false); } - return groupName.substring(0, groupName.length() - 1); + return new Placeholder(groupName.substring(0, groupName.length() - 1), true); } /** * @author Christoph Strobl + * @author Mark Paluch * @since 1.9 */ static class BindingContext { final MongoParameters parameters; - final Map bindings; + final Map bindings; /** * Creates new {@link BindingContext}. @@ -279,13 +284,13 @@ public List getBindings() { /** * Get the concrete {@link ParameterBinding} for a given {@literal placeholder}. - * + * * @param placeholder must not be {@literal null}. * @return * @throws java.util.NoSuchElementException * @since 1.10 */ - ParameterBinding getBindingFor(String placeholder) { + ParameterBinding getBindingFor(Placeholder placeholder) { if (!bindings.containsKey(placeholder)) { throw new NoSuchElementException(String.format("Could not to find binding for placeholder '%s'.", placeholder)); @@ -296,20 +301,43 @@ ParameterBinding getBindingFor(String placeholder) { /** * Get the associated {@link MongoParameters}. - * + * * @return */ public MongoParameters getParameters() { return parameters; } - private static Map mapBindings(List bindings) { + private static Map mapBindings(List bindings) { - Map map = new LinkedHashMap(bindings.size(), 1); + Map map = new LinkedHashMap(bindings.size(), 1); for (ParameterBinding binding : bindings) { - map.put(binding.getParameter(), binding); + map.put(new Placeholder(binding.getParameter(), binding.isQuoted()), binding); } return map; } } + + /** + * Encapsulates a quoted/unquoted parameter placeholder. + * + * @author Mark Paluch + * @since 1.9 + */ + @Data + @RequiredArgsConstructor + static class Placeholder { + + private final String parameter; + private final boolean quoted; + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return quoted ? String.format("'%s'", parameter) : parameter; + } + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index 72520b9c15..c9de1d253e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-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. @@ -50,6 +50,7 @@ import org.springframework.data.repository.query.DefaultEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; +import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; @@ -86,9 +87,9 @@ public void setUp() { public void bindsSimplePropertyCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -100,13 +101,13 @@ public void bindsComplexPropertyCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByAddress", Address.class); Address address = new Address("Foo", "0123", "Bar"); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, address); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, address); DBObject dbObject = new BasicDBObject(); converter.write(address, dbObject); dbObject.removeField(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); BasicDBObject queryObject = new BasicDBObject("address", dbObject); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(queryObject); @@ -119,7 +120,7 @@ public void bindsMultipleParametersCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAndAddress", String.class, Address.class); Address address = new Address("Foo", "0123", "Bar"); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews", address); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews", address); DBObject addressDbObject = new BasicDBObject(); converter.write(address, addressDbObject); @@ -128,7 +129,7 @@ public void bindsMultipleParametersCorrectly() throws Exception { DBObject reference = new BasicDBObject("address", addressDbObject); reference.put("lastname", "Matthews"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(reference)); } @@ -229,10 +230,10 @@ public void shouldParseQueryWithParametersInExpression() throws Exception { @Test public void bindsSimplePropertyAlreadyQuotedCorrectly() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -244,10 +245,10 @@ public void bindsSimplePropertyAlreadyQuotedCorrectly() throws Exception { @Test public void bindsSimplePropertyAlreadyQuotedWithRegexCorrectly() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -260,9 +261,9 @@ public void bindsSimplePropertyAlreadyQuotedWithRegexCorrectly() throws Exceptio public void bindsSimplePropertyWithRegexCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : '^Mat.*'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -305,10 +306,10 @@ public void shouldParseJsonKeyReplacementCorrectly() throws Exception { @Test public void shouldSupportExpressionsInCustomQueries() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpression", String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : 'Matthews'}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -320,11 +321,11 @@ public void shouldSupportExpressionsInCustomQueries() throws Exception { @Test public void shouldSupportExpressionsInCustomQueriesWithNestedObject() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndNestedObject", boolean.class, String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{ \"id\" : { \"$exists\" : true}}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); @@ -336,11 +337,11 @@ public void shouldSupportExpressionsInCustomQueriesWithNestedObject() throws Exc @Test public void shouldSupportExpressionsInCustomQueriesWithMultipleNestedObjects() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithExpressionAndMultipleNestedObjects", boolean.class, String.class, String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery( "{ \"id\" : { \"$exists\" : true} , \"foo\" : 42 , \"bar\" : { \"$exists\" : false}}"); @@ -354,16 +355,36 @@ public void shouldSupportExpressionsInCustomQueriesWithMultipleNestedObjects() t public void shouldSupportNonQuotedBinaryDataReplacement() throws Exception { byte[] binaryData = "Matthews".getBytes("UTF-8"); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, binaryData); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, binaryData); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameAsBinary", byte[].class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); org.springframework.data.mongodb.core.query.Query reference = new BasicQuery("{'lastname' : { '$binary' : '" + DatatypeConverter.printBase64Binary(binaryData) + "', '$type' : " + BSON.B_GENERAL + "}}"); assertThat(query.getQueryObject(), is(reference.getQueryObject())); } + /** + * @see DATAMONGO-1565 + */ + @Test + public void bindsPropertyReferenceMultipleTimesCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByAgeQuotedAndUnquoted", Integer.TYPE); + + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, 3); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + BasicDBList or = new BasicDBList(); + or.add(new BasicDBObject("age", 3)); + or.add(new BasicDBObject("displayAge", "3")); + BasicDBObject queryObject = new BasicDBObject("$or", or); + org.springframework.data.mongodb.core.query.Query reference = new BasicQuery(queryObject); + + assertThat(query.getQueryObject(), is(reference.getQueryObject())); + } + /** * @see DATAMONGO-1454 */ @@ -381,12 +402,12 @@ public void shouldSupportExistsProjection() throws Exception { @Test public void shouldIgnorePlaceholderPatternInReplacementValue() throws Exception { - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "argWith?1andText", + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "argWith?1andText", "nothing-special"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByStringWithWildcardChar", String.class, String.class); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(JSON.parse("{ \"arg0\" : \"argWith?1andText\" , \"arg1\" : \"nothing-special\"}"))); } @@ -524,6 +545,9 @@ private interface SampleRepository extends Repository { @Query("{'id':?#{ [0] ? { $exists :true} : [1] }, 'foo':42, 'bar': ?#{ [0] ? { $exists :false} : [1] }}") List findByQueryWithExpressionAndMultipleNestedObjects(boolean param0, String param1, String param2); + @Query(value = "{ $or : [{'age' : ?0 }, {'displayAge' : '?0'}] }") + boolean findByAgeQuotedAndUnquoted(int age); + @Query(value = "{ 'lastname' : ?0 }", exists = true) boolean existsByLastname(String lastname); From 29e405b800e10f470362a4cc0025b2ab9a971ab6 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 19 Dec 2016 14:26:12 +0100 Subject: [PATCH 054/118] DATAMONGO-1565 - Polishing. Formatting in ExpressionEvaluatingParameterBinder and StringBasedMongoQueryUnitTests. Turned Placeholder into value object. --- .../ExpressionEvaluatingParameterBinder.java | 24 ++++++++++++------- .../query/StringBasedMongoQueryUnitTests.java | 22 ++++++++--------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index c81afb6259..a86378e1d8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -15,8 +15,7 @@ */ package org.springframework.data.mongodb.repository.query; -import lombok.Data; -import lombok.RequiredArgsConstructor; +import lombok.Value; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -144,19 +143,24 @@ private void postProcessQuotedBinding(StringBuffer buffer, String valueForBindin while (quotationMark != '\'' && quotationMark != '"') { quotationMarkIndex--; + if (quotationMarkIndex < 0) { throw new IllegalArgumentException("Could not find opening quotes for quoted parameter"); } + quotationMark = buffer.charAt(quotationMarkIndex); } if (valueForBinding.startsWith("{")) { // remove quotation char before the complex object string + buffer.deleteCharAt(quotationMarkIndex); + } else { if (quotationMark == '\'') { buffer.replace(quotationMarkIndex, quotationMarkIndex + 1, "\""); } + buffer.append("\""); } } @@ -220,7 +224,9 @@ private Object evaluateExpression(String expressionString, MongoParameters param private Pattern createReplacementPattern(List bindings) { StringBuilder regex = new StringBuilder(); + for (ParameterBinding binding : bindings) { + regex.append("|"); regex.append(Pattern.quote(binding.getParameter())); regex.append("['\"]?"); // potential quotation char (as in { foo : '?0' }). @@ -238,10 +244,9 @@ private Pattern createReplacementPattern(List bindings) { */ private Placeholder extractPlaceholder(String groupName) { - if (!groupName.endsWith("'") && !groupName.endsWith("\"")) { - return new Placeholder(groupName, false); - } - return new Placeholder(groupName.substring(0, groupName.length() - 1), true); + return !groupName.endsWith("'") && !groupName.endsWith("\"") ? // + Placeholder.of(groupName, false) : // + Placeholder.of(groupName.substring(0, groupName.length() - 1), true); } /** @@ -311,9 +316,11 @@ public MongoParameters getParameters() { private static Map mapBindings(List bindings) { Map map = new LinkedHashMap(bindings.size(), 1); + for (ParameterBinding binding : bindings) { - map.put(new Placeholder(binding.getParameter(), binding.isQuoted()), binding); + map.put(Placeholder.of(binding.getParameter(), binding.isQuoted()), binding); } + return map; } } @@ -324,8 +331,7 @@ private static Map mapBindings(List Date: Mon, 19 Dec 2016 08:24:05 +0100 Subject: [PATCH 055/118] DATAMONGO-1467 - Add support for MongoDB 3.2 partialFilterExpression for index creation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now support partial filter expression on indexes via Index.partial(…). This allows to create partial indexes that only index the documents in a collection that meet a specified filter expression. new Index().named("idx").on("k3y", ASC).partial(filter(where("age").gte(10))) The filter expression can be set via a plain DBObject or a CriteriaDefinition and is mapped against the associated domain type. Original pull request: #431. --- .../mongodb/core/DefaultIndexOperations.java | 97 ++++++++-------- .../data/mongodb/core/MongoTemplate.java | 2 +- .../mongodb/core/index/GeospatialIndex.java | 23 +++- .../data/mongodb/core/index/Index.java | 22 +++- .../data/mongodb/core/index/IndexFilter.java | 36 ++++++ .../data/mongodb/core/index/IndexInfo.java | 81 ++++++++++++- .../core/index/PartialIndexFilter.java | 73 ++++++++++++ .../core/index/TextIndexDefinition.java | 32 +++++- ...efaultIndexOperationsIntegrationTests.java | 108 +++++++++++++++++- 9 files changed, 414 insertions(+), 60 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java index a1f2c96725..a019ed036a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-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. @@ -15,17 +15,15 @@ */ package org.springframework.data.mongodb.core; -import static org.springframework.data.domain.Sort.Direction.*; - import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import org.springframework.dao.DataAccessException; +import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.index.IndexDefinition; -import org.springframework.data.mongodb.core.index.IndexField; import org.springframework.data.mongodb.core.index.IndexInfo; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.util.Assert; import com.mongodb.DBCollection; @@ -42,12 +40,11 @@ */ public class DefaultIndexOperations implements IndexOperations { - private static final Double ONE = Double.valueOf(1); - private static final Double MINUS_ONE = Double.valueOf(-1); - private static final Collection TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere"); - + public static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression"; private final MongoOperations mongoOperations; private final String collectionName; + private final QueryMapper mapper; + private final Class type; /** * Creates a new {@link DefaultIndexOperations}. @@ -56,12 +53,26 @@ public class DefaultIndexOperations implements IndexOperations { * @param collectionName must not be {@literal null}. */ public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName) { + this(mongoOperations, collectionName, null); + } + + /** + * Creates a new {@link DefaultIndexOperations}. + * + * @param mongoOperations must not be {@literal null}. + * @param collectionName must not be {@literal null}. + * @param type Type used for mapping potential partial index filter expression. Can be {@literal null}. + * @since 1.10 + */ + public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName, Class type) { Assert.notNull(mongoOperations, "MongoOperations must not be null!"); Assert.notNull(collectionName, "Collection name can not be null!"); this.mongoOperations = mongoOperations; this.collectionName = collectionName; + this.mapper = new QueryMapper(mongoOperations.getConverter()); + this.type = type; } /* @@ -69,9 +80,20 @@ public DefaultIndexOperations(MongoOperations mongoOperations, String collection * @see org.springframework.data.mongodb.core.IndexOperations#ensureIndex(org.springframework.data.mongodb.core.index.IndexDefinition) */ public void ensureIndex(final IndexDefinition indexDefinition) { + mongoOperations.execute(collectionName, new CollectionCallback() { public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { DBObject indexOptions = indexDefinition.getIndexOptions(); + + if (indexOptions != null && indexOptions.containsField(PARTIAL_FILTER_EXPRESSION_KEY)) { + + Assert.isInstanceOf(DBObject.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY)); + + indexOptions.put(PARTIAL_FILTER_EXPRESSION_KEY, + mapper.getMappedObject((DBObject) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), + lookupPersistentEntity(type, collectionName))); + } + if (indexOptions != null) { collection.createIndex(indexDefinition.getIndexKeys(), indexOptions); } else { @@ -79,6 +101,24 @@ public Object doInCollection(DBCollection collection) throws MongoException, Dat } return null; } + + private MongoPersistentEntity lookupPersistentEntity(Class entityType, String collection) { + + if (entityType != null) { + return mongoOperations.getConverter().getMappingContext().getPersistentEntity(entityType); + } + + Collection> entities = mongoOperations.getConverter().getMappingContext() + .getPersistentEntities(); + + for (MongoPersistentEntity entity : entities) { + if (entity.getCollection().equals(collection)) { + return entity; + } + } + + return null; + } }); } @@ -136,44 +176,7 @@ private List getIndexData(List dbObjectList) { List indexInfoList = new ArrayList(); for (DBObject ix : dbObjectList) { - - DBObject keyDbObject = (DBObject) ix.get("key"); - int numberOfElements = keyDbObject.keySet().size(); - - List indexFields = new ArrayList(numberOfElements); - - for (String key : keyDbObject.keySet()) { - - Object value = keyDbObject.get(key); - - if (TWO_D_IDENTIFIERS.contains(value)) { - indexFields.add(IndexField.geo(key)); - } else if ("text".equals(value)) { - - DBObject weights = (DBObject) ix.get("weights"); - for (String fieldName : weights.keySet()) { - indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString()))); - } - - } else { - - Double keyValue = new Double(value.toString()); - - if (ONE.equals(keyValue)) { - indexFields.add(IndexField.create(key, ASC)); - } else if (MINUS_ONE.equals(keyValue)) { - indexFields.add(IndexField.create(key, DESC)); - } - } - } - - String name = ix.get("name").toString(); - - boolean unique = ix.containsField("unique") ? (Boolean) ix.get("unique") : false; - boolean dropDuplicates = ix.containsField("dropDups") ? (Boolean) ix.get("dropDups") : false; - boolean sparse = ix.containsField("sparse") ? (Boolean) ix.get("sparse") : false; - String language = ix.containsField("default_language") ? (String) ix.get("default_language") : ""; - indexInfoList.add(new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language)); + indexInfoList.add(IndexInfo.indexInfoOf(ix)); } return indexInfoList; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index a126edb269..242563b05e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -557,7 +557,7 @@ public IndexOperations indexOps(String collectionName) { } public IndexOperations indexOps(Class entityClass) { - return new DefaultIndexOperations(this, determineCollectionName(entityClass)); + return new DefaultIndexOperations(this, determineCollectionName(entityClass), entityClass); } public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java index 64d8841a66..e7d585f868 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/GeospatialIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 the original author or authors. + * Copyright 2010-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. @@ -39,6 +39,7 @@ public class GeospatialIndex implements IndexDefinition { private GeoSpatialIndexType type = GeoSpatialIndexType.GEO_2D; private Double bucketSize = 1.0; private String additionalField; + private IndexFilter filter; /** * Creates a new {@link GeospatialIndex} for the given field. @@ -119,6 +120,22 @@ public GeospatialIndex withAdditionalField(String fieldName) { return this; } + + /** + * Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}. + * + * @param filter can be {@literal null}. + * @return + * @see https://docs.mongodb.com/manual/core/index-partial/ + * @since 1.10 + */ + public GeospatialIndex partial(IndexFilter filter) { + + this.filter = filter; + return this; + } + public DBObject getIndexKeys() { DBObject dbo = new BasicDBObject(); @@ -186,6 +203,10 @@ public DBObject getIndexOptions() { break; } + if (filter != null) { + dbo.put("partialFilterExpression", filter.getFilterObject()); + } + return dbo; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java index dbf59f6e2b..29e84f716a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-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. @@ -63,6 +63,8 @@ public enum Duplicates { private long expire = -1; + private IndexFilter filter; + public Index() {} public Index(String key, Direction direction) { @@ -176,6 +178,21 @@ public Index unique(Duplicates duplicates) { return unique(); } + /** + * Only index the documents in a collection that meet a specified {@link IndexFilter filter expression}. + * + * @param filter can be {@literal null}. + * @return + * @see https://docs.mongodb.com/manual/core/index-partial/ + * @since 1.10 + */ + public Index partial(IndexFilter filter) { + + this.filter = filter; + return this; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.index.IndexDefinition#getIndexKeys() @@ -213,6 +230,9 @@ public DBObject getIndexOptions() { dbo.put("expireAfterSeconds", expire); } + if (filter != null) { + dbo.put("partialFilterExpression", filter.getFilterObject()); + } return dbo; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java new file mode 100644 index 0000000000..b2e73627ec --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java @@ -0,0 +1,36 @@ +/* + * 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.index; + +import com.mongodb.DBObject; + +/** + * Use {@link IndexFilter} to create the partial filter expression used when creating + * Partial Indexes. + * + * @author Christoph Strobl + * @since 1.10 + */ +public interface IndexFilter { + + /** + * Get the raw (unmapped) filter expression. + * + * @return + */ + DBObject getFilterObject(); + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java index 2073fc1c28..2fc1e840da 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-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. @@ -15,11 +15,15 @@ */ package org.springframework.data.mongodb.core.index; +import static org.springframework.data.domain.Sort.Direction.*; + import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import com.mongodb.DBObject; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -30,6 +34,10 @@ */ public class IndexInfo { + private static final Double ONE = Double.valueOf(1); + private static final Double MINUS_ONE = Double.valueOf(-1); + private static final Collection TWO_D_IDENTIFIERS = Arrays.asList("2d", "2dsphere"); + private final List indexFields; private final String name; @@ -37,6 +45,7 @@ public class IndexInfo { private final boolean dropDuplicates; private final boolean sparse; private final String language; + private String partialFilterExpression; /** * @deprecated Will be removed in 1.7. Please use {@link #IndexInfo(List, String, boolean, boolean, boolean, String)} @@ -62,6 +71,61 @@ public IndexInfo(List indexFields, String name, boolean unique, bool this.language = language; } + /** + * Creates new {@link IndexInfo} parsing required properties from the given {@literal sourceDocument}. + * + * @param sourceDocument + * @return + * @since 1.10 + */ + public static IndexInfo indexInfoOf(DBObject sourceDocument) { + + DBObject keyDbObject = (DBObject) sourceDocument.get("key"); + int numberOfElements = keyDbObject.keySet().size(); + + List indexFields = new ArrayList(numberOfElements); + + for (String key : keyDbObject.keySet()) { + + Object value = keyDbObject.get(key); + + if (TWO_D_IDENTIFIERS.contains(value)) { + indexFields.add(IndexField.geo(key)); + } else if ("text".equals(value)) { + + DBObject weights = (DBObject) sourceDocument.get("weights"); + for (String fieldName : weights.keySet()) { + indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString()))); + } + + } else { + + Double keyValue = new Double(value.toString()); + + if (ONE.equals(keyValue)) { + indexFields.add(IndexField.create(key, ASC)); + } else if (MINUS_ONE.equals(keyValue)) { + indexFields.add(IndexField.create(key, DESC)); + } + } + } + + String name = sourceDocument.get("name").toString(); + + boolean unique = sourceDocument.containsField("unique") ? (Boolean) sourceDocument.get("unique") : false; + boolean dropDuplicates = sourceDocument.containsField("dropDups") ? (Boolean) sourceDocument.get("dropDups") + : false; + boolean sparse = sourceDocument.containsField("sparse") ? (Boolean) sourceDocument.get("sparse") : false; + String language = sourceDocument.containsField("default_language") ? (String) sourceDocument.get("default_language") + : ""; + String partialFilter = sourceDocument.containsField("partialFilterExpression") + ? sourceDocument.get("partialFilterExpression").toString() : ""; + + IndexInfo info = new IndexInfo(indexFields, name, unique, dropDuplicates, sparse, language); + info.partialFilterExpression = partialFilter; + return info; + } + /** * Returns the individual index fields of the index. * @@ -113,10 +177,19 @@ public String getLanguage() { return language; } + /** + * @return + * @since 1.0 + */ + public String getPartialFilterExpression() { + return partialFilterExpression; + } + @Override public String toString() { return "IndexInfo [indexFields=" + indexFields + ", name=" + name + ", unique=" + unique + ", dropDuplicates=" - + dropDuplicates + ", sparse=" + sparse + ", language=" + language + "]"; + + dropDuplicates + ", sparse=" + sparse + ", language=" + language + ", partialFilterExpression=" + + partialFilterExpression + "]"; } @Override @@ -130,6 +203,7 @@ public int hashCode() { result = prime * result + (sparse ? 1231 : 1237); result = prime * result + (unique ? 1231 : 1237); result = prime * result + ObjectUtils.nullSafeHashCode(language); + result = prime * result + ObjectUtils.nullSafeHashCode(partialFilterExpression); return result; } @@ -171,6 +245,9 @@ public boolean equals(Object obj) { if (!ObjectUtils.nullSafeEquals(language, other.language)) { return false; } + if (!ObjectUtils.nullSafeEquals(partialFilterExpression, other.partialFilterExpression)) { + return false; + } return true; } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java new file mode 100644 index 0000000000..d4a1ed2679 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.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.index; + +import org.springframework.data.mongodb.core.query.CriteriaDefinition; +import org.springframework.util.Assert; + +import com.mongodb.DBObject; + +/** + * {@link IndexFilter} implementation for usage with plain {@link DBObject} as well as {@link CriteriaDefinition} filter + * expressions. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class PartialIndexFilter implements IndexFilter { + + private final Object filterExpression; + + private PartialIndexFilter(Object filterExpression) { + + Assert.notNull(filterExpression, "FilterExpression must not be null!"); + this.filterExpression = filterExpression; + } + + /** + * Create new {@link PartialIndexFilter} for given {@link DBObject filter expression}. + * + * @param where must not be {@literal null}. + * @return + */ + public static PartialIndexFilter filter(DBObject where) { + return new PartialIndexFilter(where); + } + + /** + * Create new {@link PartialIndexFilter} for given {@link CriteriaDefinition filter expression}. + * + * @param where must not be {@literal null}. + * @return + */ + public static PartialIndexFilter filter(CriteriaDefinition where) { + return new PartialIndexFilter(where); + } + + public DBObject getFilterObject() { + + if (filterExpression instanceof DBObject) { + return (DBObject) filterExpression; + } + + if (filterExpression instanceof CriteriaDefinition) { + return ((CriteriaDefinition) filterExpression).getCriteriaObject(); + } + + throw new IllegalArgumentException( + String.format("Unknown type %s used as filter expression.", filterExpression.getClass())); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java index 9fb64be3fb..8e56da94f6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-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. @@ -39,6 +39,7 @@ public class TextIndexDefinition implements IndexDefinition { private Set fieldSpecs; private String defaultLanguage; private String languageOverride; + private IndexFilter filter; TextIndexDefinition() { fieldSpecs = new LinkedHashSet(); @@ -129,6 +130,10 @@ public DBObject getIndexOptions() { options.put("language_override", languageOverride); } + if (filter != null) { + options.put("partialFilterExpression", filter.getFilterObject()); + } + return options; } @@ -288,8 +293,8 @@ public TextIndexDefinitionBuilder onField(String fieldname) { public TextIndexDefinitionBuilder onField(String fieldname, Float weight) { if (this.instance.fieldSpecs.contains(ALL_FIELDS)) { - throw new InvalidDataAccessApiUsageException(String.format("Cannot add %s to field spec for all fields.", - fieldname)); + throw new InvalidDataAccessApiUsageException( + String.format("Cannot add %s to field spec for all fields.", fieldname)); } this.instance.fieldSpecs.add(new TextIndexedFieldSpec(fieldname, weight)); @@ -318,15 +323,30 @@ public TextIndexDefinitionBuilder withDefaultLanguage(String language) { public TextIndexDefinitionBuilder withLanguageOverride(String fieldname) { if (StringUtils.hasText(this.instance.languageOverride)) { - throw new InvalidDataAccessApiUsageException(String.format( - "Cannot set language override on %s as it is already defined on %s.", fieldname, - this.instance.languageOverride)); + throw new InvalidDataAccessApiUsageException( + String.format("Cannot set language override on %s as it is already defined on %s.", fieldname, + this.instance.languageOverride)); } this.instance.languageOverride = fieldname; return this; } + /** + * Only index the documents that meet the specified {@link IndexFilter filter expression}. + * + * @param filter can be {@literal null}. + * @return + * @see https://docs.mongodb.com/manual/core/index-partial/ + * @since 1.10 + */ + public TextIndexDefinitionBuilder partial(IndexFilter filter) { + + this.instance.filter = filter; + return this; + } + public TextIndexDefinition build() { return this.instance; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java index 744387de80..03e10d092f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-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. @@ -17,18 +17,28 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.junit.Assume.*; import static org.springframework.data.mongodb.core.ReflectiveDBCollectionInvoker.*; +import static org.springframework.data.mongodb.core.index.PartialIndexFilter.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.index.Index; +import org.springframework.data.mongodb.core.index.IndexDefinition; import org.springframework.data.mongodb.core.index.IndexInfo; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.util.Version; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBObject; +import com.mongodb.CommandResult; import com.mongodb.DBCollection; import com.mongodb.DBObject; @@ -42,6 +52,9 @@ @ContextConfiguration("classpath:infrastructure.xml") public class DefaultIndexOperationsIntegrationTests { + private static final Version THREE_DOT_TWO = new Version(3, 2); + private static Version mongoVersion; + static final DBObject GEO_SPHERE_2D = new BasicDBObject("loaction", "2dsphere"); @Autowired MongoTemplate template; @@ -51,6 +64,7 @@ public class DefaultIndexOperationsIntegrationTests { @Before public void setUp() { + queryMongoVersionIfNecessary(); String collectionName = this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class); this.collection = this.template.getDb().getCollection(collectionName); @@ -59,6 +73,14 @@ public void setUp() { this.indexOps = new DefaultIndexOperations(template, collectionName); } + private void queryMongoVersionIfNecessary() { + + if (mongoVersion == null) { + CommandResult result = template.executeCommand("{ buildInfo: 1 }"); + mongoVersion = Version.parse(result.get("version").toString()); + } + } + /** * @see DATAMONGO-1008 */ @@ -71,6 +93,78 @@ public void getIndexInfoShouldBeAbleToRead2dsphereIndex() { assertThat(info.getIndexFields().get(0).isGeo(), is(true)); } + /** + * @see DATAMONGO-1467 + */ + @Test + public void shouldApplyPartialFilterCorrectly() { + + assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); + + IndexDefinition id = new Index().named("partial-with-criteria").on("k3y", Direction.ASC) + .partial(filter(where("q-t-y").gte(10))); + + indexOps.ensureIndex(id); + + IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-criteria"); + assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"q-t-y\" : { \"$gte\" : 10}}"))); + } + + /** + * @see DATAMONGO-1467 + */ + @Test + public void shouldApplyPartialFilterWithMappedPropertyCorrectly() { + + assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); + + IndexDefinition id = new Index().named("partial-with-mapped-criteria").on("k3y", Direction.ASC) + .partial(filter(where("quantity").gte(10))); + + indexOps.ensureIndex(id); + + IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-mapped-criteria"); + assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"qty\" : { \"$gte\" : 10}}"))); + } + + /** + * @see DATAMONGO-1467 + */ + @Test + public void shouldApplyPartialDBOFilterCorrectly() { + + assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); + + IndexDefinition id = new Index().named("partial-with-dbo").on("k3y", Direction.ASC) + .partial(filter(new BasicDBObject("qty", new BasicDBObject("$gte", 10)))); + + indexOps.ensureIndex(id); + + IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-dbo"); + assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"qty\" : { \"$gte\" : 10}}"))); + } + + /** + * @see DATAMONGO-1467 + */ + @Test + public void shouldFavorExplicitMappingHintViaClass() { + + assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); + + IndexDefinition id = new Index().named("partial-with-inheritance").on("k3y", Direction.ASC) + .partial(filter(where("age").gte(10))); + + indexOps = new DefaultIndexOperations(template, + this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class), + MappingToSameCollection.class); + + indexOps.ensureIndex(id); + + IndexInfo info = findAndReturnIndexInfo(indexOps.getIndexInfo(), "partial-with-inheritance"); + assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"a_g_e\" : { \"$gte\" : 10}}"))); + } + private IndexInfo findAndReturnIndexInfo(DBObject keys) { return findAndReturnIndexInfo(indexOps.getIndexInfo(), keys); } @@ -89,5 +183,15 @@ private static IndexInfo findAndReturnIndexInfo(Iterable candidates, throw new AssertionError(String.format("Index with %s was not found", name)); } - static class DefaultIndexOperationsIntegrationTestsSample {} + @Document(collection = "default-index-operations-tests") + static class DefaultIndexOperationsIntegrationTestsSample { + + @Field("qty") Integer quantity; + } + + @Document(collection = "default-index-operations-tests") + static class MappingToSameCollection extends DefaultIndexOperationsIntegrationTestsSample { + + @Field("a_g_e") Integer age; + } } From 7914e8a63012f71c8f05cc7c73b0c536fbe02d00 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 19 Dec 2016 19:42:23 +0100 Subject: [PATCH 056/118] DATAMONGO-1467 - Polishing. Original pull request: #431. --- .../mongodb/core/DefaultIndexOperations.java | 5 ++++- .../data/mongodb/core/index/IndexFilter.java | 3 +-- .../data/mongodb/core/index/IndexInfo.java | 6 ++++- .../core/index/PartialIndexFilter.java | 22 ++++++++++--------- ...efaultIndexOperationsIntegrationTests.java | 8 +++---- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java index a019ed036a..f82371c742 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java @@ -40,7 +40,8 @@ */ public class DefaultIndexOperations implements IndexOperations { - public static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression"; + private static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression"; + private final MongoOperations mongoOperations; private final String collectionName; private final QueryMapper mapper; @@ -166,7 +167,9 @@ public Void doInCollection(DBCollection collection) throws MongoException, DataA public List getIndexInfo() { return mongoOperations.execute(collectionName, new CollectionCallback>() { + public List doInCollection(DBCollection collection) throws MongoException, DataAccessException { + List dbObjectList = collection.getIndexInfo(); return getIndexData(dbObjectList); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java index b2e73627ec..1ddf07451d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016. the original author or authors. + * 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. @@ -32,5 +32,4 @@ public interface IndexFilter { * @return */ DBObject getFilterObject(); - } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java index 2fc1e840da..f4d4d6700d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java @@ -23,10 +23,11 @@ import java.util.Collections; import java.util.List; -import com.mongodb.DBObject; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; +import com.mongodb.DBObject; + /** * @author Mark Pollack * @author Oliver Gierke @@ -90,10 +91,13 @@ public static IndexInfo indexInfoOf(DBObject sourceDocument) { Object value = keyDbObject.get(key); if (TWO_D_IDENTIFIERS.contains(value)) { + indexFields.add(IndexField.geo(key)); + } else if ("text".equals(value)) { DBObject weights = (DBObject) sourceDocument.get("weights"); + for (String fieldName : weights.keySet()) { indexFields.add(IndexField.text(fieldName, Float.valueOf(weights.get(fieldName).toString()))); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java index d4a1ed2679..a7245766eb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/PartialIndexFilter.java @@ -15,8 +15,11 @@ */ package org.springframework.data.mongodb.core.index; +import lombok.AccessLevel; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + import org.springframework.data.mongodb.core.query.CriteriaDefinition; -import org.springframework.util.Assert; import com.mongodb.DBObject; @@ -27,15 +30,10 @@ * @author Christoph Strobl * @since 1.10 */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class PartialIndexFilter implements IndexFilter { - private final Object filterExpression; - - private PartialIndexFilter(Object filterExpression) { - - Assert.notNull(filterExpression, "FilterExpression must not be null!"); - this.filterExpression = filterExpression; - } + private final @NonNull Object filterExpression; /** * Create new {@link PartialIndexFilter} for given {@link DBObject filter expression}. @@ -43,7 +41,7 @@ private PartialIndexFilter(Object filterExpression) { * @param where must not be {@literal null}. * @return */ - public static PartialIndexFilter filter(DBObject where) { + public static PartialIndexFilter of(DBObject where) { return new PartialIndexFilter(where); } @@ -53,10 +51,14 @@ public static PartialIndexFilter filter(DBObject where) { * @param where must not be {@literal null}. * @return */ - public static PartialIndexFilter filter(CriteriaDefinition where) { + public static PartialIndexFilter of(CriteriaDefinition where) { return new PartialIndexFilter(where); } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.index.IndexFilter#getFilterObject() + */ public DBObject getFilterObject() { if (filterExpression instanceof DBObject) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java index 03e10d092f..b8ded92d6e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java @@ -102,7 +102,7 @@ public void shouldApplyPartialFilterCorrectly() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); IndexDefinition id = new Index().named("partial-with-criteria").on("k3y", Direction.ASC) - .partial(filter(where("q-t-y").gte(10))); + .partial(of(where("q-t-y").gte(10))); indexOps.ensureIndex(id); @@ -119,7 +119,7 @@ public void shouldApplyPartialFilterWithMappedPropertyCorrectly() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); IndexDefinition id = new Index().named("partial-with-mapped-criteria").on("k3y", Direction.ASC) - .partial(filter(where("quantity").gte(10))); + .partial(of(where("quantity").gte(10))); indexOps.ensureIndex(id); @@ -136,7 +136,7 @@ public void shouldApplyPartialDBOFilterCorrectly() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); IndexDefinition id = new Index().named("partial-with-dbo").on("k3y", Direction.ASC) - .partial(filter(new BasicDBObject("qty", new BasicDBObject("$gte", 10)))); + .partial(of(new BasicDBObject("qty", new BasicDBObject("$gte", 10)))); indexOps.ensureIndex(id); @@ -153,7 +153,7 @@ public void shouldFavorExplicitMappingHintViaClass() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); IndexDefinition id = new Index().named("partial-with-inheritance").on("k3y", Direction.ASC) - .partial(filter(where("age").gte(10))); + .partial(of(where("age").gte(10))); indexOps = new DefaultIndexOperations(template, this.template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class), From 982adf317eaa45c488f2f1d18594c84e7a9be77b Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 21 Dec 2016 16:15:39 +0100 Subject: [PATCH 057/118] DATAMONGO-1469 - Updated changelog. --- src/main/resources/changelog.txt | 54 ++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 72b5907c92..d2dcc89b53 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,60 @@ Spring Data MongoDB Changelog ============================= +Changes in version 1.10.0.RC1 (2016-12-21) +------------------------------------------ +* DATAMONGO-1567 - Upgrade to a newer JDK version on TravisCI. +* DATAMONGO-1566 - Adapt API in RepositoryFactoryBeanSupport implementation. +* DATAMONGO-1565 - Placeholders in manually defined queries not escaped properly. +* DATAMONGO-1564 - Split up AggregationExpressions. +* DATAMONGO-1558 - Upgrade travis-ci profile to MongoDB 3.4. +* DATAMONGO-1552 - Add $facet, $bucket and $bucketAuto aggregation stages. +* DATAMONGO-1551 - Add $graphLookup aggregation stage. +* DATAMONGO-1550 - Add $replaceRoot aggregation stage. +* DATAMONGO-1549 - Add $count aggregation stage. +* DATAMONGO-1548 - Add new MongoDB 3.4 aggregation operators. +* DATAMONGO-1547 - Register repository factory in spring.factories for multi-store support. +* DATAMONGO-1546 - Switch to new way of registering custom Jackson modules. +* DATAMONGO-1542 - Refactor CondOperator and IfNullOperator to children of AggregationExpressions. +* DATAMONGO-1540 - Add support for $map to aggregation. +* DATAMONGO-1539 - Add dedicated annotations for manually declared count and delete queries. +* DATAMONGO-1538 - Add support for $let to aggregation. +* DATAMONGO-1536 - Add missing aggregation operators. +* DATAMONGO-1534 - Type hint is missing when using BulkOperations.insert. +* DATAMONGO-1533 - Add support for SpEL in GroupOperations (aggregation). +* DATAMONGO-1530 - Support missing aggregation pipeline operators in expression support. +* DATAMONGO-1525 - Reading empty EnumSet fails. +* DATAMONGO-1521 - Aggregation.skip(...) expects int but new SkipOperation(...) supports long. +* DATAMONGO-1520 - Aggregation.match should accept CriteriaDefinition. +* DATAMONGO-1514 - SpringDataMongodbQuery should be public. +* DATAMONGO-1513 - Non-ObjectId identifiers generated by event listeners are not populated if documents are inserted as batch. +* DATAMONGO-1504 - Assert compatibility with MongoDB 3.4 server and driver. +* DATAMONGO-1500 - RuntimeException for query methods with fields declaration and Pageable parameters. +* DATAMONGO-1498 - MongoMappingContext doesn't know about types usually auto-detected (JodaTime, JDK 8 date time types). +* DATAMONGO-1493 - Typos in reference documentation. +* DATAMONGO-1492 - Interface AggregationExpression in package org.springframework.data.mongodb.core.aggregation should be public. +* DATAMONGO-1491 - Add support for $filter to aggregation. +* DATAMONGO-1490 - Change the XML data type of boolean flags to String. +* DATAMONGO-1486 - Changes to MappingMongoConverter Result in Class Cast Exception. +* DATAMONGO-1485 - Querydsl MongodbSerializer does not take registered converters for Enums into account. +* DATAMONGO-1480 - Add support for noCursorTimeout in Query. +* DATAMONGO-1479 - MappingMongoConverter.convertToMongoType causes StackOverflowError for parameterized map value types. +* DATAMONGO-1476 - New stream method only partially makes use of collection name. +* DATAMONGO-1471 - MappingMongoConverter attempts to set null value on potentially primitive identifier. +* DATAMONGO-1470 - AbstractMongoConfiguraton should allow multiple base package for @Document scanning. +* DATAMONGO-1469 - Release 1.10 RC1 (Ingalls). +* DATAMONGO-1467 - Support partial filter expressions for indexing introduced in MongoDB 3.2. +* DATAMONGO-1465 - String arguments passed to DefaultScriptOperations.execute() appear quoted in script. +* DATAMONGO-1454 - Add support for exists projection in repository query derivation. +* DATAMONGO-1406 - Query mapper does not use @Field field name when querying nested fields in combination with nested keywords. +* DATAMONGO-1328 - Add support for mongodb 3.2 specific arithmetic operators to aggregation. +* DATAMONGO-1327 - Add support for $stdDevSamp and $stdDevPop to aggregation ($group stage). +* DATAMONGO-1299 - Add support for date aggregations. +* DATAMONGO-1141 - Add support for $push $sort in Update. +* DATAMONGO-861 - Add support for $cond and $ifNull operators in aggregation operation. +* DATAMONGO-784 - Add support for $cmp in group or project aggregation. + + Changes in version 2.0.0.M1 (2016-11-23) ---------------------------------------- * DATAMONGO-1527 - Release 2.0 M1 (Kay). From 408c5d86840871c9eb6277544e917b8e6ea2b6ef Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 21 Dec 2016 16:15:47 +0100 Subject: [PATCH 058/118] DATAMONGO-1469 - Prepare 1.10 RC1 (Ingalls). --- pom.xml | 8 ++++---- src/main/resources/notice.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index ed9720988d..0d4c7959da 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ org.springframework.data.build spring-data-parent - 1.9.0.BUILD-SNAPSHOT + 1.9.0.RC1 @@ -28,7 +28,7 @@ multi spring-data-mongodb - 1.13.0.BUILD-SNAPSHOT + 1.13.0.RC1 2.14.3 2.13.0 @@ -229,8 +229,8 @@ - spring-libs-snapshot - https://repo.spring.io/libs-snapshot + spring-libs-milestone + https://repo.spring.io/libs-milestone diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt index 5bc8d4cc27..b9dbc9eb86 100644 --- a/src/main/resources/notice.txt +++ b/src/main/resources/notice.txt @@ -1,4 +1,4 @@ -Spring Data MongoDB 1.10 M1 +Spring Data MongoDB 1.10 RC1 Copyright (c) [2010-2015] Pivotal Software, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License"). From 737f7b4f304d777fed14a6065f02267bf467c403 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 21 Dec 2016 16:16:51 +0100 Subject: [PATCH 059/118] DATAMONGO-1469 - Release version 1.10 RC1 (Ingalls). --- 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 0d4c7959da..18aba7943e 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.RC1 pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index ae0a5d6c8f..ce0f0a21c8 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.RC1 ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.10.0.BUILD-SNAPSHOT + 1.10.0.RC1 diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 2d02722262..c4a21b9b2f 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.RC1 ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index ee5e3336db..78c78666ea 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.RC1 ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 8072d3f665..35abb7f47b 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.RC1 ../pom.xml From e55e748cfd741117626a82cb0968dc47b8db00cf Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 21 Dec 2016 16:33:10 +0100 Subject: [PATCH 060/118] DATAMONGO-1469 - Prepare next development iteration. --- 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 18aba7943e..0d4c7959da 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.RC1 + 1.10.0.BUILD-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index ce0f0a21c8..ae0a5d6c8f 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.RC1 + 1.10.0.BUILD-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.10.0.RC1 + 1.10.0.BUILD-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index c4a21b9b2f..2d02722262 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.RC1 + 1.10.0.BUILD-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index 78c78666ea..ee5e3336db 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.RC1 + 1.10.0.BUILD-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 35abb7f47b..8072d3f665 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.RC1 + 1.10.0.BUILD-SNAPSHOT ../pom.xml From 320a28740a79cee8af6618a20260374c756bced6 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 21 Dec 2016 16:33:13 +0100 Subject: [PATCH 061/118] DATAMONGO-1469 - After release cleanups. --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 0d4c7959da..ed9720988d 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ org.springframework.data.build spring-data-parent - 1.9.0.RC1 + 1.9.0.BUILD-SNAPSHOT @@ -28,7 +28,7 @@ multi spring-data-mongodb - 1.13.0.RC1 + 1.13.0.BUILD-SNAPSHOT 2.14.3 2.13.0 @@ -229,8 +229,8 @@ - spring-libs-milestone - https://repo.spring.io/libs-milestone + spring-libs-snapshot + https://repo.spring.io/libs-snapshot From a96752da803de400bf6643dbb124157aa2543172 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 21 Dec 2016 19:03:32 +0100 Subject: [PATCH 062/118] DATAMONGO-1522 - Updated changelog. --- src/main/resources/changelog.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index d2dcc89b53..017db12244 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,14 @@ Spring Data MongoDB Changelog ============================= +Changes in version 1.9.6.RELEASE (2016-12-21) +--------------------------------------------- +* DATAMONGO-1565 - Placeholders in manually defined queries not escaped properly. +* DATAMONGO-1534 - Type hint is missing when using BulkOperations.insert. +* DATAMONGO-1525 - Reading empty EnumSet fails. +* DATAMONGO-1522 - Release 1.9.6 (Hopper SR6). + + Changes in version 1.10.0.RC1 (2016-12-21) ------------------------------------------ * DATAMONGO-1567 - Upgrade to a newer JDK version on TravisCI. From fda72d6eb23db53627ec25f5bea3244bd05a798a Mon Sep 17 00:00:00 2001 From: John Lilley Date: Mon, 10 Oct 2016 08:40:51 -0600 Subject: [PATCH 063/118] DATAMONGO-1508 - Document authentication-dbname attribute in db-factory. Original pull request: #399. --- src/main/asciidoc/reference/mongodb.adoc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 6a49c3f114..230c4cabd5 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -387,6 +387,20 @@ You can also provide the host and port for the underlying `com.mongodb.Mongo` in password="secret"/> ---- +If your mongodb authentication database differs from the target database, use the authentication-dbname attribute, as shown below. + +[source,xml] +---- + +---- + If you need to configure additional options on the `com.mongodb.Mongo` instance that is used to create a `SimpleMongoDbFactory` you can refer to an existing bean using the `mongo-ref` attribute as shown below. To show another common usage pattern, this listing shows the use of a property placeholder to parametrise the configuration and creating `MongoTemplate`. [source,xml] From e784e58a0a79190c43027084ae4e73549c60f3e9 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 2 Jan 2017 10:42:03 +0100 Subject: [PATCH 064/118] DATAMONGO-1508 - Polishing. Highlight attribute name. Replace tabs with spaces. Original pull request: #399. --- src/main/asciidoc/reference/mongodb.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 230c4cabd5..963606d913 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -387,7 +387,7 @@ You can also provide the host and port for the underlying `com.mongodb.Mongo` in password="secret"/> ---- -If your mongodb authentication database differs from the target database, use the authentication-dbname attribute, as shown below. +If your MongoDB authentication database differs from the target database, use the `authentication-dbname` attribute, as shown below. [source,xml] ---- @@ -397,8 +397,8 @@ If your mongodb authentication database differs from the target database, use th dbname="database" username="joe" password="secret" - authentication-dbname="admin" - /> + authentication-dbname="admin" + /> ---- If you need to configure additional options on the `com.mongodb.Mongo` instance that is used to create a `SimpleMongoDbFactory` you can refer to an existing bean using the `mongo-ref` attribute as shown below. To show another common usage pattern, this listing shows the use of a property placeholder to parametrise the configuration and creating `MongoTemplate`. From e83866253635ddf9e4e20ffe8cd8c889cf1476bc Mon Sep 17 00:00:00 2001 From: Ken Dombeck Date: Thu, 8 Dec 2016 09:20:21 -0600 Subject: [PATCH 065/118] DATAMONGO-1577 - Fix Reference and JavaDoc spelling issues. Replaced invalid class name MongoMappingConverter with actual class name of MappingMongoConverter. Fix typos. Original pull request: #425. --- .../data/mongodb/core/MongoTemplate.java | 86 +++++++++---------- .../core/convert/MappingMongoConverter.java | 14 +-- src/main/asciidoc/reference/mapping.adoc | 12 +-- src/main/asciidoc/reference/mongodb.adoc | 8 +- 4 files changed, 60 insertions(+), 60 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 242563b05e..f0b4eb540a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -126,7 +126,7 @@ /** * Primary implementation of {@link MongoOperations}. - * + * * @author Thomas Risberg * @author Graeme Rocher * @author Mark Pollack @@ -177,7 +177,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { /** * Constructor used for a basic template configuration - * + * * @param mongo must not be {@literal null}. * @param databaseName must not be {@literal null} or empty. */ @@ -188,7 +188,7 @@ public MongoTemplate(Mongo mongo, String databaseName) { /** * Constructor used for a template configuration with user credentials in the form of * {@link org.springframework.data.authentication.UserCredentials} - * + * * @param mongo must not be {@literal null}. * @param databaseName must not be {@literal null} or empty. * @param userCredentials @@ -199,7 +199,7 @@ public MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCrede /** * Constructor used for a basic template configuration. - * + * * @param mongoDbFactory must not be {@literal null}. */ public MongoTemplate(MongoDbFactory mongoDbFactory) { @@ -208,7 +208,7 @@ public MongoTemplate(MongoDbFactory mongoDbFactory) { /** * Constructor used for a basic template configuration. - * + * * @param mongoDbFactory must not be {@literal null}. * @param mongoConverter */ @@ -237,7 +237,7 @@ public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverte /** * Configures the {@link WriteResultChecking} to be used with the template. Setting {@literal null} will reset the * default of {@value #DEFAULT_WRITE_RESULT_CHECKING}. - * + * * @param resultChecking */ public void setWriteResultChecking(WriteResultChecking resultChecking) { @@ -248,7 +248,7 @@ public void setWriteResultChecking(WriteResultChecking resultChecking) { * Configures the {@link WriteConcern} to be used with the template. If none is configured the {@link WriteConcern} * configured on the {@link MongoDbFactory} will apply. If you configured a {@link Mongo} instance no * {@link WriteConcern} will be used. - * + * * @param writeConcern */ public void setWriteConcern(WriteConcern writeConcern) { @@ -257,7 +257,7 @@ public void setWriteConcern(WriteConcern writeConcern) { /** * Configures the {@link WriteConcernResolver} to be used with the template. - * + * * @param writeConcernResolver */ public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) { @@ -267,7 +267,7 @@ public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) { /** * Used by @{link {@link #prepareCollection(DBCollection)} to set the {@link ReadPreference} before any operations are * performed. - * + * * @param readPreference */ public void setReadPreference(ReadPreference readPreference) { @@ -294,7 +294,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws * they were registered for the current {@link MappingContext}. If no creator for the current {@link MappingContext} * can be found we manually add the internally created one as {@link ApplicationListener} to make sure indexes get * created appropriately for entity types persisted through this {@link MongoTemplate} instance. - * + * * @param context must not be {@literal null}. */ private void prepareIndexCreator(ApplicationContext context) { @@ -314,15 +314,15 @@ private void prepareIndexCreator(ApplicationContext context) { } /** - * Returns the default {@link org.springframework.data.mongodb.core.core.convert.MongoConverter}. - * + * Returns the default {@link org.springframework.data.mongodb.core.convert.MongoConverter}. + * * @return */ public MongoConverter getConverter() { return this.mongoConverter; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#executeAsStream(org.springframework.data.mongodb.core.query.Query, java.lang.Class) */ @@ -332,7 +332,7 @@ public CloseableIterator stream(final Query query, final Class entityT return stream(query, entityType, determineCollectionName(entityType)); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#stream(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ @@ -428,7 +428,7 @@ public void executeQuery(Query query, String collectionName, DocumentCallbackHan /** * Execute a MongoDB query and iterate over the query results on a per-document basis with a * {@link DocumentCallbackHandler} using the provided CursorPreparer. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields * specification, must not be {@literal null}. * @param collectionName name of the collection to retrieve the objects from @@ -699,7 +699,7 @@ public GeoResults geoNear(NearQuery near, Class entityClass, String co /* * As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries * we skip the elements ourselves to avoid at least the document 2 object mapping overhead. - * + * * @see https://jira.mongodb.org/browse/SERVER-3925 */ if (index >= elementsToSkip) { @@ -804,7 +804,7 @@ protected void ensureNotIterable(Object o) { /** * Prepare the collection before any processing is done using it. This allows a convenient way to apply settings like * slaveOk() etc. Can be overridden in sub-classes. - * + * * @param collection */ protected void prepareCollection(DBCollection collection) { @@ -818,7 +818,7 @@ protected void prepareCollection(DBCollection collection) { * settings in sub-classes.
* In case of using MongoDB Java driver version 3 the returned {@link WriteConcern} will be defaulted to * {@link WriteConcern#ACKNOWLEDGED} when {@link WriteResultChecking} is set to {@link WriteResultChecking#EXCEPTION}. - * + * * @param writeConcern any WriteConcern already configured or null * @return The prepared WriteConcern or null */ @@ -1228,7 +1228,7 @@ public WriteResult remove(Object object, String collection) { /** * Returns {@link Entry} containing the field name of the id property as {@link Entry#getKey()} and the {@link Id}s * property value as its {@link Entry#getValue()}. - * + * * @param object * @return */ @@ -1255,7 +1255,7 @@ private Entry extractIdPropertyAndValue(Object object) { /** * Returns a {@link Query} for the given entity by its id. - * + * * @param object must not be {@literal null}. * @return */ @@ -1267,7 +1267,7 @@ private Query getIdQueryFor(Object object) { /** * Returns a {@link Query} for the given entities by their ids. - * + * * @param objects must not be {@literal null} or {@literal empty}. * @return */ @@ -1538,7 +1538,7 @@ public List findAllAndRemove(Query query, Class entityClass, String co * Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)} * and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is * constructed out of the find result. - * + * * @param collectionName * @param query * @param entityClass @@ -1578,7 +1578,7 @@ protected AggregationResults aggregate(Aggregation aggregation, String co /** * Returns the potentially mapped results of the given {@commandResult} contained some. - * + * * @param outputType * @param commandResult * @return @@ -1690,7 +1690,7 @@ protected void maybeEmitEvent(MongoMappingEvent event) { /** * Create the specified collection using the provided options - * + * * @param collectionName * @param collectionOptions * @return the collection that was created @@ -1711,7 +1711,7 @@ public DBCollection doInDB(DB db) throws MongoException, DataAccessException { /** * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. * The query document is specified as a standard {@link DBObject} and so is the fields specification. - * + * * @param collectionName name of the collection to retrieve the objects from. * @param query the query document that specifies the criteria used to find a record. * @param fields the document that specifies the fields to be returned. @@ -1736,7 +1736,7 @@ protected T doFindOne(String collectionName, DBObject query, DBObject fields /** * Map the results of an ad-hoc query on the default MongoDB collection to a List using the template's converter. The * query document is specified as a standard DBObject and so is the fields specification. - * + * * @param collectionName name of the collection to retrieve the objects from * @param query the query document that specifies the criteria used to find a record * @param fields the document that specifies the fields to be returned @@ -1752,7 +1752,7 @@ protected List doFind(String collectionName, DBObject query, DBObject fie * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified type. The object is * converted from the MongoDB native representation using an instance of {@see MongoConverter}. The query document is * specified as a standard DBObject and so is the fields specification. - * + * * @param collectionName name of the collection to retrieve the objects from. * @param query the query document that specifies the criteria used to find a record. * @param fields the document that specifies the fields to be returned. @@ -1805,7 +1805,7 @@ protected DBObject convertToDbObject(CollectionOptions collectionOptions) { * The first document that matches the query is returned and also removed from the collection in the database. *

* The query document is specified as a standard DBObject and so is the fields specification. - * + * * @param collectionName name of the collection to retrieve the objects from * @param query the query document that specifies the criteria used to find a record * @param entityClass the parameterized type of the returned list. @@ -1856,7 +1856,7 @@ protected T doFindAndModify(String collectionName, DBObject query, DBObject /** * Populates the id property of the saved object, if it's not set already. - * + * * @param savedObject * @param id */ @@ -1906,7 +1906,7 @@ private DBCollection getAndPrepareCollection(DB db, String collectionName) { *

  • Execute the given {@link ConnectionCallback} for a {@link DBObject}.
  • *
  • Apply the given {@link DbObjectCallback} to each of the {@link DBObject}s to obtain the result.
  • *
      - * + * * @param * @param collectionCallback the callback to retrieve the {@link DBObject} with * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type @@ -1935,7 +1935,7 @@ private T executeFindOneInternal(CollectionCallback collectionCall *
    1. Iterate over the {@link DBCursor} and applies the given {@link DbObjectCallback} to each of the * {@link DBObject}s collecting the actual result {@link List}.
    2. *
        - * + * * @param * @param collectionCallback the callback to retrieve the {@link DBCursor} with * @param preparer the {@link CursorPreparer} to potentially modify the {@link DBCursor} before ireating over it @@ -2042,7 +2042,7 @@ String determineCollectionName(Class entityClass) { /** * Handles {@link WriteResult} errors based on the configured {@link WriteResultChecking}. - * + * * @param writeResult * @param query * @param operation @@ -2086,7 +2086,7 @@ protected void handleAnyWriteResultErrors(WriteResult writeResult, DBObject quer /** * Inspects the given {@link CommandResult} for erros and potentially throws an * {@link InvalidDataAccessApiUsageException} for that error. - * + * * @param result must not be {@literal null}. * @param source must not be {@literal null}. */ @@ -2124,7 +2124,7 @@ private DBObject getMappedSortObject(Query query, Class type) { /** * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original * exception if the conversation failed. Thus allows safe re-throwing of the return value. - * + * * @param ex the exception to translate * @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation * @return @@ -2162,7 +2162,7 @@ private static List consolidateIdentifiers(List ids, List { @@ -2272,7 +2272,7 @@ public DBObject doInCollection(DBCollection collection) throws MongoException, D /** * Simple internal callback to allow operations on a {@link DBObject}. - * + * * @author Oliver Gierke * @author Thomas Darimont */ @@ -2285,7 +2285,7 @@ interface DbObjectCallback { /** * Simple {@link DbObjectCallback} that will transform {@link DBObject} into the given target type using the given * {@link MongoReader}. - * + * * @author Oliver Gierke * @author Christoph Strobl */ @@ -2431,7 +2431,7 @@ public DBCursor prepare(DBCursor cursor) { /** * {@link DbObjectCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to * a delegate and creates a {@link GeoResult} from the result. - * + * * @author Oliver Gierke */ static class GeoNearResultDbObjectCallback implements DbObjectCallback> { @@ -2442,7 +2442,7 @@ static class GeoNearResultDbObjectCallback implements DbObjectCallback delegate, Metric metric) { @@ -2464,7 +2464,7 @@ public GeoResult doWith(DBObject object) { /** * A {@link CloseableIterator} that is backed by a MongoDB {@link Cursor}. - * + * * @since 1.7 * @author Thomas Darimont */ @@ -2476,7 +2476,7 @@ static class CloseableIterableCursorAdapter implements CloseableIterator { /** * Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link Cursor}. - * + * * @param cursor * @param exceptionTranslator * @param objectReadCallback diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 4ffd911c3f..7047995a3b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -101,7 +101,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App /** * Creates a new {@link MappingMongoConverter} given the new {@link DbRefResolver} and {@link MappingContext}. * - * @param mongoDbFactory must not be {@literal null}. + * @param dbRefResolver must not be {@literal null}. * @param mappingContext must not be {@literal null}. */ public MappingMongoConverter(DbRefResolver dbRefResolver, @@ -319,12 +319,12 @@ public void doWithAssociation(Association association) * (non-Javadoc) * @see org.springframework.data.mongodb.core.convert.MongoWriter#toDBRef(java.lang.Object, org.springframework.data.mongodb.core.mapping.MongoPersistentProperty) */ - public DBRef toDBRef(Object object, MongoPersistentProperty referingProperty) { + public DBRef toDBRef(Object object, MongoPersistentProperty referringProperty) { org.springframework.data.mongodb.core.mapping.DBRef annotation = null; - if (referingProperty != null) { - annotation = referingProperty.getDBRef(); + if (referringProperty != null) { + annotation = referringProperty.getDBRef(); Assert.isTrue(annotation != null, "The referenced property has to be mapped with @DBRef!"); } @@ -333,14 +333,14 @@ public DBRef toDBRef(Object object, MongoPersistentProperty referingProperty) { return ((LazyLoadingProxy) object).toDBRef(); } - return createDBRef(object, referingProperty); + return createDBRef(object, referringProperty); } /** * Root entry method into write conversion. Adds a type discriminator to the {@link DBObject}. Shouldn't be called for * nested conversions. * - * @see org.springframework.data.mongodb.core.core.convert.MongoWriter#write(java.lang.Object, com.mongodb.DBObject) + * @see org.springframework.data.mongodb.core.convert.MongoWriter#write(java.lang.Object, com.mongodb.DBObject) */ public void write(final Object obj, final DBObject dbo) { diff --git a/src/main/asciidoc/reference/mapping.adoc b/src/main/asciidoc/reference/mapping.adoc index e3912b4772..d0184a638a 100644 --- a/src/main/asciidoc/reference/mapping.adoc +++ b/src/main/asciidoc/reference/mapping.adoc @@ -1,16 +1,16 @@ [[mapping-chapter]] = Mapping -Rich mapping support is provided by the `MongoMappingConverter`. `MongoMappingConverter` has a rich metadata model that provides a full feature set of functionality to map domain objects to MongoDB documents.The mapping metadata model is populated using annotations on your domain objects. However, the infrastructure is not limited to using annotations as the only source of metadata information. The `MongoMappingConverter` also allows you to map objects to documents without providing any additional metadata, by following a set of conventions. +Rich mapping support is provided by the `MappingMongoConverter`. `MappingMongoConverter` has a rich metadata model that provides a full feature set of functionality to map domain objects to MongoDB documents.The mapping metadata model is populated using annotations on your domain objects. However, the infrastructure is not limited to using annotations as the only source of metadata information. The `MappingMongoConverter` also allows you to map objects to documents without providing any additional metadata, by following a set of conventions. -In this section we will describe the features of the `MongoMappingConverter`. How to use conventions for mapping objects to documents and how to override those conventions with annotation based mapping metadata. +In this section we will describe the features of the `MappingMongoConverter`. How to use conventions for mapping objects to documents and how to override those conventions with annotation based mapping metadata. NOTE: `SimpleMongoConverter` has been deprecated in Spring Data MongoDB M3 as all of its functionality has been subsumed into `MappingMongoConverter`. [[mapping-conventions]] == Convention based Mapping -`MongoMappingConverter` has a few conventions for mapping objects to documents when no additional mapping metadata is provided. The conventions are: +`MappingMongoConverter` has a few conventions for mapping objects to documents when no additional mapping metadata is provided. The conventions are: * The short Java class name is mapped to the collection name in the following manner. The class `com.bigbank.SavingsAccount` maps to `savingsAccount` collection name. * All nested objects are stored as nested objects in the document and *not* as DBRefs @@ -21,7 +21,7 @@ NOTE: `SimpleMongoConverter` has been deprecated in Spring Data MongoDB M3 as al [[mapping.conventions.id-field]] === How the `_id` field is handled in the mapping layer -MongoDB requires that you have an `_id` field for all documents. If you don't provide one the driver will assign a ObjectId with a generated value. The "_id" field can be of any type the, other than arrays, so long as it is unique. The driver naturally supports all primitive types and Dates. When using the `MongoMappingConverter` there are certain rules that govern how properties from the Java class is mapped to this `_id` field. +MongoDB requires that you have an `_id` field for all documents. If you don't provide one the driver will assign a ObjectId with a generated value. The "_id" field can be of any type the, other than arrays, so long as it is unique. The driver naturally supports all primitive types and Dates. When using the `MappingMongoConverter` there are certain rules that govern how properties from the Java class is mapped to this `_id` field. The following outlines what field will be mapped to the `_id` document field: @@ -246,9 +246,9 @@ calling `get()` before the actual conversion [[mapping-configuration]] == Mapping Configuration -Unless explicitly configured, an instance of `MongoMappingConverter` is created by default when creating a `MongoTemplate`. You can create your own instance of the `MappingMongoConverter` so as to tell it where to scan the classpath at startup your domain classes in order to extract metadata and construct indexes. Also, by creating your own instance you can register Spring converters to use for mapping specific classes to and from the database. +Unless explicitly configured, an instance of `MappingMongoConverter` is created by default when creating a `MongoTemplate`. You can create your own instance of the `MappingMongoConverter` so as to tell it where to scan the classpath at startup your domain classes in order to extract metadata and construct indexes. Also, by creating your own instance you can register Spring converters to use for mapping specific classes to and from the database. -You can configure the `MongoMappingConverter` as well as `com.mongodb.Mongo` and MongoTemplate either using Java or XML based metadata. Here is an example using Spring's Java based configuration +You can configure the `MappingMongoConverter` as well as `com.mongodb.Mongo` and MongoTemplate either using Java or XML based metadata. Here is an example using Spring's Java based configuration .@Configuration class to configure MongoDB mapping support ==== diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 963606d913..6155a18abc 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -436,15 +436,15 @@ The class `MongoTemplate`, located in the package `org.springframework.data.mong NOTE: Once configured, `MongoTemplate` is thread-safe and can be reused across multiple instances. -The mapping between MongoDB documents and domain classes is done by delegating to an implementation of the interface `MongoConverter`. Spring provides two implementations, `SimpleMappingConverter` and `MongoMappingConverter`, but you can also write your own converter. Please refer to the section on MongoConverters for more detailed information. +The mapping between MongoDB documents and domain classes is done by delegating to an implementation of the interface `MongoConverter`. Spring provides two implementations, `SimpleMappingConverter` and `MappingMongoConverter`, but you can also write your own converter. Please refer to the section on MongoConverters for more detailed information. The `MongoTemplate` class implements the interface `MongoOperations`. In as much as possible, the methods on `MongoOperations` are named after methods available on the MongoDB driver `Collection` object to make the API familiar to existing MongoDB developers who are used to the driver API. For example, you will find methods such as "find", "findAndModify", "findOne", "insert", "remove", "save", "update" and "updateMulti". The design goal was to make it as easy as possible to transition between the use of the base MongoDB driver and `MongoOperations`. A major difference in between the two APIs is that MongoOperations can be passed domain objects instead of `DBObject` and there are fluent APIs for `Query`, `Criteria`, and `Update` operations instead of populating a `DBObject` to specify the parameters for those operations. NOTE: The preferred way to reference the operations on `MongoTemplate` instance is via its interface `MongoOperations`. -The default converter implementation used by `MongoTemplate` is MongoMappingConverter. While the `MongoMappingConverter` can make use of additional metadata to specify the mapping of objects to documents it is also capable of converting objects that contain no additional metadata by using some conventions for the mapping of IDs and collection names. These conventions as well as the use of mapping annotations is explained in the <>. +The default converter implementation used by `MongoTemplate` is MappingMongoConverter. While the `MappingMongoConverter` can make use of additional metadata to specify the mapping of objects to documents it is also capable of converting objects that contain no additional metadata by using some conventions for the mapping of IDs and collection names. These conventions as well as the use of mapping annotations is explained in the <>. -NOTE: In the M2 release `SimpleMappingConverter`, was the default and this class is now deprecated as its functionality has been subsumed by the `MongoMappingConverter`. +NOTE: In the M2 release `SimpleMappingConverter`, was the default and this class is now deprecated as its functionality has been subsumed by the `MappingMongoConverter`. Another central feature of MongoTemplate is exception translation of exceptions thrown in the MongoDB Java driver into Spring's portable Data Access Exception hierarchy. Refer to the section on <> for more information. @@ -659,7 +659,7 @@ The query syntax used in the example is explained in more detail in the section [[mongo-template.id-handling]] === How the `_id` field is handled in the mapping layer -MongoDB requires that you have an `_id` field for all documents. If you don't provide one the driver will assign a `ObjectId` with a generated value. When using the `MongoMappingConverter` there are certain rules that govern how properties from the Java class is mapped to this `_id` field. +MongoDB requires that you have an `_id` field for all documents. If you don't provide one the driver will assign a `ObjectId` with a generated value. When using the `MappingMongoConverter` there are certain rules that govern how properties from the Java class is mapped to this `_id` field. The following outlines what property will be mapped to the `_id` document field: From 8149273df9cf2b3213f4714d8c2227ee79834c77 Mon Sep 17 00:00:00 2001 From: Lukasz Kryger Date: Tue, 15 Nov 2016 10:15:55 +0100 Subject: [PATCH 066/118] DATAMONGO-1577 - Fix wording repetition in MongoRepository JavaDoc. Original pull request: #407. --- .../data/mongodb/repository/MongoRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java index 5361f3d6c6..35b3e6e31c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/MongoRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 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. @@ -58,7 +58,7 @@ public interface MongoRepository List findAll(Sort sort); /** - * Inserts the given a given entity. Assumes the instance to be new to be able to apply insertion optimizations. Use + * Inserts the given entity. Assumes the instance to be new to be able to apply insertion optimizations. Use * the returned instance for further operations as the save operation might have changed the entity instance * completely. Prefer using {@link #save(Object)} instead to avoid the usage of store-specific API. * From 97a03d824ab5ca5708247cf976f136ed6f67497f Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 2 Jan 2017 11:09:48 +0100 Subject: [PATCH 067/118] DATAMONGO-1508 - Improve reference documentation. Replace Spring Data Document with Spring Data MongoDB. Extend copyright year range. Replace static Spring version leftover with variable. Fix typos. --- src/main/asciidoc/index.adoc | 2 +- src/main/asciidoc/preface.adoc | 12 ++++++------ src/main/asciidoc/reference/introduction.adoc | 2 +- src/main/asciidoc/reference/mongo-3.adoc | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index aee79f8aed..74b274a81d 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -6,7 +6,7 @@ Mark Pollack; Thomas Risberg; Oliver Gierke; Costin Leau; Jon Brisbin; Thomas Da :toc-placement!: :spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc -(C) 2008-2015 The original authors. +(C) 2008-2017 The original authors. NOTE: _Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically._ diff --git a/src/main/asciidoc/preface.adoc b/src/main/asciidoc/preface.adoc index 8efc6f2d27..e8dd0f5b56 100644 --- a/src/main/asciidoc/preface.adoc +++ b/src/main/asciidoc/preface.adoc @@ -5,19 +5,19 @@ The Spring Data MongoDB project applies core Spring concepts to the development This document is the reference guide for Spring Data - Document Support. It explains Document module concepts and semantics and the syntax for various store namespaces. -This section provides some basic introduction to Spring and Document database. The rest of the document refers only to Spring Data Document features and assumes the user is familiar with document databases such as MongoDB and CouchDB as well as Spring concepts. +This section provides some basic introduction to Spring and Document databases. The rest of the document refers only to Spring Data MongoDB features and assumes the user is familiar with MongoDB and Spring concepts. [[get-started:first-steps:spring]] == Knowing Spring -Spring Data uses Spring framework's http://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/spring-core.html[core] functionality, such as the http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/beans.html[IoC] container, http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/validation.html#core-convert[type conversion system], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/expressions.html[expression language], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/jmx.html[JMX integration], and portable http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html#dao-exceptions[DAO exception hierarchy]. While it is not important to know the Spring APIs, understanding the concepts behind them is. At a minimum, the idea behind IoC should be familiar for whatever IoC container you choose to use. +Spring Data uses Spring framework's http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/spring-core.html[core] functionality, such as the http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/beans.html[IoC] container, http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/validation.html#core-convert[type conversion system], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/expressions.html[expression language], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/jmx.html[JMX integration], and portable http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html#dao-exceptions[DAO exception hierarchy]. While it is not important to know the Spring APIs, understanding the concepts behind them is. At a minimum, the idea behind IoC should be familiar for whatever IoC container you choose to use. -The core functionality of the MongoDB and CouchDB support can be used directly, with no need to invoke the IoC services of the Spring Container. This is much like `JdbcTemplate` which can be used 'standalone' without any other services of the Spring container. To leverage all the features of Spring Data document, such as the repository support, you will need to configure some parts of the library using Spring. +The core functionality of the MongoDB support can be used directly, with no need to invoke the IoC services of the Spring Container. This is much like `JdbcTemplate` which can be used 'standalone' without any other services of the Spring container. To leverage all the features of Spring Data MongoDB, such as the repository support, you will need to configure some parts of the library using Spring. -To learn more about Spring, you can refer to the comprehensive (and sometimes disarming) documentation that explains in detail the Spring Framework. There are a lot of articles, blog entries and books on the matter - take a look at the Spring framework http://spring.io/docs[home page ] for more information. +To learn more about Spring, you can refer to the comprehensive (and sometimes disarming) documentation that explains in detail the Spring Framework. There are a lot of articles, blog entries and books on the matter - take a look at the Spring framework http://spring.io/docs[home page] for more information. [[get-started:first-steps:nosql]] == Knowing NoSQL and Document databases -NoSQL stores have taken the storage world by storm. It is a vast domain with a plethora of solutions, terms and patterns (to make things worse even the term itself has multiple http://www.google.com/search?q=nosoql+acronym[meanings]). While some of the principles are common, it is crucial that the user is familiar to some degree with the stores supported by DATADOC. The best way to get acquainted to this solutions is to read their documentation and follow their examples - it usually doesn't take more then 5-10 minutes to go through them and if you are coming from an RDMBS-only background many times these exercises can be an eye opener. +NoSQL stores have taken the storage world by storm. It is a vast domain with a plethora of solutions, terms and patterns (to make things worse even the term itself has multiple http://www.google.com/search?q=nosoql+acronym[meanings]). While some of the principles are common, it is crucial that the user is familiar to some degree with MongoDB. The best way to get acquainted to this solutions is to read their documentation and follow their examples - it usually doesn't take more then 5-10 minutes to go through them and if you are coming from an RDMBS-only background many times these exercises can be an eye opener. The jumping off ground for learning about MongoDB is http://www.mongodb.org/[www.mongodb.org]. Here is a list of other useful resources: @@ -36,7 +36,7 @@ In terms of document stores, http://www.mongodb.org/[MongoDB] at least 2.6. == Additional Help Resources -Learning a new framework is not always straight forward. In this section, we try to provide what we think is an easy to follow guide for starting with Spring Data Document module. However, if you encounter issues or you are just looking for an advice, feel free to use one of the links below: +Learning a new framework is not always straight forward. In this section, we try to provide what we think is an easy to follow guide for starting with Spring Data MongoDB module. However, if you encounter issues or you are just looking for an advice, feel free to use one of the links below: [[get-started:help]] === Support diff --git a/src/main/asciidoc/reference/introduction.adoc b/src/main/asciidoc/reference/introduction.adoc index d224c29d37..ff14c7a69c 100644 --- a/src/main/asciidoc/reference/introduction.adoc +++ b/src/main/asciidoc/reference/introduction.adoc @@ -3,7 +3,7 @@ == Document Structure -This part of the reference documentation explains the core functionality offered by Spring Data Document. +This part of the reference documentation explains the core functionality offered by Spring Data MongoDB. <> introduces the MongoDB module feature set. diff --git a/src/main/asciidoc/reference/mongo-3.adoc b/src/main/asciidoc/reference/mongo-3.adoc index c0aee3c949..41d37542b9 100644 --- a/src/main/asciidoc/reference/mongo-3.adoc +++ b/src/main/asciidoc/reference/mongo-3.adoc @@ -88,7 +88,7 @@ This section covers additional things to keep in mind when using the 3.0 driver. * `IndexOperations.resetIndexCache()` is no longer supported. * Any `MapReduceOptions.extraOption` is silently ignored. -* `WriteResult` does not longer hold error informations but throws an Exception. +* `WriteResult` does not longer hold error information but throws an Exception. * `MongoOperations.executeInSession(…)` no longer calls `requestStart` / `requestDone`. * Index name generation has become a driver internal operations, still we use the 2.x schema to generate names. * Some Exception messages differ between the generation 2 and 3 servers as well as between _MMap.v1_ and _WiredTiger_ storage engine. From 9f3319928b41e00aea594d023a163e09a0e3ffb2 Mon Sep 17 00:00:00 2001 From: Martin Macko Date: Sun, 9 Oct 2016 14:29:38 +0200 Subject: [PATCH 068/118] DATAMONGO-1578 - Add missing @Test annotation to ProjectionOperationUnitTests. Original pull request: #398. --- .../mongodb/core/aggregation/ProjectionOperationUnitTests.java | 1 + 1 file changed, 1 insertion(+) 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 e5b4a4857e..bc59f5a9d5 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 @@ -114,6 +114,7 @@ public void aliasesArithmeticProjection() { assertThat(addClause.get(1), is((Object) 41)); } + @Test public void arithmenticProjectionOperationWithoutAlias() { String fieldName = "a"; From 3c6db34870a1172027cec5847dd5bb04a5f2a5a9 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 2 Jan 2017 11:36:06 +0100 Subject: [PATCH 069/118] DATAMONGO-1578 - Polishing. Add ticket references to test methods. Extend license years in copyright header. Original pull request: #398. --- .../ProjectionOperationUnitTests.java | 71 +++++++++++++++---- 1 file changed, 57 insertions(+), 14 deletions(-) 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 bc59f5a9d5..256c0282c1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -19,9 +19,9 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; -import static org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable.*; import static org.springframework.data.mongodb.core.aggregation.AggregationFunctionExpressions.*; import static org.springframework.data.mongodb.core.aggregation.Fields.*; +import static org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; import static org.springframework.data.mongodb.util.DBObjectUtils.*; @@ -32,11 +32,11 @@ import org.junit.Test; import org.springframework.data.domain.Range; import org.springframework.data.mongodb.core.DBObjectTestUtils; -import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.PropertyExpression; import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.Variable; import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; @@ -60,11 +60,17 @@ public class ProjectionOperationUnitTests { static final String DIVIDE = "$divide"; static final String PROJECT = "$project"; + /** + * @see DATAMONGO-586 + */ @Test(expected = IllegalArgumentException.class) public void rejectsNullFields() { new ProjectionOperation(null); } + /** + * @see DATAMONGO-586 + */ @Test public void declaresBackReferenceCorrectly() { @@ -76,6 +82,9 @@ public void declaresBackReferenceCorrectly() { assertThat(projectClause.get("prop"), is((Object) Fields.UNDERSCORE_ID_REF)); } + /** + * @see DATAMONGO-586 + */ @Test public void alwaysUsesExplicitReference() { @@ -88,6 +97,9 @@ public void alwaysUsesExplicitReference() { assertThat(projectClause.get("bar"), is((Object) "$foobar")); } + /** + * @see DATAMONGO-586 + */ @Test public void aliasesSimpleFieldProjection() { @@ -99,6 +111,9 @@ public void aliasesSimpleFieldProjection() { assertThat(projectClause.get("bar"), is((Object) "$foo")); } + /** + * @see DATAMONGO-586 + */ @Test public void aliasesArithmeticProjection() { @@ -114,6 +129,9 @@ public void aliasesArithmeticProjection() { assertThat(addClause.get(1), is((Object) 41)); } + /** + * @see DATAMONGO-586 + */ @Test public void arithmenticProjectionOperationWithoutAlias() { @@ -127,6 +145,9 @@ public void arithmenticProjectionOperationWithoutAlias() { assertThat(oper.get(ADD), is((Object) Arrays. asList("$a", 1))); } + /** + * @see DATAMONGO-586 + */ @Test public void arithmenticProjectionOperationPlus() { @@ -141,6 +162,9 @@ public void arithmenticProjectionOperationPlus() { assertThat(oper.get(ADD), is((Object) Arrays. asList("$a", 1))); } + /** + * @see DATAMONGO-586 + */ @Test public void arithmenticProjectionOperationMinus() { @@ -155,6 +179,9 @@ public void arithmenticProjectionOperationMinus() { assertThat(oper.get(SUBTRACT), is((Object) Arrays. asList("$a", 1))); } + /** + * @see DATAMONGO-586 + */ @Test public void arithmenticProjectionOperationMultiply() { @@ -169,6 +196,9 @@ public void arithmenticProjectionOperationMultiply() { assertThat(oper.get(MULTIPLY), is((Object) Arrays. asList("$a", 1))); } + /** + * @see DATAMONGO-586 + */ @Test public void arithmenticProjectionOperationDivide() { @@ -183,12 +213,18 @@ public void arithmenticProjectionOperationDivide() { assertThat(oper.get(DIVIDE), is((Object) Arrays. asList("$a", 1))); } + /** + * @see DATAMONGO-586 + */ @Test(expected = IllegalArgumentException.class) public void arithmenticProjectionOperationDivideByZeroException() { new ProjectionOperation().and("a").divide(0); } + /** + * @see DATAMONGO-586 + */ @Test public void arithmenticProjectionOperationMod() { @@ -1757,7 +1793,8 @@ public void shouldRenderLetExpressionCorrectly() { .define( newVariable("total") .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))), - newVariable("discounted").forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D))) + newVariable("discounted") + .forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D))) .andApply(AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) // .as("finalTotal").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1807,7 +1844,8 @@ public void shouldRenderIndexOfBytesCorrectly() { DBObject agg = project().and(StringOperators.valueOf("item").indexOf("foo")).as("byteLocation") .toDBObject(Aggregation.DEFAULT_CONTEXT); - assertThat(agg, Matchers.is(JSON.parse("{ $project: { byteLocation: { $indexOfBytes: [ \"$item\", \"foo\" ] } } }"))); + assertThat(agg, + Matchers.is(JSON.parse("{ $project: { byteLocation: { $indexOfBytes: [ \"$item\", \"foo\" ] } } }"))); } /** @@ -1914,8 +1952,8 @@ public void shouldRenderIndexOfArrayCorrectly() { @Test public void shouldRenderRangeCorrectly() { - DBObject agg = project().and(ArrayOperators.RangeOperator.rangeStartingAt(0L).to("distance").withStepSize(25L)).as("rest_stops") - .toDBObject(Aggregation.DEFAULT_CONTEXT); + DBObject agg = project().and(ArrayOperators.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)); @@ -1995,7 +2033,8 @@ 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\" ] } } }"))); + assertThat(agg, + Matchers.is(JSON.parse("{ $project : { has_bananas : { $in : [\"bananas\", \"$in_stock\" ] } } }"))); } /** @@ -2061,11 +2100,16 @@ public void shouldRenderSwitchCorrectly() { " }\n" + // "}"; - CaseOperator cond1 = CaseOperator.when(ComparisonOperators.Gte.valueOf(AccumulatorOperators.Avg.avgOf("scores")).greaterThanEqualToValue(90)) + CaseOperator cond1 = CaseOperator + .when(ComparisonOperators.Gte.valueOf(AccumulatorOperators.Avg.avgOf("scores")).greaterThanEqualToValue(90)) .then("Doing great!"); - CaseOperator cond2 = CaseOperator.when(BooleanOperators.And.and(ComparisonOperators.Gte.valueOf(AccumulatorOperators.Avg.avgOf("scores")).greaterThanEqualToValue(80), - ComparisonOperators.Lt.valueOf(AccumulatorOperators.Avg.avgOf("scores")).lessThanValue(90))).then("Doing pretty well."); - CaseOperator cond3 = CaseOperator.when(ComparisonOperators.Lt.valueOf(AccumulatorOperators.Avg.avgOf("scores")).lessThanValue(80)) + CaseOperator cond2 = CaseOperator + .when(BooleanOperators.And.and( + ComparisonOperators.Gte.valueOf(AccumulatorOperators.Avg.avgOf("scores")).greaterThanEqualToValue(80), + ComparisonOperators.Lt.valueOf(AccumulatorOperators.Avg.avgOf("scores")).lessThanValue(90))) + .then("Doing pretty well."); + CaseOperator cond3 = CaseOperator + .when(ComparisonOperators.Lt.valueOf(AccumulatorOperators.Avg.avgOf("scores")).lessThanValue(80)) .then("Needs improvement."); DBObject agg = project().and(ConditionalOperators.switchCases(cond1, cond2, cond3).defaultTo("No scores found.")) @@ -2080,8 +2124,7 @@ public void shouldRenderSwitchCorrectly() { @Test public void shouldTypeCorrectly() { - DBObject agg = project().and(DataTypeOperators.Type.typeOf("a")).as("a") - .toDBObject(Aggregation.DEFAULT_CONTEXT); + DBObject agg = project().and(DataTypeOperators.Type.typeOf("a")).as("a").toDBObject(Aggregation.DEFAULT_CONTEXT); assertThat(agg, Matchers.is(JSON.parse("{ $project : { a: { $type: \"$a\" } } }"))); } From 8340c02d9a13fd42d898dcdc5a6b822cddf73f40 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 10 Jan 2017 11:23:56 +0100 Subject: [PATCH 070/118] DATAMONGO-1576 - Update lifecycle event documentation. Add note on lifecycle event handling for property types. --- src/main/asciidoc/reference/mongodb.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 6155a18abc..d358e0cc54 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -2424,6 +2424,8 @@ The list of callback methods that are present in AbstractMappingEventListener ar * `onAfterLoad` - called in MongoTemplate find, findAndRemove, findOne and getCollection methods after the DBObject is retrieved from the database. * `onAfterConvert` - called in MongoTemplate find, findAndRemove, findOne and getCollection methods after the DBObject retrieved from the database was converted to a POJO. +NOTE: Lifecycle events are only emitted for root level types. Complex types used as properties within a document root are not subject of event publication unless they are document references annotated with `@DBRef`. + [[mongo.exception]] == Exception Translation From c3e5fca73d5fa5129777e68f96860985f1e3ef3b Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 9 Jan 2017 14:47:52 +0100 Subject: [PATCH 071/118] DATAMONGO-1585 - Expose synthetic fields in $project aggregation stage. Field projections now expose their fields as synthetic simple fields. Projection aggregation stage redefines the available field set available for later aggregation stages entirely so projected fields are considered synthetic. A simple synthetic field has no target field which causes later aggregation stages to not pick up the underlying target but the exposed field name when rendering aggregation operations to Mongo documents. The change is motivated by a bug where previously an aggregation consisting of projection of an aliased field and sort caused the sort projection stage to render with the original field name instead of the aliased field. The sort did not apply any sorting since projection redefines the available field set entirely and the original field is no longer accessible. Original Pull Request: #433 --- .../core/aggregation/ProjectionOperation.java | 12 +++-- .../aggregation/AggregationUnitTests.java | 52 +++++++++++++++++-- ...dAggregationOperationContextUnitTests.java | 28 ++++++++-- src/main/asciidoc/reference/mongodb.adoc | 20 +++++-- 4 files changed, 93 insertions(+), 19 deletions(-) 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 bc18aed8ad..4cc791c440 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -21,12 +21,12 @@ import java.util.Collections; import java.util.List; -import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond; import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection; +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.util.Assert; import com.mongodb.BasicDBObject; @@ -1208,8 +1208,9 @@ public ProjectionOperationBuilder dateAsFormattedString(String format) { * @since 1.10 */ public ProjectionOperationBuilder let(AggregationExpression valueExpression, String variableName, - AggregationExpression in) { - return this.operation.and(VariableOperators.Let.define(ExpressionVariable.newVariable(variableName).forExpression(valueExpression)).andApply(in)); + AggregationExpression in) { + return this.operation.and(VariableOperators.Let + .define(ExpressionVariable.newVariable(variableName).forExpression(valueExpression)).andApply(in)); } /** @@ -1281,6 +1282,7 @@ public DBObject toDBObject(AggregationOperationContext context) { * * @author Oliver Gierke * @author Thomas Darimont + * @author Mark Paluch */ static class FieldProjection extends Projection { @@ -1299,7 +1301,7 @@ public FieldProjection(String name, Object value) { private FieldProjection(Field field, Object value) { - super(field); + super(new ExposedField(field.getName(), true)); this.field = field; this.value = value; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index ae1c239d0f..76b228acd0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -31,11 +31,12 @@ import org.junit.rules.ExpectedException; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.test.util.BasicDbListBuilder; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; -import org.springframework.data.mongodb.test.util.BasicDbListBuilder; +import com.mongodb.util.JSON; /** * Unit tests for {@link Aggregation}. @@ -282,6 +283,47 @@ public void shouldSupportReferingToNestedPropertiesInGroupOperation() { assertThat(id.get("ruleType"), is((Object) "$rules.ruleType")); } + /** + * @see DATAMONGO-1585 + */ + @Test + public void shouldSupportSortingBySyntheticAndExposedGroupFields() { + + DBObject agg = newAggregation( // + group("cmsParameterId").addToSet("title").as("titles"), // + sort(Direction.ASC, "cmsParameterId", "titles") // + ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(notNullValue())); + + DBObject sort = ((List) agg.get("pipeline")).get(1); + + assertThat(getAsDBObject(sort, "$sort"), is(JSON.parse("{ \"_id.cmsParameterId\" : 1 , \"titles\" : 1}"))); + } + + /** + * @see DATAMONGO-1585 + */ + @Test + public void shouldSupportSortingByProjectedFields() { + + DBObject agg = newAggregation( // + project("cmsParameterId") // + .and(SystemVariable.CURRENT + ".titles").as("titles") // + .and("field").as("alias"), // + sort(Direction.ASC, "cmsParameterId", "titles", "alias") // + ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(notNullValue())); + + DBObject sort = ((List) agg.get("pipeline")).get(1); + + assertThat(getAsDBObject(sort, "$sort"), + isBsonObject().containing("cmsParameterId", 1) // + .containing("titles", 1) // + .containing("alias", 1)); + } + /** * @see DATAMONGO-924 */ @@ -339,7 +381,7 @@ public void shouldRenderAggregationWithCustomOptionsCorrectly() { } /** - * @see DATAMONGO-954 + * @see DATAMONGO-954, DATAMONGO-1585 */ @Test public void shouldSupportReferencingSystemVariables() { @@ -348,7 +390,7 @@ public void shouldSupportReferencingSystemVariables() { project("someKey") // .and("a").as("a1") // .and(Aggregation.CURRENT + ".a").as("a2") // - , sort(Direction.DESC, "a") // + , sort(Direction.DESC, "a1") // , group("someKey").first(Aggregation.ROOT).as("doc") // ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); @@ -357,7 +399,7 @@ public void shouldSupportReferencingSystemVariables() { is((DBObject) new BasicDBObject("someKey", 1).append("a1", "$a").append("a2", "$$CURRENT.a"))); DBObject sort = extractPipelineElement(agg, 1, "$sort"); - assertThat(sort, is((DBObject) new BasicDBObject("a", -1))); + assertThat(sort, is((DBObject) new BasicDBObject("a1", -1))); DBObject group = extractPipelineElement(agg, 2, "$group"); assertThat(group, diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java index b35cf59614..1458f41936 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -107,7 +107,8 @@ public void returnsReferencesToNestedFieldsCorrectly() { public void aliasesIdFieldCorrectly() { AggregationOperationContext context = getContext(Foo.class); - assertThat(context.getReference("id"), is((FieldReference) new DirectFieldReference(new ExposedField(field("id", "_id"), true)))); + assertThat(context.getReference("id"), + is((FieldReference) new DirectFieldReference(new ExposedField(field("id", "_id"), true)))); } /** @@ -175,6 +176,23 @@ public void rendersAggregationOptionsInTypedAggregationContextCorrectly() { assertThat(dbo.get("cursor"), is((Object) new BasicDBObject("foo", 1))); } + /** + * @see DATAMONGO-1585 + */ + @Test + public void rendersSortOfProjectedFieldCorrectly() { + + TypeBasedAggregationOperationContext context = getContext(MeterData.class); + TypedAggregation agg = newAggregation(MeterData.class, project().and("counterName").as("counter"), // + sort(Direction.ASC, "counter")); + + DBObject dbo = agg.toDbObject("meterData", context); + DBObject sort = getPipelineElementFromAggregationAt(dbo, 1); + + DBObject definition = (DBObject) sort.get("$sort"); + assertThat(definition.get("counter"), is(equalTo((Object) 1))); + } + /** * @see DATAMONGO-1133 */ @@ -194,14 +212,15 @@ public void shouldHonorAliasedFieldsInGroupExpressions() { } /** - * @see DATAMONGO-1326 + * @see DATAMONGO-1326, DATAMONGO-1585 */ @Test public void lookupShouldInheritFieldsFromInheritingAggregationOperation() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); TypedAggregation agg = newAggregation(MeterData.class, - lookup("OtherCollection", "resourceId", "otherId", "lookup"), sort(Direction.ASC, "resourceId")); + lookup("OtherCollection", "resourceId", "otherId", "lookup"), // + sort(Direction.ASC, "resourceId", "counterName")); DBObject dbo = agg.toDbObject("meterData", context); DBObject sort = getPipelineElementFromAggregationAt(dbo, 1); @@ -209,6 +228,7 @@ public void lookupShouldInheritFieldsFromInheritingAggregationOperation() { DBObject definition = (DBObject) sort.get("$sort"); assertThat(definition.get("resourceId"), is(equalTo((Object) 1))); + assertThat(definition.get("counter_name"), is(equalTo((Object) 1))); } /** diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index d358e0cc54..bc562c233c 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1732,8 +1732,8 @@ Note that the aggregation operations not listed here are currently not supported [[mongo.aggregation.projection]] === Projection Expressions -Projection expressions are used to define the fields that are the outcome of a particular aggregation step. Projection expressions can be defined via the `project` method of the `Aggregate` class either by passing a list of `String` 's or an aggregation framework `Fields` object. The projection can be extended with additional fields through a fluent API via the `and(String)` method and aliased via the `as(String)` method. -Note that one can also define fields with aliases via the static factory method `Fields.field` of the aggregation framework that can then be used to construct a new `Fields` instance. +Projection expressions are used to define the fields that are the outcome of a particular aggregation step. Projection expressions can be defined via the `project` method of the `Aggregate` class either by passing a list of ``String``'s or an aggregation framework `Fields` object. The projection can be extended with additional fields through a fluent API via the `and(String)` method and aliased via the `as(String)` method. +Note that one can also define fields with aliases via the static factory method `Fields.field` of the aggregation framework that can then be used to construct a new `Fields` instance. References to projected fields in later aggregation stages are only valid by using the field name of included fields or their alias of aliased or newly defined fields. Fields not included in the projection cannot be referenced in later aggregation stages. .Projection expression examples ==== @@ -1745,9 +1745,19 @@ project("a","b").and("foo").as("bar") // will generate {$project: {a: 1, b: 1, b ---- ==== -Note that more examples for project operations can be found in the `AggregationTests` class. +.Multi-Stage Aggregation using Projection and Sorting +==== +[source,java] +---- +project("name", "netPrice"), sort(ASC, "name") // will generate {$project: {name: 1, netPrice: 1}}, {$sort: {name: 1}} + +project().and("foo").as("bar"), sort(ASC, "bar") // will generate {$project: {bar: $foo}}, {$sort: {bar: 1}} + +project().and("foo").as("bar"), sort(ASC, "foo") // this will not work +---- +==== -Note that further details regarding the projection expressions can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/project/#pipe._S_project[corresponding section] of the MongoDB Aggregation Framework reference documentation. +More examples for project operations can be found in the `AggregationTests` class. Note that further details regarding the projection expressions can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/project/#pipe._S_project[corresponding section] of the MongoDB Aggregation Framework reference documentation. [[mongo.aggregation.facet]] === Faceted classification @@ -1998,7 +2008,7 @@ ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0); * The class `ZipInfo` maps the structure of the given input-collection. The class `ZipInfoStats` defines the structure in the desired output format. * As a first step we use the `group` operation to define a group from the input-collection. The grouping criteria is the combination of the fields `"state"` and `"city"` which forms the id structure of the group. We aggregate the value of the `"population"` property from the grouped elements with by using the `sum` operator saving the result in the field `"pop"`. -* In a second step we use the `sort` operation to sort the intermediate-result by the fields `"pop"`, `"state"` and `"city"` in ascending order, such that the smallest city is at the top and the biggest city is at the bottom of the result. Note that the sorting on "state" and `"city"` is implicitly performed against the group id fields which Spring Data MongoDB took care of. +* In a second step we use the `sort` operation to sort the intermediate-result by the fields `"pop"`, `"state"` and `"city"` in ascending order, such that the smallest city is at the top and the biggest city is at the bottom of the result. Note that the sorting on `"state"` and `"city"` is implicitly performed against the group id fields which Spring Data MongoDB took care of. * In the third step we use a `group` operation again to group the intermediate result by `"state"`. Note that `"state"` again implicitly references an group-id field. We select the name and the population count of the biggest and smallest city with calls to the `last(…)` and `first(...)` operator respectively via the `project` operation. * As the forth step we select the `"state"` field from the previous `group` operation. Note that `"state"` again implicitly references an group-id field. As we do not want an implicitly generated id to appear, we exclude the id from the previous operation via `and(previousOperation()).exclude()`. As we want to populate the nested `City` structures in our output-class accordingly we have to emit appropriate sub-documents with the nested method. * Finally as the fifth step we sort the resulting list of `StateStats` by their state name in ascending order via the `sort` operation. From 437bf8953344043d201de773f5fa4c2b56e4e842 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 10 Jan 2017 08:20:49 +0100 Subject: [PATCH 072/118] DATAMONGO-1585 - Polishing. Update documentation for better readability in html and pdf format. Original Pull Request: #433 --- src/main/asciidoc/reference/mongodb.adoc | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index bc562c233c..342ff17f86 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1739,9 +1739,14 @@ Note that one can also define fields with aliases via the static factory method ==== [source,java] ---- -project("name", "netPrice") // will generate {$project: {name: 1, netPrice: 1}} -project().and("foo").as("bar") // will generate {$project: {bar: $foo}} -project("a","b").and("foo").as("bar") // will generate {$project: {a: 1, b: 1, bar: $foo}} +// will generate {$project: {name: 1, netPrice: 1}} +project("name", "netPrice") + +// will generate {$project: {bar: $foo}} +project().and("foo").as("bar") + +// will generate {$project: {a: 1, b: 1, bar: $foo}} +project("a","b").and("foo").as("bar") ---- ==== @@ -1749,11 +1754,14 @@ project("a","b").and("foo").as("bar") // will generate {$project: {a: 1, b: 1, b ==== [source,java] ---- -project("name", "netPrice"), sort(ASC, "name") // will generate {$project: {name: 1, netPrice: 1}}, {$sort: {name: 1}} +// will generate {$project: {name: 1, netPrice: 1}}, {$sort: {name: 1}} +project("name", "netPrice"), sort(ASC, "name") -project().and("foo").as("bar"), sort(ASC, "bar") // will generate {$project: {bar: $foo}}, {$sort: {bar: 1}} +// will generate {$project: {bar: $foo}}, {$sort: {bar: 1}} +project().and("foo").as("bar"), sort(ASC, "bar") -project().and("foo").as("bar"), sort(ASC, "foo") // this will not work +// this will not work +project().and("foo").as("bar"), sort(ASC, "foo") ---- ==== From 174f7f08864c6ae1b4d6014668a80862a269eaaa Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 9 Jan 2017 15:40:51 +0100 Subject: [PATCH 073/118] =?UTF-8?q?DATAMONGO-1586=20-=20Consider=20field?= =?UTF-8?q?=20name=20in=20TypeBasedAggregationOperationContext.getReferenc?= =?UTF-8?q?eFor(=E2=80=A6).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now consider the provided field name (alias) in mapped fields with which it is exposed. The field name applies to the exposed field after property path resolution in TypeBasedAggregationOperationContext. Previously, the field reference used the property name which caused fields to be considered non-aliased, so aggregation projection operations dropped the alias and exposed the field with its leaf property name. Original Pull Request: #434 --- .../TypeBasedAggregationOperationContext.java | 5 +++-- ...dAggregationOperationContextUnitTests.java | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) 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 d671d97830..8b22de53c3 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -35,6 +35,7 @@ * property references into document field names. * * @author Oliver Gierke + * @author Mark Paluch * @since 1.3 */ public class TypeBasedAggregationOperationContext implements AggregationOperationContext { @@ -96,7 +97,7 @@ private FieldReference getReferenceFor(Field field) { PersistentPropertyPath propertyPath = mappingContext.getPersistentPropertyPath( field.getTarget(), type); - Field mappedField = field(propertyPath.getLeafProperty().getName(), + Field mappedField = field(field.getName(), propertyPath.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)); return new DirectFieldReference(new ExposedField(mappedField, true)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java index 1458f41936..42952d09dc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java @@ -17,6 +17,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.DBObjectTestUtils.*; import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; import static org.springframework.data.mongodb.core.aggregation.Fields.*; import static org.springframework.data.mongodb.test.util.IsBsonObject.*; @@ -193,6 +194,27 @@ public void rendersSortOfProjectedFieldCorrectly() { assertThat(definition.get("counter"), is(equalTo((Object) 1))); } + /** + * @see DATAMONGO-1586 + */ + @Test + public void rendersFieldAliasingProjectionCorrectly() { + + AggregationOperationContext context = getContext(FooPerson.class); + TypedAggregation agg = newAggregation(FooPerson.class, + project() // + .and("name").as("person_name") // + .and("age.value").as("age")); + + DBObject dbo = agg.toDbObject("person", context); + + DBObject projection = getPipelineElementFromAggregationAt(dbo, 0); + assertThat(getAsDBObject(projection, "$project"), + isBsonObject() // + .containing("person_name", "$name") // + .containing("age", "$age.value")); + } + /** * @see DATAMONGO-1133 */ From 27651ef0be9e52634c2d8d16a2055f341196cde9 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 12 Jan 2017 16:17:38 +0100 Subject: [PATCH 074/118] DATAMONGO-1587 - Migrate ticket references in test code to Spring Framework style. --- .../log4j/MongoLog4jAppenderUnitTests.java | 7 +- .../core/convert/MappingMongoConverter.java | 2 +- .../java/ConfigClassInDefaultPackage.java | 3 +- .../ConfigClassInDefaultPackageUnitTests.java | 8 +- .../AbstractMongoConfigurationUnitTests.java | 42 +- .../config/AuditingIntegrationTests.java | 7 +- ...uditingViaJavaConfigRepositoriesTests.java | 17 +- .../GeoJsonConfigurationIntegrationTests.java | 7 +- ...gMongoConverterParserIntegrationTests.java | 42 +- ...erterParserValidationIntegrationTests.java | 23 +- .../MongoAuditingRegistrarUnitTests.java | 7 +- .../MongoClientParserIntegrationTests.java | 22 +- ...ongoCredentialPropertyEditorUnitTests.java | 77 +- .../MongoDbFactoryNoDatabaseRunningTests.java | 7 +- .../MongoDbFactoryParserIntegrationTests.java | 57 +- .../mongodb/config/MongoNamespaceTests.java | 47 +- .../config/MongoParserIntegrationTests.java | 7 +- ...ReadPreferencePropertyEditorUnitTests.java | 17 +- .../ServerAddressPropertyEditorUnitTests.java | 56 +- ...oseableIterableCursorAdapterUnitTests.java | 18 +- ...DefaultBulkOperationsIntegrationTests.java | 86 +- ...efaultIndexOperationsIntegrationTests.java | 27 +- .../core/DefaultScriptOperationsTests.java | 57 +- .../DefaultScriptOperationsUnitTests.java | 47 +- .../core/GeoCommandStatisticsUnitTests.java | 17 +- ...entOptionsFactoryBeanIntegrationTests.java | 7 +- .../core/MongoDbUtilsIntegrationTests.java | 12 +- .../mongodb/core/MongoDbUtilsUnitTests.java | 22 +- .../MongoFactoryBeanIntegrationTests.java | 12 +- .../core/MongoOperationsUnitTests.java | 22 +- .../MongoOptionsFactoryBeanUnitTests.java | 14 +- .../data/mongodb/core/MongoTemplateTests.java | 557 +++---------- .../mongodb/core/MongoTemplateUnitTests.java | 112 +-- .../data/mongodb/core/NoExplicitIdTests.java | 17 +- .../mongodb/core/QueryByExampleTests.java | 37 +- .../core/QueryCursorPreparerUnitTests.java | 37 +- .../core/SerializationUtilsUnitTests.java | 27 +- .../core/SimpleMongoDbFactoryUnitTests.java | 53 +- .../aggregation/AggregationOptionsTests.java | 12 +- .../core/aggregation/AggregationTests.java | 276 ++----- .../aggregation/AggregationUnitTests.java | 155 +--- .../BucketAutoOperationUnitTests.java | 47 +- .../aggregation/BucketOperationUnitTests.java | 92 +-- .../aggregation/CondExpressionUnitTests.java | 47 +- .../aggregation/CountOperationUnitTests.java | 17 +- .../aggregation/FacetOperationUnitTests.java | 22 +- .../core/aggregation/FieldsUnitTests.java | 17 +- .../FilterExpressionUnitTests.java | 15 +- .../GeoNearOperationUnitTests.java | 7 +- .../GraphLookupOperationUnitTests.java | 37 +- .../aggregation/GroupOperationUnitTests.java | 27 +- .../aggregation/LookupOperationUnitTests.java | 62 +- .../aggregation/OutOperationUnitTest.java | 7 +- .../ProjectionOperationUnitTests.java | 765 ++++-------------- .../ReplaceRootOperationUnitTests.java | 37 +- ...ExpressionTransformerIntegrationTests.java | 7 +- .../SpelExpressionTransformerUnitTests.java | 547 +++---------- ...dAggregationOperationContextUnitTests.java | 75 +- .../aggregation/UnwindOperationUnitTests.java | 37 +- .../mongodb/core/aggregation/ZipInfo.java | 4 +- .../AbstractMongoConverterUnitTests.java | 7 +- .../convert/CustomConversionsUnitTests.java | 82 +- .../convert/CustomConvertersUnitTests.java | 11 +- .../convert/DBObjectAccessorUnitTests.java | 25 +- .../core/convert/DataMongo273Tests.java | 17 +- .../DbRefMappingMongoConverterUnitTests.java | 122 +-- .../DefaultDbRefResolverUnitTests.java | 22 +- .../DefaultMongoTypeMapperUnitTests.java | 7 +- .../core/convert/GeoConvertersUnitTests.java | 42 +- .../convert/GeoJsonConverterUnitTests.java | 141 +--- .../LazyLoadingInterceptorUnitTests.java | 7 +- .../MappingMongoConverterUnitTests.java | 508 +++--------- .../convert/MongoConvertersUnitTests.java | 57 +- .../convert/MongoExampleMapperUnitTests.java | 122 +-- .../NamedMongoScriptConvertsUnitTests.java | 32 +- ...mberToNumberConverterFactoryUnitTests.java | 7 +- .../core/convert/QueryMapperUnitTests.java | 203 +---- .../ReflectiveDBRefResolverUnitTests.java | 17 +- .../TermToStringConverterUnitTests.java | 10 +- .../core/convert/UpdateMapperUnitTests.java | 282 ++----- .../core/geo/AbstractGeoSpatialTests.java | 7 +- .../core/geo/GeoJsonModuleUnitTests.java | 32 +- .../data/mongodb/core/geo/GeoJsonTests.java | 82 +- .../core/geo/GeoSpatial2DSphereTests.java | 17 +- .../mongodb/core/geo/GeoSpatial2DTests.java | 7 +- .../core/geo/GeoSpatialIndexTests.java | 22 +- .../core/index/IndexingIntegrationTests.java | 12 +- ...entEntityIndexCreatorIntegrationTests.java | 12 +- ...PersistentEntityIndexCreatorUnitTests.java | 47 +- ...ersistentEntityIndexResolverUnitTests.java | 287 ++----- .../mongodb/core/index/PathUnitTests.java | 17 +- .../mongodb/core/index/TextIndexTests.java | 7 +- .../data/mongodb/core/mapping/BasePerson.java | 6 +- .../BasicMongoPersistentEntityUnitTests.java | 57 +- ...BasicMongoPersistentPropertyUnitTests.java | 57 +- .../mongodb/core/mapping/MappingTests.java | 17 +- .../mapping/MongoMappingContextUnitTests.java | 52 +- .../AbstractMongoEventListenerUnitTests.java | 47 +- .../event/ApplicationContextEventTests.java | 47 +- .../event/AuditingEventListenerUnitTests.java | 17 +- .../event/PersonBeforeSaveListener.java | 2 +- .../data/mongodb/core/mapping/event/User.java | 3 +- .../ValidatingMongoEventListenerTests.java | 5 +- .../mapreduce/MapReduceCountsUnitTests.java | 12 +- .../core/mapreduce/MapReduceOptionsTests.java | 12 +- .../mapreduce/MapReduceResultsUnitTests.java | 22 +- .../core/mapreduce/MapReduceTests.java | 7 +- .../core/query/BasicQueryUnitTests.java | 37 +- .../mongodb/core/query/CriteriaTests.java | 87 +- .../mongodb/core/query/IndexUnitTests.java | 12 +- .../core/query/NearQueryUnitTests.java | 32 +- .../data/mongodb/core/query/QueryTests.java | 17 +- .../data/mongodb/core/query/SortTests.java | 7 +- .../core/query/TextCriteriaUnitTests.java | 52 +- .../mongodb/core/query/TextQueryTests.java | 52 +- .../core/query/TextQueryUnitTests.java | 37 +- .../data/mongodb/core/query/UpdateTests.java | 182 +---- .../ExecutableMongoScriptUnitTests.java | 17 +- .../script/NamedMongoScriptUnitTests.java | 22 +- .../core/spel/ExpressionNodeUnitTests.java | 5 +- .../GridFsTemplateIntegrationTests.java | 57 +- .../monitor/MongoMonitorIntegrationTests.java | 60 +- ...tractPersonRepositoryIntegrationTests.java | 366 ++------- .../ComplexIdRepositoryIntegrationTests.java | 32 +- .../ContactRepositoryIntegrationTests.java | 7 +- ...oRepositoryTextSearchIntegrationTests.java | 42 +- .../mongodb/repository/PersonRepository.java | 152 +--- ...RepositoryLazyLoadingIntegrationTests.java | 17 +- .../RedeclaringRepositoryMethodsTests.java | 12 +- .../cdi/CdiExtensionIntegrationTests.java | 7 +- .../cdi/SamplePersonRepository.java | 3 +- .../cdi/SamplePersonRepositoryCustom.java | 3 +- .../cdi/SamplePersonRepositoryImpl.java | 3 +- .../MongoNamespaceIntegrationTests.java | 7 +- ...sitoryConfigurationExtensionUnitTests.java | 17 +- ...ongoRepositoriesRepositoryConfigTests.java | 7 +- .../lazy/ClassWithNestedRepository.java | 3 +- ...estedMongoRepositoriesJavaConfigTests.java | 7 +- .../CustomRepositoryImplementationTests.java | 7 +- .../query/AbstractMongoQueryUnitTests.java | 66 +- .../ConvertingParameterAccessorUnitTests.java | 12 +- ...appingMongoEntityInformationUnitTests.java | 12 +- ...oParametersParameterAccessorUnitTests.java | 17 +- .../query/MongoParametersUnitTests.java | 22 +- .../query/MongoQueryCreatorUnitTests.java | 203 +---- .../query/MongoQueryExecutionUnitTests.java | 27 +- .../query/MongoQueryMethodUnitTests.java | 51 +- .../query/PartTreeMongoQueryUnitTests.java | 57 +- .../query/StringBasedMongoQueryUnitTests.java | 117 +-- .../MongoRepositoryFactoryUnitTests.java | 7 +- ...eryDslMongoRepositoryIntegrationTests.java | 12 +- .../QuerydslRepositorySupportTests.java | 12 +- .../support/SimpleMongoRepositoryTests.java | 92 +-- .../SpringDataMongodbSerializerUnitTests.java | 32 +- 154 files changed, 1890 insertions(+), 6798 deletions(-) diff --git a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderUnitTests.java b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderUnitTests.java index 2be86e958d..e192fffc37 100644 --- a/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderUnitTests.java +++ b/spring-data-mongodb-log4j/src/test/java/org/springframework/data/mongodb/log4j/MongoLog4jAppenderUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -24,10 +24,7 @@ */ public class MongoLog4jAppenderUnitTests { - /** - * @see DATAMONGO-641 - */ - @Test + @Test // DATAMONGO-641 public void closesWithoutMongoInstancePresent() { new MongoLog4jAppender().close(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 7047995a3b..bb78d33fe7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -328,7 +328,7 @@ public DBRef toDBRef(Object object, MongoPersistentProperty referringProperty) { Assert.isTrue(annotation != null, "The referenced property has to be mapped with @DBRef!"); } - // @see DATAMONGO-913 + // DATAMONGO-913 if (object instanceof LazyLoadingProxy) { return ((LazyLoadingProxy) object).toDBRef(); } diff --git a/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackage.java b/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackage.java index 4a77bc00e3..45ae3b7901 100644 --- a/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackage.java +++ b/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackage.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -22,7 +22,6 @@ /** * Sample configuration class in default package. * - * @see DATAMONGO-877 * @author Oliver Gierke */ @Configuration diff --git a/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackageUnitTests.java b/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackageUnitTests.java index f9b9a78cab..4e8b9f25d5 100644 --- a/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackageUnitTests.java +++ b/spring-data-mongodb/src/test/java/ConfigClassInDefaultPackageUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -19,15 +19,11 @@ /** * Unit test for {@link ConfigClassInDefaultPackage}. * - * @see DATAMONGO-877 * @author Oliver Gierke */ public class ConfigClassInDefaultPackageUnitTests { - /** - * @see DATAMONGO-877 - */ - @Test + @Test // DATAMONGO-877 public void loadsConfigClassFromDefaultPackage() { new AnnotationConfigApplicationContext(ConfigClassInDefaultPackage.class).close(); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java index 2bb1bd0a0d..bc9a3e216a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AbstractMongoConfigurationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -55,10 +55,7 @@ public class AbstractMongoConfigurationUnitTests { @Rule public ExpectedException exception = ExpectedException.none(); - /** - * @see DATAMONGO-496 - */ - @Test + @Test // DATAMONGO-496 public void usesConfigClassPackageAsBaseMappingPackage() throws ClassNotFoundException { AbstractMongoConfiguration configuration = new SampleMongoConfiguration(); @@ -67,30 +64,21 @@ public void usesConfigClassPackageAsBaseMappingPackage() throws ClassNotFoundExc assertThat(configuration.getInitialEntitySet(), hasItem(Entity.class)); } - /** - * @see DATAMONGO-496 - */ - @Test + @Test // DATAMONGO-496 public void doesNotScanPackageIfMappingPackageIsNull() throws ClassNotFoundException { assertScanningDisabled(null); } - /** - * @see DATAMONGO-496 - */ - @Test + @Test // DATAMONGO-496 public void doesNotScanPackageIfMappingPackageIsEmpty() throws ClassNotFoundException { assertScanningDisabled(""); assertScanningDisabled(" "); } - /** - * @see DATAMONGO-569 - */ - @Test + @Test // DATAMONGO-569 public void containsMongoDbFactoryButNoMongoBean() { AbstractApplicationContext context = new AnnotationConfigApplicationContext(SampleMongoConfiguration.class); @@ -113,10 +101,7 @@ public void returnsUninitializedMappingContext() throws Exception { assertThat(context.getPersistentEntities(), is(not(emptyIterable()))); } - /** - * @see DATAMONGO-717 - */ - @Test + @Test // DATAMONGO-717 public void lifecycleCallbacksAreInvokedInAppropriateOrder() { AbstractApplicationContext context = new AnnotationConfigApplicationContext(SampleMongoConfiguration.class); @@ -128,10 +113,7 @@ public void lifecycleCallbacksAreInvokedInAppropriateOrder() { context.close(); } - /** - * @see DATAMONGO-725 - */ - @Test + @Test // DATAMONGO-725 public void shouldBeAbleToConfigureCustomTypeMapperViaJavaConfig() { AbstractApplicationContext context = new AnnotationConfigApplicationContext(SampleMongoConfiguration.class); @@ -143,18 +125,12 @@ public void shouldBeAbleToConfigureCustomTypeMapperViaJavaConfig() { context.close(); } - /** - * @see DATAMONGO-789 - */ - @Test + @Test // DATAMONGO-789 public void authenticationDatabaseShouldDefaultToNull() { assertThat(new SampleMongoConfiguration().getAuthenticationDatabaseName(), is(nullValue())); } - /** - * @see DATAMONGO-1470 - */ - @Test + @Test // DATAMONGO-1470 @SuppressWarnings("unchecked") public void allowsMultipleEntityBasePackages() throws ClassNotFoundException { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingIntegrationTests.java index 47cee10e51..fe84ccb3a1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 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. @@ -35,10 +35,7 @@ */ public class AuditingIntegrationTests { - /** - * @see DATAMONGO-577, DATAMONGO-800, DATAMONGO-883 - */ - @Test + @Test // DATAMONGO-577, DATAMONGO-800, DATAMONGO-883 public void enablesAuditingAndSetsPropertiesAccordingly() throws Exception { AbstractApplicationContext context = new ClassPathXmlApplicationContext("auditing.xml", getClass()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingViaJavaConfigRepositoriesTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingViaJavaConfigRepositoriesTests.java index 288e09cb91..7f7bc5eebb 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingViaJavaConfigRepositoriesTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/AuditingViaJavaConfigRepositoriesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -79,10 +79,7 @@ public void setup() { this.auditor = auditablePersonRepository.save(new AuditablePerson("auditor")); } - /** - * @see DATAMONGO-792, DATAMONGO-883 - */ - @Test + @Test // DATAMONGO-792, DATAMONGO-883 public void basicAuditing() { doReturn(this.auditor).when(this.auditorAware).getCurrentAuditor(); @@ -96,19 +93,13 @@ public void basicAuditing() { assertThat(savedUser.getCreatedAt(), is(notNullValue())); } - /** - * @see DATAMONGO-843 - */ - @Test + @Test // DATAMONGO-843 @SuppressWarnings("resource") public void auditingUsesFallbackMappingContextIfNoneConfiguredWithRepositories() { new AnnotationConfigApplicationContext(SimpleConfigWithRepositories.class); } - /** - * @see DATAMONGO-843 - */ - @Test + @Test // DATAMONGO-843 @SuppressWarnings("resource") public void auditingUsesFallbackMappingContextIfNoneConfigured() { new AnnotationConfigApplicationContext(SimpleConfig.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/GeoJsonConfigurationIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/GeoJsonConfigurationIntegrationTests.java index 7efa806778..bed30fc8ba 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/GeoJsonConfigurationIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/GeoJsonConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -43,10 +43,7 @@ static class Config {} @Autowired GeoJsonModule geoJsonModule; - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void picksUpGeoJsonModuleConfigurationByDefault() { assertThat(geoJsonModule, is(notNullValue())); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java index 5082cc3ae9..6910aff4f5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 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. @@ -58,10 +58,7 @@ public class MappingMongoConverterParserIntegrationTests { DefaultListableBeanFactory factory; - /** - * @see DATAMONGO-243 - */ - @Test + @Test // DATAMONGO-243 public void allowsDbFactoryRefAttribute() { loadValidConfiguration(); @@ -69,10 +66,7 @@ public void allowsDbFactoryRefAttribute() { factory.getBean("converter"); } - /** - * @see DATAMONGO-725 - */ - @Test + @Test // DATAMONGO-725 public void hasCustomTypeMapper() { loadValidConfiguration(); @@ -82,10 +76,7 @@ public void hasCustomTypeMapper() { assertThat(converter.getTypeMapper(), is(customMongoTypeMapper)); } - /** - * @see DATAMONGO-301 - */ - @Test + @Test // DATAMONGO-301 public void scansForConverterAndSetsUpCustomConversionsAccordingly() { loadValidConfiguration(); @@ -94,10 +85,7 @@ public void scansForConverterAndSetsUpCustomConversionsAccordingly() { assertThat(conversions.hasCustomWriteTarget(Account.class), is(true)); } - /** - * @see DATAMONGO-607 - */ - @Test + @Test // DATAMONGO-607 public void activatesAbbreviatingPropertiesCorrectly() { loadValidConfiguration(); @@ -109,10 +97,7 @@ public void activatesAbbreviatingPropertiesCorrectly() { assertThat(strategy.getBeanClassName(), is(CamelCaseAbbreviatingFieldNamingStrategy.class.getName())); } - /** - * @see DATAMONGO-866 - */ - @Test + @Test // DATAMONGO-866 public void rejectsInvalidFieldNamingStrategyConfiguration() { exception.expect(BeanDefinitionParsingException.class); @@ -124,10 +109,7 @@ public void rejectsInvalidFieldNamingStrategyConfiguration() { reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-invalid.xml")); } - /** - * @see DATAMONGO-892 - */ - @Test + @Test // DATAMONGO-892 public void shouldThrowBeanDefinitionParsingExceptionIfConverterDefinedAsNestedBean() { exception.expect(BeanDefinitionParsingException.class); @@ -136,18 +118,12 @@ public void shouldThrowBeanDefinitionParsingExceptionIfConverterDefinedAsNestedB loadNestedBeanConfiguration(); } - /** - * @see DATAMONGO-925, DATAMONGO-928 - */ - @Test + @Test // DATAMONGO-925, DATAMONGO-928 public void shouldSupportCustomFieldNamingStrategy() { assertStrategyReferenceSetFor("mappingConverterWithCustomFieldNamingStrategy"); } - /** - * @see DATAMONGO-925, DATAMONGO-928 - */ - @Test + @Test // DATAMONGO-925, DATAMONGO-928 public void shouldNotFailLoadingConfigIfAbbreviationIsDisabledAndStrategySet() { assertStrategyReferenceSetFor("mappingConverterWithCustomFieldNamingStrategyAndAbbreviationDisabled"); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java index d9f34df89f..dda5299914 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MappingMongoConverterParserValidationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 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. @@ -31,7 +31,6 @@ * {@link org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener} by defining * {@code } in context XML. * - * @see DATAMONGO-36 * @author Maciej Walkowiak * @author Thomas Darimont * @author Oliver Gierke @@ -47,40 +46,28 @@ public void setUp() { reader = new XmlBeanDefinitionReader(factory); } - /** - * @see DATAMONGO-36 - */ - @Test + @Test // DATAMONGO-36 public void validatingEventListenerCreatedWithDefaultConfig() { reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-default.xml")); assertThat(factory.getBean(BeanNames.VALIDATING_EVENT_LISTENER_BEAN_NAME), is(not(nullValue()))); } - /** - * @see DATAMONGO-36 - */ - @Test + @Test // DATAMONGO-36 public void validatingEventListenerCreatedWhenValidationEnabled() { reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-validation-enabled.xml")); assertThat(factory.getBean(BeanNames.VALIDATING_EVENT_LISTENER_BEAN_NAME), is(not(nullValue()))); } - /** - * @see DATAMONGO-36 - */ - @Test(expected = NoSuchBeanDefinitionException.class) + @Test(expected = NoSuchBeanDefinitionException.class) // DATAMONGO-36 public void validatingEventListenersIsNotCreatedWhenDisabled() { reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-validation-disabled.xml")); factory.getBean(BeanNames.VALIDATING_EVENT_LISTENER_BEAN_NAME); } - /** - * @see DATAMONGO-36 - */ - @Test + @Test // DATAMONGO-36 public void validatingEventListenerCreatedWithCustomTypeMapperConfig() { reader.loadBeanDefinitions(new ClassPathResource("namespace/converter-custom-typeMapper.xml")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoAuditingRegistrarUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoAuditingRegistrarUnitTests.java index ad076c90d3..c918433ef7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoAuditingRegistrarUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoAuditingRegistrarUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -25,7 +25,6 @@ /** * Unit tests for {@link JpaAuditingRegistrar}. * - * @see DATAMONGO-792 * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) @@ -36,12 +35,12 @@ public class MongoAuditingRegistrarUnitTests { @Mock AnnotationMetadata metadata; @Mock BeanDefinitionRegistry registry; - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-792 public void rejectsNullAnnotationMetadata() { registrar.registerBeanDefinitions(null, registry); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-792 public void rejectsNullBeanDefinitionRegistry() { registrar.registerBeanDefinitions(metadata, null); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoClientParserIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoClientParserIntegrationTests.java index a2abd7c6e1..b3f9c05410 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoClientParserIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoClientParserIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -51,10 +51,7 @@ public void setUp() { this.reader = new XmlBeanDefinitionReader(factory); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void createsMongoClientCorrectlyWhenGivenHostAndPort() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongoClient-bean.xml")); @@ -62,10 +59,7 @@ public void createsMongoClientCorrectlyWhenGivenHostAndPort() { assertThat(factory.getBean("mongo-client-with-host-and-port"), instanceOf(MongoClient.class)); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void createsMongoClientWithOptionsCorrectly() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongoClient-bean.xml")); @@ -84,10 +78,7 @@ public void createsMongoClientWithOptionsCorrectly() { } } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void createsMongoClientWithDefaultsCorrectly() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongoClient-bean.xml")); @@ -105,10 +96,7 @@ public void createsMongoClientWithDefaultsCorrectly() { } } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void createsMongoClientWithCredentialsCorrectly() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongoClient-bean.xml")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditorUnitTests.java index ba9257cbe2..bcebbce444 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoCredentialPropertyEditorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -75,10 +75,7 @@ public void setUp() { this.editor = new MongoCredentialPropertyEditor(); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldReturnNullValueForNullText() { editor.setAsText(null); @@ -86,10 +83,7 @@ public void shouldReturnNullValueForNullText() { assertThat(editor.getValue(), nullValue()); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldReturnNullValueForEmptyText() { editor.setAsText(" "); @@ -97,26 +91,17 @@ public void shouldReturnNullValueForEmptyText() { assertThat(editor.getValue(), nullValue()); } - /** - * @see DATAMONGO-1158 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1158 public void shouldThrowExceptionForMalformatedCredentialsString() { editor.setAsText("tyrion"); } - /** - * @see DATAMONGO-1158 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1158 public void shouldThrowExceptionForMalformatedAuthMechanism() { editor.setAsText(USER_2_AUTH_STRING + "?uri.authMechanism=Targaryen"); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleUserNamePasswordStringWithDatabaseAndNoOptions() { @@ -125,10 +110,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleUserNamePassword assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS)); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleUserNamePasswordStringWithDatabaseAndAuthOptions() { @@ -137,10 +119,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleUserNamePassword assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH)); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndNoOptions() { @@ -150,10 +129,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswo assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS)); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndAuthOptions() { @@ -164,10 +140,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswo contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS_CR_AUTH)); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswordStringWithDatabaseAndMixedOptions() { @@ -177,10 +150,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleUserNamePasswo assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS_PLAIN_AUTH, USER_2_CREDENTIALS)); } - /** - * @see DATAMONGO-1257 - */ - @Test + @Test // DATAMONGO-1257 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleQuotedUserNamePasswordStringWithDatabaseAndNoOptions() { @@ -190,10 +160,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenMultipleQuotedUserName assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS, USER_2_CREDENTIALS)); } - /** - * @see DATAMONGO-1257 - */ - @Test + @Test // DATAMONGO-1257 @SuppressWarnings("unchecked") public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleQuotedUserNamePasswordStringWithDatabaseAndNoOptions() { @@ -202,10 +169,7 @@ public void shouldReturnCredentialsValueCorrectlyWhenGivenSingleQuotedUserNamePa assertThat((List) editor.getValue(), contains(USER_1_CREDENTIALS)); } - /** - * @see DATAMONGO-1257 - */ - @Test + @Test // DATAMONGO-1257 @SuppressWarnings("unchecked") public void shouldReturnX509CredentialsCorrectly() { @@ -214,10 +178,7 @@ public void shouldReturnX509CredentialsCorrectly() { assertThat((List) editor.getValue(), contains(USER_3_CREDENTIALS_X509_AUTH)); } - /** - * @see DATAMONGO-1257 - */ - @Test + @Test // DATAMONGO-1257 @SuppressWarnings("unchecked") public void shouldReturnX509CredentialsCorrectlyWhenNoDbSpecified() { @@ -226,10 +187,7 @@ public void shouldReturnX509CredentialsCorrectlyWhenNoDbSpecified() { assertThat((List) editor.getValue(), contains(MongoCredential.createMongoX509Credential("tyrion"))); } - /** - * @see DATAMONGO-1257 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1257 public void shouldThrowExceptionWhenNoDbSpecifiedForMongodbCR() { editor.setAsText("tyrion?uri.authMechanism=MONGODB-CR"); @@ -237,10 +195,7 @@ public void shouldThrowExceptionWhenNoDbSpecifiedForMongodbCR() { editor.getValue(); } - /** - * @see DATAMONGO-1257 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1257 public void shouldThrowExceptionWhenDbIsEmptyForMongodbCR() { editor.setAsText("tyrion@?uri.authMechanism=MONGODB-CR"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryNoDatabaseRunningTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryNoDatabaseRunningTests.java index 6e0972c5dd..c364fdc5fc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryNoDatabaseRunningTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryNoDatabaseRunningTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 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. @@ -39,10 +39,7 @@ public class MongoDbFactoryNoDatabaseRunningTests { @Autowired MongoTemplate mongoTemplate; - /** - * @see DATAMONGO-139 - */ - @Test + @Test // DATAMONGO-139 public void startsUpWithoutADatabaseRunning() { assertThat(mongoTemplate.getClass().getName(), is("org.springframework.data.mongodb.core.MongoTemplate")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java index a98a2c06d0..3f013ac207 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -91,10 +91,7 @@ public void parsesCustomWriteConcern() { assertWriteConcern(ctx, new WriteConcern("rack1")); } - /** - * @see DATAMONGO-331 - */ - @Test + @Test // DATAMONGO-331 public void readsReplicasWriteConcernCorrectly() { AbstractApplicationContext ctx = new ClassPathXmlApplicationContext( @@ -122,10 +119,7 @@ public void createsDbFactoryBean() { factory.getBean("first"); } - /** - * @see DATAMONGO-280 - */ - @Test + @Test // DATAMONGO-280 @SuppressWarnings("deprecation") public void parsesMaxAutoConnectRetryTimeCorrectly() { @@ -134,10 +128,7 @@ public void parsesMaxAutoConnectRetryTimeCorrectly() { assertThat(ReflectiveMongoOptionsInvokerTestUtil.getMaxAutoConnectRetryTime(mongo.getMongoOptions()), is(27L)); } - /** - * @see DATAMONGO-295 - */ - @Test + @Test // DATAMONGO-295 public void setsUpMongoDbFactoryUsingAMongoUri() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-uri.xml")); @@ -149,10 +140,7 @@ public void setsUpMongoDbFactoryUsingAMongoUri() { assertThat(argument, is(notNullValue())); } - /** - * @see DATAMONGO-306 - */ - @Test + @Test // DATAMONGO-306 public void setsUpMongoDbFactoryUsingAMongoUriWithoutCredentials() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-uri-no-credentials.xml")); @@ -168,18 +156,12 @@ public void setsUpMongoDbFactoryUsingAMongoUriWithoutCredentials() { assertThat(db.getName(), is("database")); } - /** - * @see DATAMONGO-295 - */ - @Test(expected = BeanDefinitionParsingException.class) + @Test(expected = BeanDefinitionParsingException.class) // DATAMONGO-295 public void rejectsUriPlusDetailedConfiguration() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-uri-and-details.xml")); } - /** - * @see DATAMONGO-1218 - */ - @Test + @Test // DATAMONGO-1218 public void setsUpMongoDbFactoryUsingAMongoClientUri() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-client-uri.xml")); @@ -191,18 +173,12 @@ public void setsUpMongoDbFactoryUsingAMongoClientUri() { assertThat(argument, is(notNullValue())); } - /** - * @see DATAMONGO-1218 - */ - @Test(expected = BeanDefinitionParsingException.class) + @Test(expected = BeanDefinitionParsingException.class) // DATAMONGO-1218 public void rejectsClientUriPlusDetailedConfiguration() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-client-uri-and-details.xml")); } - /** - * @see DATAMONGO-1293 - */ - @Test + @Test // DATAMONGO-1293 public void setsUpClientUriWithId() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-client-uri-and-id.xml")); @@ -214,10 +190,7 @@ public void setsUpClientUriWithId() { assertThat(argument, is(notNullValue())); } - /** - * @see DATAMONGO-1293 - */ - @Test + @Test // DATAMONGO-1293 public void setsUpUriWithId() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-uri-and-id.xml")); @@ -229,18 +202,12 @@ public void setsUpUriWithId() { assertThat(argument, is(notNullValue())); } - /** - * @see DATAMONGO-1293 - */ - @Test(expected = BeanDefinitionParsingException.class) + @Test(expected = BeanDefinitionParsingException.class) // DATAMONGO-1293 public void rejectsClientUriPlusDetailedConfigurationAndWriteConcern() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-client-uri-write-concern-and-details.xml")); } - /** - * @see DATAMONGO-1293 - */ - @Test(expected = BeanDefinitionParsingException.class) + @Test(expected = BeanDefinitionParsingException.class) // DATAMONGO-1293 public void rejectsUriPlusDetailedConfigurationAndWriteConcern() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-client-uri-write-concern-and-details.xml")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceTests.java index 2debb0afcd..1b64630466 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoNamespaceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 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. @@ -90,10 +90,7 @@ public void testMongoSingletonWithAttributes() throws Exception { options.getSocketFactory() instanceof SSLSocketFactory); } - /** - * @see DATAMONGO-764 - */ - @Test + @Test // DATAMONGO-764 public void testMongoSingletonWithSslEnabled() throws Exception { assertTrue(ctx.containsBean("mongoSsl")); @@ -103,10 +100,7 @@ public void testMongoSingletonWithSslEnabled() throws Exception { assertTrue("socketFactory should be a SSLSocketFactory", options.getSocketFactory() instanceof SSLSocketFactory); } - /** - * @see DATAMONGO-1490 - */ - @Test + @Test // DATAMONGO-1490 public void testMongoClientSingletonWithSslEnabled() { assertTrue(ctx.containsBean("mongoClientSsl")); @@ -116,10 +110,7 @@ public void testMongoClientSingletonWithSslEnabled() { assertTrue("socketFactory should be a SSLSocketFactory", options.getSocketFactory() instanceof SSLSocketFactory); } - /** - * @see DATAMONGO-764 - */ - @Test + @Test // DATAMONGO-764 public void testMongoSingletonWithSslEnabledAndCustomSslSocketFactory() throws Exception { assertTrue(ctx.containsBean("mongoSslWithCustomSslFactory")); @@ -145,10 +136,7 @@ public void testSecondMongoDbFactory() { assertEquals("database", getField(dbf, "databaseName")); } - /** - * @see DATAMONGO-789 - */ - @Test + @Test // DATAMONGO-789 public void testThirdMongoDbFactory() { assertTrue(ctx.containsBean("thirdMongoDbFactory")); @@ -163,10 +151,7 @@ public void testThirdMongoDbFactory() { assertEquals("admin", getField(dbf, "authenticationDatabaseName")); } - /** - * @see DATAMONGO-140 - */ - @Test + @Test // DATAMONGO-140 public void testMongoTemplateFactory() { assertTrue(ctx.containsBean("mongoTemplate")); @@ -179,10 +164,7 @@ public void testMongoTemplateFactory() { assertNotNull(converter); } - /** - * @see DATAMONGO-140 - */ - @Test + @Test // DATAMONGO-140 public void testSecondMongoTemplateFactory() { assertTrue(ctx.containsBean("anotherMongoTemplate")); @@ -195,10 +177,7 @@ public void testSecondMongoTemplateFactory() { assertEquals(WriteConcern.SAFE, writeConcern); } - /** - * @see DATAMONGO-628 - */ - @Test + @Test // DATAMONGO-628 public void testGridFsTemplateFactory() { assertTrue(ctx.containsBean("gridFsTemplate")); @@ -211,10 +190,7 @@ public void testGridFsTemplateFactory() { assertNotNull(converter); } - /** - * @see DATAMONGO-628 - */ - @Test + @Test // DATAMONGO-628 public void testSecondGridFsTemplateFactory() { assertTrue(ctx.containsBean("secondGridFsTemplate")); @@ -228,10 +204,7 @@ public void testSecondGridFsTemplateFactory() { assertNotNull(converter); } - /** - * @see DATAMONGO-823 - */ - @Test + @Test // DATAMONGO-823 public void testThirdGridFsTemplateFactory() { assertTrue(ctx.containsBean("thirdGridFsTemplate")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoParserIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoParserIntegrationTests.java index 0ca28717e4..b8d1b525da 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoParserIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoParserIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2017 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. @@ -62,10 +62,7 @@ public void readsMongoAttributesCorrectly() { factory.getBean("mongo"); } - /** - * @see DATAMONGO-343 - */ - @Test + @Test // DATAMONGO-343 public void readsServerAddressesCorrectly() { reader.loadBeanDefinitions(new ClassPathResource("namespace/mongo-bean.xml")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditorUnitTests.java index 6f8f0c9548..a334dbab84 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ReadPreferencePropertyEditorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -41,10 +41,7 @@ public void setUp() { editor = new ReadPreferencePropertyEditor(); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldThrowExceptionOnUndefinedPreferenceString() { expectedException.expect(IllegalArgumentException.class); @@ -54,10 +51,7 @@ public void shouldThrowExceptionOnUndefinedPreferenceString() { editor.setAsText("foo"); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldAllowUsageNativePreferenceStrings() { editor.setAsText("secondary"); @@ -65,10 +59,7 @@ public void shouldAllowUsageNativePreferenceStrings() { assertThat(editor.getValue(), is((Object) ReadPreference.secondary())); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldAllowUsageOfUppcaseEnumStringsForPreferences() { editor.setAsText("NEAREST"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java index 19565433c2..34fa52a861 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/ServerAddressPropertyEditorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 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. @@ -48,11 +48,7 @@ public void setUp() { editor = new ServerAddressPropertyEditor(); } - /** - * @see DATAMONGO-454 - * @see DATAMONGO-1062 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-454, DATAMONGO-1062 public void rejectsAddressConfigWithoutASingleParsableAndResolvableServerAddress() { String unknownHost1 = "gugu.nonexistant.example.org"; @@ -63,40 +59,28 @@ public void rejectsAddressConfigWithoutASingleParsableAndResolvableServerAddress editor.setAsText(unknownHost1 + "," + unknownHost2); } - /** - * @see DATAMONGO-454 - */ - @Test + @Test // DATAMONGO-454 public void skipsUnparsableAddressIfAtLeastOneIsParsable() throws UnknownHostException { editor.setAsText("foo, localhost"); assertSingleAddressOfLocalhost(editor.getValue()); } - /** - * @see DATAMONGO-454 - */ - @Test + @Test // DATAMONGO-454 public void handlesEmptyAddressAsParseError() throws UnknownHostException { editor.setAsText(", localhost"); assertSingleAddressOfLocalhost(editor.getValue()); } - /** - * @see DATAMONGO-693 - */ - @Test + @Test // DATAMONGO-693 public void interpretEmptyStringAsNull() { editor.setAsText(""); assertNull(editor.getValue()); } - /** - * @see DATAMONGO-808 - */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressLoopbackShort() throws UnknownHostException { String hostAddress = "::1"; @@ -105,10 +89,7 @@ public void handleIPv6HostaddressLoopbackShort() throws UnknownHostException { assertSingleAddressWithPort(hostAddress, null, editor.getValue()); } - /** - * @see DATAMONGO-808 - */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressLoopbackShortWithPort() throws UnknownHostException { String hostAddress = "::1"; @@ -120,10 +101,8 @@ public void handleIPv6HostaddressLoopbackShortWithPort() throws UnknownHostExcep /** * Here we detect no port since the last segment of the address contains leading zeros. - * - * @see DATAMONGO-808 */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressLoopbackLong() throws UnknownHostException { String hostAddress = "0000:0000:0000:0000:0000:0000:0000:0001"; @@ -132,10 +111,7 @@ public void handleIPv6HostaddressLoopbackLong() throws UnknownHostException { assertSingleAddressWithPort(hostAddress, null, editor.getValue()); } - /** - * @see DATAMONGO-808 - */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressLoopbackLongWithBrackets() throws UnknownHostException { String hostAddress = "[0000:0000:0000:0000:0000:0000:0000:0001]"; @@ -146,10 +122,8 @@ public void handleIPv6HostaddressLoopbackLongWithBrackets() throws UnknownHostEx /** * We can't tell whether the last part of the hostAddress represents a port or not. - * - * @see DATAMONGO-808 */ - @Test + @Test // DATAMONGO-808 public void shouldFailToHandleAmbiguousIPv6HostaddressLongWithoutPortAndWithoutBrackets() throws UnknownHostException { expectedException.expect(IllegalArgumentException.class); @@ -158,10 +132,7 @@ public void shouldFailToHandleAmbiguousIPv6HostaddressLongWithoutPortAndWithoutB editor.setAsText(hostAddress); } - /** - * @see DATAMONGO-808 - */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressExampleAddressWithPort() throws UnknownHostException { String hostAddress = "0000:0000:0000:0000:0000:0000:0000:0001"; @@ -171,10 +142,7 @@ public void handleIPv6HostaddressExampleAddressWithPort() throws UnknownHostExce assertSingleAddressWithPort(hostAddress, port, editor.getValue()); } - /** - * @see DATAMONGO-808 - */ - @Test + @Test // DATAMONGO-808 public void handleIPv6HostaddressExampleAddressInBracketsWithPort() throws UnknownHostException { String hostAddress = "[0000:0000:0000:0000:0000:0000:0000:0001]"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CloseableIterableCursorAdapterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CloseableIterableCursorAdapterUnitTests.java index 1ac451b619..daca6484ff 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CloseableIterableCursorAdapterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/CloseableIterableCursorAdapterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -33,7 +33,6 @@ * Unit tests for {@link CloseableIterableCursorAdapter}. * * @author Oliver Gierke - * @see DATAMONGO-1276 */ @RunWith(MockitoJUnitRunner.class) public class CloseableIterableCursorAdapterUnitTests { @@ -51,30 +50,21 @@ public void setUp() { this.adapter = new CloseableIterableCursorAdapter(cursor, exceptionTranslator, callback); } - /** - * @see DATAMONGO-1276 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1276 public void propagatesOriginalExceptionFromAdapterDotNext() { cursor.next(); adapter.next(); } - /** - * @see DATAMONGO-1276 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1276 public void propagatesOriginalExceptionFromAdapterDotHasNext() { cursor.hasNext(); adapter.hasNext(); } - /** - * @see DATAMONGO-1276 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1276 public void propagatesOriginalExceptionFromAdapterDotClose() { cursor.close(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java index 26c09f53fa..e4b9d9e168 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -65,34 +65,22 @@ public void setUp() { this.collection.remove(new BasicDBObject()); } - /** - * @see DATAMONGO-934 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-934 public void rejectsNullMongoOperations() { new DefaultBulkOperations(null, null, COLLECTION_NAME, null); } - /** - * @see DATAMONGO-934 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-934 public void rejectsNullCollectionName() { new DefaultBulkOperations(operations, null, null, null); } - /** - * @see DATAMONGO-934 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-934 public void rejectsEmptyCollectionName() { new DefaultBulkOperations(operations, null, "", null); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void insertOrdered() { List documents = Arrays.asList(newDoc("1"), newDoc("2")); @@ -100,10 +88,7 @@ public void insertOrdered() { assertThat(createBulkOps(BulkMode.ORDERED).insert(documents).execute().getInsertedCount(), is(2)); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void insertOrderedFails() { List documents = Arrays.asList(newDoc("1"), newDoc("1"), newDoc("2")); @@ -118,10 +103,7 @@ public void insertOrderedFails() { } } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void insertUnOrdered() { List documents = Arrays.asList(newDoc("1"), newDoc("2")); @@ -129,10 +111,7 @@ public void insertUnOrdered() { assertThat(createBulkOps(BulkMode.UNORDERED).insert(documents).execute().getInsertedCount(), is(2)); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void insertUnOrderedContinuesOnError() { List documents = Arrays.asList(newDoc("1"), newDoc("1"), newDoc("2")); @@ -147,10 +126,7 @@ public void insertUnOrderedContinuesOnError() { } } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void upsertDoesUpdate() { insertSomeDocuments(); @@ -167,10 +143,7 @@ public void upsertDoesUpdate() { assertThat(result.getUpserts().size(), is(0)); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void upsertDoesInsert() { BulkWriteResult result = createBulkOps(BulkMode.ORDERED).// @@ -184,60 +157,40 @@ public void upsertDoesInsert() { assertThat(result.getUpserts().size(), is(1)); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void updateOneOrdered() { testUpdate(BulkMode.ORDERED, false, 2); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void updateMultiOrdered() { testUpdate(BulkMode.ORDERED, true, 4); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void updateOneUnOrdered() { testUpdate(BulkMode.UNORDERED, false, 2); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void updateMultiUnOrdered() { testUpdate(BulkMode.UNORDERED, true, 4); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void removeOrdered() { testRemove(BulkMode.ORDERED); } - /** - * @see DATAMONGO-934 - */ - @Test + @Test // DATAMONGO-934 public void removeUnordered() { testRemove(BulkMode.UNORDERED); } /** * If working on the same set of documents, only an ordered bulk operation will yield predictable results. - * - * @see DATAMONGO-934 */ - @Test + @Test // DATAMONGO-934 public void mixedBulkOrdered() { BulkWriteResult result = createBulkOps(BulkMode.ORDERED).insert(newDoc("1", "v1")).// @@ -271,10 +224,7 @@ public void mixedBulkOrderedWithList() { assertThat(result.getRemovedCount(), is(1)); } - /** - * @see DATAMONGO-1534 - */ - @Test + @Test // DATAMONGO-1534 public void insertShouldConsiderInheritance() { SpecialDoc specialDoc = new SpecialDoc(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java index b8ded92d6e..04ab43f423 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -81,10 +81,7 @@ private void queryMongoVersionIfNecessary() { } } - /** - * @see DATAMONGO-1008 - */ - @Test + @Test // DATAMONGO-1008 public void getIndexInfoShouldBeAbleToRead2dsphereIndex() { collection.createIndex(GEO_SPHERE_2D); @@ -93,10 +90,7 @@ public void getIndexInfoShouldBeAbleToRead2dsphereIndex() { assertThat(info.getIndexFields().get(0).isGeo(), is(true)); } - /** - * @see DATAMONGO-1467 - */ - @Test + @Test // DATAMONGO-1467 public void shouldApplyPartialFilterCorrectly() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); @@ -110,10 +104,7 @@ public void shouldApplyPartialFilterCorrectly() { assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"q-t-y\" : { \"$gte\" : 10}}"))); } - /** - * @see DATAMONGO-1467 - */ - @Test + @Test // DATAMONGO-1467 public void shouldApplyPartialFilterWithMappedPropertyCorrectly() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); @@ -127,10 +118,7 @@ public void shouldApplyPartialFilterWithMappedPropertyCorrectly() { assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"qty\" : { \"$gte\" : 10}}"))); } - /** - * @see DATAMONGO-1467 - */ - @Test + @Test // DATAMONGO-1467 public void shouldApplyPartialDBOFilterCorrectly() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); @@ -144,10 +132,7 @@ public void shouldApplyPartialDBOFilterCorrectly() { assertThat(info.getPartialFilterExpression(), is(equalTo("{ \"qty\" : { \"$gte\" : 10}}"))); } - /** - * @see DATAMONGO-1467 - */ - @Test + @Test // DATAMONGO-1467 public void shouldFavorExplicitMappingHintViaClass() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO), is(true)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java index 04834ae69a..9bbaa7c9eb 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -82,18 +82,12 @@ public void setUp() { this.scriptOps = new DefaultScriptOperations(template); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void executeShouldDirectlyRunExecutableMongoScript() { assertThat(scriptOps.execute(EXECUTABLE_SCRIPT, 10), is((Object) 10D)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void saveShouldStoreCallableScriptCorrectly() { Query query = query(where("_id").is(SCRIPT_NAME)); @@ -104,10 +98,7 @@ public void saveShouldStoreCallableScriptCorrectly() { assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(true)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void saveShouldStoreExecutableScriptCorrectly() { NamedMongoScript script = scriptOps.register(EXECUTABLE_SCRIPT); @@ -116,10 +107,7 @@ public void saveShouldStoreExecutableScriptCorrectly() { assumeThat(template.exists(query, JAVASCRIPT_COLLECTION_NAME), is(true)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void executeShouldRunCallableScriptThatHasBeenSavedBefore() { scriptOps.register(CALLABLE_SCRIPT); @@ -132,10 +120,7 @@ public void executeShouldRunCallableScriptThatHasBeenSavedBefore() { assertThat(result, is((Object) 10D)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void existsShouldReturnTrueIfScriptAvailableOnServer() { scriptOps.register(CALLABLE_SCRIPT); @@ -143,18 +128,12 @@ public void existsShouldReturnTrueIfScriptAvailableOnServer() { assertThat(scriptOps.exists(SCRIPT_NAME), is(true)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void existsShouldReturnFalseIfScriptNotAvailableOnServer() { assertThat(scriptOps.exists(SCRIPT_NAME), is(false)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void callShouldExecuteExistingScript() { scriptOps.register(CALLABLE_SCRIPT); @@ -164,18 +143,12 @@ public void callShouldExecuteExistingScript() { assertThat(result, is((Object) 10D)); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = UncategorizedDataAccessException.class) + @Test(expected = UncategorizedDataAccessException.class) // DATAMONGO-479 public void callShouldThrowExceptionWhenCallingScriptThatDoesNotExist() { scriptOps.call(SCRIPT_NAME, 10); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void scriptNamesShouldContainNameOfRegisteredScript() { scriptOps.register(CALLABLE_SCRIPT); @@ -183,18 +156,12 @@ public void scriptNamesShouldContainNameOfRegisteredScript() { assertThat(scriptOps.getScriptNames(), hasItems("echo")); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void scriptNamesShouldReturnEmptySetWhenNoScriptRegistered() { assertThat(scriptOps.getScriptNames(), is(empty())); } - /** - * @see DATAMONGO-1465 - */ - @Test + @Test // DATAMONGO-1465 public void executeShouldNotQuoteStrings() { assertThat(scriptOps.execute(EXECUTABLE_SCRIPT, "spring-data"), is((Object) "spring-data")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java index 833bf1028a..3a42d72122 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultScriptOperationsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 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. @@ -47,26 +47,17 @@ public void setUp() { this.scriptOps = new DefaultScriptOperations(mongoOperations); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void rejectsNullExecutableMongoScript() { scriptOps.register((ExecutableMongoScript) null); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void rejectsNullNamedMongoScript() { scriptOps.register((NamedMongoScript) null); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void saveShouldUseCorrectCollectionName() { scriptOps.register(new NamedMongoScript("foo", "function...")); @@ -74,10 +65,7 @@ public void saveShouldUseCorrectCollectionName() { verify(mongoOperations, times(1)).save(any(NamedMongoScript.class), eq("system.js")); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void saveShouldGenerateScriptNameForExecutableMongoScripts() { scriptOps.register(new ExecutableMongoScript("function...")); @@ -88,42 +76,27 @@ public void saveShouldGenerateScriptNameForExecutableMongoScripts() { Assert.assertThat(captor.getValue().getName(), notNullValue()); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void executeShouldThrowExceptionWhenScriptIsNull() { scriptOps.execute(null); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void existsShouldThrowExceptionWhenScriptNameIsNull() { scriptOps.exists(null); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void existsShouldThrowExceptionWhenScriptNameIsEmpty() { scriptOps.exists(""); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void callShouldThrowExceptionWhenScriptNameIsNull() { scriptOps.call(null); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void callShouldThrowExceptionWhenScriptNameIsEmpty() { scriptOps.call(""); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/GeoCommandStatisticsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/GeoCommandStatisticsUnitTests.java index 7fd4a093e1..ec4d05e091 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/GeoCommandStatisticsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/GeoCommandStatisticsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -30,18 +30,12 @@ */ public class GeoCommandStatisticsUnitTests { - /** - * @see DATAMONGO-1361 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1361 public void rejectsNullCommandResult() { GeoCommandStatistics.from(null); } - /** - * @see DATAMONGO-1361 - */ - @Test + @Test // DATAMONGO-1361 public void fallsBackToNanIfNoAverageDistanceIsAvailable() { GeoCommandStatistics statistics = GeoCommandStatistics.from(new BasicDBObject("stats", null)); @@ -51,10 +45,7 @@ public void fallsBackToNanIfNoAverageDistanceIsAvailable() { assertThat(statistics.getAverageDistance(), is(Double.NaN)); } - /** - * @see DATAMONGO-1361 - */ - @Test + @Test // DATAMONGO-1361 public void returnsAverageDistanceIfPresent() { GeoCommandStatistics statistics = GeoCommandStatistics diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientOptionsFactoryBeanIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientOptionsFactoryBeanIntegrationTests.java index 7bd5388d56..ab58426464 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientOptionsFactoryBeanIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoClientOptionsFactoryBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -33,10 +33,7 @@ */ public class MongoClientOptionsFactoryBeanIntegrationTests { - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void convertsReadPreferenceConcernCorrectly() { RootBeanDefinition definition = new RootBeanDefinition(MongoClientOptionsFactoryBean.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsIntegrationTests.java index f2032acc57..f292fd842d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 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. @@ -89,10 +89,7 @@ public Void doInDB(DB db) throws MongoException, DataAccessException { }); } - /** - * @see DATAMONGO-585 - */ - @Test + @Test // DATAMONGO-585 public void authenticatesCorrectlyInMultithreadedEnvironment() throws Exception { // Create sample user @@ -131,10 +128,7 @@ public Void call() throws Exception { } } - /** - * @see DATAMONGO-789 - */ - @Test + @Test // DATAMONGO-789 public void authenticatesCorrectlyWithAuthenticationDB() throws Exception { // Create sample user diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java index b7cf04196c..e92d77294c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoDbUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 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. @@ -89,10 +89,7 @@ public void returnsSameInstanceForSameDatabaseName() { assertThat(MongoDbUtils.getDB(mongo, "first"), is(sameInstance(first))); } - /** - * @see DATAMONGO-737 - */ - @Test + @Test // DATAMONGO-737 public void handlesTransactionSynchronizationLifecycle() { // ensure transaction synchronization manager has no registered @@ -121,10 +118,7 @@ public void handlesTransactionSynchronizationLifecycle() { assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(true)); } - /** - * @see DATAMONGO-737 - */ - @Test + @Test // DATAMONGO-737 public void handlesTransactionSynchronizationsLifecycle() { // ensure transaction synchronization manager has no registered @@ -154,10 +148,7 @@ public void handlesTransactionSynchronizationsLifecycle() { assertThat(TransactionSynchronizationManager.getResourceMap().isEmpty(), is(true)); } - /** - * @see DATAMONGO-1218 - */ - @Test + @Test // DATAMONGO-1218 @SuppressWarnings("deprecation") public void getDBDAuthenticateViaAuthDbWhenCalledWithMongoInstance() { @@ -174,10 +165,7 @@ public void getDBDAuthenticateViaAuthDbWhenCalledWithMongoInstance() { verify(mongo, times(1)).getDB("authdb"); } - /** - * @see DATAMONGO-1218 - */ - @Test + @Test // DATAMONGO-1218 @SuppressWarnings("deprecation") public void getDBDShouldSkipAuthenticationViaAuthDbWhenCalledWithMongoClientInstance() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoFactoryBeanIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoFactoryBeanIntegrationTests.java index 1d60455231..f31205b397 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoFactoryBeanIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoFactoryBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2017 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. @@ -37,10 +37,7 @@ */ public class MongoFactoryBeanIntegrationTests { - /** - * @see DATAMONGO-408 - */ - @Test + @Test // DATAMONGO-408 public void convertsWriteConcernCorrectly() { RootBeanDefinition definition = new RootBeanDefinition(MongoFactoryBean.class); @@ -54,10 +51,7 @@ public void convertsWriteConcernCorrectly() { assertThat(ReflectionTestUtils.getField(bean, "writeConcern"), is((Object) WriteConcern.SAFE)); } - /** - * @see DATAMONGO-693 - */ - @Test + @Test // DATAMONGO-693 public void createMongoInstanceWithHostAndEmptyReplicaSets() { RootBeanDefinition definition = new RootBeanDefinition(MongoFactoryBean.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOperationsUnitTests.java index 9614330608..9d2d82ab04 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOperationsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 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. @@ -302,10 +302,7 @@ public void doWith(MongoOperations operations) { }.assertDataAccessException(); } - /** - * @see DATAMONGO-341 - */ - @Test + @Test // DATAMONGO-341 public void geoNearRejectsNullNearQuery() { new Execution() { @@ -316,10 +313,7 @@ public void doWith(MongoOperations operations) { }.assertDataAccessException(); } - /** - * @see DATAMONGO-341 - */ - @Test + @Test // DATAMONGO-341 public void geoNearRejectsNullNearQueryifCollectionGiven() { new Execution() { @@ -330,10 +324,7 @@ public void doWith(MongoOperations operations) { }.assertDataAccessException(); } - /** - * @see DATAMONGO-341 - */ - @Test + @Test // DATAMONGO-341 public void geoNearRejectsNullEntityClass() { final NearQuery query = NearQuery.near(new Point(10, 20)); @@ -346,10 +337,7 @@ public void doWith(MongoOperations operations) { }.assertDataAccessException(); } - /** - * @see DATAMONGO-341 - */ - @Test + @Test // DATAMONGO-341 public void geoNearRejectsNullEntityClassIfCollectionGiven() { final NearQuery query = NearQuery.near(new Point(10, 20)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOptionsFactoryBeanUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOptionsFactoryBeanUnitTests.java index 19db55a393..c490ec5671 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOptionsFactoryBeanUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoOptionsFactoryBeanUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -43,11 +43,7 @@ public static void validateMongoDriver() { assumeFalse(isMongo3Driver()); } - /** - * @throws Exception - * @see DATADOC-280 - */ - @Test + @Test // DATADOC-280 public void setsMaxConnectRetryTime() throws Exception { MongoOptionsFactoryBean bean = new MongoOptionsFactoryBean(); @@ -58,11 +54,7 @@ public void setsMaxConnectRetryTime() throws Exception { assertThat(getMaxAutoConnectRetryTime(options), is(27L)); } - /** - * @throws Exception - * @see DATAMONGO-764 - */ - @Test + @Test // DATAMONGO-764 public void testSslConnection() throws Exception { MongoOptionsFactoryBean bean = new MongoOptionsFactoryBean(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index 385b501ec6..c68cc8e41e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -243,10 +243,7 @@ public void bogusUpdateDoesNotTriggerException() throws Exception { mongoTemplate.updateFirst(q, u, Person.class); } - /** - * @see DATAMONGO-480 - */ - @Test + @Test // DATAMONGO-480 public void throwsExceptionForDuplicateIds() { MongoTemplate template = new MongoTemplate(factory); @@ -265,11 +262,7 @@ public void throwsExceptionForDuplicateIds() { } } - /** - * @see DATAMONGO-480 - * @see DATAMONGO-799 - */ - @Test + @Test // DATAMONGO-480, DATAMONGO-799 public void throwsExceptionForUpdateWithInvalidPushOperator() { MongoTemplate template = new MongoTemplate(factory); @@ -291,10 +284,7 @@ public void throwsExceptionForUpdateWithInvalidPushOperator() { template.updateFirst(query, upd, Person.class); } - /** - * @see DATAMONGO-480 - */ - @Test + @Test // DATAMONGO-480 public void throwsExceptionForIndexViolationIfConfigured() { MongoTemplate template = new MongoTemplate(factory); @@ -317,10 +307,7 @@ public void throwsExceptionForIndexViolationIfConfigured() { } } - /** - * @see DATAMONGO-480 - */ - @Test + @Test // DATAMONGO-480 public void rejectsDuplicateIdInInsertAll() { thrown.expect(DataIntegrityViolationException.class); @@ -394,10 +381,7 @@ public void testEnsureIndex() throws Exception { assertThat(field, is(IndexField.create("age", Direction.DESC))); } - /** - * @see DATAMONGO-746 - */ - @Test + @Test // DATAMONGO-746 public void testReadIndexInfoForIndicesCreatedViaMongoShellCommands() throws Exception { String command = "db." + template.getCollectionName(Person.class) @@ -578,9 +562,7 @@ private void testProperHandlingOfDifferentIdTypes(MongoTemplate mongoTemplate) t assertThat(p9q.getId(), is(p9.getId())); checkCollectionContents(PersonWithIdPropertyOfTypeInteger.class, 1); - /* - * @see DATAMONGO-602 - */ + // DATAMONGO-602 // BigInteger id - provided PersonWithIdPropertyOfTypeBigInteger p9bi = new PersonWithIdPropertyOfTypeBigInteger(); p9bi.setFirstName("Sven_9bi"); @@ -650,10 +632,7 @@ private void checkCollectionContents(Class entityClass, int count) { assertThat(template.findAll(entityClass).size(), is(count)); } - /** - * @see DATAMONGO-234 - */ - @Test + @Test // DATAMONGO-234 public void testFindAndUpdate() { template.insert(new Person("Tom", 21)); @@ -821,10 +800,7 @@ public void testUsingAnInQueryWithLongId() throws Exception { assertThat(results3.size(), is(2)); } - /** - * @see DATAMONGO-602 - */ - @Test + @Test // DATAMONGO-602 public void testUsingAnInQueryWithBigIntegerId() throws Exception { template.remove(new Query(), PersonWithIdPropertyOfTypeBigInteger.class); @@ -1141,18 +1117,12 @@ public Object doInCollection(DBCollection collection) throws MongoException, Dat }); } - /** - * @see DATADOC-166 - */ - @Test + @Test // DATADOC-166 public void removingNullIsANoOp() { template.remove(null); } - /** - * @see DATADOC-240, DATADOC-212 - */ - @Test + @Test // DATADOC-240, DATADOC-212 public void updatesObjectIdsCorrectly() { PersonWithIdPropertyOfTypeObjectId person = new PersonWithIdPropertyOfTypeObjectId(); @@ -1211,10 +1181,7 @@ public MongoAction getMongoAction() { } } - /** - * @see DATADOC-246 - */ - @Test + @Test // DATADOC-246 public void updatesDBRefsCorrectly() { DBRef first = new DBRef("foo", new ObjectId()); @@ -1227,10 +1194,7 @@ class ClassWithDBRefs { List dbrefs; } - /** - * @see DATADOC-202 - */ - @Test + @Test // DATADOC-202 public void executeDocument() { template.insert(new Person("Tom")); template.insert(new Person("Dick")); @@ -1248,10 +1212,7 @@ public void processDocument(DBObject dbObject) { // template.remove(new Query(), Person.class); } - /** - * @see DATADOC-202 - */ - @Test + @Test // DATADOC-202 public void executeDocumentWithCursorPreparer() { template.insert(new Person("Tom")); template.insert(new Person("Dick")); @@ -1276,10 +1237,7 @@ public DBCursor prepare(DBCursor cursor) { // template.remove(new Query(), Person.class); } - /** - * @see DATADOC-183 - */ - @Test + @Test // DATADOC-183 public void countsDocumentsCorrectly() { assertThat(template.count(new Query(), Person.class), is(0L)); @@ -1294,26 +1252,17 @@ public void countsDocumentsCorrectly() { assertThat(template.count(query(where("firstName").is("Carter")), Person.class), is(1L)); } - /** - * @see DATADOC-183 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATADOC-183 public void countRejectsNullEntityClass() { template.count(null, (Class) null); } - /** - * @see DATADOC-183 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATADOC-183 public void countRejectsEmptyCollectionName() { template.count(null, ""); } - /** - * @see DATADOC-183 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATADOC-183 public void countRejectsNullCollectionName() { template.count(null, (String) null); } @@ -1331,10 +1280,7 @@ public void returnsEntityWhenQueryingForDateTime() { assertThat(testClassList.get(0).myDate, is(testClass.myDate)); } - /** - * @see DATADOC-230 - */ - @Test + @Test // DATADOC-230 public void removesEntityFromCollection() { template.remove(new Query(), "mycollection"); @@ -1348,10 +1294,7 @@ public void removesEntityFromCollection() { assertThat(template.findAll(Person.class, "mycollection").isEmpty(), is(true)); } - /** - * @see DATADOC-349 - */ - @Test + @Test // DATADOC-349 public void removesEntityWithAnnotatedIdIfIdNeedsMassaging() { String id = new ObjectId().toString(); @@ -1367,10 +1310,7 @@ public void removesEntityWithAnnotatedIdIfIdNeedsMassaging() { assertThat(template.findOne(query(where("id").is(id)), Sample.class), is(nullValue())); } - /** - * @see DATAMONGO-423 - */ - @Test + @Test // DATAMONGO-423 public void executesQueryWithNegatedRegexCorrectly() { Sample first = new Sample(); @@ -1389,10 +1329,7 @@ public void executesQueryWithNegatedRegexCorrectly() { assertThat(result.get(0).field, is("Beauford")); } - /** - * @see DATAMONGO-447 - */ - @Test + @Test // DATAMONGO-447 public void storesAndRemovesTypeWithComplexId() { MyId id = new MyId(); @@ -1406,10 +1343,7 @@ public void storesAndRemovesTypeWithComplexId() { template.remove(query(where("id").is(id)), TypeWithMyId.class); } - /** - * @see DATAMONGO-506 - */ - @Test + @Test // DATAMONGO-506 public void exceutesBasicQueryCorrectly() { Address address = new Address(); @@ -1435,10 +1369,7 @@ public void exceutesBasicQueryCorrectly() { assertThat(result.get(0), hasProperty("name", is("Oleg"))); } - /** - * @see DATAMONGO-279 - */ - @Test(expected = OptimisticLockingFailureException.class) + @Test(expected = OptimisticLockingFailureException.class) // DATAMONGO-279 public void optimisticLockingHandling() { // Init version @@ -1473,10 +1404,7 @@ public void optimisticLockingHandling() { template.save(person); } - /** - * @see DATAMONGO-562 - */ - @Test + @Test // DATAMONGO-562 public void optimisticLockingHandlingWithExistingId() { PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger(); @@ -1486,10 +1414,7 @@ public void optimisticLockingHandlingWithExistingId() { template.save(person); } - /** - * @see DATAMONGO-617 - */ - @Test + @Test // DATAMONGO-617 public void doesNotFailOnVersionInitForUnversionedEntity() { DBObject dbObject = new BasicDBObject(); @@ -1498,10 +1423,7 @@ public void doesNotFailOnVersionInitForUnversionedEntity() { template.insert(dbObject, template.determineCollectionName(PersonWithVersionPropertyOfTypeInteger.class)); } - /** - * @see DATAMONGO-539 - */ - @Test + @Test // DATAMONGO-539 public void removesObjectFromExplicitCollection() { String collectionName = "explicit"; @@ -1516,9 +1438,7 @@ public void removesObjectFromExplicitCollection() { assertThat(template.findAll(PersonWithConvertedId.class, collectionName).isEmpty(), is(true)); } - /** - * @see DATAMONGO-549 - */ + // DATAMONGO-549 public void savesMapCorrectly() { Map map = new HashMap(); @@ -1527,26 +1447,17 @@ public void savesMapCorrectly() { template.save(map, "maps"); } - /** - * @see DATAMONGO-549 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-549 public void savesMongoPrimitiveObjectCorrectly() { template.save(new Object(), "collection"); } - /** - * @see DATAMONGO-549 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-549 public void rejectsNullObjectToBeSaved() { template.save(null); } - /** - * @see DATAMONGO-550 - */ - @Test + @Test // DATAMONGO-550 public void savesPlainDbObjectCorrectly() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -1555,10 +1466,7 @@ public void savesPlainDbObjectCorrectly() { assertThat(dbObject.containsField("_id"), is(true)); } - /** - * @see DATAMONGO-550 - */ - @Test(expected = InvalidDataAccessApiUsageException.class) + @Test(expected = InvalidDataAccessApiUsageException.class) // DATAMONGO-550 public void rejectsPlainObjectWithOutExplicitCollection() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -1567,10 +1475,7 @@ public void rejectsPlainObjectWithOutExplicitCollection() { template.findById(dbObject.get("_id"), DBObject.class); } - /** - * @see DATAMONGO-550 - */ - @Test + @Test // DATAMONGO-550 public void readsPlainDbObjectById() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -1581,26 +1486,17 @@ public void readsPlainDbObjectById() { assertThat(result.get("_id"), is(dbObject.get("_id"))); } - /** - * @see DATAMONGO-551 - */ - @Test + @Test // DATAMONGO-551 public void writesPlainString() { template.save("{ 'foo' : 'bar' }", "collection"); } - /** - * @see DATAMONGO-551 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-551 public void rejectsNonJsonStringForSave() { template.save("Foobar!", "collection"); } - /** - * @see DATAMONGO-588 - */ - @Test + @Test // DATAMONGO-588 public void initializesVersionOnInsert() { PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger(); @@ -1611,10 +1507,7 @@ public void initializesVersionOnInsert() { assertThat(person.version, is(0)); } - /** - * @see DATAMONGO-588 - */ - @Test + @Test // DATAMONGO-588 public void initializesVersionOnBatchInsert() { PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger(); @@ -1625,20 +1518,14 @@ public void initializesVersionOnBatchInsert() { assertThat(person.version, is(0)); } - /** - * @see DATAMONGO-568 - */ - @Test + @Test // DATAMONGO-568 public void queryCantBeNull() { List result = template.findAll(PersonWithIdPropertyOfTypeObjectId.class); assertThat(template.find(null, PersonWithIdPropertyOfTypeObjectId.class), is(result)); } - /** - * @see DATAMONGO-620 - */ - @Test + @Test // DATAMONGO-620 public void versionsObjectIntoDedicatedCollection() { PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger(); @@ -1651,10 +1538,7 @@ public void versionsObjectIntoDedicatedCollection() { assertThat(person.version, is(1)); } - /** - * @see DATAMONGO-621 - */ - @Test + @Test // DATAMONGO-621 public void correctlySetsLongVersionProperty() { PersonWithVersionPropertyOfTypeLong person = new PersonWithVersionPropertyOfTypeLong(); @@ -1664,10 +1548,7 @@ public void correctlySetsLongVersionProperty() { assertThat(person.version, is(0L)); } - /** - * @see DATAMONGO-622 - */ - @Test(expected = DuplicateKeyException.class) + @Test(expected = DuplicateKeyException.class) // DATAMONGO-622 public void preventsDuplicateInsert() { template.setWriteConcern(WriteConcern.SAFE); @@ -1682,10 +1563,7 @@ public void preventsDuplicateInsert() { template.save(person); } - /** - * @see DATAMONGO-629 - */ - @Test + @Test // DATAMONGO-629 public void countAndFindWithoutTypeInformation() { Person person = new Person(); @@ -1698,10 +1576,7 @@ public void countAndFindWithoutTypeInformation() { assertThat(template.count(query, collectionName), is(1L)); } - /** - * @see DATAMONGO-571 - */ - @Test + @Test // DATAMONGO-571 public void nullsPropertiesForVersionObjectUpdates() { VersionedPerson person = new VersionedPerson(); @@ -1718,10 +1593,7 @@ public void nullsPropertiesForVersionObjectUpdates() { assertThat(person.lastname, is(nullValue())); } - /** - * @see DATAMONGO-571 - */ - @Test + @Test // DATAMONGO-571 public void nullsValuesForUpdatesOfUnversionedEntity() { Person person = new Person("Dave"); @@ -1734,10 +1606,7 @@ public void nullsValuesForUpdatesOfUnversionedEntity() { assertThat(person.getFirstName(), is(nullValue())); } - /** - * @see DATAMONGO-679 - */ - @Test + @Test // DATAMONGO-679 public void savesJsonStringCorrectly() { DBObject dbObject = new BasicDBObject().append("first", "first").append("second", "second"); @@ -1762,10 +1631,7 @@ public void executesExistsCorrectly() { assertThat(template.exists(query, Sample.class, template.getCollectionName(Sample.class)), is(true)); } - /** - * @see DATAMONGO-675 - */ - @Test + @Test // DATAMONGO-675 public void updateConsidersMappingAnnotations() { TypeWithFieldAnnotation entity = new TypeWithFieldAnnotation(); @@ -1781,10 +1647,7 @@ public void updateConsidersMappingAnnotations() { assertThat(result.emailAddress, is("new")); } - /** - * @see DATAMONGO-671 - */ - @Test + @Test // DATAMONGO-671 public void findsEntityByDateReference() { TypeWithDate entity = new TypeWithDate(); @@ -1798,10 +1661,7 @@ public void findsEntityByDateReference() { assertThat(result.get(0).date, is(notNullValue())); } - /** - * @see DATAMONGO-540 - */ - @Test + @Test // DATAMONGO-540 public void findOneAfterUpsertForNonExistingObjectReturnsTheInsertedObject() { String idValue = "4711"; @@ -1818,10 +1678,7 @@ public void findOneAfterUpsertForNonExistingObjectReturnsTheInsertedObject() { assertThat(result.id, is(idValue)); } - /** - * @see DATAMONGO-392 - */ - @Test + @Test // DATAMONGO-392 public void updatesShouldRetainTypeInformation() { Document doc = new Document(); @@ -1842,10 +1699,7 @@ public void updatesShouldRetainTypeInformation() { assertThat(result.model.value(), is(newModelValue)); } - /** - * @see DATAMONGO-702 - */ - @Test + @Test // DATAMONGO-702 public void queryShouldSupportRealAndAliasedPropertyNamesForFieldInclusions() { ObjectWith3AliasedFields obj = new ObjectWith3AliasedFields(); @@ -1869,10 +1723,7 @@ public void queryShouldSupportRealAndAliasedPropertyNamesForFieldInclusions() { assertThat(result.property3, is(obj.property3)); } - /** - * @see DATAMONGO-702 - */ - @Test + @Test // DATAMONGO-702 public void queryShouldSupportRealAndAliasedPropertyNamesForFieldExclusions() { ObjectWith3AliasedFields obj = new ObjectWith3AliasedFields(); @@ -1896,10 +1747,7 @@ public void queryShouldSupportRealAndAliasedPropertyNamesForFieldExclusions() { assertThat(result.property3, is(nullValue())); } - /** - * @see DATAMONGO-702 - */ - @Test + @Test // DATAMONGO-702 public void findMultipleWithQueryShouldSupportRealAndAliasedPropertyNamesForFieldExclusions() { ObjectWith3AliasedFields obj0 = new ObjectWith3AliasedFields(); @@ -1941,10 +1789,7 @@ public void findMultipleWithQueryShouldSupportRealAndAliasedPropertyNamesForFiel assertThat(result1.property3, is(nullValue())); } - /** - * @see DATAMONGO-702 - */ - @Test + @Test // DATAMONGO-702 public void queryShouldSupportNestedPropertyNamesForFieldInclusions() { ObjectWith3AliasedFieldsAndNestedAddress obj = new ObjectWith3AliasedFieldsAndNestedAddress(); @@ -1977,10 +1822,7 @@ public void queryShouldSupportNestedPropertyNamesForFieldInclusions() { assertThat(result.address.state, is(stateValue)); } - /** - * @see DATAMONGO-709 - */ - @Test + @Test // DATAMONGO-709 public void aQueryRestrictedWithOneRestrictedResultTypeShouldReturnOnlyInstancesOfTheRestrictedType() { BaseDoc doc0 = new BaseDoc(); @@ -2006,10 +1848,7 @@ public void aQueryRestrictedWithOneRestrictedResultTypeShouldReturnOnlyInstances assertThat(result.get(0), is(instanceOf(SpecialDoc.class))); } - /** - * @see DATAMONGO-709 - */ - @Test + @Test // DATAMONGO-709 public void aQueryRestrictedWithMultipleRestrictedResultTypesShouldReturnOnlyInstancesOfTheRestrictedTypes() { BaseDoc doc0 = new BaseDoc(); @@ -2036,10 +1875,7 @@ public void aQueryRestrictedWithMultipleRestrictedResultTypesShouldReturnOnlyIns assertThat(result.get(1).getClass(), is((Object) VerySpecialDoc.class)); } - /** - * @see DATAMONGO-709 - */ - @Test + @Test // DATAMONGO-709 public void aQueryWithNoRestrictedResultTypesShouldReturnAllInstancesWithinTheGivenCollection() { BaseDoc doc0 = new BaseDoc(); @@ -2067,10 +1903,7 @@ public void aQueryWithNoRestrictedResultTypesShouldReturnAllInstancesWithinTheGi assertThat(result.get(2).getClass(), is((Object) VerySpecialDoc.class)); } - /** - * @see DATAMONGO-771 - */ - @Test + @Test // DATAMONGO-771 public void allowInsertWithPlainJsonString() { String id = "4711"; @@ -2085,10 +1918,7 @@ public void allowInsertWithPlainJsonString() { assertThat(result.get(0).field, is(value)); } - /** - * @see DATAMONGO-816 - */ - @Test + @Test // DATAMONGO-816 public void shouldExecuteQueryShouldMapQueryBeforeQueryExecution() { ObjectWithEnumValue o = new ObjectWithEnumValue(); @@ -2112,10 +1942,7 @@ public void processDocument(DBObject dbObject) throws MongoException, DataAccess }); } - /** - * @see DATAMONGO-811 - */ - @Test + @Test // DATAMONGO-811 public void updateFirstShouldIncreaseVersionForVersionedEntity() { VersionedPerson person = new VersionedPerson(); @@ -2135,10 +1962,7 @@ public void updateFirstShouldIncreaseVersionForVersionedEntity() { assertThat(personAfterUpdateFirst.lastname, is("Bubu")); } - /** - * @see DATAMONGO-811 - */ - @Test + @Test // DATAMONGO-811 public void updateFirstShouldIncreaseVersionOnlyForFirstMatchingEntity() { VersionedPerson person1 = new VersionedPerson(); @@ -2162,10 +1986,7 @@ public void updateFirstShouldIncreaseVersionOnlyForFirstMatchingEntity() { } } - /** - * @see DATAMONGO-811 - */ - @Test + @Test // DATAMONGO-811 public void updateMultiShouldIncreaseVersionOfAllUpdatedEntities() { VersionedPerson person1 = new VersionedPerson(); @@ -2185,10 +2006,7 @@ public void updateMultiShouldIncreaseVersionOfAllUpdatedEntities() { } } - /** - * @see DATAMONGO-686 - */ - @Test + @Test // DATAMONGO-686 public void itShouldBePossibleToReuseAnExistingQuery() { Sample sample = new Sample(); @@ -2208,10 +2026,7 @@ public void itShouldBePossibleToReuseAnExistingQuery() { assertThat(template.find(query, Sample.class), is(not(empty()))); } - /** - * @see DATAMONGO-807 - */ - @Test + @Test // DATAMONGO-807 public void findAndModifyShouldRetrainTypeInformationWithinUpdatedType() { Document document = new Document(); @@ -2228,10 +2043,7 @@ public void findAndModifyShouldRetrainTypeInformationWithinUpdatedType() { assertThat(retrieved.model.value(), equalTo("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollectionWhenWholeCollectionIsReplaced() { DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); @@ -2265,10 +2077,7 @@ public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentW assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentWithNestedCollectionWhenFirstElementIsReplaced() { DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); @@ -2302,10 +2111,7 @@ public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnDocumentW assertThat(retrieved.models.get(0).get("key2").value(), equalTo("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldAddTypeInformationOnDocumentWithNestedCollectionObjectInsertedAtSecondIndex() { DocumentWithNestedCollection doc = new DocumentWithNestedCollection(); @@ -2338,10 +2144,7 @@ public void findAndModifyShouldAddTypeInformationOnDocumentWithNestedCollectionO assertThat(retrieved.models.get(1).get("key2").value(), equalTo("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenUpdatingPositionedElement() throws Exception { @@ -2368,10 +2171,7 @@ public void findAndModifyShouldRetainTypeInformationWithinUpdatedTypeOnEmbeddedD assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenUpdatingSecondElement() throws Exception { @@ -2399,10 +2199,7 @@ public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocu assertThat(retrieved.embeddedDocument.models.get(1).value(), is("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocumentWithCollectionWhenRewriting() throws Exception { @@ -2429,10 +2226,7 @@ public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnEmbeddedDocu assertThat(retrieved.embeddedDocument.models.get(0).value(), is("value2")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnDocumentWithNestedLists() { DocumentWithNestedList doc = new DocumentWithNestedList(); @@ -2465,10 +2259,7 @@ public void findAndModifyShouldAddTypeInformationWithinUpdatedTypeOnDocumentWith assertThat(retrieved.models.get(0).get(1).value(), equalTo("value2")); } - /** - * @see DATAMONGO-407 - */ - @Test + @Test // DATAMONGO-407 public void updatesShouldRetainTypeInformationEvenForCollections() { List models = Arrays. asList(new ModelA("foo")); @@ -2493,10 +2284,7 @@ public void updatesShouldRetainTypeInformationEvenForCollections() { assertThat(result.models.get(0).value(), is(newModelValue)); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithComplexTypes() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR), is(true)); @@ -2512,10 +2300,7 @@ public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithComplexTypes assertThat(template.findOne(query, DocumentWithCollection.class).models, hasSize(3)); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithSimpleTypes() { assumeThat(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR), is(true)); @@ -2533,10 +2318,7 @@ public void updateMultiShouldAddValuesCorrectlyWhenUsingPushEachWithSimpleTypes( assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3)); } - /** - * @see DATAMONOGO-828 - */ - @Test + @Test // DATAMONOGO-828 public void updateFirstShouldDoNothingWhenCalledForEntitiesThatDoNotExist() { Query q = query(where("id").is(Long.MIN_VALUE)); @@ -2545,10 +2327,7 @@ public void updateFirstShouldDoNothingWhenCalledForEntitiesThatDoNotExist() { assertThat(template.findOne(q, VersionedPerson.class), nullValue()); } - /** - * @see DATAMONGO-354 - */ - @Test + @Test // DATAMONGO-354 public void testUpdateShouldAllowMultiplePushAll() { DocumentWithMultipleCollections doc = new DocumentWithMultipleCollections(); @@ -2570,10 +2349,7 @@ public void testUpdateShouldAllowMultiplePushAll() { } - /** - * @see DATAMONGO-404 - */ - @Test + @Test // DATAMONGO-404 public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollection() { Sample sample1 = new Sample("1", "A"); @@ -2602,10 +2378,7 @@ public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollection() { assertThat(result.dbRefAnnotatedList.get(0).id, is((Object) "1")); } - /** - * @see DATAMONGO-404 - */ - @Test + @Test // DATAMONGO-404 public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollectionWhenGivenAnIdValueOfComponentTypeEntity() { Sample sample1 = new Sample("1", "A"); @@ -2634,10 +2407,7 @@ public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollectionWhen assertThat(result.dbRefAnnotatedList.get(0).id, is((Object) "1")); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void updateShouldNotBumpVersionNumberIfVersionPropertyIncludedInUpdate() { VersionedPerson person = new VersionedPerson(); @@ -2657,10 +2427,7 @@ public void updateShouldNotBumpVersionNumberIfVersionPropertyIncludedInUpdate() assertThat(personAfterUpdateFirst.lastname, is("Bubu")); } - /** - * @see DATAMONGO-468 - */ - @Test + @Test // DATAMONGO-468 public void shouldBeAbleToUpdateDbRefPropertyWithDomainObject() { Sample sample1 = new Sample("1", "A"); @@ -2686,10 +2453,7 @@ public void shouldBeAbleToUpdateDbRefPropertyWithDomainObject() { assertThat(updatedDoc.dbRefProperty.field, is(sample2.field)); } - /** - * @see DATAMONGO-862 - */ - @Test + @Test // DATAMONGO-862 public void testUpdateShouldWorkForPathsOnInterfaceMethods() { DocumentWithCollection document = new DocumentWithCollection(Arrays. asList(new ModelA("spring"), @@ -2705,10 +2469,7 @@ public void testUpdateShouldWorkForPathsOnInterfaceMethods() { assertThat(result.models.get(0).value(), is("mongodb")); } - /** - * @see DATAMONGO-773 - */ - @Test + @Test // DATAMONGO-773 public void testShouldSupportQueryWithIncludedDbRefField() { Sample sample = new Sample("47111", "foo"); @@ -2732,10 +2493,7 @@ public void testShouldSupportQueryWithIncludedDbRefField() { assertThat(result.get(0).dbRefProperty.field, is(sample.field)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void testFindAllAndRemoveFullyReturnsAndRemovesDocuments() { Sample spring = new Sample("100", "spring"); @@ -2755,10 +2513,7 @@ public void testFindAllAndRemoveFullyReturnsAndRemovesDocuments() { assertThat(template.getDb().getCollection("sample").find(new BasicDBObject("field", "data")).count(), is(1)); } - /** - * @see DATAMONGO-1001 - */ - @Test + @Test // DATAMONGO-1001 public void shouldAllowSavingOfLazyLoadedDbRefs() { template.dropCollection(SomeTemplate.class); @@ -2786,10 +2541,7 @@ public void shouldAllowSavingOfLazyLoadedDbRefs() { } - /** - * @see DATAMONGO-880 - */ - @Test + @Test // DATAMONGO-880 public void savingAndReassigningLazyLoadingProxies() { template.dropCollection(SomeTemplate.class); @@ -2822,10 +2574,7 @@ public void savingAndReassigningLazyLoadingProxies() { assertThat(savedMessage.normalContent.text, is(content.text)); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void callingNonObjectMethodsOnLazyLoadingProxyShouldReturnNullIfUnderlyingDbrefWasDeletedInbetween() { template.dropCollection(SomeTemplate.class); @@ -2851,10 +2600,7 @@ public void callingNonObjectMethodsOnLazyLoadingProxyShouldReturnNullIfUnderlyin assertThat(savedTmpl.getContent().getText(), is(nullValue())); } - /** - * @see DATAMONGO-471 - */ - @Test + @Test // DATAMONGO-471 public void updateMultiShouldAddValuesCorrectlyWhenUsingAddToSetWithEach() { DocumentWithCollectionOfSimpleType document = new DocumentWithCollectionOfSimpleType(); @@ -2870,10 +2616,7 @@ public void updateMultiShouldAddValuesCorrectlyWhenUsingAddToSetWithEach() { assertThat(template.findOne(query, DocumentWithCollectionOfSimpleType.class).values, hasSize(3)); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void findAndModifyAddToSetWithEachShouldNotAddDuplicatesNorTypeHintForSimpleDocuments() { DocumentWithCollectionOfSamples doc = new DocumentWithCollectionOfSamples(); @@ -2897,10 +2640,7 @@ public void findAndModifyAddToSetWithEachShouldNotAddDuplicatesNorTypeHintForSim assertThat(retrieved.samples.get(1).field, is("sample2")); } - /** - * @see DATAMONGO-888 - */ - @Test + @Test // DATAMONGO-888 public void sortOnIdFieldPropertyShouldBeMappedCorrectly() { DoucmentWithNamedIdField one = new DoucmentWithNamedIdField(); @@ -2918,10 +2658,7 @@ public void sortOnIdFieldPropertyShouldBeMappedCorrectly() { assertThat(template.find(query, DoucmentWithNamedIdField.class), contains(two, one)); } - /** - * @see DATAMONGO-888 - */ - @Test + @Test // DATAMONGO-888 public void sortOnAnnotatedFieldPropertyShouldBeMappedCorrectly() { DoucmentWithNamedIdField one = new DoucmentWithNamedIdField(); @@ -2939,10 +2676,7 @@ public void sortOnAnnotatedFieldPropertyShouldBeMappedCorrectly() { assertThat(template.find(query, DoucmentWithNamedIdField.class), contains(two, one)); } - /** - * @see DATAMONGO-913 - */ - @Test + @Test // DATAMONGO-913 public void shouldRetrieveInitializedValueFromDbRefAssociationAfterLoad() { SomeContent content = new SomeContent(); @@ -2967,10 +2701,7 @@ public void shouldRetrieveInitializedValueFromDbRefAssociationAfterLoad() { assertThat(result.getContent().getText(), is(content.getText())); } - /** - * @see DATAMONGO-913 - */ - @Test + @Test // DATAMONGO-913 public void shouldReuseExistingDBRefInQueryFromDbRefAssociationAfterLoad() { SomeContent content = new SomeContent(); @@ -2995,10 +2726,7 @@ public void shouldReuseExistingDBRefInQueryFromDbRefAssociationAfterLoad() { assertThat(result.getContent().getName(), is(content.getName())); } - /** - * @see DATAMONGO-970 - */ - @Test + @Test // DATAMONGO-970 public void insertsAndRemovesBasicDbObjectCorrectly() { BasicDBObject object = new BasicDBObject("key", "value"); @@ -3011,10 +2739,7 @@ public void insertsAndRemovesBasicDbObjectCorrectly() { assertThat(template.findAll(DBObject.class, "collection"), hasSize(0)); } - /** - * @see DATAMONGO-1207 - */ - @Test + @Test // DATAMONGO-1207 public void ignoresNullElementsForInsertAll() { Address newYork = new Address("NY", "New York"); @@ -3028,10 +2753,7 @@ public void ignoresNullElementsForInsertAll() { assertThat(result, hasItems(newYork, washington)); } - /** - * @see DATAMONGO-1208 - */ - @Test + @Test // DATAMONGO-1208 public void takesSortIntoAccountWhenStreaming() { Person youngestPerson = new Person("John", 20); @@ -3047,10 +2769,7 @@ public void takesSortIntoAccountWhenStreaming() { assertThat(stream.next().getAge(), is(oldestPerson.getAge())); } - /** - * @see DATAMONGO-1208 - */ - @Test + @Test // DATAMONGO-1208 public void takesLimitIntoAccountWhenStreaming() { Person youngestPerson = new Person("John", 20); @@ -3066,10 +2785,7 @@ public void takesLimitIntoAccountWhenStreaming() { assertThat(stream.hasNext(), is(false)); } - /** - * @see DATAMONGO-1204 - */ - @Test + @Test // DATAMONGO-1204 public void resolvesCyclicDBRefCorrectly() { SomeMessage message = new SomeMessage(); @@ -3091,10 +2807,7 @@ public void resolvesCyclicDBRefCorrectly() { assertThat(contentLoaded.dbrefMessage.id, is(messageLoaded.id)); } - /** - * @see DATAMONGO-1287 - */ - @Test + @Test // DATAMONGO-1287 public void shouldReuseAlreadyResolvedLazyLoadedDBRefWhenUsedAsPersistenceConstrcutorArgument() { Document docInCtor = new Document(); @@ -3112,10 +2825,7 @@ public void shouldReuseAlreadyResolvedLazyLoadedDBRefWhenUsedAsPersistenceConstr assertThat(loaded.refToDocNotUsedInCtor, nullValue()); } - /** - * @see DATAMONGO-1287 - */ - @Test + @Test // DATAMONGO-1287 public void shouldNotReuseLazyLoadedDBRefWhenTypeUsedInPersistenceConstrcutorButValueRefersToAnotherProperty() { Document docNotUsedInCtor = new Document(); @@ -3134,10 +2844,7 @@ public void shouldNotReuseLazyLoadedDBRefWhenTypeUsedInPersistenceConstrcutorBut assertThat(loaded.refToDocUsedInCtor, nullValue()); } - /** - * @see DATAMONGO-1287 - */ - @Test + @Test // DATAMONGO-1287 public void shouldRespectParamterValueWhenAttemptingToReuseLazyLoadedDBRefUsedInPersistenceConstrcutor() { Document docInCtor = new Document(); @@ -3160,10 +2867,7 @@ public void shouldRespectParamterValueWhenAttemptingToReuseLazyLoadedDBRefUsedIn assertThat(loaded.refToDocNotUsedInCtor, instanceOf(LazyLoadingProxy.class)); } - /** - * @see DATAMONGO-1401 - */ - @Test + @Test // DATAMONGO-1401 public void updateShouldWorkForTypesContainingGeoJsonTypes() { WithGeoJson wgj = new WithGeoJson(); @@ -3179,10 +2883,7 @@ public void updateShouldWorkForTypesContainingGeoJsonTypes() { assertThat(template.findOne(query(where("id").is(wgj.id)), WithGeoJson.class).point, is(equalTo(wgj.point))); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesDateValueCorrectlyWhenUsingMinOperator() { Calendar cal = Calendar.getInstance(Locale.US); @@ -3197,10 +2898,7 @@ public void updatesDateValueCorrectlyWhenUsingMinOperator() { assertThat(loaded.date, equalTo(cal.getTime())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesNumericValueCorrectlyWhenUsingMinOperator() { TypeWithNumbers twn = new TypeWithNumbers(); @@ -3239,10 +2937,7 @@ public void updatesNumericValueCorrectlyWhenUsingMinOperator() { assertThat(loaded.bigDeciamVal, equalTo(new BigDecimal("690"))); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesDateValueCorrectlyWhenUsingMaxOperator() { Calendar cal = Calendar.getInstance(Locale.US); @@ -3259,10 +2954,7 @@ public void updatesDateValueCorrectlyWhenUsingMaxOperator() { assertThat(loaded.date, equalTo(cal.getTime())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesNumericValueCorrectlyWhenUsingMaxOperator() { TypeWithNumbers twn = new TypeWithNumbers(); @@ -3301,10 +2993,7 @@ public void updatesNumericValueCorrectlyWhenUsingMaxOperator() { assertThat(loaded.bigDeciamVal, equalTo(new BigDecimal("790"))); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesBigNumberValueUsingStringComparisonWhenUsingMaxOperator() { TypeWithNumbers twn = new TypeWithNumbers(); @@ -3328,10 +3017,7 @@ public void updatesBigNumberValueUsingStringComparisonWhenUsingMaxOperator() { assertThat(loaded.bigDeciamVal, equalTo(new BigDecimal("80"))); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void updatesBigNumberValueUsingStringComparisonWhenUsingMinOperator() { TypeWithNumbers twn = new TypeWithNumbers(); @@ -3355,10 +3041,7 @@ public void updatesBigNumberValueUsingStringComparisonWhenUsingMinOperator() { assertThat(loaded.bigDeciamVal, equalTo(new BigDecimal("800"))); } - /** - * @see DATAMONGO-1431 - */ - @Test + @Test // DATAMONGO-1431 public void streamExecutionUsesExplicitCollectionName() { template.remove(new Query(), "some_special_collection"); @@ -3379,10 +3062,7 @@ public void streamExecutionUsesExplicitCollectionName() { assertThat(stream.hasNext(), is(false)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldFetchListOfReferencesCorrectly() { Sample one = new Sample("1", "jon snow"); @@ -3399,10 +3079,7 @@ public void shouldFetchListOfReferencesCorrectly() { assertThat(template.findOne(query(where("id").is(source.id)), DocumentWithDBRefCollection.class), is(source)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldFetchListOfLazyReferencesCorrectly() { Sample one = new Sample("1", "jon snow"); @@ -3423,10 +3100,7 @@ public void shouldFetchListOfLazyReferencesCorrectly() { assertThat(target.getLazyDbRefAnnotatedList(), contains(two, one)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldFetchMapOfLazyReferencesCorrectly() { Sample one = new Sample("1", "jon snow"); @@ -3448,10 +3122,7 @@ public void shouldFetchMapOfLazyReferencesCorrectly() { assertThat(target.lazyDbRefAnnotatedMap.values(), contains(two, one)); } - /** - * @see DATAMONGO-1513 - */ - @Test + @Test // DATAMONGO-1513 @DirtiesContext public void populatesIdsAddedByEventListener() { @@ -3534,18 +3205,18 @@ static class DocumentWithDBRefCollection { @Id public String id; - @Field("db_ref_list") /** @see DATAMONGO-1058 */ + @Field("db_ref_list") // DATAMONGO-1058 @org.springframework.data.mongodb.core.mapping.DBRef // public List dbRefAnnotatedList; @org.springframework.data.mongodb.core.mapping.DBRef // public Sample dbRefProperty; - @Field("lazy_db_ref_list") /** @see DATAMONGO-1194 */ + @Field("lazy_db_ref_list") // DATAMONGO-1194 @org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) // public List lazyDbRefAnnotatedList; - @Field("lazy_db_ref_map") /** @see DATAMONGO-1194 */ + @Field("lazy_db_ref_map") // DATAMONGO-1194 @org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) public Map lazyDbRefAnnotatedMap; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java index 40af85d3e1..a4bc0595ca 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 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. @@ -144,20 +144,14 @@ public void rejectsNotFoundMapReduceResource() { template.mapReduce("foo", "classpath:doesNotExist.js", "function() {}", Person.class); } - /** - * @see DATAMONGO-322 - */ - @Test(expected = InvalidDataAccessApiUsageException.class) + @Test(expected = InvalidDataAccessApiUsageException.class) // DATAMONGO-322 public void rejectsEntityWithNullIdIfNotSupportedIdType() { Object entity = new NotAutogenerateableId(); template.save(entity); } - /** - * @see DATAMONGO-322 - */ - @Test + @Test // DATAMONGO-322 public void storesEntityWithSetIdAlthoughNotAutogenerateable() { NotAutogenerateableId entity = new NotAutogenerateableId(); @@ -166,10 +160,7 @@ public void storesEntityWithSetIdAlthoughNotAutogenerateable() { template.save(entity); } - /** - * @see DATAMONGO-322 - */ - @Test + @Test // DATAMONGO-322 public void autogeneratesIdForEntityWithAutogeneratableId() { this.converter.afterPropertiesSet(); @@ -184,10 +175,7 @@ public void autogeneratesIdForEntityWithAutogeneratableId() { assertThat(entity.id, is(notNullValue())); } - /** - * @see DATAMONGO-374 - */ - @Test + @Test // DATAMONGO-374 public void convertsUpdateConstraintsUsingConverters() { CustomConversions conversions = new CustomConversions(Collections.singletonList(MyConverter.INSTANCE)); @@ -205,10 +193,7 @@ public void convertsUpdateConstraintsUsingConverters() { verify(collection, times(1)).update(Mockito.any(DBObject.class), eq(reference), anyBoolean(), anyBoolean()); } - /** - * @see DATAMONGO-474 - */ - @Test + @Test // DATAMONGO-474 public void setsUnpopulatedIdField() { NotAutogenerateableId entity = new NotAutogenerateableId(); @@ -217,10 +202,7 @@ public void setsUnpopulatedIdField() { assertThat(entity.id, is(5)); } - /** - * @see DATAMONGO-474 - */ - @Test + @Test // DATAMONGO-474 public void doesNotSetAlreadyPopulatedId() { NotAutogenerateableId entity = new NotAutogenerateableId(); @@ -230,10 +212,7 @@ public void doesNotSetAlreadyPopulatedId() { assertThat(entity.id, is(5)); } - /** - * @see DATAMONGO-868 - */ - @Test + @Test // DATAMONGO-868 public void findAndModifyShouldBumpVersionByOneWhenVersionFieldNotIncludedInUpdate() { VersionedEntity v = new VersionedEntity(); @@ -250,10 +229,7 @@ public void findAndModifyShouldBumpVersionByOneWhenVersionFieldNotIncludedInUpda Assert.assertThat(captor.getValue().get("$inc"), Is. is(new BasicDBObject("version", 1L))); } - /** - * @see DATAMONGO-868 - */ - @Test + @Test // DATAMONGO-868 public void findAndModifyShouldNotBumpVersionByOneWhenVersionFieldAlreadyIncludedInUpdate() { VersionedEntity v = new VersionedEntity(); @@ -270,10 +246,7 @@ public void findAndModifyShouldNotBumpVersionByOneWhenVersionFieldAlreadyInclude Assert.assertThat(captor.getValue().get("$inc"), nullValue()); } - /** - * @see DATAMONGO-533 - */ - @Test + @Test // DATAMONGO-533 public void registersDefaultEntityIndexCreatorIfApplicationContextHasOneForDifferentMappingContext() { GenericApplicationContext applicationContext = new GenericApplicationContext(); @@ -300,10 +273,7 @@ public boolean matches(Object argument) { })); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void findAllAndRemoveShouldRetrieveMatchingDocumentsPriorToRemoval() { BasicQuery query = new BasicQuery("{'foo':'bar'}"); @@ -311,10 +281,7 @@ public void findAllAndRemoveShouldRetrieveMatchingDocumentsPriorToRemoval() { verify(collection, times(1)).find(Matchers.eq(query.getQueryObject())); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void findAllAndRemoveShouldRemoveDocumentsReturedByFindQuery() { Mockito.when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false); @@ -331,20 +298,14 @@ public void findAllAndRemoveShouldRemoveDocumentsReturedByFindQuery() { assertThat((Object[]) idField.get("$in"), is(new Object[] { Integer.valueOf(0), Integer.valueOf(1) })); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void findAllAndRemoveShouldNotTriggerRemoveIfFindResultIsEmpty() { template.findAllAndRemove(new BasicQuery("{'foo':'bar'}"), VersionedEntity.class); verify(collection, never()).remove(Mockito.any(DBObject.class)); } - /** - * @see DATAMONGO-948 - */ - @Test + @Test // DATAMONGO-948 public void sortShouldBeTakenAsIsWhenExecutingQueryWithoutSpecificTypeInformation() { Query query = Query.query(Criteria.where("foo").is("bar")).with(new Sort("foo")); @@ -361,10 +322,7 @@ public void processDocument(DBObject dbObject) throws MongoException, DataAccess assertThat(captor.getValue(), equalTo(new BasicDBObjectBuilder().add("foo", 1).get())); } - /** - * @see DATAMONGO-1166 - */ - @Test + @Test // DATAMONGO-1166 public void aggregateShouldHonorReadPreferenceWhenSet() { when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( @@ -377,10 +335,7 @@ public void aggregateShouldHonorReadPreferenceWhenSet() { verify(this.db, times(1)).command(Mockito.any(DBObject.class), eq(ReadPreference.secondary())); } - /** - * @see DATAMONGO-1166 - */ - @Test + @Test // DATAMONGO-1166 public void aggregateShouldIgnoreReadPreferenceWhenNotSet() { when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( @@ -392,10 +347,7 @@ public void aggregateShouldIgnoreReadPreferenceWhenNotSet() { verify(this.db, times(1)).command(Mockito.any(DBObject.class)); } - /** - * @see DATAMONGO-1166 - */ - @Test + @Test // DATAMONGO-1166 public void geoNearShouldHonorReadPreferenceWhenSet() { when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( @@ -409,10 +361,7 @@ public void geoNearShouldHonorReadPreferenceWhenSet() { verify(this.db, times(1)).command(Mockito.any(DBObject.class), eq(ReadPreference.secondary())); } - /** - * @see DATAMONGO-1166 - */ - @Test + @Test // DATAMONGO-1166 public void geoNearShouldIgnoreReadPreferenceWhenNotSet() { when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( @@ -425,10 +374,7 @@ public void geoNearShouldIgnoreReadPreferenceWhenNotSet() { verify(this.db, times(1)).command(Mockito.any(DBObject.class)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void mapReduceShouldUseZeroAsDefaultLimit() { ArgumentCaptor captor = ArgumentCaptor.forClass(MapReduceCommand.class); @@ -446,10 +392,7 @@ public void mapReduceShouldUseZeroAsDefaultLimit() { assertThat(captor.getValue().getLimit(), is(0)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromQuery() { ArgumentCaptor captor = ArgumentCaptor.forClass(MapReduceCommand.class); @@ -468,10 +411,7 @@ public void mapReduceShouldPickUpLimitFromQuery() { assertThat(captor.getValue().getLimit(), is(100)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromOptions() { ArgumentCaptor captor = ArgumentCaptor.forClass(MapReduceCommand.class); @@ -489,10 +429,7 @@ public void mapReduceShouldPickUpLimitFromOptions() { assertThat(captor.getValue().getLimit(), is(1000)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromOptionsWhenQueryIsNotPresent() { ArgumentCaptor captor = ArgumentCaptor.forClass(MapReduceCommand.class); @@ -508,10 +445,7 @@ public void mapReduceShouldPickUpLimitFromOptionsWhenQueryIsNotPresent() { assertThat(captor.getValue().getLimit(), is(1000)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void mapReduceShouldPickUpLimitFromOptionsEvenWhenQueryDefinesItDifferently() { ArgumentCaptor captor = ArgumentCaptor.forClass(MapReduceCommand.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NoExplicitIdTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NoExplicitIdTests.java index de8b22176c..c8e72686f3 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NoExplicitIdTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NoExplicitIdTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -68,10 +68,7 @@ public void setUp() { mongoOps.dropCollection(TypeWithoutIdProperty.class); } - /** - * @see DATAMONGO-1289 - */ - @Test + @Test // DATAMONGO-1289 public void saveAndRetrieveTypeWithoutIdPorpertyViaTemplate() { TypeWithoutIdProperty noid = new TypeWithoutIdProperty(); @@ -85,10 +82,7 @@ public void saveAndRetrieveTypeWithoutIdPorpertyViaTemplate() { assertThat(retrieved.someString, is(noid.someString)); } - /** - * @see DATAMONGO-1289 - */ - @Test + @Test // DATAMONGO-1289 public void saveAndRetrieveTypeWithoutIdPorpertyViaRepository() { TypeWithoutIdProperty noid = new TypeWithoutIdProperty(); @@ -100,10 +94,7 @@ public void saveAndRetrieveTypeWithoutIdPorpertyViaRepository() { assertThat(retrieved.someString, is(noid.someString)); } - /** - * @see DATAMONGO-1289 - */ - @Test + @Test // DATAMONGO-1289 @SuppressWarnings("unchecked") public void saveAndRetrieveTypeWithoutIdPorpertyViaRepositoryFindOne() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java index faa45f77ff..11e2be5733 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryByExampleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -72,10 +72,7 @@ public void setUp() throws UnknownHostException { operations.save(p3); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldWorkForSimpleProperty() { Person sample = new Person(); @@ -88,10 +85,7 @@ public void findByExampleShouldWorkForSimpleProperty() { assertThat(result, hasItems(p1, p3)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldWorkForMultipleProperties() { Person sample = new Person(); @@ -105,10 +99,7 @@ public void findByExampleShouldWorkForMultipleProperties() { assertThat(result, hasItem(p3)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldWorkForIdProperty() { Person p4 = new Person(); @@ -124,10 +115,7 @@ public void findByExampleShouldWorkForIdProperty() { assertThat(result, hasItem(p4)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldReturnEmptyListIfNotMatching() { Person sample = new Person(); @@ -140,10 +128,7 @@ public void findByExampleShouldReturnEmptyListIfNotMatching() { assertThat(result, is(empty())); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldReturnEverythingWhenSampleIsEmpty() { Person sample = new Person(); @@ -155,10 +140,7 @@ public void findByExampleShouldReturnEverythingWhenSampleIsEmpty() { assertThat(result, hasItems(p1, p2, p3)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleWithCriteria() { Person sample = new Person(); @@ -170,10 +152,7 @@ public void findByExampleWithCriteria() { assertThat(result.size(), is(1)); } - /** - * @see DATAMONGO-1459 - */ - @Test + @Test // DATAMONGO-1459 public void findsExampleUsingAnyMatch() { Person probe = new Person(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryCursorPreparerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryCursorPreparerUnitTests.java index 21ca3f8c03..e534c8fc82 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryCursorPreparerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryCursorPreparerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -55,10 +55,7 @@ public void setUp() { when(cursor.copy()).thenReturn(cursorToUse); } - /** - * @see DATAMONGO-185 - */ - @Test + @Test // DATAMONGO-185 public void appliesHintsCorrectly() { Query query = query(where("foo").is("bar")).withHint("hint"); @@ -68,10 +65,7 @@ public void appliesHintsCorrectly() { verify(cursorToUse).hint("hint"); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void doesNotApplyMetaWhenEmpty() { Query query = query(where("foo").is("bar")); @@ -83,10 +77,7 @@ public void doesNotApplyMetaWhenEmpty() { verify(cursorToUse, never()).addSpecial(any(String.class), anyObject()); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void appliesMaxScanCorrectly() { Query query = query(where("foo").is("bar")).maxScan(100); @@ -96,10 +87,7 @@ public void appliesMaxScanCorrectly() { verify(cursorToUse).addSpecial(eq("$maxScan"), eq(100L)); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void appliesMaxTimeCorrectly() { Query query = query(where("foo").is("bar")).maxTime(1, TimeUnit.SECONDS); @@ -109,10 +97,7 @@ public void appliesMaxTimeCorrectly() { verify(cursorToUse).addSpecial(eq("$maxTimeMS"), eq(1000L)); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void appliesCommentCorrectly() { Query query = query(where("foo").is("bar")).comment("spring data"); @@ -122,10 +107,7 @@ public void appliesCommentCorrectly() { verify(cursorToUse).addSpecial(eq("$comment"), eq("spring data")); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void appliesSnapshotCorrectly() { Query query = query(where("foo").is("bar")).useSnapshot(); @@ -135,10 +117,7 @@ public void appliesSnapshotCorrectly() { verify(cursorToUse).addSpecial(eq("$snapshot"), eq(true)); } - /** - * @see DATAMONGO-1480 - */ - @Test + @Test // DATAMONGO-1480 public void appliesNoCursorTimeoutCorrectly() { Query query = query(where("foo").is("bar")).noCursorTimeout(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java index 0b42b27c03..8d12f96b6b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SerializationUtilsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -64,10 +64,7 @@ public void writesCollection() { assertThat(serializeToJsonSafely(dbObject), is(expectedOutput)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void flattenMapShouldFlatOutNestedStructureCorrectly() { DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("value", "conflux")).get(); @@ -76,10 +73,7 @@ public void flattenMapShouldFlatOutNestedStructureCorrectly() { assertThat(flattenMap(dbo), hasEntry("nested.value", (Object) "conflux")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void flattenMapShouldFlatOutNestedStructureWithListCorrectly() { BasicDBList dbl = new BasicDBList(); @@ -91,10 +85,7 @@ public void flattenMapShouldFlatOutNestedStructureWithListCorrectly() { assertThat(flattenMap(dbo), hasEntry("nested.value", (Object) dbl)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void flattenMapShouldLeaveKeywordsUntouched() { DBObject dbo = new BasicDBObjectBuilder().add("_id", 1).add("nested", new BasicDBObject("$regex", "^conflux$")) @@ -107,10 +98,7 @@ public void flattenMapShouldLeaveKeywordsUntouched() { assertThat(((Map) map.get("nested")).get("$regex"), is((Object) "^conflux$")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void flattenMapShouldAppendCommandsCorrectly() { DBObject dbo = new BasicDBObjectBuilder().add("_id", 1) @@ -124,10 +112,7 @@ public void flattenMapShouldAppendCommandsCorrectly() { assertThat(((Map) map.get("nested")).get("$options"), is((Object) "i")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void flattenMapShouldReturnEmptyMapWhenSourceIsNull() { assertThat(flattenMap(null).isEmpty(), is(true)); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java index 9967de75ec..ab753b1b22 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoDbFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -49,19 +49,13 @@ public class SimpleMongoDbFactoryUnitTests { public @Rule ExpectedException expectedException = ExpectedException.none(); @Mock Mongo mongo; - /** - * @see DATADOC-254 - */ - @Test + @Test // DATADOC-254 public void rejectsIllegalDatabaseNames() { rejectsDatabaseName("foo.bar"); rejectsDatabaseName("foo!bar"); } - /** - * @see DATADOC-254 - */ - @Test + @Test // DATADOC-254 @SuppressWarnings("deprecation") public void allowsDatabaseNames() { new SimpleMongoDbFactory(mongo, "foo-bar"); @@ -69,11 +63,7 @@ public void allowsDatabaseNames() { new SimpleMongoDbFactory(mongo, "foo01231bar"); } - /** - * @see DATADOC-295 - * @throws UnknownHostException - */ - @Test + @Test // DATADOC-295 @SuppressWarnings("deprecation") public void mongoUriConstructor() throws UnknownHostException { @@ -84,10 +74,7 @@ public void mongoUriConstructor() throws UnknownHostException { assertThat(getField(mongoDbFactory, "databaseName").toString(), is("myDatabase")); } - /** - * @see DATAMONGO-789 - */ - @Test + @Test // DATAMONGO-789 @SuppressWarnings("deprecation") public void defaultsAuthenticationDatabaseToDatabase() { @@ -95,10 +82,7 @@ public void defaultsAuthenticationDatabaseToDatabase() { assertThat(getField(factory, "authenticationDatabaseName"), is((Object) "foo")); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void constructsMongoClientAccordingToMongoUri() throws UnknownHostException { MongoClientURI uri = new MongoClientURI("mongodb://myUserName:myPassWord@127.0.0.1:27017/myDataBase.myCollection"); @@ -107,10 +91,7 @@ public void constructsMongoClientAccordingToMongoUri() throws UnknownHostExcepti assertThat(getField(factory, "databaseName").toString(), is("myDataBase")); } - /** - * @see DATAMONGO-1158 - */ - @Test + @Test // DATAMONGO-1158 public void shouldDefaultAuthenticationDbNameToDbNameWhenUsingMongoClient() throws UnknownHostException { MongoClient clientMock = mock(MongoClient.class); @@ -119,10 +100,7 @@ public void shouldDefaultAuthenticationDbNameToDbNameWhenUsingMongoClient() thro assertThat(getField(factory, "authenticationDatabaseName").toString(), is("FooBar")); } - /** - * @see DATAMONGO-1260 - */ - @Test + @Test // DATAMONGO-1260 public void rejectsMongoClientWithUserCredentials() { expectedException.expect(InvalidDataAccessApiUsageException.class); @@ -131,10 +109,7 @@ public void rejectsMongoClientWithUserCredentials() { new SimpleMongoDbFactory(mock(MongoClient.class), "cairhienin", new UserCredentials("moiraine", "sedai")); } - /** - * @see DATAMONGO-1260 - */ - @Test + @Test // DATAMONGO-1260 public void rejectsMongoClientWithUserCredentialsAndAuthDb() { expectedException.expect(InvalidDataAccessApiUsageException.class); @@ -143,18 +118,12 @@ public void rejectsMongoClientWithUserCredentialsAndAuthDb() { new SimpleMongoDbFactory(mock(MongoClient.class), "malkieri", new UserCredentials("lan", "mandragoran"), "authdb"); } - /** - * @see DATAMONGO-1260 - */ - @Test + @Test // DATAMONGO-1260 public void shouldNotRejectMongoClientWithNoCredentials() { new SimpleMongoDbFactory(mock(MongoClient.class), "andoran", UserCredentials.NO_CREDENTIALS); } - /** - * @see DATAMONGO-1260 - */ - @Test + @Test // DATAMONGO-1260 public void shouldNotRejectMongoClientWithEmptyUserCredentials() { new SimpleMongoDbFactory(mock(MongoClient.class), "shangtai", new UserCredentials("", "")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java index ea3f556d4b..45549e1146 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -42,10 +42,7 @@ public void setup() { } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void aggregationOptionsBuilderShouldSetOptionsAccordingly() { assertThat(aggregationOptions.isAllowDiskUse(), is(true)); @@ -53,10 +50,7 @@ public void aggregationOptionsBuilderShouldSetOptionsAccordingly() { assertThat(aggregationOptions.getCursor(), is((DBObject) new BasicDBObject("foo", 1))); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void aggregationOptionsToString() { assertThat(aggregationOptions.toString(), is("{ \"allowDiskUse\" : true , \"explain\" : true , \"cursor\" : { \"foo\" : 1}}")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 8adb939dfa..9330b3e260 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -81,8 +81,7 @@ /** * Tests for {@link MongoTemplate#aggregate(String, AggregationPipeline, Class)}. - * - * @see DATAMONGO-586 + * * @author Tobias Trelle * @author Thomas Darimont * @author Oliver Gierke @@ -150,10 +149,10 @@ private void cleanDb() { } /** - * Imports the sample dataset (zips.json) if necessary (e.g. if it doen't exist yet). The dataset can originally be + * Imports the sample dataset (zips.json) if necessary (e.g. if it doesn't exist yet). The dataset can originally be * found on the mongodb aggregation framework example website: * - * @see http://docs.mongodb.org/manual/tutorial/aggregation-examples/. + * @see MongoDB Aggregation Examples */ private void initSampleDataIfNecessary() { @@ -192,22 +191,22 @@ public Void doInCollection(DBCollection collection) throws MongoException, DataA } } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void shouldHandleMissingInputCollection() { mongoTemplate.aggregate(newAggregation(), (String) null, TagCount.class); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void shouldHandleMissingAggregationPipeline() { mongoTemplate.aggregate(null, INPUT_COLLECTION, TagCount.class); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void shouldHandleMissingEntityClass() { mongoTemplate.aggregate(newAggregation(), INPUT_COLLECTION, null); } - @Test + @Test // DATAMONGO-586 public void shouldAggregate() { createTagDocuments(); @@ -236,7 +235,7 @@ public void shouldAggregate() { assertTagCount("nosql", 1, tagCount.get(2)); } - @Test + @Test // DATAMONGO-586 public void shouldAggregateEmptyCollection() { Aggregation aggregation = newAggregation(// @@ -259,10 +258,7 @@ public void shouldAggregateEmptyCollection() { assertThat(tagCount.size(), is(0)); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void shouldUnwindWithIndex() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -290,10 +286,7 @@ public void shouldUnwindWithIndex() { assertThat(tagCount.size(), is(3)); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void shouldUnwindPreserveEmpty() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -321,7 +314,7 @@ public void shouldUnwindPreserveEmpty() { assertThat(tagCount.get(3), isBsonObject().notContaining("n")); } - @Test + @Test // DATAMONGO-586 public void shouldDetectResultMismatch() { createTagDocuments(); @@ -346,7 +339,7 @@ public void shouldDetectResultMismatch() { assertTagCount(null, 0, tagCount.get(1)); } - @Test + @Test // DATAMONGO-586 public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { /* //complex mongodb aggregation framework example from http://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state @@ -454,7 +447,7 @@ public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { assertThat(lastZipInfoStats.biggestCity.population, is(70185)); } - @Test + @Test // DATAMONGO-586 public void findStatesWithPopulationOver10MillionAggregationExample() { /* //complex mongodb aggregation framework example from @@ -501,10 +494,10 @@ public void findStatesWithPopulationOver10MillionAggregationExample() { } /** - * @see DATAMONGO-861 - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/cond/#example + * @see MongoDB Aggregation + * Framework: $cond */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingConditionalProjectionToCalculateDiscount() { /* @@ -554,10 +547,10 @@ public void aggregationUsingConditionalProjectionToCalculateDiscount() { } /** - * @see DATAMONGO-861 - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/#example + * @see MongoDB Aggregation + * Framework: $ifNull */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingIfNullToProjectSaneDefaults() { /* @@ -597,10 +590,7 @@ public void aggregationUsingIfNullToProjectSaneDefaults() { assertThat(second.get("description"), is((Object) "Unspecified")); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingConditionalProjection() { TypedAggregation aggregation = newAggregation(ZipInfo.class, // @@ -622,10 +612,7 @@ public void aggregationUsingConditionalProjection() { assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingNestedConditionalProjection() { TypedAggregation aggregation = newAggregation(ZipInfo.class, // @@ -648,10 +635,7 @@ public void aggregationUsingNestedConditionalProjection() { assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingIfNullProjection() { mongoTemplate.insert(new LineItem("id", "caption", 0)); @@ -675,10 +659,7 @@ public void aggregationUsingIfNullProjection() { assertThat((String) idonly.get("caption"), is(equalTo("unknown"))); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() { mongoTemplate.insert(new LineItem("id", "caption", 0)); @@ -702,10 +683,7 @@ public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() { assertThat((String) idonly.get("caption"), is(equalTo("idonly"))); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldAllowGroupingUsingConditionalExpressions() { mongoTemplate.dropCollection(CarPerson.class); @@ -749,9 +727,11 @@ public void shouldAllowGroupingUsingConditionalExpressions() { } /** - * @see http://docs.mongodb.org/manual/tutorial/aggregation-examples/#return-the-five-most-common-likes + * @see Return + * the Five Most Common “Likes” */ - @Test + @Test // DATAMONGO-586 public void returnFiveMostCommonLikesAggregationFrameworkExample() { createUserWithLikesDocuments(); @@ -783,7 +763,7 @@ protected TypedAggregation createUsersWithCommonLikesAggregation( ); } - @Test + @Test // DATAMONGO-586 public void arithmenticOperatorsInProjectionExample() { Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); @@ -825,10 +805,7 @@ public void arithmenticOperatorsInProjectionExample() { assertThat((Integer) resultList.get(0).get("spaceUnitsModSpaceUnits"), is(product.spaceUnits % product.spaceUnits)); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void expressionsInProjectionExample() { Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); @@ -860,10 +837,7 @@ public void expressionsInProjectionExample() { is((product.netPrice * 0.8 + 1.2) * 1.19)); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void stringExpressionsInProjectionExample() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); @@ -885,10 +859,7 @@ public void stringExpressionsInProjectionExample() { assertThat((String) resultList.get(0).get("name_bubu"), is(product.name + "_bubu")); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void expressionsInProjectionExampleShowcase() { Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); @@ -930,12 +901,11 @@ public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressio } /** - * @see DATAMONGO-753 - * @see http - * ://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group - * -operati + * @see Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation */ - @Test + @Test // DATAMONGO-753 public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); @@ -960,12 +930,11 @@ public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { } /** - * @see DATAMONGO-753 - * @see http - * ://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group - * -operati + * @see Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation */ - @Test + @Test // DATAMONGO-753 public void aliasesNestedFieldInProjectionImmediately() { mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); @@ -984,10 +953,7 @@ public void aliasesNestedFieldInProjectionImmediately() { } } - /** - * @DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void shouldPerformDateProjectionOperatorsCorrectly() throws ParseException { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); @@ -1016,10 +982,7 @@ public void shouldPerformDateProjectionOperatorsCorrectly() throws ParseExceptio assertThat((String) dbo.get("toUpper"), is("ABC")); } - /** - * @DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void shouldPerformStringProjectionOperatorsCorrectly() throws ParseException { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); @@ -1058,10 +1021,7 @@ public void shouldPerformStringProjectionOperatorsCorrectly() throws ParseExcept assertThat((Integer) dbo.get("millisecond"), is(789)); } - /** - * @see DATAMONGO-1550 - */ - @Test + @Test // DATAMONGO-1550 public void shouldPerformReplaceRootOperatorCorrectly() throws ParseException { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); @@ -1084,10 +1044,7 @@ public void shouldPerformReplaceRootOperatorCorrectly() throws ParseException { assertThat((Integer) dbo.keySet().size(), is(1)); } - /** - * @see DATAMONGO-788 - */ - @Test + @Test // DATAMONGO-788 public void referencesToGroupIdsShouldBeRenderedProperly() { mongoTemplate.insert(new DATAMONGO788(1, 1)); @@ -1114,10 +1071,7 @@ public void referencesToGroupIdsShouldBeRenderedProperly() { assertThat((Integer) items.get(1).get("y"), is(1)); } - /** - * @see DATAMONGO-806 - */ - @Test + @Test // DATAMONGO-806 public void shouldAllowGroupByIdFields() { mongoTemplate.dropCollection(User.class); @@ -1148,10 +1102,7 @@ public void shouldAllowGroupByIdFields() { assertThat(String.valueOf(firstItem.get("_id")), is("u1")); } - /** - * @see DATAMONGO-840 - */ - @Test + @Test // DATAMONGO-840 public void shouldAggregateOrderDataToAnInvoice() { mongoTemplate.dropCollection(Order.class); @@ -1188,10 +1139,7 @@ public void shouldAggregateOrderDataToAnInvoice() { assertThat(invoice.getTotalAmount(), is(closeTo(9.877, 000001))); } - /** - * @see DATAMONGO-924 - */ - @Test + @Test // DATAMONGO-924 public void shouldAllowGroupingByAliasedFieldDefinedInFormerAggregationStage() { mongoTemplate.dropCollection(CarPerson.class); @@ -1222,10 +1170,7 @@ public void shouldAllowGroupingByAliasedFieldDefinedInFormerAggregationStage() { assertThat(result.getMappedResults(), hasSize(3)); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabled() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1250,10 +1195,7 @@ public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOp assertLikeStats(result.getMappedResults().get(4), "e", 3); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithExplainOptionEnabled() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1273,10 +1215,7 @@ public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithEx assertThat(rawResult.containsField("stages"), is(true)); } - /** - * @see DATAMONGO-954 - */ - @Test + @Test // DATAMONGO-954 public void shouldSupportReturningCurrentAggregationRoot() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1301,11 +1240,9 @@ public void shouldSupportReturningCurrentAggregationRoot() { } /** - * @see DATAMONGO-954 - * @see http - * ://stackoverflow.com/questions/24185987/using-root-inside-spring-data-mongodb-for-retrieving-whole-document + * {@link http://stackoverflow.com/questions/24185987/using-root-inside-spring-data-mongodb-for-retrieving-whole-document} */ - @Test + @Test // DATAMONGO-954 public void shouldSupportReturningCurrentAggregationRootInReference() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1326,10 +1263,7 @@ public void shouldSupportReturningCurrentAggregationRootInReference() { assertThat(result.getMappedResults(), hasSize(2)); } - /** - * @see DATAMONGO-1549 - */ - @Test + @Test // DATAMONGO-1549 public void shouldApplyCountCorrectly() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); @@ -1350,10 +1284,7 @@ public void shouldApplyCountCorrectly() { assertThat(dbObject, isBsonObject().containing("documents", 3).containing("twice", 6)); } - /** - * @see DATAMONGO-975 - */ - @Test + @Test // DATAMONGO-975 public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception { mongoTemplate.dropCollection(ObjectWithDate.class); @@ -1405,10 +1336,7 @@ public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception { assertThat(dbo.get("dayOfYearPlus1DayManually"), is((Object) dateTime.plusDays(1).getDayOfYear())); } - /** - * @see DATAMONGO-1127 - */ - @Test + @Test // DATAMONGO-1127 public void shouldSupportGeoNearQueriesForAggregationWithDistanceField() { mongoTemplate.insert(new Venue("Penn Station", -73.99408, 40.75057)); @@ -1429,10 +1357,7 @@ public void shouldSupportGeoNearQueriesForAggregationWithDistanceField() { assertThat((Double) firstResult.get("distance"), closeTo(117.620092203928, 0.00001)); } - /** - * @see DATAMONGO-1133 - */ - @Test + @Test // DATAMONGO-1133 public void shouldHonorFieldAliasesForFieldReferences() { mongoTemplate.insert(new MeterData("m1", "counter1", 42)); @@ -1452,10 +1377,7 @@ public void shouldHonorFieldAliasesForFieldReferences() { assertThat(result.get("totalValue"), is(equalTo((Object) 100.0))); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void shouldLookupPeopleCorectly() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -1476,10 +1398,7 @@ public void shouldLookupPeopleCorectly() { assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void shouldGroupByAndLookupPeopleCorectly() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -1501,10 +1420,7 @@ public void shouldGroupByAndLookupPeopleCorectly() { assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); } - /** - * @see DATAMONGO-1418 - */ - @Test + @Test // DATAMONGO-1418 public void shouldCreateOutputCollection() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1533,10 +1449,7 @@ public void shouldCreateOutputCollection() { mongoTemplate.dropCollection(tempOutCollection); } - /** - * @see DATAMONGO-1418 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1418 public void outShouldOutBeTheLastOperation() { newAggregation(match(new Criteria()), // @@ -1545,10 +1458,7 @@ public void outShouldOutBeTheLastOperation() { skip(100L)); } - /** - * @see DATAMONGO-1457 - */ - @Test + @Test // DATAMONGO-1457 public void sliceShouldBeAppliedCorrectly() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -1566,10 +1476,7 @@ public void sliceShouldBeAppliedCorrectly() { } } - /** - * @see DATAMONGO-1491 - */ - @Test + @Test // DATAMONGO-1491 public void filterShouldBeAppliedCorrectly() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -1602,10 +1509,7 @@ public void filterShouldBeAppliedCorrectly() { Sales.builder().id("2").items(Collections. emptyList()).build())); } - /** - * @see DATAMONGO-1538 - */ - @Test + @Test // DATAMONGO-1538 public void letShouldBeAppliedCorrectly() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -1632,10 +1536,7 @@ public void letShouldBeAppliedCorrectly() { new BasicDBObjectBuilder().add("_id", "2").add("finalTotal", 10.25D).get())); } - /** - * @see DATAMONGO-1551 - */ - @Test + @Test // DATAMONGO-1551 public void graphLookupShouldBeAppliedCorrectly() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); @@ -1666,10 +1567,7 @@ public void graphLookupShouldBeAppliedCorrectly() { assertThat((DBObject) list.get(1), isBsonObject().containing("name", "Eliot").containing("depth", 0L)); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void bucketShouldCollectDocumentsIntoABucket() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); @@ -1706,10 +1604,7 @@ public void bucketShouldCollectDocumentsIntoABucket() { assertThat((Double) bound100.get("sum"), is(closeTo(3672.9, 0.1))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void bucketAutoShouldCollectDocumentsIntoABucket() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); @@ -1743,10 +1638,7 @@ public void bucketAutoShouldCollectDocumentsIntoABucket() { assertThat((Double) bound1.get("sum"), is(closeTo(1673.0, 0.1))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void facetShouldCreateFacets() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); @@ -1895,9 +1787,7 @@ public DATAMONGO788(int x, int y) { } } - /** - * @see DATAMONGO-806 - */ + // DATAMONGO-806 static class User { @Id String id; @@ -1911,9 +1801,7 @@ public User(String id, PushMessage... msgs) { } } - /** - * @see DATAMONGO-806 - */ + // DATAMONGO-806 static class PushMessage { @Id String id; @@ -2003,9 +1891,7 @@ public ObjectWithDate(Date dateValue) { } } - /** - * @see DATAMONGO-861 - */ + // DATAMONGO-861 @Document(collection = "inventory") static class InventoryItem { @@ -2032,9 +1918,7 @@ public InventoryItem(int id, String item, String description, int qty) { } } - /** - * @see DATAMONGO-1491 - */ + // DATAMONGO-1491 @lombok.Data @Builder static class Sales { @@ -2043,9 +1927,7 @@ static class Sales { List items; } - /** - * @see DATAMONGO-1491 - */ + // DATAMONGO-1491 @lombok.Data @Builder static class Item { @@ -2056,9 +1938,7 @@ static class Item { Long price; } - /** - * @see DATAMONGO-1538 - */ + // DATAMONGO-1538 @lombok.Data @Builder static class Sales2 { @@ -2069,9 +1949,7 @@ static class Sales2 { boolean applyDiscount; } - /** - * @see DATAMONGO-1551 - */ + // DATAMONGO-1551 @lombok.Data @Builder static class Employee { @@ -2081,9 +1959,7 @@ static class Employee { String reportsTo; } - /** - * @see DATAMONGO-1552 - */ + // DATAMONGO-1552 @lombok.Data @Builder static class Art { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index 76b228acd0..9af74d70ff 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -70,10 +70,7 @@ public void rejectsNoTypedAggregationOperation() { newAggregation(String.class, new AggregationOperation[0]); } - /** - * @see DATAMONGO-753 - */ - @Test + @Test // DATAMONGO-753 public void checkForCorrectFieldScopeTransfer() { exception.expect(IllegalArgumentException.class); @@ -87,10 +84,7 @@ public void checkForCorrectFieldScopeTransfer() { ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); // -> triggers IllegalArgumentException } - /** - * @see DATAMONGO-753 - */ - @Test + @Test // DATAMONGO-753 public void unwindOperationShouldNotChangeAvailableFields() { newAggregation( // @@ -100,10 +94,7 @@ public void unwindOperationShouldNotChangeAvailableFields() { ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindOperationWithIndexShouldPreserveFields() { newAggregation( // @@ -113,10 +104,7 @@ public void unwindOperationWithIndexShouldPreserveFields() { ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindOperationWithIndexShouldAddIndexField() { newAggregation( // @@ -126,10 +114,7 @@ public void unwindOperationWithIndexShouldAddIndexField() { ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void fullUnwindOperationShouldBuildCorrectClause() { DBObject agg = newAggregation( // @@ -143,10 +128,7 @@ public void fullUnwindOperationShouldBuildCorrectClause() { containing("preserveNullAndEmptyArrays", true)); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindOperationWithPreserveNullShouldBuildCorrectClause() { DBObject agg = newAggregation( // @@ -158,10 +140,7 @@ public void unwindOperationWithPreserveNullShouldBuildCorrectClause() { isBsonObject().notContaining("includeArrayIndex").containing("preserveNullAndEmptyArrays", true)); } - /** - * @see DATAMONGO-1550 - */ - @Test + @Test // DATAMONGO-1550 public void replaceRootOperationShouldBuildCorrectClause() { DBObject agg = newAggregation( // @@ -173,10 +152,7 @@ public void replaceRootOperationShouldBuildCorrectClause() { assertThat(unwind, isBsonObject().containing("$replaceRoot.newRoot", new BasicDBObject("field", "value"))); } - /** - * @see DATAMONGO-753 - */ - @Test + @Test // DATAMONGO-753 public void matchOperationShouldNotChangeAvailableFields() { newAggregation( // @@ -186,10 +162,7 @@ public void matchOperationShouldNotChangeAvailableFields() { ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); } - /** - * @see DATAMONGO-788 - */ - @Test + @Test // DATAMONGO-788 public void referencesToGroupIdsShouldBeRenderedAsReferences() { DBObject agg = newAggregation( // @@ -205,10 +178,7 @@ public void referencesToGroupIdsShouldBeRenderedAsReferences() { assertThat(fields.get("a"), is((Object) "$_id.a")); } - /** - * @see DATAMONGO-791 - */ - @Test + @Test // DATAMONGO-791 public void allowAggregationOperationsToBePassedAsIterable() { List ops = new ArrayList(); @@ -225,10 +195,7 @@ public void allowAggregationOperationsToBePassedAsIterable() { assertThat(fields.get("a"), is((Object) "$_id.a")); } - /** - * @see DATAMONGO-791 - */ - @Test + @Test // DATAMONGO-791 public void allowTypedAggregationOperationsToBePassedAsIterable() { List ops = new ArrayList(); @@ -245,10 +212,7 @@ public void allowTypedAggregationOperationsToBePassedAsIterable() { assertThat(fields.get("a"), is((Object) "$_id.a")); } - /** - * @see DATAMONGO-838 - */ - @Test + @Test // DATAMONGO-838 public void expressionBasedFieldsShouldBeReferencableInFollowingOperations() { DBObject agg = newAggregation( // @@ -262,10 +226,7 @@ public void expressionBasedFieldsShouldBeReferencableInFollowingOperations() { assertThat(fields.get("foosum"), is((Object) new BasicDBObject("$sum", "$foo"))); } - /** - * @see DATAMONGO-908 - */ - @Test + @Test // DATAMONGO-908 public void shouldSupportReferingToNestedPropertiesInGroupOperation() { DBObject agg = newAggregation( // @@ -283,10 +244,7 @@ public void shouldSupportReferingToNestedPropertiesInGroupOperation() { assertThat(id.get("ruleType"), is((Object) "$rules.ruleType")); } - /** - * @see DATAMONGO-1585 - */ - @Test + @Test // DATAMONGO-1585 public void shouldSupportSortingBySyntheticAndExposedGroupFields() { DBObject agg = newAggregation( // @@ -301,10 +259,7 @@ public void shouldSupportSortingBySyntheticAndExposedGroupFields() { assertThat(getAsDBObject(sort, "$sort"), is(JSON.parse("{ \"_id.cmsParameterId\" : 1 , \"titles\" : 1}"))); } - /** - * @see DATAMONGO-1585 - */ - @Test + @Test // DATAMONGO-1585 public void shouldSupportSortingByProjectedFields() { DBObject agg = newAggregation( // @@ -324,10 +279,7 @@ public void shouldSupportSortingByProjectedFields() { .containing("alias", 1)); } - /** - * @see DATAMONGO-924 - */ - @Test + @Test // DATAMONGO-924 public void referencingProjectionAliasesFromPreviousStepShouldReferToTheSameFieldTarget() { DBObject agg = newAggregation( // @@ -342,10 +294,7 @@ public void referencingProjectionAliasesFromPreviousStepShouldReferToTheSameFiel assertThat(projection1, is((DBObject) new BasicDBObject("b", "$ba"))); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void shouldRenderAggregationWithDefaultOptionsCorrectly() { DBObject agg = newAggregation( // @@ -356,10 +305,7 @@ public void shouldRenderAggregationWithDefaultOptionsCorrectly() { is("{ \"aggregate\" : \"foo\" , \"pipeline\" : [ { \"$project\" : { \"aa\" : \"$a\"}}]}")); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void shouldRenderAggregationWithCustomOptionsCorrectly() { AggregationOptions aggregationOptions = newAggregationOptions().explain(true).cursor(new BasicDBObject("foo", 1)) @@ -380,10 +326,7 @@ public void shouldRenderAggregationWithCustomOptionsCorrectly() { )); } - /** - * @see DATAMONGO-954, DATAMONGO-1585 - */ - @Test + @Test // DATAMONGO-954, DATAMONGO-1585 public void shouldSupportReferencingSystemVariables() { DBObject agg = newAggregation( // @@ -406,10 +349,7 @@ public void shouldSupportReferencingSystemVariables() { is((DBObject) new BasicDBObject("_id", "$someKey").append("doc", new BasicDBObject("$first", "$$ROOT")))); } - /** - * @see DATAMONGO-1254 - */ - @Test + @Test // DATAMONGO-1254 public void shouldExposeAliasedFieldnameForProjectionsIncludingOperationsDownThePipeline() { DBObject agg = Aggregation.newAggregation(// @@ -423,10 +363,7 @@ public void shouldExposeAliasedFieldnameForProjectionsIncludingOperationsDownThe assertThat(getAsDBObject(group, "count"), is(new BasicDBObjectBuilder().add("$sum", "$tags_count").get())); } - /** - * @see DATAMONGO-1254 - */ - @Test + @Test // DATAMONGO-1254 public void shouldUseAliasedFieldnameForProjectionsIncludingOperationsDownThePipelineWhenUsingSpEL() { DBObject agg = Aggregation.newAggregation(// @@ -440,10 +377,7 @@ public void shouldUseAliasedFieldnameForProjectionsIncludingOperationsDownThePip assertThat(getAsDBObject(group, "count"), is(new BasicDBObjectBuilder().add("$sum", "$tags_count").get())); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void conditionExpressionBasedFieldsShouldBeReferencableInFollowingOperations() { DBObject agg = newAggregation( // @@ -461,10 +395,7 @@ public void conditionExpressionBasedFieldsShouldBeReferencableInFollowingOperati assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.else", "no-answer")); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldRenderProjectionConditionalExpressionCorrectly() { DBObject agg = Aggregation.newAggregation(// @@ -483,10 +414,7 @@ public void shouldRenderProjectionConditionalExpressionCorrectly() { assertThat(getAsDBObject(project, "color"), isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldRenderProjectionConditionalCorrectly() { DBObject agg = Aggregation.newAggregation(// @@ -506,10 +434,7 @@ public void shouldRenderProjectionConditionalCorrectly() { assertThat(getAsDBObject(project, "color"), isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldRenderProjectionConditionalWithCriteriaCorrectly() { DBObject agg = Aggregation @@ -528,10 +453,7 @@ public void shouldRenderProjectionConditionalWithCriteriaCorrectly() { assertThat(getAsDBObject(project, "color"), isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void referencingProjectionAliasesShouldRenderProjectionConditionalWithFieldReferenceCorrectly() { DBObject agg = Aggregation @@ -553,10 +475,7 @@ public void referencingProjectionAliasesShouldRenderProjectionConditionalWithFie assertThat(getAsDBObject(project, "luminosity"), isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void referencingProjectionAliasesShouldRenderProjectionConditionalWithCriteriaReferenceCorrectly() { DBObject agg = Aggregation @@ -578,10 +497,7 @@ public void referencingProjectionAliasesShouldRenderProjectionConditionalWithCri assertThat(getAsDBObject(project, "luminosity"), isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldRenderProjectionIfNullWithFieldReferenceCorrectly() { DBObject agg = Aggregation @@ -599,10 +515,7 @@ public void shouldRenderProjectionIfNullWithFieldReferenceCorrectly() { isBsonObject().containing("$ifNull", Arrays. asList("$chroma", "unknown"))); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void shouldRenderProjectionIfNullWithFallbackFieldReferenceCorrectly() { DBObject agg = Aggregation @@ -619,10 +532,7 @@ public void shouldRenderProjectionIfNullWithFallbackFieldReferenceCorrectly() { isBsonObject().containing("$ifNull", Arrays.asList("$chroma", "$fallback"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldHonorDefaultCountField() { DBObject agg = Aggregation @@ -636,10 +546,7 @@ public void shouldHonorDefaultCountField() { assertThat(project, isBsonObject().containing("count", 1)); } - /** - * @see DATAMONGO-1533 - */ - @Test + @Test // DATAMONGO-1533 public void groupOperationShouldAllowUsageOfDerivedSpELAggregationExpression() { DBObject agg = newAggregation( // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java index 19459dc093..30fe6f1589 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -33,26 +33,17 @@ */ public class BucketAutoOperationUnitTests { - /** - * @see DATAMONGO-1552 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1552 public void rejectsNullFields() { new BucketAutoOperation((Field) null, 0); } - /** - * @see DATAMONGO-1552 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1552 public void rejectsNonPositiveIntegerNullFields() { new BucketAutoOperation(Fields.field("field"), 0); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderBucketOutputExpressions() { BucketAutoOperation operation = Aggregation.bucketAuto("field", 5) // @@ -64,18 +55,12 @@ public void shouldRenderBucketOutputExpressions() { "{ \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"titles\" : { $push: \"$title\" } }}"))); } - /** - * @see DATAMONGO-1552 - */ - @Test(expected = IllegalStateException.class) + @Test(expected = IllegalStateException.class) // DATAMONGO-1552 public void shouldRenderEmptyAggregationExpression() { bucket("groupby").andOutput("field").as("alias"); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderBucketOutputOperators() { BucketAutoOperation operation = Aggregation.bucketAuto("field", 5) // @@ -85,10 +70,7 @@ public void shouldRenderBucketOutputOperators() { assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $sum: 1 } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderCorrectly() { DBObject agg = bucketAuto("field", 1).withBuckets(5).toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -96,10 +78,7 @@ public void shouldRenderCorrectly() { assertThat(agg, is(JSON.parse("{ $bucketAuto: { groupBy: \"$field\", buckets: 5 } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderGranulariy() { DBObject agg = bucketAuto("field", 1) // @@ -109,10 +88,7 @@ public void shouldRenderGranulariy() { assertThat(agg, is(JSON.parse("{ $bucketAuto: { buckets: 1, granularity: \"E24\", groupBy: \"$field\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderSumOperator() { BucketAutoOperation operation = bucketAuto("field", 5) // @@ -122,10 +98,7 @@ public void shouldRenderSumOperator() { assertThat(extractOutput(dbObject), is(JSON.parse("{ cummulated_score : { $sum: \"$score\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderSumWithOwnOutputExpression() { BucketAutoOperation operation = bucketAuto("field", 5) // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java index 989267e7b9..f6d50bc873 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/BucketOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -33,18 +33,12 @@ */ public class BucketOperationUnitTests { - /** - * @see DATAMONGO-1552 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1552 public void rejectsNullFields() { new BucketOperation((Field) null); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderBucketOutputExpressions() { BucketOperation operation = Aggregation.bucket("field") // @@ -56,18 +50,12 @@ public void shouldRenderBucketOutputExpressions() { "{ \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"titles\" : { $push: \"$title\" } }}"))); } - /** - * @see DATAMONGO-1552 - */ - @Test(expected = IllegalStateException.class) + @Test(expected = IllegalStateException.class) // DATAMONGO-1552 public void shouldRenderEmptyAggregationExpression() { bucket("groupby").andOutput("field").as("alias"); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderBucketOutputOperators() { BucketOperation operation = Aggregation.bucket("field") // @@ -77,10 +65,7 @@ public void shouldRenderBucketOutputOperators() { assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $sum: 1 } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderSumAggregationExpression() { DBObject agg = bucket("field") // @@ -91,10 +76,7 @@ public void shouldRenderSumAggregationExpression() { "{ $bucket: { groupBy: \"$field\", boundaries: [], output : { quizTotal: { $sum: \"$quizzes\"} } } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderDefault() { DBObject agg = bucket("field").withDefaultBucket("default bucket").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -103,10 +85,7 @@ public void shouldRenderDefault() { is(JSON.parse("{ $bucket: { groupBy: \"$field\", boundaries: [], default: \"default bucket\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderBoundaries() { DBObject agg = bucket("field") // @@ -118,10 +97,7 @@ public void shouldRenderBoundaries() { is(JSON.parse("{ $bucket: { boundaries: [0, 10, 20], default: \"default bucket\", groupBy: \"$field\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderSumOperator() { BucketOperation operation = bucket("field") // @@ -131,10 +107,7 @@ public void shouldRenderSumOperator() { assertThat(extractOutput(dbObject), is(JSON.parse("{ cummulated_score : { $sum: \"$score\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderSumWithValueOperator() { BucketOperation operation = bucket("field") // @@ -144,10 +117,7 @@ public void shouldRenderSumWithValueOperator() { assertThat(extractOutput(dbObject), is(JSON.parse("{ cummulated_score : { $sum: 4 } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderAvgOperator() { BucketOperation operation = bucket("field") // @@ -157,10 +127,7 @@ public void shouldRenderAvgOperator() { assertThat(extractOutput(dbObject), is(JSON.parse("{ average : { $avg: \"$score\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderFirstOperator() { BucketOperation operation = bucket("field") // @@ -170,10 +137,7 @@ public void shouldRenderFirstOperator() { assertThat(extractOutput(dbObject), is(JSON.parse("{ first_title : { $first: \"$title\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderLastOperator() { BucketOperation operation = bucket("field") // @@ -183,10 +147,7 @@ public void shouldRenderLastOperator() { assertThat(extractOutput(dbObject), is(JSON.parse("{ last_title : { $last: \"$title\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderMinOperator() { BucketOperation operation = bucket("field") // @@ -196,10 +157,7 @@ public void shouldRenderMinOperator() { assertThat(extractOutput(dbObject), is(JSON.parse("{ min_score : { $min: \"$score\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderPushOperator() { BucketOperation operation = bucket("field") // @@ -209,10 +167,7 @@ public void shouldRenderPushOperator() { assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $push: \"$title\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderAddToSetOperator() { BucketOperation operation = bucket("field") // @@ -222,10 +177,7 @@ public void shouldRenderAddToSetOperator() { assertThat(extractOutput(dbObject), is(JSON.parse("{ titles : { $addToSet: \"$title\" } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderSumWithExpression() { BucketOperation operation = bucket("field") // @@ -235,10 +187,7 @@ public void shouldRenderSumWithExpression() { assertThat(extractOutput(dbObject), is(JSON.parse("{ total : { $sum: { $add : [\"$netPrice\", \"$tax\"]} } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderSumWithOwnOutputExpression() { BucketOperation operation = bucket("field") // @@ -249,10 +198,7 @@ public void shouldRenderSumWithOwnOutputExpression() { is(JSON.parse("{ total : { $multiply: [ {$add : [\"$netPrice\", \"$tax\"]}, 5] } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldExposeDefaultCountField() { BucketOperation operation = bucket("field"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java index 009cf79e4d..45afe8873d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CondExpressionUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -36,42 +36,27 @@ */ public class CondExpressionUnitTests { - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-861 public void builderRejectsEmptyFieldName() { newBuilder().when(""); } - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-861 public void builderRejectsNullFieldName() { newBuilder().when((DBObject) null); } - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-861 public void builderRejectsNullCriteriaName() { newBuilder().when((Criteria) null); } - /** - * @see DATAMONGO-861 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-861 public void builderRejectsBuilderAsThenValue() { newBuilder().when("isYellow").then(newBuilder().when("field").then("then-value")).otherwise("otherwise"); } - /** - * @see DATAMONGO-861, DATAMONGO-1542 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void simpleBuilderShouldRenderCorrectly() { Cond operator = ConditionalOperators.when("isYellow").thenValueOf("bright").otherwise("dark"); @@ -85,10 +70,7 @@ public void simpleBuilderShouldRenderCorrectly() { assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861, DATAMONGO-1542 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void simpleCriteriaShouldRenderCorrectly() { Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100)).thenValueOf("bright") @@ -103,10 +85,7 @@ public void simpleCriteriaShouldRenderCorrectly() { assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void andCriteriaShouldRenderCorrectly() { Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100) // @@ -128,10 +107,7 @@ public void andCriteriaShouldRenderCorrectly() { assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861, DATAMONGO-1542 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void twoArgsCriteriaShouldRenderCorrectly() { Criteria criteria = Criteria.where("luminosity").gte(100) // @@ -151,10 +127,7 @@ public void twoArgsCriteriaShouldRenderCorrectly() { assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition)); } - /** - * @see DATAMONGO-861, DATAMONGO-1542 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void nestedCriteriaShouldRenderCorrectly() { Cond operator = ConditionalOperators.when(Criteria.where("luminosity").gte(100)) // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java index a91441558d..73a2201d9e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -29,28 +29,19 @@ */ public class CountOperationUnitTests { - /** - * @see DATAMONGO-1549 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1549 public void rejectsEmptyFieldName() { new CountOperation(""); } - /** - * @see DATAMONGO-1549 - */ - @Test + @Test // DATAMONGO-1549 public void shouldRenderCorrectly() { CountOperation countOperation = new CountOperation("field"); assertThat(countOperation.toDBObject(Aggregation.DEFAULT_CONTEXT), is(JSON.parse("{$count : \"field\" }"))); } - /** - * @see DATAMONGO-1549 - */ - @Test + @Test // DATAMONGO-1549 public void countExposesFields() { CountOperation countOperation = new CountOperation("field"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java index 228102b7cd..83b5f8ab65 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -33,10 +33,7 @@ */ public class FacetOperationUnitTests { - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderCorrectly() throws Exception { FacetOperation facetOperation = new FacetOperation() @@ -58,10 +55,7 @@ public void shouldRenderCorrectly() throws Exception { + "categorizedByYears: [ { $bucketAuto: { buckets: 5, groupBy: \"$year\" } } ] } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldRenderEmpty() throws Exception { FacetOperation facetOperation = facet(); @@ -71,10 +65,7 @@ public void shouldRenderEmpty() throws Exception { assertThat(dbObject, is(JSON.parse("{ $facet: { } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1552 public void shouldRejectNonExistingFields() throws Exception { FacetOperation facetOperation = new FacetOperation() @@ -95,10 +86,7 @@ public void shouldRejectNonExistingFields() throws Exception { + "categorizedByYears: [ { $bucketAuto: { buckets: 5, groupBy: \"$year\" } } ] } }"))); } - /** - * @see DATAMONGO-1552 - */ - @Test + @Test // DATAMONGO-1552 public void shouldHonorProjectedFields() { FacetOperation facetOperation = new FacetOperation() diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FieldsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FieldsUnitTests.java index 77cd200207..014e0dd95c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FieldsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FieldsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -106,28 +106,19 @@ public void rejectsAmbiguousFieldNames() { fields("b", "a.b"); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void stripsLeadingDollarsFromName() { assertThat(Fields.field("$name").getName(), is("name")); assertThat(Fields.field("$$$$name").getName(), is("name")); } - /** - * @see DATAMONGO-774 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-774 public void rejectsNameConsistingOfDollarOnly() { Fields.field("$"); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void stripsLeadingDollarsFromTarget() { assertThat(Fields.field("$target").getTarget(), is("target")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java index 6bb7219046..e2b389327d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FilterExpressionUnitTests.java @@ -56,10 +56,7 @@ public void setUp() { new QueryMapper(new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), mappingContext))); } - /** - * @see DATAMONGO-1491 - */ - @Test + @Test // DATAMONGO-1491 public void shouldConstructFilterExpressionCorrectly() { TypedAggregation agg = Aggregation.newAggregation(Sales.class, @@ -83,10 +80,7 @@ public void shouldConstructFilterExpressionCorrectly() { assertThat($filter, is(expected)); } - /** - * @see DATAMONGO-1491 - */ - @Test + @Test // DATAMONGO-1491 public void shouldConstructFilterExpressionCorrectlyWhenUsingFilterOnProjectionBuilder() { TypedAggregation agg = Aggregation.newAggregation(Sales.class, Aggregation.project().and("items") @@ -108,10 +102,7 @@ public void shouldConstructFilterExpressionCorrectlyWhenUsingFilterOnProjectionB assertThat($filter, is(expected)); } - /** - * @see DATAMONGO-1491 - */ - @Test + @Test // DATAMONGO-1491 public void shouldConstructFilterExpressionCorrectlyWhenInputMapToArray() { TypedAggregation agg = Aggregation.newAggregation(Sales.class, diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java index 95e2f13a73..f676ed5a93 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2017 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. @@ -33,10 +33,7 @@ */ public class GeoNearOperationUnitTests { - /** - * @see DATAMONGO-1127 - */ - @Test + @Test // DATAMONGO-1127 public void rendersNearQueryAsAggregationOperation() { NearQuery query = NearQuery.near(10.0, 10.0); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java index 3400911a87..f4e91860f0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -35,18 +35,12 @@ */ public class GraphLookupOperationUnitTests { - /** - * @see DATAMONGO-1551 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1551 public void rejectsNullFromCollection() { GraphLookupOperation.builder().from(null); } - /** - * @see DATAMONGO-1551 - */ - @Test + @Test // DATAMONGO-1551 public void shouldRenderCorrectly() { GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // @@ -63,10 +57,7 @@ public void shouldRenderCorrectly() { isBsonObject().containing("$graphLookup.depthField", "depth").containing("$graphLookup.maxDepth", 42L)); } - /** - * @see DATAMONGO-1551 - */ - @Test + @Test // DATAMONGO-1551 public void shouldRenderCriteriaCorrectly() { GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // @@ -82,10 +73,7 @@ public void shouldRenderCriteriaCorrectly() { isBsonObject().containing("$graphLookup.restrictSearchWithMatch", new BasicDBObject("key", "value"))); } - /** - * @see DATAMONGO-1551 - */ - @Test + @Test // DATAMONGO-1551 public void shouldRenderArrayOfStartsWithCorrectly() { GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // @@ -102,10 +90,7 @@ public void shouldRenderArrayOfStartsWithCorrectly() { + "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"))); } - /** - * @see DATAMONGO-1551 - */ - @Test + @Test // DATAMONGO-1551 public void shouldRenderMixedArrayOfStartsWithCorrectly() { GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // @@ -122,10 +107,7 @@ public void shouldRenderMixedArrayOfStartsWithCorrectly() { + "connectFromField: \"reportsTo\", connectToField: \"name\", as: \"reportingHierarchy\" } }"))); } - /** - * @see DATAMONGO-1551 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1551 public void shouldRejectUnknownTypeInMixedArrayOfStartsWithCorrectly() { GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // @@ -136,10 +118,7 @@ public void shouldRejectUnknownTypeInMixedArrayOfStartsWithCorrectly() { .as("reportingHierarchy"); } - /** - * @see DATAMONGO-1551 - */ - @Test + @Test // DATAMONGO-1551 public void shouldRenderStartWithAggregationExpressions() { GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java index 0bb274e851..ad765224c0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -42,10 +42,7 @@ public void rejectsNullFields() { new GroupOperation((Fields) null); } - /** - * @see DATAMONGO-759 - */ - @Test + @Test // DATAMONGO-759 public void groupOperationWithNoGroupIdFieldsShouldGenerateNullAsGroupId() { GroupOperation operation = new GroupOperation(Fields.from()); @@ -57,10 +54,7 @@ public void groupOperationWithNoGroupIdFieldsShouldGenerateNullAsGroupId() { assertThat(groupClause.get(UNDERSCORE_ID), is(nullValue())); } - /** - * @see DATAMONGO-759 - */ - @Test + @Test // DATAMONGO-759 public void groupOperationWithNoGroupIdFieldsButAdditionalFieldsShouldGenerateNullAsGroupId() { GroupOperation operation = new GroupOperation(Fields.from()).count().as("cnt").last("foo").as("foo"); @@ -188,10 +182,7 @@ public void groupOperationAddToSetWithValue() { assertThat(push, is((DBObject) new BasicDBObject("$addToSet", 42))); } - /** - * @see DATAMONGO-979 - */ - @Test + @Test // DATAMONGO-979 public void shouldRenderSizeExpressionInGroup() { GroupOperation groupOperation = Aggregation // @@ -205,10 +196,7 @@ public void shouldRenderSizeExpressionInGroup() { assertThat(tagsCount.get("$first"), is((Object) new BasicDBObject("$size", Arrays.asList("$tags")))); } - /** - * @see DATAMONGO-1327 - */ - @Test + @Test // DATAMONGO-1327 public void groupOperationStdDevSampWithValue() { GroupOperation groupOperation = Aggregation.group("a", "b").stdDevSamp("field").as("fieldStdDevSamp"); @@ -219,10 +207,7 @@ public void groupOperationStdDevSampWithValue() { assertThat(push, is((DBObject) new BasicDBObject("$stdDevSamp", "$field"))); } - /** - * @see DATAMONGO-1327 - */ - @Test + @Test // DATAMONGO-1327 public void groupOperationStdDevPopWithValue() { GroupOperation groupOperation = Aggregation.group("a", "b").stdDevPop("field").as("fieldStdDevPop"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java index 9ff31ecaab..2784f172d3 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LookupOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -33,42 +33,27 @@ */ public class LookupOperationUnitTests { - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void rejectsNullForFrom() { new LookupOperation(null, Fields.field("localField"), Fields.field("foreignField"), Fields.field("as")); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void rejectsNullLocalFieldField() { new LookupOperation(Fields.field("from"), null, Fields.field("foreignField"), Fields.field("as")); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void rejectsNullForeignField() { new LookupOperation(Fields.field("from"), Fields.field("localField"), null, Fields.field("as")); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void rejectsNullForAs() { new LookupOperation(Fields.field("from"), Fields.field("localField"), Fields.field("foreignField"), null); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupOperationWithValues() { LookupOperation lookupOperation = Aggregation.lookup("a", "b", "c", "d"); @@ -82,10 +67,7 @@ public void lookupOperationWithValues() { .containing("as", "d")); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupOperationExposesAsField() { LookupOperation lookupOperation = Aggregation.lookup("a", "b", "c", "d"); @@ -102,42 +84,27 @@ private DBObject extractDbObjectFromLookupOperation(LookupOperation lookupOperat return lookupClause; } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void builderRejectsNullFromField() { LookupOperation.newLookup().from(null); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void builderRejectsNullLocalField() { LookupOperation.newLookup().from("a").localField(null); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void builderRejectsNullForeignField() { LookupOperation.newLookup().from("a").localField("b").foreignField(null); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void builderRejectsNullAsField() { LookupOperation.newLookup().from("a").localField("b").foreignField("c").as(null); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupBuilderBuildsCorrectClause() { LookupOperation lookupOperation = LookupOperation.newLookup().from("a").localField("b").foreignField("c").as("d"); @@ -151,10 +118,7 @@ public void lookupBuilderBuildsCorrectClause() { .containing("as", "d")); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupBuilderExposesFields() { LookupOperation lookupOperation = LookupOperation.newLookup().from("a").localField("b").foreignField("c").as("d"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java index 0d5775d359..485eea0a27 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -24,10 +24,7 @@ */ public class OutOperationUnitTest { - /** - * @see DATAMONGO-1418 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1418 public void shouldCheckNPEInCreation() { new OutOperation(null); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java index 256c0282c1..668f1feb1b 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 @@ -60,18 +60,12 @@ public class ProjectionOperationUnitTests { static final String DIVIDE = "$divide"; static final String PROJECT = "$project"; - /** - * @see DATAMONGO-586 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void rejectsNullFields() { new ProjectionOperation(null); } - /** - * @see DATAMONGO-586 - */ - @Test + @Test // DATAMONGO-586 public void declaresBackReferenceCorrectly() { ProjectionOperation operation = new ProjectionOperation(); @@ -82,10 +76,7 @@ public void declaresBackReferenceCorrectly() { assertThat(projectClause.get("prop"), is((Object) Fields.UNDERSCORE_ID_REF)); } - /** - * @see DATAMONGO-586 - */ - @Test + @Test // DATAMONGO-586 public void alwaysUsesExplicitReference() { ProjectionOperation operation = new ProjectionOperation(Fields.fields("foo").and("bar", "foobar")); @@ -97,10 +88,7 @@ public void alwaysUsesExplicitReference() { assertThat(projectClause.get("bar"), is((Object) "$foobar")); } - /** - * @see DATAMONGO-586 - */ - @Test + @Test // DATAMONGO-586 public void aliasesSimpleFieldProjection() { ProjectionOperation operation = new ProjectionOperation(); @@ -111,10 +99,7 @@ public void aliasesSimpleFieldProjection() { assertThat(projectClause.get("bar"), is((Object) "$foo")); } - /** - * @see DATAMONGO-586 - */ - @Test + @Test // DATAMONGO-586 public void aliasesArithmeticProjection() { ProjectionOperation operation = new ProjectionOperation(); @@ -129,10 +114,7 @@ public void aliasesArithmeticProjection() { assertThat(addClause.get(1), is((Object) 41)); } - /** - * @see DATAMONGO-586 - */ - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationWithoutAlias() { String fieldName = "a"; @@ -145,10 +127,7 @@ public void arithmenticProjectionOperationWithoutAlias() { assertThat(oper.get(ADD), is((Object) Arrays. asList("$a", 1))); } - /** - * @see DATAMONGO-586 - */ - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationPlus() { String fieldName = "a"; @@ -162,10 +141,7 @@ public void arithmenticProjectionOperationPlus() { assertThat(oper.get(ADD), is((Object) Arrays. asList("$a", 1))); } - /** - * @see DATAMONGO-586 - */ - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationMinus() { String fieldName = "a"; @@ -179,10 +155,7 @@ public void arithmenticProjectionOperationMinus() { assertThat(oper.get(SUBTRACT), is((Object) Arrays. asList("$a", 1))); } - /** - * @see DATAMONGO-586 - */ - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationMultiply() { String fieldName = "a"; @@ -196,10 +169,7 @@ public void arithmenticProjectionOperationMultiply() { assertThat(oper.get(MULTIPLY), is((Object) Arrays. asList("$a", 1))); } - /** - * @see DATAMONGO-586 - */ - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationDivide() { String fieldName = "a"; @@ -213,19 +183,13 @@ public void arithmenticProjectionOperationDivide() { assertThat(oper.get(DIVIDE), is((Object) Arrays. asList("$a", 1))); } - /** - * @see DATAMONGO-586 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 public void arithmenticProjectionOperationDivideByZeroException() { new ProjectionOperation().and("a").divide(0); } - /** - * @see DATAMONGO-586 - */ - @Test + @Test // DATAMONGO-586 public void arithmenticProjectionOperationMod() { String fieldName = "a"; @@ -239,19 +203,13 @@ public void arithmenticProjectionOperationMod() { assertThat(oper.get(MOD), is((Object) Arrays. asList("$a", 3))); } - /** - * @see DATAMONGO-758 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-758 public void excludeShouldThrowExceptionForFieldsOtherThanUnderscoreId() { new ProjectionOperation().andExclude("foo"); } - /** - * @see DATAMONGO-758 - */ - @Test + @Test // DATAMONGO-758 public void excludeShouldAllowExclusionOfUnderscoreId() { ProjectionOperation projectionOp = new ProjectionOperation().andExclude(Fields.UNDERSCORE_ID); @@ -260,10 +218,7 @@ public void excludeShouldAllowExclusionOfUnderscoreId() { assertThat((Integer) projectClause.get(Fields.UNDERSCORE_ID), is(0)); } - /** - * @see DATAMONGO-757 - */ - @Test + @Test // DATAMONGO-757 public void usesImplictAndExplicitFieldAliasAndIncludeExclude() { ProjectionOperation operation = Aggregation.project("foo").and("foobar").as("bar").andInclude("inc1", "inc2") @@ -285,10 +240,7 @@ public void arithmenticProjectionOperationModByZeroException() { new ProjectionOperation().and("a").mod(0); } - /** - * @see DATAMONGO-769 - */ - @Test + @Test // DATAMONGO-769 public void allowArithmeticOperationsWithFieldReferences() { ProjectionOperation operation = Aggregation.project() // @@ -313,10 +265,7 @@ public void allowArithmeticOperationsWithFieldReferences() { is(new BasicDBObject("$mod", dbList("$foo", "$bar")))); } - /** - * @see DATAMONGO-774 - */ - @Test + @Test // DATAMONGO-774 public void projectionExpressions() { ProjectionOperation operation = Aggregation.project() // @@ -328,10 +277,7 @@ public void projectionExpressions() { "{ \"$project\" : { \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"bar\" : \"$foo\"}}")); } - /** - * @see DATAMONGO-975 - */ - @Test + @Test // DATAMONGO-975 public void shouldRenderDateTimeFragmentExtractionsForSimpleFieldProjectionsCorrectly() { ProjectionOperation operation = Aggregation.project() // @@ -364,10 +310,7 @@ public void shouldRenderDateTimeFragmentExtractionsForSimpleFieldProjectionsCorr assertThat(projected.get("dayOfWeek"), is((Object) new BasicDBObject("$dayOfWeek", Arrays.asList("$date")))); } - /** - * @see DATAMONGO-975 - */ - @Test + @Test // DATAMONGO-975 public void shouldRenderDateTimeFragmentExtractionsForExpressionProjectionsCorrectly() throws Exception { ProjectionOperation operation = Aggregation.project() // @@ -384,10 +327,7 @@ public void shouldRenderDateTimeFragmentExtractionsForExpressionProjectionsCorre Arrays.asList(new BasicDBObject("$add", Arrays. asList("$date", 86400000)))))); } - /** - * @see DATAMONGO-979 - */ - @Test + @Test // DATAMONGO-979 public void shouldRenderSizeExpressionInProjection() { ProjectionOperation operation = Aggregation // @@ -402,10 +342,7 @@ public void shouldRenderSizeExpressionInProjection() { assertThat(projected.get("tags_count"), is((Object) new BasicDBObject("$size", Arrays.asList("$tags")))); } - /** - * @see DATAMONGO-979 - */ - @Test + @Test // DATAMONGO-979 public void shouldRenderGenericSizeExpressionInProjection() { ProjectionOperation operation = Aggregation // @@ -419,10 +356,7 @@ public void shouldRenderGenericSizeExpressionInProjection() { assertThat(projected.get("tags_count"), is((Object) new BasicDBObject("$size", Arrays.asList("$tags")))); } - /** - * @see DATAMONGO-1457 - */ - @Test + @Test // DATAMONGO-1457 public void shouldRenderSliceCorrectly() throws Exception { ProjectionOperation operation = Aggregation.project().and("field").slice(10).as("renamed"); @@ -434,10 +368,7 @@ public void shouldRenderSliceCorrectly() throws Exception { is((Object) new BasicDBObject("$slice", Arrays. asList("$field", 10)))); } - /** - * @see DATAMONGO-1457 - */ - @Test + @Test // DATAMONGO-1457 public void shouldRenderSliceWithPositionCorrectly() throws Exception { ProjectionOperation operation = Aggregation.project().and("field").slice(10, 5).as("renamed"); @@ -449,10 +380,7 @@ public void shouldRenderSliceWithPositionCorrectly() throws Exception { is((Object) new BasicDBObject("$slice", Arrays. asList("$field", 5, 10)))); } - /** - * @see DATAMONGO-784 - */ - @Test + @Test // DATAMONGO-784 public void shouldRenderCmpCorrectly() { ProjectionOperation operation = Aggregation.project().and("field").cmp(10).as("cmp10"); @@ -461,10 +389,7 @@ public void shouldRenderCmpCorrectly() { isBsonObject().containing("$project.cmp10.$cmp.[0]", "$field").containing("$project.cmp10.$cmp.[1]", 10)); } - /** - * @see DATAMONGO-784 - */ - @Test + @Test // DATAMONGO-784 public void shouldRenderEqCorrectly() { ProjectionOperation operation = Aggregation.project().and("field").eq(10).as("eq10"); @@ -473,10 +398,7 @@ public void shouldRenderEqCorrectly() { isBsonObject().containing("$project.eq10.$eq.[0]", "$field").containing("$project.eq10.$eq.[1]", 10)); } - /** - * @see DATAMONGO-784 - */ - @Test + @Test // DATAMONGO-784 public void shouldRenderGtCorrectly() { ProjectionOperation operation = Aggregation.project().and("field").gt(10).as("gt10"); @@ -485,10 +407,7 @@ public void shouldRenderGtCorrectly() { isBsonObject().containing("$project.gt10.$gt.[0]", "$field").containing("$project.gt10.$gt.[1]", 10)); } - /** - * @see DATAMONGO-784 - */ - @Test + @Test // DATAMONGO-784 public void shouldRenderGteCorrectly() { ProjectionOperation operation = Aggregation.project().and("field").gte(10).as("gte10"); @@ -497,10 +416,7 @@ public void shouldRenderGteCorrectly() { isBsonObject().containing("$project.gte10.$gte.[0]", "$field").containing("$project.gte10.$gte.[1]", 10)); } - /** - * @see DATAMONGO-784 - */ - @Test + @Test // DATAMONGO-784 public void shouldRenderLtCorrectly() { ProjectionOperation operation = Aggregation.project().and("field").lt(10).as("lt10"); @@ -509,10 +425,7 @@ public void shouldRenderLtCorrectly() { isBsonObject().containing("$project.lt10.$lt.[0]", "$field").containing("$project.lt10.$lt.[1]", 10)); } - /** - * @see DATAMONGO-784 - */ - @Test + @Test // DATAMONGO-784 public void shouldRenderLteCorrectly() { ProjectionOperation operation = Aggregation.project().and("field").lte(10).as("lte10"); @@ -521,10 +434,7 @@ public void shouldRenderLteCorrectly() { isBsonObject().containing("$project.lte10.$lte.[0]", "$field").containing("$project.lte10.$lte.[1]", 10)); } - /** - * @see DATAMONGO-784 - */ - @Test + @Test // DATAMONGO-784 public void shouldRenderNeCorrectly() { ProjectionOperation operation = Aggregation.project().and("field").ne(10).as("ne10"); @@ -533,10 +443,7 @@ public void shouldRenderNeCorrectly() { isBsonObject().containing("$project.ne10.$ne.[0]", "$field").containing("$project.ne10.$ne.[1]", 10)); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSetEquals() { DBObject agg = project("A", "B").and("A").equalsArrays("B").as("sameElements") @@ -545,10 +452,7 @@ public void shouldRenderSetEquals() { assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, sameElements: { $setEquals: [ \"$A\", \"$B\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSetEqualsAggregationExpresssion() { DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").isEqualTo("B")).as("sameElements") @@ -557,10 +461,7 @@ public void shouldRenderSetEqualsAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, sameElements: { $setEquals: [ \"$A\", \"$B\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSetIntersection() { DBObject agg = project("A", "B").and("A").intersectsArrays("B").as("commonToBoth") @@ -570,10 +471,7 @@ public void shouldRenderSetIntersection() { is(JSON.parse("{ $project: { A: 1, B: 1, commonToBoth: { $setIntersection: [ \"$A\", \"$B\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSetIntersectionAggregationExpresssion() { DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").intersects("B")).as("commonToBoth") @@ -583,10 +481,7 @@ public void shouldRenderSetIntersectionAggregationExpresssion() { is(JSON.parse("{ $project: { A: 1, B: 1, commonToBoth: { $setIntersection: [ \"$A\", \"$B\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSetUnion() { DBObject agg = project("A", "B").and("A").unionArrays("B").as("allValues").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -594,10 +489,7 @@ public void shouldRenderSetUnion() { assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, allValues: { $setUnion: [ \"$A\", \"$B\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSetUnionAggregationExpresssion() { DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").union("B")).as("allValues") @@ -606,10 +498,7 @@ public void shouldRenderSetUnionAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, allValues: { $setUnion: [ \"$A\", \"$B\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSetDifference() { DBObject agg = project("A", "B").and("B").differenceToArray("A").as("inBOnly") @@ -618,10 +507,7 @@ public void shouldRenderSetDifference() { assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, inBOnly: { $setDifference: [ \"$B\", \"$A\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSetDifferenceAggregationExpresssion() { DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("B").differenceTo("A")).as("inBOnly") @@ -630,10 +516,7 @@ public void shouldRenderSetDifferenceAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, inBOnly: { $setDifference: [ \"$B\", \"$A\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSetIsSubset() { DBObject agg = project("A", "B").and("A").subsetOfArray("B").as("aIsSubsetOfB") @@ -642,10 +525,7 @@ public void shouldRenderSetIsSubset() { assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, aIsSubsetOfB: { $setIsSubset: [ \"$A\", \"$B\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSetIsSubsetAggregationExpresssion() { DBObject agg = project("A", "B").and(SetOperators.arrayAsSet("A").isSubsetOf("B")).as("aIsSubsetOfB") @@ -654,10 +534,7 @@ public void shouldRenderSetIsSubsetAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { A: 1, B: 1, aIsSubsetOfB: { $setIsSubset: [ \"$A\", \"$B\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderAnyElementTrue() { DBObject agg = project("responses").and("responses").anyElementInArrayTrue().as("isAnyTrue") @@ -666,10 +543,7 @@ public void shouldRenderAnyElementTrue() { assertThat(agg, is(JSON.parse("{ $project: { responses: 1, isAnyTrue: { $anyElementTrue: [ \"$responses\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderAnyElementTrueAggregationExpresssion() { DBObject agg = project("responses").and(SetOperators.arrayAsSet("responses").anyElementTrue()).as("isAnyTrue") @@ -678,10 +552,7 @@ public void shouldRenderAnyElementTrueAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { responses: 1, isAnyTrue: { $anyElementTrue: [ \"$responses\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderAllElementsTrue() { DBObject agg = project("responses").and("responses").allElementsInArrayTrue().as("isAllTrue") @@ -691,10 +562,7 @@ public void shouldRenderAllElementsTrue() { is(JSON.parse("{ $project: { responses: 1, isAllTrue: { $allElementsTrue: [ \"$responses\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderAllElementsTrueAggregationExpresssion() { DBObject agg = project("responses").and(SetOperators.arrayAsSet("responses").allElementsTrue()).as("isAllTrue") @@ -704,10 +572,7 @@ public void shouldRenderAllElementsTrueAggregationExpresssion() { is(JSON.parse("{ $project: { responses: 1, isAllTrue: { $allElementsTrue: [ \"$responses\" ] }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderAbs() { DBObject agg = project().and("anyNumber").absoluteValue().as("absoluteValue") @@ -716,10 +581,7 @@ public void shouldRenderAbs() { assertThat(agg, is(JSON.parse("{ $project: { absoluteValue : { $abs: \"$anyNumber\" }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderAbsAggregationExpresssion() { DBObject agg = project() @@ -730,10 +592,7 @@ public void shouldRenderAbsAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { delta: { $abs: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderAddAggregationExpresssion() { DBObject agg = project().and(ArithmeticOperators.valueOf("price").add("fee")).as("total") @@ -742,10 +601,7 @@ public void shouldRenderAddAggregationExpresssion() { assertThat(agg, is(JSON.parse(" { $project: { total: { $add: [ \"$price\", \"$fee\" ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderCeil() { DBObject agg = project().and("anyNumber").ceil().as("ceilValue").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -753,10 +609,7 @@ public void shouldRenderCeil() { assertThat(agg, is(JSON.parse("{ $project: { ceilValue : { $ceil: \"$anyNumber\" }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderCeilAggregationExpresssion() { DBObject agg = project().and( @@ -766,10 +619,7 @@ public void shouldRenderCeilAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { delta: { $ceil: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderDivide() { DBObject agg = project().and("value") @@ -780,10 +630,7 @@ public void shouldRenderDivide() { is(JSON.parse("{ $project: { result: { $divide: [ \"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderDivideAggregationExpresssion() { DBObject agg = project() @@ -795,10 +642,7 @@ public void shouldRenderDivideAggregationExpresssion() { .parse("{ $project: { result: { $divide: [ \"$anyNumber\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderExp() { DBObject agg = project().and("value").exp().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -806,10 +650,7 @@ public void shouldRenderExp() { assertThat(agg, is(JSON.parse("{ $project: { result: { $exp: \"$value\" } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderExpAggregationExpresssion() { DBObject agg = project() @@ -820,10 +661,7 @@ public void shouldRenderExpAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { result: { $exp: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderFloor() { DBObject agg = project().and("value").floor().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -831,10 +669,7 @@ public void shouldRenderFloor() { assertThat(agg, is(JSON.parse("{ $project: { result: { $floor: \"$value\" } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderFloorAggregationExpresssion() { DBObject agg = project().and( @@ -844,10 +679,7 @@ public void shouldRenderFloorAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { result: { $floor: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLn() { DBObject agg = project().and("value").ln().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -855,10 +687,7 @@ public void shouldRenderLn() { assertThat(agg, is(JSON.parse("{ $project: { result: { $ln: \"$value\"} }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLnAggregationExpresssion() { DBObject agg = project() @@ -868,10 +697,7 @@ public void shouldRenderLnAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { result: { $ln: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLog() { DBObject agg = project().and("value").log(2).as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -879,10 +705,7 @@ public void shouldRenderLog() { assertThat(agg, is(JSON.parse("{ $project: { result: { $log: [ \"$value\", 2] } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLogAggregationExpresssion() { DBObject agg = project().and( @@ -892,10 +715,7 @@ public void shouldRenderLogAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { result: { $log: [ { $subtract: [ \"$start\", \"$end\" ] }, 2] } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLog10() { DBObject agg = project().and("value").log10().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -903,10 +723,7 @@ public void shouldRenderLog10() { assertThat(agg, is(JSON.parse("{ $project: { result: { $log10: \"$value\" } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLog10AggregationExpresssion() { DBObject agg = project().and( @@ -916,10 +733,7 @@ public void shouldRenderLog10AggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { result: { $log10: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderMod() { DBObject agg = project().and("value").mod(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))) @@ -929,10 +743,7 @@ public void shouldRenderMod() { is(JSON.parse("{ $project: { result: { $mod: [\"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderModAggregationExpresssion() { DBObject agg = project().and( @@ -942,10 +753,7 @@ public void shouldRenderModAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { result: { $mod: [{ $subtract: [ \"$start\", \"$end\" ] }, 2] } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderMultiply() { DBObject agg = project().and("value") @@ -956,10 +764,7 @@ public void shouldRenderMultiply() { JSON.parse("{ $project: { result: { $multiply: [\"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderMultiplyAggregationExpresssion() { DBObject agg = project() @@ -971,10 +776,7 @@ public void shouldRenderMultiplyAggregationExpresssion() { "{ $project: { result: { $multiply: [{ $subtract: [ \"$start\", \"$end\" ] }, 2, \"$refToAnotherNumber\"] } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderPow() { DBObject agg = project().and("value").pow(2).as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -982,10 +784,7 @@ public void shouldRenderPow() { assertThat(agg, is(JSON.parse("{ $project: { result: { $pow: [\"$value\", 2] } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderPowAggregationExpresssion() { DBObject agg = project().and( @@ -995,10 +794,7 @@ public void shouldRenderPowAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { result: { $pow: [{ $subtract: [ \"$start\", \"$end\" ] }, 2] } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSqrt() { DBObject agg = project().and("value").sqrt().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1006,10 +802,7 @@ public void shouldRenderSqrt() { assertThat(agg, is(JSON.parse("{ $project: { result: { $sqrt: \"$value\" } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSqrtAggregationExpresssion() { DBObject agg = project().and( @@ -1019,10 +812,7 @@ public void shouldRenderSqrtAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { result: { $sqrt: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSubtract() { DBObject agg = project().and("numericField").minus(AggregationFunctionExpressions.SIZE.of(field("someArray"))) @@ -1032,10 +822,7 @@ public void shouldRenderSubtract() { is(JSON.parse("{ $project: { result: { $subtract: [ \"$numericField\", { $size : [\"$someArray\"]}] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSubtractAggregationExpresssion() { DBObject agg = project() @@ -1047,10 +834,7 @@ public void shouldRenderSubtractAggregationExpresssion() { is(JSON.parse("{ $project: { result: { $subtract: [ \"$numericField\", { $size : [\"$someArray\"]}] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderTrunc() { DBObject agg = project().and("value").trunc().as("result").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1058,10 +842,7 @@ public void shouldRenderTrunc() { assertThat(agg, is(JSON.parse("{ $project: { result : { $trunc: \"$value\" }}}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderTruncAggregationExpresssion() { DBObject agg = project().and( @@ -1071,10 +852,7 @@ public void shouldRenderTruncAggregationExpresssion() { assertThat(agg, is(JSON.parse("{ $project: { result: { $trunc: { $subtract: [ \"$start\", \"$end\" ] } } }}"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderConcat() { DBObject agg = project().and("item").concat(" - ", field("description")).as("itemDescription") @@ -1085,10 +863,7 @@ public void shouldRenderConcat() { } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderConcatAggregationExpression() { DBObject agg = project().and(StringOperators.valueOf("item").concat(" - ").concatValueOf("description")) @@ -1099,10 +874,7 @@ public void shouldRenderConcatAggregationExpression() { } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSubstr() { DBObject agg = project().and("quarter").substring(0, 2).as("yearSubstring").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1110,10 +882,7 @@ public void shouldRenderSubstr() { assertThat(agg, is(JSON.parse("{ $project: { yearSubstring: { $substr: [ \"$quarter\", 0, 2 ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSubstrAggregationExpression() { DBObject agg = project().and(StringOperators.valueOf("quarter").substring(0, 2)).as("yearSubstring") @@ -1122,10 +891,7 @@ public void shouldRenderSubstrAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { yearSubstring: { $substr: [ \"$quarter\", 0, 2 ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderToLower() { DBObject agg = project().and("item").toLower().as("item").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1133,10 +899,7 @@ public void shouldRenderToLower() { assertThat(agg, is(JSON.parse("{ $project: { item: { $toLower: \"$item\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderToLowerAggregationExpression() { DBObject agg = project().and(StringOperators.valueOf("item").toLower()).as("item") @@ -1145,10 +908,7 @@ public void shouldRenderToLowerAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { item: { $toLower: \"$item\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderToUpper() { DBObject agg = project().and("item").toUpper().as("item").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1156,10 +916,7 @@ public void shouldRenderToUpper() { assertThat(agg, is(JSON.parse("{ $project: { item: { $toUpper: \"$item\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderToUpperAggregationExpression() { DBObject agg = project().and(StringOperators.valueOf("item").toUpper()).as("item") @@ -1168,10 +925,7 @@ public void shouldRenderToUpperAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { item: { $toUpper: \"$item\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderStrCaseCmp() { DBObject agg = project().and("quarter").strCaseCmp("13q4").as("comparisonResult") @@ -1180,10 +934,7 @@ public void shouldRenderStrCaseCmp() { assertThat(agg, is(JSON.parse("{ $project: { comparisonResult: { $strcasecmp: [ \"$quarter\", \"13q4\" ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderStrCaseCmpAggregationExpression() { DBObject agg = project().and(StringOperators.valueOf("quarter").strCaseCmp("13q4")).as("comparisonResult") @@ -1192,10 +943,7 @@ public void shouldRenderStrCaseCmpAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { comparisonResult: { $strcasecmp: [ \"$quarter\", \"13q4\" ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderArrayElementAt() { DBObject agg = project().and("favorites").arrayElementAt(0).as("first").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1203,10 +951,7 @@ public void shouldRenderArrayElementAt() { assertThat(agg, is(JSON.parse("{ $project: { first: { $arrayElemAt: [ \"$favorites\", 0 ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderArrayElementAtAggregationExpression() { DBObject agg = project().and(ArrayOperators.arrayOf("favorites").elementAt(0)).as("first") @@ -1215,10 +960,7 @@ public void shouldRenderArrayElementAtAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { first: { $arrayElemAt: [ \"$favorites\", 0 ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderConcatArrays() { DBObject agg = project().and("instock").concatArrays("ordered").as("items").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1226,10 +968,7 @@ public void shouldRenderConcatArrays() { assertThat(agg, is(JSON.parse("{ $project: { items: { $concatArrays: [ \"$instock\", \"$ordered\" ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderConcatArraysAggregationExpression() { DBObject agg = project().and(ArrayOperators.arrayOf("instock").concat("ordered")).as("items") @@ -1238,10 +977,7 @@ public void shouldRenderConcatArraysAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { items: { $concatArrays: [ \"$instock\", \"$ordered\" ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderIsArray() { DBObject agg = project().and("instock").isArray().as("isAnArray").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1249,10 +985,7 @@ public void shouldRenderIsArray() { assertThat(agg, is(JSON.parse("{ $project: { isAnArray: { $isArray: \"$instock\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderIsArrayAggregationExpression() { DBObject agg = project().and(ArrayOperators.arrayOf("instock").isArray()).as("isAnArray") @@ -1261,10 +994,7 @@ public void shouldRenderIsArrayAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { isAnArray: { $isArray: \"$instock\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSizeAggregationExpression() { DBObject agg = project().and(ArrayOperators.arrayOf("instock").length()).as("arraySize") @@ -1273,10 +1003,7 @@ public void shouldRenderSizeAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { arraySize: { $size: \"$instock\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSliceAggregationExpression() { DBObject agg = project().and(ArrayOperators.arrayOf("favorites").slice().itemCount(3)).as("threeFavorites") @@ -1285,10 +1012,7 @@ public void shouldRenderSliceAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { threeFavorites: { $slice: [ \"$favorites\", 3 ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSliceWithPositionAggregationExpression() { DBObject agg = project().and(ArrayOperators.arrayOf("favorites").slice().offset(2).itemCount(3)) @@ -1297,10 +1021,7 @@ public void shouldRenderSliceWithPositionAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { threeFavorites: { $slice: [ \"$favorites\", 2, 3 ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLiteral() { DBObject agg = project().and("$1").asLiteral().as("literalOnly").toDBObject(Aggregation.DEFAULT_CONTEXT); @@ -1308,10 +1029,7 @@ public void shouldRenderLiteral() { assertThat(agg, is(JSON.parse("{ $project: { literalOnly: { $literal: \"$1\"} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLiteralAggregationExpression() { DBObject agg = project().and(LiteralOperators.valueOf("$1").asLiteral()).as("literalOnly") @@ -1320,10 +1038,7 @@ public void shouldRenderLiteralAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { literalOnly: { $literal: \"$1\"} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderDayOfYearAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").dayOfYear()).as("dayOfYear") @@ -1332,10 +1047,7 @@ public void shouldRenderDayOfYearAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { dayOfYear: { $dayOfYear: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderDayOfMonthAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").dayOfMonth()).as("day") @@ -1344,10 +1056,7 @@ public void shouldRenderDayOfMonthAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { day: { $dayOfMonth: \"$date\" }} }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderDayOfWeekAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").dayOfWeek()).as("dayOfWeek") @@ -1356,10 +1065,7 @@ public void shouldRenderDayOfWeekAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { dayOfWeek: { $dayOfWeek: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderYearAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").year()).as("year") @@ -1368,10 +1074,7 @@ public void shouldRenderYearAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { year: { $year: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderMonthAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").month()).as("month") @@ -1380,10 +1083,7 @@ public void shouldRenderMonthAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { month: { $month: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderWeekAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").week()).as("week") @@ -1392,10 +1092,7 @@ public void shouldRenderWeekAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { week: { $week: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderHourAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").hour()).as("hour") @@ -1404,10 +1101,7 @@ public void shouldRenderHourAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { hour: { $hour: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderMinuteAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").minute()).as("minute") @@ -1416,10 +1110,7 @@ public void shouldRenderMinuteAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { minute: { $minute: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSecondAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").second()).as("second") @@ -1428,10 +1119,7 @@ public void shouldRenderSecondAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { second: { $second: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderMillisecondAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").millisecond()).as("msec") @@ -1440,10 +1128,7 @@ public void shouldRenderMillisecondAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { msec: { $millisecond: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderDateToString() { DBObject agg = project().and("date").dateAsFormattedString("%H:%M:%S:%L").as("time") @@ -1453,10 +1138,7 @@ public void shouldRenderDateToString() { is(JSON.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderDateToStringAggregationExpression() { DBObject agg = project().and(DateOperators.dateOf("date").toString("%H:%M:%S:%L")).as("time") @@ -1466,10 +1148,7 @@ public void shouldRenderDateToStringAggregationExpression() { is(JSON.parse("{ $project: { time: { $dateToString: { format: \"%H:%M:%S:%L\", date: \"$date\" } } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSumAggregationExpression() { DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").sum()).as("quizTotal") @@ -1478,10 +1157,7 @@ public void shouldRenderSumAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { quizTotal: { $sum: \"$quizzes\"} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderSumWithMultipleArgsAggregationExpression() { DBObject agg = project().and(ArithmeticOperators.valueOf("final").sum().and("midterm")).as("examTotal") @@ -1490,10 +1166,7 @@ public void shouldRenderSumWithMultipleArgsAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { examTotal: { $sum: [ \"$final\", \"$midterm\" ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderAvgAggregationExpression() { DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").avg()).as("quizAvg") @@ -1502,10 +1175,7 @@ public void shouldRenderAvgAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { quizAvg: { $avg: \"$quizzes\"} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderAvgWithMultipleArgsAggregationExpression() { DBObject agg = project().and(ArithmeticOperators.valueOf("final").avg().and("midterm")).as("examAvg") @@ -1514,10 +1184,7 @@ public void shouldRenderAvgWithMultipleArgsAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { examAvg: { $avg: [ \"$final\", \"$midterm\" ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderMaxAggregationExpression() { DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").max()).as("quizMax") @@ -1526,10 +1193,7 @@ public void shouldRenderMaxAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { quizMax: { $max: \"$quizzes\"} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderMaxWithMultipleArgsAggregationExpression() { DBObject agg = project().and(ArithmeticOperators.valueOf("final").max().and("midterm")).as("examMax") @@ -1538,10 +1202,7 @@ public void shouldRenderMaxWithMultipleArgsAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { examMax: { $max: [ \"$final\", \"$midterm\" ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderMinAggregationExpression() { DBObject agg = project().and(ArithmeticOperators.valueOf("quizzes").min()).as("quizMin") @@ -1550,10 +1211,7 @@ public void shouldRenderMinAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { quizMin: { $min: \"$quizzes\"} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderMinWithMultipleArgsAggregationExpression() { DBObject agg = project().and(ArithmeticOperators.valueOf("final").min().and("midterm")).as("examMin") @@ -1562,10 +1220,7 @@ public void shouldRenderMinWithMultipleArgsAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { examMin: { $min: [ \"$final\", \"$midterm\" ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderStdDevPopAggregationExpression() { DBObject agg = project().and(ArithmeticOperators.valueOf("scores").stdDevPop()).as("stdDev") @@ -1574,10 +1229,7 @@ public void shouldRenderStdDevPopAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { stdDev: { $stdDevPop: \"$scores\"} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderStdDevSampAggregationExpression() { DBObject agg = project().and(ArithmeticOperators.valueOf("scores").stdDevSamp()).as("stdDev") @@ -1586,10 +1238,7 @@ public void shouldRenderStdDevSampAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { stdDev: { $stdDevSamp: \"$scores\"} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderCmpAggregationExpression() { DBObject agg = project().and(ComparisonOperators.valueOf("qty").compareToValue(250)).as("cmp250") @@ -1598,10 +1247,7 @@ public void shouldRenderCmpAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { cmp250: { $cmp: [\"$qty\", 250]} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderEqAggregationExpression() { DBObject agg = project().and(ComparisonOperators.valueOf("qty").equalToValue(250)).as("eq250") @@ -1610,10 +1256,7 @@ public void shouldRenderEqAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { eq250: { $eq: [\"$qty\", 250]} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderGtAggregationExpression() { DBObject agg = project().and(ComparisonOperators.valueOf("qty").greaterThanValue(250)).as("gt250") @@ -1622,10 +1265,7 @@ public void shouldRenderGtAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { gt250: { $gt: [\"$qty\", 250]} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderGteAggregationExpression() { DBObject agg = project().and(ComparisonOperators.valueOf("qty").greaterThanEqualToValue(250)).as("gte250") @@ -1634,10 +1274,7 @@ public void shouldRenderGteAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { gte250: { $gte: [\"$qty\", 250]} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLtAggregationExpression() { DBObject agg = project().and(ComparisonOperators.valueOf("qty").lessThanValue(250)).as("lt250") @@ -1646,10 +1283,7 @@ public void shouldRenderLtAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { lt250: { $lt: [\"$qty\", 250]} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLteAggregationExpression() { DBObject agg = project().and(ComparisonOperators.valueOf("qty").lessThanEqualToValue(250)).as("lte250") @@ -1658,10 +1292,7 @@ public void shouldRenderLteAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { lte250: { $lte: [\"$qty\", 250]} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderNeAggregationExpression() { DBObject agg = project().and(ComparisonOperators.valueOf("qty").notEqualToValue(250)).as("ne250") @@ -1670,10 +1301,7 @@ public void shouldRenderNeAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { ne250: { $ne: [\"$qty\", 250]} } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLogicAndAggregationExpression() { DBObject agg = project() @@ -1685,10 +1313,7 @@ public void shouldRenderLogicAndAggregationExpression() { JSON.parse("{ $project: { result: { $and: [ { $gt: [ \"$qty\", 100 ] }, { $lt: [ \"$qty\", 250 ] } ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderLogicOrAggregationExpression() { DBObject agg = project() @@ -1700,10 +1325,7 @@ public void shouldRenderLogicOrAggregationExpression() { JSON.parse("{ $project: { result: { $or: [ { $gt: [ \"$qty\", 250 ] }, { $lt: [ \"$qty\", 200 ] } ] } } }"))); } - /** - * @see DATAMONGO-1536 - */ - @Test + @Test // DATAMONGO-1536 public void shouldRenderNotAggregationExpression() { DBObject agg = project().and(BooleanOperators.not(ComparisonOperators.valueOf("qty").greaterThanValue(250))) @@ -1712,10 +1334,7 @@ public void shouldRenderNotAggregationExpression() { assertThat(agg, is(JSON.parse("{ $project: { result: { $not: [ { $gt: [ \"$qty\", 250 ] } ] } } }"))); } - /** - * @see DATAMONGO-1540 - */ - @Test + @Test // DATAMONGO-1540 public void shouldRenderMapAggregationExpression() { DBObject agg = Aggregation.project() @@ -1727,10 +1346,7 @@ public void shouldRenderMapAggregationExpression() { "{ $project:{ adjustedGrades:{ $map: { input: \"$quizzes\", as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}"))); } - /** - * @see DATAMONGO-1540 - */ - @Test + @Test // DATAMONGO-1540 public void shouldRenderMapAggregationExpressionOnExpression() { DBObject agg = Aggregation.project() @@ -1742,10 +1358,7 @@ public void shouldRenderMapAggregationExpressionOnExpression() { "{ $project:{ adjustedGrades:{ $map: { input: { $size : [\"foo\"]}, as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}"))); } - /** - * @see DATAMONGO-861, DATAMONGO-1542 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void shouldRenderIfNullConditionAggregationExpression() { DBObject agg = project().and( @@ -1756,10 +1369,7 @@ public void shouldRenderIfNullConditionAggregationExpression() { "{ $project: { result: { $ifNull: [ { $arrayElemAt: [\"$array\", 1] }, \"a more sophisticated value\" ] } } }"))); } - /** - * @see DATAMONGO-1542 - */ - @Test + @Test // DATAMONGO-1542 public void shouldRenderIfNullValueAggregationExpression() { DBObject agg = project() @@ -1770,10 +1380,7 @@ public void shouldRenderIfNullValueAggregationExpression() { is(JSON.parse("{ $project: { result: { $ifNull: [ \"$field\", { $arrayElemAt: [\"$array\", 1] } ] } } }"))); } - /** - * @see DATAMONGO-861, DATAMONGO-1542 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void fieldReplacementIfNullShouldRenderCorrectly() { DBObject agg = project().and(ConditionalOperators.ifNull("optional").thenValueOf("$never-null")).as("result") @@ -1782,10 +1389,7 @@ public void fieldReplacementIfNullShouldRenderCorrectly() { assertThat(agg, is(JSON.parse("{ $project: { result: { $ifNull: [ \"$optional\", \"$never-null\" ] } } }"))); } - /** - * @see DATAMONGO-1538 - */ - @Test + @Test // DATAMONGO-1538 public void shouldRenderLetExpressionCorrectly() { DBObject agg = Aggregation.project() @@ -1808,10 +1412,7 @@ public void shouldRenderLetExpressionCorrectly() { "}}}}"))); } - /** - * @see DATAMONGO-1538 - */ - @Test + @Test // DATAMONGO-1538 public void shouldRenderLetExpressionCorrectlyWhenUsingLetOnProjectionBuilder() { ExpressionVariable var1 = newVariable("total") @@ -1835,10 +1436,7 @@ public void shouldRenderLetExpressionCorrectlyWhenUsingLetOnProjectionBuilder() "}}}}"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderIndexOfBytesCorrectly() { DBObject agg = project().and(StringOperators.valueOf("item").indexOf("foo")).as("byteLocation") @@ -1848,10 +1446,7 @@ public void shouldRenderIndexOfBytesCorrectly() { Matchers.is(JSON.parse("{ $project: { byteLocation: { $indexOfBytes: [ \"$item\", \"foo\" ] } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderIndexOfBytesWithRangeCorrectly() { DBObject agg = project().and(StringOperators.valueOf("item").indexOf("foo").within(new Range(5L, 9L))) @@ -1861,10 +1456,7 @@ public void shouldRenderIndexOfBytesWithRangeCorrectly() { .containing("$project.byteLocation.$indexOfBytes.[3]", 9L)); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderIndexOfCPCorrectly() { DBObject agg = project().and(StringOperators.valueOf("item").indexOfCP("foo")).as("cpLocation") @@ -1873,10 +1465,7 @@ public void shouldRenderIndexOfCPCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project: { cpLocation: { $indexOfCP: [ \"$item\", \"foo\" ] } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderIndexOfCPWithRangeCorrectly() { DBObject agg = project().and(StringOperators.valueOf("item").indexOfCP("foo").within(new Range(5L, 9L))) @@ -1886,10 +1475,7 @@ public void shouldRenderIndexOfCPWithRangeCorrectly() { .containing("$project.cpLocation.$indexOfCP.[3]", 9L)); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderSplitCorrectly() { DBObject agg = project().and(StringOperators.valueOf("city").split(", ")).as("city_state") @@ -1898,10 +1484,7 @@ public void shouldRenderSplitCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project : { city_state : { $split: [\"$city\", \", \"] }} }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderStrLenBytesCorrectly() { DBObject agg = project().and(StringOperators.valueOf("name").length()).as("length") @@ -1910,10 +1493,7 @@ public void shouldRenderStrLenBytesCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project : { \"length\": { $strLenBytes: \"$name\" } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderStrLenCPCorrectly() { DBObject agg = project().and(StringOperators.valueOf("name").lengthCP()).as("length") @@ -1922,10 +1502,7 @@ public void shouldRenderStrLenCPCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project : { \"length\": { $strLenCP: \"$name\" } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderSubstrCPCorrectly() { DBObject agg = project().and(StringOperators.valueOf("quarter").substringCP(0, 2)).as("yearSubstring") @@ -1934,10 +1511,7 @@ public void shouldRenderSubstrCPCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project : { yearSubstring: { $substrCP: [ \"$quarter\", 0, 2 ] } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderIndexOfArrayCorrectly() { DBObject agg = project().and(ArrayOperators.arrayOf("items").indexOf(2)).as("index") @@ -1946,10 +1520,7 @@ public void shouldRenderIndexOfArrayCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project : { index: { $indexOfArray: [ \"$items\", 2 ] } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderRangeCorrectly() { DBObject agg = project().and(ArrayOperators.RangeOperator.rangeStartingAt(0L).to("distance").withStepSize(25L)) @@ -1959,10 +1530,7 @@ public void shouldRenderRangeCorrectly() { .containing("$project.rest_stops.$range.[1]", "$distance").containing("$project.rest_stops.$range.[2]", 25L)); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderReverseArrayCorrectly() { DBObject agg = project().and(ArrayOperators.arrayOf("favorites").reverse()).as("reverseFavorites") @@ -1971,10 +1539,7 @@ public void shouldRenderReverseArrayCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project : { reverseFavorites: { $reverseArray: \"$favorites\" } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderReduceWithSimpleObjectCorrectly() { DBObject agg = project() @@ -1986,10 +1551,7 @@ public void shouldRenderReduceWithSimpleObjectCorrectly() { "{ $project : { \"results\": { $reduce: { input: \"$probabilityArr\", initialValue: 1, in: { $multiply: [ \"$$value\", \"$$this\" ] } } } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderReduceWithComplexObjectCorrectly() { PropertyExpression sum = PropertyExpression.property("sum").definedAs( @@ -2006,10 +1568,7 @@ public void shouldRenderReduceWithComplexObjectCorrectly() { "{ $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 + @Test // DATAMONGO-1548 public void shouldRenderZipCorrectly() { AggregationExpression elemAt0 = ArrayOperators.arrayOf("matrix").elementAt(0); @@ -2024,10 +1583,7 @@ public void shouldRenderZipCorrectly() { "{ $project : { transposed: { $zip: { inputs: [ { $arrayElemAt: [ \"$matrix\", 0 ] }, { $arrayElemAt: [ \"$matrix\", 1 ] }, { $arrayElemAt: [ \"$matrix\", 2 ] } ], useLongestLength : true, defaults: [1,2] } } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderInCorrectly() { DBObject agg = project().and(ArrayOperators.arrayOf("in_stock").containsValue("bananas")).as("has_bananas") @@ -2037,10 +1593,7 @@ public void shouldRenderInCorrectly() { Matchers.is(JSON.parse("{ $project : { has_bananas : { $in : [\"bananas\", \"$in_stock\" ] } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderIsoDayOfWeekCorrectly() { DBObject agg = project().and(DateOperators.dateOf("birthday").isoDayOfWeek()).as("dayOfWeek") @@ -2049,10 +1602,7 @@ public void shouldRenderIsoDayOfWeekCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project : { dayOfWeek: { $isoDayOfWeek: \"$birthday\" } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderIsoWeekCorrectly() { DBObject agg = project().and(DateOperators.dateOf("date").isoWeek()).as("weekNumber") @@ -2061,10 +1611,7 @@ public void shouldRenderIsoWeekCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project : { weekNumber: { $isoWeek: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderIsoWeekYearCorrectly() { DBObject agg = project().and(DateOperators.dateOf("date").isoWeekYear()).as("yearNumber") @@ -2073,10 +1620,7 @@ public void shouldRenderIsoWeekYearCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project : { yearNumber: { $isoWeekYear: \"$date\" } } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderSwitchCorrectly() { String expected = "$switch:\n" + // @@ -2118,10 +1662,7 @@ public void shouldRenderSwitchCorrectly() { assertThat(agg, Matchers.is(JSON.parse("{ $project : { summary: {" + expected + "} } }"))); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldTypeCorrectly() { DBObject agg = project().and(DataTypeOperators.Type.typeOf("a")).as("a").toDBObject(Aggregation.DEFAULT_CONTEXT); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java index 49223ccb7e..90376d5ddb 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -32,26 +32,17 @@ */ public class ReplaceRootOperationUnitTests { - /** - * @see DATAMONGO-1550 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1550 public void rejectsNullField() { new ReplaceRootOperation((Field) null); } - /** - * @see DATAMONGO-1550 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1550 public void rejectsNullExpression() { new ReplaceRootOperation((AggregationExpression) null); } - /** - * @see DATAMONGO-1550 - */ - @Test + @Test // DATAMONGO-1550 public void shouldRenderCorrectly() { ReplaceRootOperation operation = ReplaceRootDocumentOperation.builder() @@ -61,10 +52,7 @@ public void shouldRenderCorrectly() { assertThat(dbObject, is(JSON.parse("{ $replaceRoot : { newRoot: { hello: \"world\" } } }"))); } - /** - * @see DATAMONGO-1550 - */ - @Test + @Test // DATAMONGO-1550 public void shouldRenderExpressionCorrectly() { ReplaceRootOperation operation = new ReplaceRootOperation(VariableOperators // @@ -78,10 +66,7 @@ public void shouldRenderExpressionCorrectly() { + "$map : { input : \"$array\" , as : \"element\" , in : { $multiply : [ \"$$element\" , 10]} } " + "} } }"))); } - /** - * @see DATAMONGO-1550 - */ - @Test + @Test // DATAMONGO-1550 public void shouldComposeDocument() { ReplaceRootOperation operation = ReplaceRootDocumentOperation.builder().withDocument() // @@ -94,10 +79,7 @@ public void shouldComposeDocument() { .parse("{ $replaceRoot : { newRoot: { key: \"value\", multiply: { $multiply : [ \"$$element\" , 10]} } } }"))); } - /** - * @see DATAMONGO-1550 - */ - @Test + @Test // DATAMONGO-1550 public void shouldComposeSubDocument() { DBObject partialReplacement = new BasicDBObject("key", "override").append("key2", "value2"); @@ -111,10 +93,7 @@ public void shouldComposeSubDocument() { assertThat(dbObject, is(JSON.parse("{ $replaceRoot : { newRoot: { key: \"override\", key2: \"value2\"} } } }"))); } - /** - * @see DATAMONGO-1550 - */ - @Test + @Test // DATAMONGO-1550 public void shouldNotExposeFields() { ReplaceRootOperation operation = new ReplaceRootOperation(Fields.field("field")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerIntegrationTests.java index a5260b712d..3710b6899c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -37,7 +37,6 @@ /** * Integration tests for {@link SpelExpressionTransformer}. * - * @see DATAMONGO-774 * @author Thomas Darimont */ @RunWith(SpringJUnit4ClassRunner.class) @@ -57,7 +56,7 @@ public void setUp() { this.dbRefResolver = new DefaultDbRefResolver(mongoDbFactory); } - @Test + @Test // DATAMONGO-774 public void shouldConvertCompoundExpressionToPropertyPath() { MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext()); @@ -67,7 +66,7 @@ public void shouldConvertCompoundExpressionToPropertyPath() { is("$item.primitiveIntValue")); } - @Test + @Test // DATAMONGO-774 public void shouldThrowExceptionIfNestedPropertyCannotBeFound() { exception.expect(MappingException.class); 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 013d6189e7..7f7423a993 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -26,7 +26,6 @@ /** * Unit tests for {@link SpelExpressionTransformer}. * - * @see DATAMONGO-774 * @author Thomas Darimont * @author Oliver Gierke * @author Christoph Strobl @@ -48,7 +47,7 @@ public void setup() { this.data.item.primitiveIntValue = 21; } - @Test + @Test // DATAMONGO-774 public void shouldRenderConstantExpression() { assertThat(transform("1"), is("1")); @@ -58,7 +57,7 @@ public void shouldRenderConstantExpression() { assertThat(transform("null"), is(nullValue())); } - @Test + @Test // DATAMONGO-774 public void shouldSupportKnownOperands() { assertThat(transform("a + b"), is("{ \"$add\" : [ \"$a\" , \"$b\"]}")); @@ -68,45 +67,45 @@ public void shouldSupportKnownOperands() { assertThat(transform("a % b"), is("{ \"$mod\" : [ \"$a\" , \"$b\"]}")); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-774 public void shouldThrowExceptionOnUnknownOperand() { transform("a++"); } - @Test + @Test // DATAMONGO-774 public void shouldRenderSumExpression() { assertThat(transform("a + 1"), is("{ \"$add\" : [ \"$a\" , 1]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderFormula() { assertThat(transform("(netPrice + surCharge) * taxrate + 42"), is( "{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderFormulaInCurlyBrackets() { assertThat(transform("{(netPrice + surCharge) * taxrate + 42}"), is( "{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderFieldReference() { assertThat(transform("foo"), is("$foo")); assertThat(transform("$foo"), is("$foo")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderNestedFieldReference() { assertThat(transform("foo.bar"), is("$foo.bar")); assertThat(transform("$foo.bar"), is("$foo.bar")); } - @Test + @Test // DATAMONGO-774 @Ignore public void shouldRenderNestedIndexedFieldReference() { @@ -114,46 +113,46 @@ public void shouldRenderNestedIndexedFieldReference() { assertThat(transform("foo[3].bar"), is("$foo[3].bar")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderConsecutiveOperation() { assertThat(transform("1 + 1 + 1"), is("{ \"$add\" : [ 1 , 1 , 1]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderComplexExpression0() { assertThat(transform("-(1 + q)"), is("{ \"$multiply\" : [ -1 , { \"$add\" : [ 1 , \"$q\"]}]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderComplexExpression1() { assertThat(transform("1 + (q + 1) / (q - 1)"), is("{ \"$add\" : [ 1 , { \"$divide\" : [ { \"$add\" : [ \"$q\" , 1]} , { \"$subtract\" : [ \"$q\" , 1]}]}]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderComplexExpression2() { assertThat(transform("(q + 1 + 4 - 5) / (q + 1 + 3 + 4)"), is( "{ \"$divide\" : [ { \"$subtract\" : [ { \"$add\" : [ \"$q\" , 1 , 4]} , 5]} , { \"$add\" : [ \"$q\" , 1 , 3 , 4]}]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderBinaryExpressionWithMixedSignsCorrectly() { assertThat(transform("-4 + 1"), is("{ \"$add\" : [ -4 , 1]}")); assertThat(transform("1 + -4"), is("{ \"$add\" : [ 1 , -4]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderConsecutiveOperationsInComplexExpression() { assertThat(transform("1 + 1 + (1 + 1 + 1) / q"), is("{ \"$add\" : [ 1 , 1 , { \"$divide\" : [ { \"$add\" : [ 1 , 1 , 1]} , \"$q\"]}]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderParameterExpressionResults() { assertThat(transform("[0] + [1] + [2]", 1, 2, 3), is("{ \"$add\" : [ 1 , 2 , 3]}")); } @@ -165,7 +164,7 @@ public void shouldRenderNestedParameterExpressionResults() { is("{ \"$add\" : [ 42 , 1.2345 , 23]}")); } - @Test + @Test // DATAMONGO-774 public void shouldRenderNestedParameterExpressionResultsInNestedExpressions() { assertThat( @@ -173,10 +172,7 @@ public void shouldRenderNestedParameterExpressionResultsInNestedExpressions() { is("{ \"$multiply\" : [ { \"$add\" : [ 1 , 42 , 1.2345]} , 23]}")); } - /** - * @see DATAMONGO-840 - */ - @Test + @Test // DATAMONGO-840 public void shouldRenderCompoundExpressionsWithIndexerAndFieldReference() { Person person = new Person(); @@ -184,825 +180,522 @@ public void shouldRenderCompoundExpressionsWithIndexerAndFieldReference() { assertThat(transform("[0].age + a.c", person), is("{ \"$add\" : [ 10 , \"$a.c\"]}")); } - /** - * @see DATAMONGO-840 - */ - @Test + @Test // DATAMONGO-840 public void shouldRenderCompoundExpressionsWithOnlyFieldReferences() { assertThat(transform("a.b + a.c"), is("{ \"$add\" : [ \"$a.b\" , \"$a.c\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeAnd() { assertThat(transform("and(a, b)"), is("{ \"$and\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeOr() { assertThat(transform("or(a, b)"), is("{ \"$or\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeNot() { assertThat(transform("not(a)"), is("{ \"$not\" : [ \"$a\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeSetEquals() { assertThat(transform("setEquals(a, b)"), is("{ \"$setEquals\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeSetEqualsForArrays() { assertThat(transform("setEquals(new int[]{1,2,3}, new int[]{4,5,6})"), is("{ \"$setEquals\" : [ [ 1 , 2 , 3] , [ 4 , 5 , 6]]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeSetEqualsMixedArrays() { assertThat(transform("setEquals(a, new int[]{4,5,6})"), is("{ \"$setEquals\" : [ \"$a\" , [ 4 , 5 , 6]]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceSetIntersection() { assertThat(transform("setIntersection(a, new int[]{4,5,6})"), is("{ \"$setIntersection\" : [ \"$a\" , [ 4 , 5 , 6]]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceSetUnion() { assertThat(transform("setUnion(a, new int[]{4,5,6})"), is("{ \"$setUnion\" : [ \"$a\" , [ 4 , 5 , 6]]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceSeDifference() { assertThat(transform("setDifference(a, new int[]{4,5,6})"), is("{ \"$setDifference\" : [ \"$a\" , [ 4 , 5 , 6]]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceSetIsSubset() { assertThat(transform("setIsSubset(a, new int[]{4,5,6})"), is("{ \"$setIsSubset\" : [ \"$a\" , [ 4 , 5 , 6]]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceAnyElementTrue() { assertThat(transform("anyElementTrue(a)"), is("{ \"$anyElementTrue\" : [ \"$a\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceAllElementsTrue() { assertThat(transform("allElementsTrue(a, new int[]{4,5,6})"), is("{ \"$allElementsTrue\" : [ \"$a\" , [ 4 , 5 , 6]]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceCmp() { assertThat(transform("cmp(a, 250)"), is("{ \"$cmp\" : [ \"$a\" , 250]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceEq() { assertThat(transform("eq(a, 250)"), is("{ \"$eq\" : [ \"$a\" , 250]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceGt() { assertThat(transform("gt(a, 250)"), is("{ \"$gt\" : [ \"$a\" , 250]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceGte() { assertThat(transform("gte(a, 250)"), is("{ \"$gte\" : [ \"$a\" , 250]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceLt() { assertThat(transform("lt(a, 250)"), is("{ \"$lt\" : [ \"$a\" , 250]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceLte() { assertThat(transform("lte(a, 250)"), is("{ \"$lte\" : [ \"$a\" , 250]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNe() { assertThat(transform("ne(a, 250)"), is("{ \"$ne\" : [ \"$a\" , 250]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceAbs() { assertThat(transform("abs(1)"), is("{ \"$abs\" : 1}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceAdd() { assertThat(transform("add(a, 250)"), is("{ \"$add\" : [ \"$a\" , 250]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceCeil() { assertThat(transform("ceil(7.8)"), is("{ \"$ceil\" : 7.8}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceDivide() { assertThat(transform("divide(a, 250)"), is("{ \"$divide\" : [ \"$a\" , 250]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceExp() { assertThat(transform("exp(2)"), is("{ \"$exp\" : 2}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceFloor() { assertThat(transform("floor(2)"), is("{ \"$floor\" : 2}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceLn() { assertThat(transform("ln(2)"), is("{ \"$ln\" : 2}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceLog() { assertThat(transform("log(100, 10)"), is("{ \"$log\" : [ 100 , 10]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceLog10() { assertThat(transform("log10(100)"), is("{ \"$log10\" : 100}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeMod() { assertThat(transform("mod(a, b)"), is("{ \"$mod\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeMultiply() { assertThat(transform("multiply(a, b)"), is("{ \"$multiply\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodePow() { assertThat(transform("pow(a, 2)"), is("{ \"$pow\" : [ \"$a\" , 2]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceSqrt() { assertThat(transform("sqrt(2)"), is("{ \"$sqrt\" : 2}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeSubtract() { assertThat(transform("subtract(a, b)"), is("{ \"$subtract\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceTrunc() { assertThat(transform("trunc(2.1)"), is("{ \"$trunc\" : 2.1}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeConcat() { assertThat(transform("concat(a, b, 'c')"), is("{ \"$concat\" : [ \"$a\" , \"$b\" , \"c\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeSubstrc() { assertThat(transform("substr(a, 0, 1)"), is("{ \"$substr\" : [ \"$a\" , 0 , 1]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceToLower() { assertThat(transform("toLower(a)"), is("{ \"$toLower\" : \"$a\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceToUpper() { assertThat(transform("toUpper(a)"), is("{ \"$toUpper\" : \"$a\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeStrCaseCmp() { assertThat(transform("strcasecmp(a, b)"), is("{ \"$strcasecmp\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceMeta() { assertThat(transform("meta('textScore')"), is("{ \"$meta\" : \"textScore\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeArrayElemAt() { assertThat(transform("arrayElemAt(a, 10)"), is("{ \"$arrayElemAt\" : [ \"$a\" , 10]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeConcatArrays() { assertThat(transform("concatArrays(a, b, c)"), is("{ \"$concatArrays\" : [ \"$a\" , \"$b\" , \"$c\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeFilter() { assertThat(transform("filter(a, 'num', '$$num' > 10)"), is("{ \"$filter\" : { \"input\" : \"$a\" , \"as\" : \"num\" , \"cond\" : { \"$gt\" : [ \"$$num\" , 10]}}}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceIsArray() { assertThat(transform("isArray(a)"), is("{ \"$isArray\" : \"$a\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceIsSize() { assertThat(transform("size(a)"), is("{ \"$size\" : \"$a\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeSlice() { assertThat(transform("slice(a, 10)"), is("{ \"$slice\" : [ \"$a\" , 10]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeMap() { assertThat(transform("map(quizzes, 'grade', '$$grade' + 2)"), is( "{ \"$map\" : { \"input\" : \"$quizzes\" , \"as\" : \"grade\" , \"in\" : { \"$add\" : [ \"$$grade\" , 2]}}}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeLet() { assertThat(transform("let({low:1, high:'$$low'}, gt('$$low', '$$high'))"), is( "{ \"$let\" : { \"vars\" : { \"low\" : 1 , \"high\" : \"$$low\"} , \"in\" : { \"$gt\" : [ \"$$low\" , \"$$high\"]}}}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceLiteral() { assertThat(transform("literal($1)"), is("{ \"$literal\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceDayOfYear() { assertThat(transform("dayOfYear($1)"), is("{ \"$dayOfYear\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceDayOfMonth() { assertThat(transform("dayOfMonth($1)"), is("{ \"$dayOfMonth\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceDayOfWeek() { assertThat(transform("dayOfWeek($1)"), is("{ \"$dayOfWeek\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceYear() { assertThat(transform("year($1)"), is("{ \"$year\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceMonth() { assertThat(transform("month($1)"), is("{ \"$month\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceWeek() { assertThat(transform("week($1)"), is("{ \"$week\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceHour() { assertThat(transform("hour($1)"), is("{ \"$hour\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceMinute() { assertThat(transform("minute($1)"), is("{ \"$minute\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceSecond() { assertThat(transform("second($1)"), is("{ \"$second\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceMillisecond() { assertThat(transform("millisecond($1)"), is("{ \"$millisecond\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceDateToString() { assertThat(transform("dateToString('%Y-%m-%d', $date)"), is("{ \"$dateToString\" : { \"format\" : \"%Y-%m-%d\" , \"date\" : \"$date\"}}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceCond() { assertThat(transform("cond(qty > 250, 30, 20)"), is("{ \"$cond\" : { \"if\" : { \"$gt\" : [ \"$qty\" , 250]} , \"then\" : 30 , \"else\" : 20}}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeIfNull() { assertThat(transform("ifNull(a, 10)"), is("{ \"$ifNull\" : [ \"$a\" , 10]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeSum() { assertThat(transform("sum(a, b)"), is("{ \"$sum\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeAvg() { assertThat(transform("avg(a, b)"), is("{ \"$avg\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceFirst() { assertThat(transform("first($1)"), is("{ \"$first\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceLast() { assertThat(transform("last($1)"), is("{ \"$last\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeMax() { assertThat(transform("max(a, b)"), is("{ \"$max\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeMin() { assertThat(transform("min(a, b)"), is("{ \"$min\" : [ \"$a\" , \"$b\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodePush() { assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"), is("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceAddToSet() { assertThat(transform("addToSet($1)"), is("{ \"$addToSet\" : \"$1\"}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeStdDevPop() { assertThat(transform("stdDevPop(scores.score)"), is("{ \"$stdDevPop\" : [ \"$scores.score\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderMethodReferenceNodeStdDevSamp() { assertThat(transform("stdDevSamp(age)"), is("{ \"$stdDevSamp\" : [ \"$age\"]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderOperationNodeEq() { assertThat(transform("foo == 10"), is("{ \"$eq\" : [ \"$foo\" , 10]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderOperationNodeNe() { assertThat(transform("foo != 10"), is("{ \"$ne\" : [ \"$foo\" , 10]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderOperationNodeGt() { assertThat(transform("foo > 10"), is("{ \"$gt\" : [ \"$foo\" , 10]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderOperationNodeGte() { assertThat(transform("foo >= 10"), is("{ \"$gte\" : [ \"$foo\" , 10]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderOperationNodeLt() { assertThat(transform("foo < 10"), is("{ \"$lt\" : [ \"$foo\" , 10]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderOperationNodeLte() { assertThat(transform("foo <= 10"), is("{ \"$lte\" : [ \"$foo\" , 10]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderOperationNodePow() { assertThat(transform("foo^2"), is("{ \"$pow\" : [ \"$foo\" , 2]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderOperationNodeOr() { assertThat(transform("true || false"), is("{ \"$or\" : [ true , false]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderComplexOperationNodeOr() { assertThat(transform("1+2 || concat(a, b) || true"), is("{ \"$or\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderOperationNodeAnd() { assertThat(transform("true && false"), is("{ \"$and\" : [ true , false]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderComplexOperationNodeAnd() { assertThat(transform("1+2 && concat(a, b) && true"), is("{ \"$and\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderNotCorrectly() { assertThat(transform("!true"), is("{ \"$not\" : [ true]}")); } - /** - * @see DATAMONGO-1530 - */ - @Test + @Test // DATAMONGO-1530 public void shouldRenderComplexNotCorrectly() { assertThat(transform("!(foo > 10)"), is("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodReferenceIndexOfBytes() { assertThat(transform("indexOfBytes(item, 'foo')"), is("{ \"$indexOfBytes\" : [ \"$item\" , \"foo\"]}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodReferenceIndexOfCP() { assertThat(transform("indexOfCP(item, 'foo')"), is("{ \"$indexOfCP\" : [ \"$item\" , \"foo\"]}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodReferenceSplit() { assertThat(transform("split(item, ',')"), is("{ \"$split\" : [ \"$item\" , \",\"]}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodReferenceStrLenBytes() { assertThat(transform("strLenBytes(item)"), is("{ \"$strLenBytes\" : \"$item\"}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodReferenceStrLenCP() { assertThat(transform("strLenCP(item)"), is("{ \"$strLenCP\" : \"$item\"}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodSubstrCP() { assertThat(transform("substrCP(item, 0, 5)"), is("{ \"$substrCP\" : [ \"$item\" , 0 , 5]}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodReferenceReverseArray() { assertThat(transform("reverseArray(array)"), is("{ \"$reverseArray\" : \"$array\"}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 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 + @Test // DATAMONGO-1548 public void shouldRenderMethodReferenceZip() { assertThat(transform("zip(new String[]{'$array1', '$array2'})"), is("{ \"$zip\" : { \"inputs\" : [ \"$array1\" , \"$array2\"]}}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 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 + @Test // DATAMONGO-1548 public void shouldRenderMethodIn() { assertThat(transform("in('item', array)"), is("{ \"$in\" : [ \"item\" , \"$array\"]}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodRefereneIsoDayOfWeek() { assertThat(transform("isoDayOfWeek(date)"), is("{ \"$isoDayOfWeek\" : \"$date\"}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodRefereneIsoWeek() { assertThat(transform("isoWeek(date)"), is("{ \"$isoWeek\" : \"$date\"}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodRefereneIsoWeekYear() { assertThat(transform("isoWeekYear(date)"), is("{ \"$isoWeekYear\" : \"$date\"}")); } - /** - * @see DATAMONGO-1548 - */ - @Test + @Test // DATAMONGO-1548 public void shouldRenderMethodRefereneType() { assertThat(transform("type(a)"), is("{ \"$type\" : \"$a\"}")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java index 42952d09dc..76f0860712 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java @@ -86,10 +86,7 @@ public void rejectsInvalidFieldReference() { getContext(Foo.class).getReference("foo"); } - /** - * @see DATAMONGO-741 - */ - @Test + @Test // DATAMONGO-741 public void returnsReferencesToNestedFieldsCorrectly() { AggregationOperationContext context = getContext(Foo.class); @@ -101,10 +98,7 @@ public void returnsReferencesToNestedFieldsCorrectly() { assertThat(context.getReference(field), is(context.getReference("bar.name"))); } - /** - * @see DATAMONGO-806 - */ - @Test + @Test // DATAMONGO-806 public void aliasesIdFieldCorrectly() { AggregationOperationContext context = getContext(Foo.class); @@ -112,10 +106,7 @@ public void aliasesIdFieldCorrectly() { is((FieldReference) new DirectFieldReference(new ExposedField(field("id", "_id"), true)))); } - /** - * @see DATAMONGO-912 - */ - @Test + @Test // DATAMONGO-912 public void shouldUseCustomConversionIfPresentAndConversionIsRequiredInFirstStage() { CustomConversions customConversions = customAgeConversions(); @@ -133,10 +124,7 @@ public void shouldUseCustomConversionIfPresentAndConversionIsRequiredInFirstStag assertThat(age, is((DBObject) new BasicDBObject("v", 10))); } - /** - * @see DATAMONGO-912 - */ - @Test + @Test // DATAMONGO-912 public void shouldUseCustomConversionIfPresentAndConversionIsRequiredInLaterStage() { CustomConversions customConversions = customAgeConversions(); @@ -154,10 +142,7 @@ public void shouldUseCustomConversionIfPresentAndConversionIsRequiredInLaterStag assertThat(age, is((DBObject) new BasicDBObject("v", 10))); } - /** - * @see DATAMONGO-960 - */ - @Test + @Test // DATAMONGO-960 public void rendersAggregationOptionsInTypedAggregationContextCorrectly() { AggregationOperationContext context = getContext(FooPerson.class); @@ -177,10 +162,7 @@ public void rendersAggregationOptionsInTypedAggregationContextCorrectly() { assertThat(dbo.get("cursor"), is((Object) new BasicDBObject("foo", 1))); } - /** - * @see DATAMONGO-1585 - */ - @Test + @Test // DATAMONGO-1585 public void rendersSortOfProjectedFieldCorrectly() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -194,10 +176,7 @@ public void rendersSortOfProjectedFieldCorrectly() { assertThat(definition.get("counter"), is(equalTo((Object) 1))); } - /** - * @see DATAMONGO-1586 - */ - @Test + @Test // DATAMONGO-1586 public void rendersFieldAliasingProjectionCorrectly() { AggregationOperationContext context = getContext(FooPerson.class); @@ -215,10 +194,7 @@ public void rendersFieldAliasingProjectionCorrectly() { .containing("age", "$age.value")); } - /** - * @see DATAMONGO-1133 - */ - @Test + @Test // DATAMONGO-1133 public void shouldHonorAliasedFieldsInGroupExpressions() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -233,10 +209,7 @@ public void shouldHonorAliasedFieldsInGroupExpressions() { assertThat(definition.get("_id"), is(equalTo((Object) "$counter_name"))); } - /** - * @see DATAMONGO-1326, DATAMONGO-1585 - */ - @Test + @Test // DATAMONGO-1326, DATAMONGO-1585 public void lookupShouldInheritFieldsFromInheritingAggregationOperation() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -253,10 +226,7 @@ public void lookupShouldInheritFieldsFromInheritingAggregationOperation() { assertThat(definition.get("counter_name"), is(equalTo((Object) 1))); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void groupLookupShouldInheritFieldsFromPreviousAggregationOperation() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -271,10 +241,7 @@ public void groupLookupShouldInheritFieldsFromPreviousAggregationOperation() { assertThat(definition.get("foreignKey"), is(equalTo((Object) 1))); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupGroupAggregationShouldUseCorrectGroupField() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -291,10 +258,7 @@ public void lookupGroupAggregationShouldUseCorrectGroupField() { assertThat(field.get("$min"), is(equalTo((Object) "$lookup.otherkey"))); } - /** - * @see DATAMONGO-1326 - */ - @Test + @Test // DATAMONGO-1326 public void lookupGroupAggregationShouldOverwriteExposedFields() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -311,10 +275,7 @@ public void lookupGroupAggregationShouldOverwriteExposedFields() { assertThat(definition.get("something_totally_different"), is(equalTo((Object) 1))); } - /** - * @see DATAMONGO-1326 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1326 public void lookupGroupAggregationShouldFailInvalidFieldReference() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); @@ -325,10 +286,7 @@ public void lookupGroupAggregationShouldFailInvalidFieldReference() { agg.toDbObject("meterData", context); } - /** - * @see DATAMONGO-861 - */ - @Test + @Test // DATAMONGO-861 public void rendersAggregationConditionalInTypedAggregationContextCorrectly() { AggregationOperationContext context = getContext(FooPerson.class); @@ -352,10 +310,7 @@ public void rendersAggregationConditionalInTypedAggregationContextCorrectly() { assertThat((DBObject) getValue(age, "$cond"), isBsonObject().containing("else", "$age")); } - /** - * @see DATAMONGO-861, DATAMONGO-1542 - */ - @Test + @Test // DATAMONGO-861, DATAMONGO-1542 public void rendersAggregationIfNullInTypedAggregationContextCorrectly() { AggregationOperationContext context = getContext(FooPerson.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/UnwindOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/UnwindOperationUnitTests.java index 7327c0e876..b9baa89872 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/UnwindOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/UnwindOperationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -32,10 +32,7 @@ */ public class UnwindOperationUnitTests { - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindWithPathOnlyShouldUsePreMongo32Syntax() { UnwindOperation unwindOperation = Aggregation.unwind("a"); @@ -45,10 +42,7 @@ public void unwindWithPathOnlyShouldUsePreMongo32Syntax() { assertThat(pipeline, isBsonObject().containing("$unwind", "$a")); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindWithArrayIndexShouldUseMongo32Syntax() { UnwindOperation unwindOperation = Aggregation.unwind("a", "index"); @@ -61,10 +55,7 @@ public void unwindWithArrayIndexShouldUseMongo32Syntax() { containing("includeArrayIndex", "index")); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindWithArrayIndexShouldExposeArrayIndex() { UnwindOperation unwindOperation = Aggregation.unwind("a", "index"); @@ -72,10 +63,7 @@ public void unwindWithArrayIndexShouldExposeArrayIndex() { assertThat(unwindOperation.getFields().getField("index"), is(not(nullValue()))); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void plainUnwindShouldNotExposeIndex() { UnwindOperation unwindOperation = Aggregation.unwind("a"); @@ -83,10 +71,7 @@ public void plainUnwindShouldNotExposeIndex() { assertThat(unwindOperation.getFields().exposesNoFields(), is(true)); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void unwindWithPreserveNullShouldUseMongo32Syntax() { UnwindOperation unwindOperation = Aggregation.unwind("a", true); @@ -99,10 +84,7 @@ public void unwindWithPreserveNullShouldUseMongo32Syntax() { notContaining("includeArrayIndex")); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void lookupBuilderBuildsCorrectClause() { UnwindOperation unwindOperation = UnwindOperation.newUnwind().path("$foo").noArrayIndex().skipNullAndEmptyArrays(); @@ -111,10 +93,7 @@ public void lookupBuilderBuildsCorrectClause() { assertThat(pipeline, isBsonObject().containing("$unwind", "$foo")); } - /** - * @see DATAMONGO-1391 - */ - @Test + @Test // DATAMONGO-1391 public void lookupBuilderBuildsCorrectClauseForMongo32() { UnwindOperation unwindOperation = UnwindOperation.newUnwind().path("$foo").arrayIndex("myindex") diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java index 005085d25a..4b63398d2c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java @@ -7,8 +7,8 @@ /** * Data model from mongodb reference data set * - * @see http://docs.mongodb.org/manual/tutorial/aggregation-examples/ - * @see http://media.mongodb.org/zips.json + * @see Aggregation Examples + * @see () { @@ -130,20 +124,14 @@ public void populatesConversionServiceCorrectly() { assertThat(conversionService.canConvert(String.class, Format.class), is(true)); } - /** - * @see DATAMONGO-259 - */ - @Test + @Test // DATAMONGO-259 public void doesNotConsiderTypeSimpleIfOnlyReadConverterIsRegistered() { CustomConversions conversions = new CustomConversions(Arrays.asList(StringToFormatConverter.INSTANCE)); assertThat(conversions.isSimpleType(Format.class), is(false)); } - /** - * @see DATAMONGO-298 - */ - @Test + @Test // DATAMONGO-298 public void discoversConvertersForSubtypesOfMongoTypes() { CustomConversions conversions = new CustomConversions(Arrays.asList(StringToIntegerConverter.INSTANCE)); @@ -151,10 +139,7 @@ public void discoversConvertersForSubtypesOfMongoTypes() { assertThat(conversions.hasCustomWriteTarget(String.class, Integer.class), is(true)); } - /** - * @see DATAMONGO-342 - */ - @Test + @Test // DATAMONGO-342 public void doesNotHaveConverterForStringToBigIntegerByDefault() { CustomConversions conversions = new CustomConversions(); @@ -166,39 +151,27 @@ public void doesNotHaveConverterForStringToBigIntegerByDefault() { assertThat(conversions.getCustomWriteTarget(String.class), is(nullValue())); } - /** - * @see DATAMONGO-390 - */ - @Test + @Test // DATAMONGO-390 public void considersBinaryASimpleType() { CustomConversions conversions = new CustomConversions(); assertThat(conversions.isSimpleType(Binary.class), is(true)); } - /** - * @see DATAMONGO-462 - */ - @Test + @Test // DATAMONGO-462 public void hasWriteConverterForURL() { CustomConversions conversions = new CustomConversions(); assertThat(conversions.hasCustomWriteTarget(URL.class), is(true)); } - /** - * @see DATAMONGO-462 - */ - @Test + @Test // DATAMONGO-462 public void readTargetForURL() { CustomConversions conversions = new CustomConversions(); assertThat(conversions.hasCustomReadTarget(String.class, URL.class), is(true)); } - /** - * @see DATAMONGO-795 - */ - @Test + @Test // DATAMONGO-795 @SuppressWarnings("rawtypes") public void favorsCustomConverterForIndeterminedTargetType() { @@ -206,10 +179,7 @@ public void favorsCustomConverterForIndeterminedTargetType() { assertThat(conversions.getCustomWriteTarget(DateTime.class, null), is(equalTo((Class) String.class))); } - /** - * @see DATAMONGO-881 - */ - @Test + @Test // DATAMONGO-881 public void customConverterOverridesDefault() { CustomConversions conversions = new CustomConversions(Arrays.asList(CustomDateTimeConverter.INSTANCE)); @@ -219,30 +189,21 @@ public void customConverterOverridesDefault() { assertThat(conversionService.convert(new DateTime(), Date.class), is(new Date(0))); } - /** - * @see DATAMONGO-1001 - */ - @Test + @Test // DATAMONGO-1001 public void shouldSelectPropertCustomWriteTargetForCglibProxiedType() { CustomConversions conversions = new CustomConversions(Arrays.asList(FormatToStringConverter.INSTANCE)); assertThat(conversions.getCustomWriteTarget(createProxyTypeFor(Format.class)), is(typeCompatibleWith(String.class))); } - /** - * @see DATAMONGO-1001 - */ - @Test + @Test // DATAMONGO-1001 public void shouldSelectPropertCustomReadTargetForCglibProxiedType() { CustomConversions conversions = new CustomConversions(Arrays.asList(CustomObjectToStringConverter.INSTANCE)); assertThat(conversions.hasCustomReadTarget(createProxyTypeFor(Object.class), String.class), is(true)); } - /** - * @see DATAMONGO-1131 - */ - @Test + @Test // DATAMONGO-1131 public void registersConvertersForJsr310() { CustomConversions customConversions = new CustomConversions(); @@ -250,10 +211,7 @@ public void registersConvertersForJsr310() { assertThat(customConversions.hasCustomWriteTarget(java.time.LocalDateTime.class), is(true)); } - /** - * @see DATAMONGO-1131 - */ - @Test + @Test // DATAMONGO-1131 public void registersConvertersForThreeTenBackPort() { CustomConversions customConversions = new CustomConversions(); @@ -261,10 +219,7 @@ public void registersConvertersForThreeTenBackPort() { assertThat(customConversions.hasCustomWriteTarget(LocalDateTime.class), is(true)); } - /** - * @see DATAMONGO-1302 - */ - @Test + @Test // DATAMONGO-1302 public void registersConverterFactoryCorrectly() { CustomConversions customConversions = new CustomConversions(Collections.singletonList(new FormatConverterFactory())); @@ -272,10 +227,7 @@ public void registersConverterFactoryCorrectly() { assertThat(customConversions.getCustomWriteTarget(String.class, SimpleDateFormat.class), notNullValue()); } - /** - * @see DATAMONGO-1372 - */ - @Test + @Test // DATAMONGO-1372 public void registersConvertersForCurrency() { CustomConversions customConversions = new CustomConversions(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java index e26394f111..236c9d3f7f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/CustomConvertersUnitTests.java @@ -41,7 +41,6 @@ * Test case to verify correct usage of custom {@link Converter} implementations to be used. * * @author Oliver Gierke - * @see DATADOC-101 */ @RunWith(MockitoJUnitRunner.class) public class CustomConvertersUnitTests { @@ -75,7 +74,7 @@ public void setUp() throws Exception { converter.afterPropertiesSet(); } - @Test + @Test // DATADOC-101 public void nestedToDBObjectConverterGetsInvoked() { Foo foo = new Foo(); @@ -85,7 +84,7 @@ public void nestedToDBObjectConverterGetsInvoked() { verify(barToDBObjectConverter).convert(any(Bar.class)); } - @Test + @Test // DATADOC-101 public void nestedFromDBObjectConverterGetsInvoked() { BasicDBObject dbObject = new BasicDBObject(); @@ -95,21 +94,21 @@ public void nestedFromDBObjectConverterGetsInvoked() { verify(dbObjectToBarConverter).convert(any(DBObject.class)); } - @Test + @Test // DATADOC-101 public void toDBObjectConverterGetsInvoked() { converter.write(new Bar(), new BasicDBObject()); verify(barToDBObjectConverter).convert(any(Bar.class)); } - @Test + @Test // DATADOC-101 public void fromDBObjectConverterGetsInvoked() { converter.read(Bar.class, new BasicDBObject()); verify(dbObjectToBarConverter).convert(any(DBObject.class)); } - @Test + @Test // DATADOC-101 public void foo() { DBObject dbObject = new BasicDBObject(); dbObject.put("foo", null); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java index 298d99c1d4..2808759e2b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -30,9 +30,8 @@ import com.mongodb.DBObject; /** - * Unit tests for {@link DbObjectAccessor}. + * Unit tests for {@link DBObjectAccessor}. * - * @see DATAMONGO-766 * @author Oliver Gierke */ public class DBObjectAccessorUnitTests { @@ -41,7 +40,7 @@ public class DBObjectAccessorUnitTests { MongoPersistentEntity projectingTypeEntity = context.getPersistentEntity(ProjectingType.class); MongoPersistentProperty fooProperty = projectingTypeEntity.getPersistentProperty("foo"); - @Test + @Test // DATAMONGO-766 public void putsNestedFieldCorrectly() { DBObject dbObject = new BasicDBObject(); @@ -53,7 +52,7 @@ public void putsNestedFieldCorrectly() { assertThat(aDbObject.get("b"), is((Object) "FooBar")); } - @Test + @Test // DATAMONGO-766 public void getsNestedFieldCorrectly() { DBObject source = new BasicDBObject("a", new BasicDBObject("b", "FooBar")); @@ -62,27 +61,24 @@ public void getsNestedFieldCorrectly() { assertThat(accessor.get(fooProperty), is((Object) "FooBar")); } - @Test + @Test // DATAMONGO-766 public void returnsNullForNonExistingFieldPath() { DBObjectAccessor accessor = new DBObjectAccessor(new BasicDBObject()); assertThat(accessor.get(fooProperty), is(nullValue())); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-766 public void rejectsNonBasicDBObjects() { new DBObjectAccessor(new BasicDBList()); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-766 public void rejectsNullDBObject() { new DBObjectAccessor(null); } - /** - * @see DATAMONGO-1335 - */ - @Test + @Test // DATAMONGO-1335 public void writesAllNestingsCorrectly() { MongoPersistentEntity entity = context.getPersistentEntity(TypeWithTwoNestings.class); @@ -101,10 +97,7 @@ public void writesAllNestingsCorrectly() { assertThat(nestedA.get("c"), is((Object) "c")); } - /** - * @see DATAMONGO-1471 - */ - @Test + @Test // DATAMONGO-1471 public void exposesAvailabilityOfFields() { DBObjectAccessor accessor = new DBObjectAccessor(new BasicDBObject("a", new BasicDBObject("c", "d"))); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DataMongo273Tests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DataMongo273Tests.java index 5c3ae804e7..7ac9c62f78 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DataMongo273Tests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DataMongo273Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2012 the original author or authors. + * Copyright 2011-2017 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. @@ -51,10 +51,7 @@ public void setupMongoConverter() { converter.afterPropertiesSet(); } - /** - * @see DATAMONGO-273 - */ - @Test + @Test // DATAMONGO-273 public void convertMapOfThings() { Plane plane = new Plane("Boeing", 4); @@ -77,10 +74,7 @@ public void convertMapOfThings() { assertTrue(mapOfThings2.get("automobile") instanceof Automobile); } - /** - * @see DATAMONGO-294 - */ - @Test + @Test // DATAMONGO-294 @SuppressWarnings({ "rawtypes", "unchecked" }) public void convertListOfThings() { Plane plane = new Plane("Boeing", 4); @@ -102,10 +96,7 @@ public void convertListOfThings() { assertTrue(listOfThings2.get(2) instanceof Automobile); } - /** - * @see DATAMONGO-294 - */ - @Test + @Test // DATAMONGO-294 @SuppressWarnings({ "rawtypes", "unchecked" }) public void convertListOfThings_NestedInMap() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java index 0018840155..240ce9760d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -87,10 +87,7 @@ public void setUp() { this.converter = new MappingMongoConverter(dbRefResolver, mappingContext); } - /** - * @see DATAMONGO-347 - */ - @Test + @Test // DATAMONGO-347 public void createsSimpleDBRefCorrectly() { Person person = new Person(); @@ -101,10 +98,7 @@ public void createsSimpleDBRefCorrectly() { assertThat(dbRef.getCollectionName(), is("person")); } - /** - * @see DATAMONGO-657 - */ - @Test + @Test // DATAMONGO-657 public void convertDocumentWithMapDBRef() { DBObject mapValDBObject = new BasicDBObject(); @@ -146,10 +140,7 @@ public void convertDocumentWithMapDBRef() { assertThat(read.map.get("test").id, is(BigInteger.ONE)); } - /** - * @see DATAMONGO-347 - */ - @Test + @Test // DATAMONGO-347 public void createsDBRefWithClientSpecCorrectly() { PropertyPath path = PropertyPath.from("person", PersonClient.class); @@ -163,10 +154,7 @@ public void createsDBRefWithClientSpecCorrectly() { assertThat(dbRef.getCollectionName(), is("person")); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForLazyDbRefOnInterface() { String id = "42"; @@ -187,10 +175,7 @@ public void lazyLoadingProxyForLazyDbRefOnInterface() { assertThat(result.dbRefToInterface.get(0).getValue(), is(value)); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForLazyDbRefOnConcreteCollection() { String id = "42"; @@ -212,10 +197,7 @@ public void lazyLoadingProxyForLazyDbRefOnConcreteCollection() { assertThat(result.dbRefToConcreteCollection.get(0).getValue(), is(value)); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForLazyDbRefOnConcreteType() { String id = "42"; @@ -236,10 +218,7 @@ public void lazyLoadingProxyForLazyDbRefOnConcreteType() { assertThat(result.dbRefToConcreteType.getValue(), is(value)); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructor() { String id = "42"; @@ -261,10 +240,7 @@ public void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructor assertThat(result.dbRefToConcreteTypeWithPersistenceConstructor.getValue(), is(value)); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructorButWithoutDefaultConstructor() { String id = "42"; @@ -286,10 +262,7 @@ public void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructor assertThat(result.dbRefToConcreteTypeWithPersistenceConstructorWithoutDefaultConstructor.getValue(), is(value)); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void lazyLoadingProxyForSerializableLazyDbRefOnConcreteType() { String id = "42"; @@ -311,10 +284,7 @@ public void lazyLoadingProxyForSerializableLazyDbRefOnConcreteType() { assertThat(deserializedResult.dbRefToSerializableTarget.getValue(), is(value)); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void lazyLoadingProxyForToStringObjectMethodOverridingDbref() { String id = "42"; @@ -335,10 +305,7 @@ public void lazyLoadingProxyForToStringObjectMethodOverridingDbref() { assertProxyIsResolved(result.dbRefToToStringObjectMethodOverride, true); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void callingToStringObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { String id = "42"; @@ -366,10 +333,7 @@ public void callingToStringObjectMethodOnLazyLoadingDbrefShouldNotInitializeProx assertProxyIsResolved(result.dbRefToPlainObject, true); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void equalsObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { String id = "42"; @@ -395,10 +359,7 @@ public void equalsObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { assertProxyIsResolved(result.dbRefToPlainObject, false); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void hashcodeObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { String id = "42"; @@ -422,10 +383,7 @@ public void hashcodeObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { assertProxyIsResolved(result.dbRefToPlainObject, false); } - /** - * @see DATAMONGO-884 - */ - @Test + @Test // DATAMONGO-884 public void lazyLoadingProxyForEqualsAndHashcodeObjectMethodOverridingDbref() { String id = "42"; @@ -454,10 +412,7 @@ public void lazyLoadingProxyForEqualsAndHashcodeObjectMethodOverridingDbref() { assertProxyIsResolved(result.dbRefEqualsAndHashcodeObjectMethodOverride2, true); } - /** - * @see DATAMONGO-987 - */ - @Test + @Test // DATAMONGO-987 public void shouldNotGenerateLazyLoadingProxyForNullValues() { DBObject dbo = new BasicDBObject(); @@ -475,10 +430,7 @@ public void shouldNotGenerateLazyLoadingProxyForNullValues() { assertThat(result.dbRefToConcreteTypeWithPersistenceConstructorWithoutDefaultConstructor, is(nullValue())); } - /** - * @see DATAMONGO-1005 - */ - @Test + @Test // DATAMONGO-1005 public void shouldBeAbleToStoreDirectReferencesToSelf() { DBObject dbo = new BasicDBObject(); @@ -494,10 +446,7 @@ public void shouldBeAbleToStoreDirectReferencesToSelf() { assertThat(found.reference, is(found)); } - /** - * @see DATAMONGO-1005 - */ - @Test + @Test // DATAMONGO-1005 public void shouldBeAbleToStoreNestedReferencesToSelf() { DBObject dbo = new BasicDBObject(); @@ -516,10 +465,7 @@ public void shouldBeAbleToStoreNestedReferencesToSelf() { assertThat(found.nested.reference, is(found)); } - /** - * @see DATAMONGO-1012 - */ - @Test + @Test // DATAMONGO-1012 public void shouldEagerlyResolveIdPropertyWithFieldAccess() { MongoPersistentEntity entity = mappingContext.getPersistentEntity(ClassWithLazyDbRefs.class); @@ -540,10 +486,7 @@ public void shouldEagerlyResolveIdPropertyWithFieldAccess() { assertProxyIsResolved(result.dbRefToConcreteType, false); } - /** - * @see DATAMONGO-1012 - */ - @Test + @Test // DATAMONGO-1012 public void shouldNotEagerlyResolveIdPropertyWithPropertyAccess() { MongoPersistentEntity entity = mappingContext.getPersistentEntity(ClassWithLazyDbRefs.class); @@ -561,10 +504,7 @@ public void shouldNotEagerlyResolveIdPropertyWithPropertyAccess() { assertProxyIsResolved(proxy, false); } - /** - * @see DATAMONGO-1076 - */ - @Test + @Test // DATAMONGO-1076 public void shouldNotTriggerResolvingOfLazyLoadedProxyWhenFinalizeMethodIsInvoked() throws Exception { MongoPersistentEntity entity = mappingContext.getPersistentEntity(WithObjectMethodOverrideLazyDbRefs.class); @@ -581,10 +521,7 @@ public void shouldNotTriggerResolvingOfLazyLoadedProxyWhenFinalizeMethodIsInvoke assertProxyIsResolved(result.dbRefToPlainObject, false); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldBulkFetchListOfReferences() { String id1 = "1"; @@ -611,10 +548,7 @@ public void shouldBulkFetchListOfReferences() { verify(converterSpy, never()).readRef(Mockito.any(DBRef.class)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldFallbackToOneByOneFetchingWhenElementsInListOfReferencesPointToDifferentCollections() { String id1 = "1"; @@ -643,10 +577,7 @@ public void shouldFallbackToOneByOneFetchingWhenElementsInListOfReferencesPointT verify(converterSpy, never()).bulkReadRefs(anyListOf(DBRef.class)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldBulkFetchMapOfReferences() { MapDBRefVal val1 = new MapDBRefVal(); @@ -678,10 +609,7 @@ public void shouldBulkFetchMapOfReferences() { verify(converterSpy, never()).readRef(Mockito.any(DBRef.class)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void shouldBulkFetchLazyMapOfReferences() { MapDBRefVal val1 = new MapDBRefVal(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java index 8bbfdde43c..ba444fe257 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -69,10 +69,7 @@ public void setUp() { resolver = new DefaultDbRefResolver(factoryMock); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 @SuppressWarnings("unchecked") public void bulkFetchShouldLoadDbRefsCorrectly() { @@ -91,10 +88,7 @@ public void bulkFetchShouldLoadDbRefsCorrectly() { assertThat($in, iterableWithSize(2)); } - /** - * @see DATAMONGO-1194 - */ - @Test(expected = InvalidDataAccessApiUsageException.class) + @Test(expected = InvalidDataAccessApiUsageException.class) // DATAMONGO-1194 public void bulkFetchShouldThrowExceptionWhenUsingDifferntCollectionsWithinSetOfReferences() { DBRef ref1 = new DBRef("collection-1", new ObjectId()); @@ -103,10 +97,7 @@ public void bulkFetchShouldThrowExceptionWhenUsingDifferntCollectionsWithinSetOf resolver.bulkFetch(Arrays.asList(ref1, ref2)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void bulkFetchShouldReturnEarlyForEmptyLists() { resolver.bulkFetch(Collections.emptyList()); @@ -114,10 +105,7 @@ public void bulkFetchShouldReturnEarlyForEmptyLists() { verify(collectionMock, never()).find(Mockito.any(DBObject.class)); } - /** - * @see DATAMONGO-1194 - */ - @Test + @Test // DATAMONGO-1194 public void bulkFetchShouldRestoreOriginalOrder() { DBObject o1 = new BasicDBObject("_id", new ObjectId()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java index 2cb4560cc2..0678922cfc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -109,10 +109,7 @@ public void readsTypeLoadingClassesForUnmappedTypesIfConfigured() { Object.class); } - /** - * @see DATAMONGO-709 - */ - @Test + @Test // DATAMONGO-709 public void writesTypeRestrictionsCorrectly() { DBObject result = new BasicDBObject(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java index 3ff7f3e514..2c31331166 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -52,10 +52,7 @@ */ public class GeoConvertersUnitTests { - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsBoxToDbObjectAndBackCorrectly() { Box box = new Box(new Point(1, 2), new Point(3, 4)); @@ -67,10 +64,7 @@ public void convertsBoxToDbObjectAndBackCorrectly() { assertThat(result.getClass().equals(Box.class), is(true)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsCircleToDbObjectAndBackCorrectlyNeutralDistance() { Circle circle = new Circle(new Point(1, 2), 3); @@ -81,10 +75,7 @@ public void convertsCircleToDbObjectAndBackCorrectlyNeutralDistance() { assertThat(result, is(circle)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsCircleToDbObjectAndBackCorrectlyMilesDistance() { Distance radius = new Distance(3, Metrics.MILES); @@ -97,10 +88,7 @@ public void convertsCircleToDbObjectAndBackCorrectlyMilesDistance() { assertThat(result.getRadius(), is(radius)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsPolygonToDbObjectAndBackCorrectly() { Polygon polygon = new Polygon(new Point(1, 2), new Point(2, 3), new Point(3, 4), new Point(5, 6)); @@ -112,10 +100,7 @@ public void convertsPolygonToDbObjectAndBackCorrectly() { assertThat(result.getClass().equals(Polygon.class), is(true)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsSphereToDbObjectAndBackCorrectlyWithNeutralDistance() { Sphere sphere = new Sphere(new Point(1, 2), 3); @@ -127,10 +112,7 @@ public void convertsSphereToDbObjectAndBackCorrectlyWithNeutralDistance() { assertThat(result.getClass().equals(Sphere.class), is(true)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsSphereToDbObjectAndBackCorrectlyWithKilometerDistance() { Distance radius = new Distance(3, Metrics.KILOMETERS); @@ -144,10 +126,7 @@ public void convertsSphereToDbObjectAndBackCorrectlyWithKilometerDistance() { assertThat(result.getClass().equals(org.springframework.data.mongodb.core.geo.Sphere.class), is(true)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsPointToListAndBackCorrectly() { Point point = new Point(1, 2); @@ -159,10 +138,7 @@ public void convertsPointToListAndBackCorrectly() { assertThat(result.getClass().equals(Point.class), is(true)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsGeoCommandToDbObjectCorrectly() { Box box = new Box(new double[] { 1, 2 }, new double[] { 3, 4 }); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java index 935d135e83..24e98979aa 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoJsonConverterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -185,26 +185,17 @@ public static class DbObjectToGeoJsonPolygonConverterUnitTests { DbObjectToGeoJsonPolygonConverter converter = DbObjectToGeoJsonPolygonConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(POLYGON_DBO), equalTo(POLYGON)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPolygon() { expectedException.expect(IllegalArgumentException.class); @@ -213,10 +204,7 @@ public void shouldThrowExceptionWhenTypeDoesNotMatchPolygon() { converter.convert(new BasicDBObject("type", "YouDontKonwMe")); } - /** - * @see DATAMONGO-1399 - */ - @Test + @Test // DATAMONGO-1399 public void shouldConvertDboWithMultipleRingsCorrectly() { assertThat(converter.convert(POLYGON_WITH_2_RINGS_DBO), equalTo(POLYGON_WITH_2_RINGS)); } @@ -231,26 +219,17 @@ public static class DbObjectToGeoJsonPointConverterUnitTests { DbObjectToGeoJsonPointConverter converter = DbObjectToGeoJsonPointConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(SINGLE_POINT_DBO), equalTo(SINGLE_POINT)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { expectedException.expect(IllegalArgumentException.class); @@ -268,26 +247,17 @@ public static class DbObjectToGeoJsonLineStringConverterUnitTests { DbObjectToGeoJsonLineStringConverter converter = DbObjectToGeoJsonLineStringConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(LINE_STRING_DBO), equalTo(LINE_STRING)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { expectedException.expect(IllegalArgumentException.class); @@ -305,26 +275,17 @@ public static class DbObjectToGeoJsonMultiLineStringConverterUnitTests { DbObjectToGeoJsonMultiLineStringConverter converter = DbObjectToGeoJsonMultiLineStringConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(MULTI_LINE_STRING_DBO), equalTo(MULTI_LINE_STRING)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { expectedException.expect(IllegalArgumentException.class); @@ -342,26 +303,17 @@ public static class DbObjectToGeoJsonMultiPointConverterUnitTests { DbObjectToGeoJsonMultiPointConverter converter = DbObjectToGeoJsonMultiPointConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(MULTI_POINT_DBO), equalTo(MULTI_POINT)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { expectedException.expect(IllegalArgumentException.class); @@ -379,26 +331,17 @@ public static class DbObjectToGeoJsonMultiPolygonConverterUnitTests { DbObjectToGeoJsonMultiPolygonConverter converter = DbObjectToGeoJsonMultiPolygonConverter.INSTANCE; public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertDboCorrectly() { assertThat(converter.convert(MULTI_POLYGON_DBO), equalTo(MULTI_POLYGON)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldReturnNullWhenConvertIsGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldThrowExceptionWhenTypeDoesNotMatchPoint() { expectedException.expect(IllegalArgumentException.class); @@ -415,73 +358,47 @@ public static class GeoJsonToDbObjectConverterUnitTests { GeoJsonToDbObjectConverter converter = GeoJsonToDbObjectConverter.INSTANCE; - /** - * @see DATAMONGO-1135 - */ + // DATAMONGO-1135 public void convertShouldReturnNullWhenGivenNull() { assertThat(converter.convert(null), nullValue()); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void shouldConvertGeoJsonPointCorrectly() { assertThat(converter.convert(SINGLE_POINT), equalTo(SINGLE_POINT_DBO)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void shouldConvertGeoJsonPolygonCorrectly() { assertThat(converter.convert(POLYGON), equalTo(POLYGON_DBO)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertGeoJsonLineStringCorrectly() { assertThat(converter.convert(LINE_STRING), equalTo(LINE_STRING_DBO)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertGeoJsonMultiLineStringCorrectly() { assertThat(converter.convert(MULTI_LINE_STRING), equalTo(MULTI_LINE_STRING_DBO)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertGeoJsonMultiPointCorrectly() { assertThat(converter.convert(MULTI_POINT), equalTo(MULTI_POINT_DBO)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertGeoJsonMultiPolygonCorrectly() { assertThat(converter.convert(MULTI_POLYGON), equalTo(MULTI_POLYGON_DBO)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouldConvertGeometryCollectionCorrectly() { assertThat(converter.convert(GEOMETRY_COLLECTION), equalTo(GEOMETRY_COLLECTION_DBO)); } - /** - * @see DATAMONGO-1399 - */ - @Test + @Test // DATAMONGO-1399 public void shouldConvertGeoJsonPolygonWithMultipleRingsCorrectly() { assertThat(converter.convert(POLYGON_WITH_2_RINGS), equalTo(POLYGON_WITH_2_RINGS_DBO)); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java index 1cdfb776bb..bec7e31ea1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -47,10 +47,7 @@ public class LazyLoadingInterceptorUnitTests { @Mock DBRef dbrefMock; @Mock DbRefResolverCallback callbackMock; - /** - * @see DATAMONGO-1437 - */ - @Test + @Test // DATAMONGO-1437 public void shouldPreserveCauseForNonTranslatableExceptions() throws Throwable { NullPointerException npe = new NullPointerException("Some Exception we did not think about."); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index a1530194a0..c4a0dfdb38 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -166,10 +166,7 @@ public void convertsCustomTypeOnConvertToMongoType() { converter.convertToMongoType(date); } - /** - * @see DATAMONGO-130 - */ - @Test + @Test // DATAMONGO-130 public void writesMapTypeCorrectly() { Map map = Collections.singletonMap(Locale.US, "Foo"); @@ -180,10 +177,7 @@ public void writesMapTypeCorrectly() { assertThat(dbObject.get(Locale.US.toString()).toString(), is("Foo")); } - /** - * @see DATAMONGO-130 - */ - @Test + @Test // DATAMONGO-130 public void readsMapWithCustomKeyTypeCorrectly() { DBObject mapObject = new BasicDBObject(Locale.US.toString(), "Value"); @@ -193,10 +187,7 @@ public void readsMapWithCustomKeyTypeCorrectly() { assertThat(result.map.get(Locale.US), is("Value")); } - /** - * @see DATAMONGO-128 - */ - @Test + @Test // DATAMONGO-128 public void usesDocumentsStoredTypeIfSubtypeOfRequest() { DBObject dbObject = new BasicDBObject(); @@ -206,10 +197,7 @@ public void usesDocumentsStoredTypeIfSubtypeOfRequest() { assertThat(converter.read(Contact.class, dbObject), is(instanceOf(Person.class))); } - /** - * @see DATAMONGO-128 - */ - @Test + @Test // DATAMONGO-128 public void ignoresDocumentsStoredTypeIfCompletelyDifferentTypeRequested() { DBObject dbObject = new BasicDBObject(); @@ -231,10 +219,7 @@ public void writesTypeDiscriminatorIntoRootObject() { assertThat(result.get(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY).toString(), is(Person.class.getName())); } - /** - * @see DATAMONGO-136 - */ - @Test + @Test // DATAMONGO-136 public void writesEnumsCorrectly() { ClassWithEnumProperty value = new ClassWithEnumProperty(); @@ -247,10 +232,7 @@ public void writesEnumsCorrectly() { assertThat(result.get("sampleEnum").toString(), is("FIRST")); } - /** - * @see DATAMONGO-209 - */ - @Test + @Test // DATAMONGO-209 public void writesEnumCollectionCorrectly() { ClassWithEnumProperty value = new ClassWithEnumProperty(); @@ -266,10 +248,7 @@ public void writesEnumCollectionCorrectly() { assertThat((String) enums.get(0), is("FIRST")); } - /** - * @see DATAMONGO-136 - */ - @Test + @Test // DATAMONGO-136 public void readsEnumsCorrectly() { DBObject dbObject = new BasicDBObject("sampleEnum", "FIRST"); ClassWithEnumProperty result = converter.read(ClassWithEnumProperty.class, dbObject); @@ -277,10 +256,7 @@ public void readsEnumsCorrectly() { assertThat(result.sampleEnum, is(SampleEnum.FIRST)); } - /** - * @see DATAMONGO-209 - */ - @Test + @Test // DATAMONGO-209 public void readsEnumCollectionsCorrectly() { BasicDBList enums = new BasicDBList(); @@ -294,10 +270,7 @@ public void readsEnumCollectionsCorrectly() { assertThat(result.enums, hasItem(SampleEnum.FIRST)); } - /** - * @see DATAMONGO-144 - */ - @Test + @Test // DATAMONGO-144 public void considersFieldNameWhenWriting() { Person person = new Person(); @@ -310,10 +283,7 @@ public void considersFieldNameWhenWriting() { assertThat(result.containsField("firstname"), is(false)); } - /** - * @see DATAMONGO-144 - */ - @Test + @Test // DATAMONGO-144 public void considersFieldNameWhenReading() { DBObject dbObject = new BasicDBObject("foo", "Oliver"); @@ -338,10 +308,7 @@ public void resolvesNestedComplexTypeForConstructorCorrectly() { assertThat(result.addresses, is(notNullValue())); } - /** - * @see DATAMONGO-145 - */ - @Test + @Test // DATAMONGO-145 public void writesCollectionWithInterfaceCorrectly() { Person person = new Person(); @@ -361,10 +328,7 @@ public void writesCollectionWithInterfaceCorrectly() { assertThat((String) personDbObject.get(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY), is(Person.class.getName())); } - /** - * @see DATAMONGO-145 - */ - @Test + @Test // DATAMONGO-145 public void readsCollectionWithInterfaceCorrectly() { BasicDBObject person = new BasicDBObject(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, Person.class.getName()); @@ -397,10 +361,7 @@ public void convertsLocalesOutOfTheBox() { assertThat(read.locale, is(Locale.US)); } - /** - * @see DATAMONGO-161 - */ - @Test + @Test // DATAMONGO-161 public void readsNestedMapsCorrectly() { Map secondLevel = new HashMap(); @@ -424,10 +385,7 @@ public void readsNestedMapsCorrectly() { assertThat(nestedMap.get("afield"), is(firstLevel)); } - /** - * @see DATACMNS-42, DATAMONGO-171 - */ - @Test + @Test // DATACMNS-42, DATAMONGO-171 public void writesClassWithBigDecimal() { BigDecimalContainer container = new BigDecimalContainer(); @@ -442,10 +400,7 @@ public void writesClassWithBigDecimal() { assertThat(((DBObject) dbObject.get("map")).get("foo"), is(instanceOf(String.class))); } - /** - * @see DATACMNS-42, DATAMONGO-171 - */ - @Test + @Test // DATACMNS-42, DATAMONGO-171 public void readsClassWithBigDecimal() { DBObject dbObject = new BasicDBObject("value", "2.5"); @@ -478,10 +433,7 @@ public void writesNestedCollectionsCorrectly() { assertThat(typedOuterString.size(), is(1)); } - /** - * @see DATAMONGO-192 - */ - @Test + @Test // DATAMONGO-192 public void readsEmptySetsCorrectly() { Person person = new Person(); @@ -507,10 +459,7 @@ public void convertsObjectIdStringsToObjectIdCorrectly() { assertThat(dbo2.get("_id"), is(instanceOf(ObjectId.class))); } - /** - * @see DATAMONGO-207 - */ - @Test + @Test // DATAMONGO-207 public void convertsCustomEmptyMapCorrectly() { DBObject map = new BasicDBObject(); @@ -522,10 +471,7 @@ public void convertsCustomEmptyMapCorrectly() { assertThat(result.map, is(instanceOf(SortedMap.class))); } - /** - * @see DATAMONGO-211 - */ - @Test + @Test // DATAMONGO-211 public void maybeConvertHandlesNullValuesCorrectly() { assertThat(converter.convertToMongoType(null), is(nullValue())); } @@ -556,10 +502,7 @@ public void readsGenericTypeCorrectly() { } - /** - * @see DATAMONGO-228 - */ - @Test + @Test // DATAMONGO-228 public void writesNullValuesForMaps() { ClassWithMapProperty foo = new ClassWithMapProperty(); @@ -591,10 +534,7 @@ public void convertsObjectsIfNecessary() { assertThat(converter.convertToMongoType(id), is((Object) id)); } - /** - * @see DATAMONGO-235 - */ - @Test + @Test // DATAMONGO-235 public void writesMapOfListsCorrectly() { ClassWithMapProperty input = new ClassWithMapProperty(); @@ -615,10 +555,7 @@ public void writesMapOfListsCorrectly() { assertThat((String) value.get(0), is("Bar")); } - /** - * @see DATAMONGO-235 - */ - @Test + @Test // DATAMONGO-235 public void readsMapListValuesCorrectly() { BasicDBList list = new BasicDBList(); @@ -629,10 +566,7 @@ public void readsMapListValuesCorrectly() { assertThat(result.mapOfLists, is(not(nullValue()))); } - /** - * @see DATAMONGO-235 - */ - @Test + @Test // DATAMONGO-235 public void writesMapsOfObjectsCorrectly() { ClassWithMapProperty input = new ClassWithMapProperty(); @@ -654,10 +588,7 @@ public void writesMapsOfObjectsCorrectly() { assertThat((String) value.get(0), is("Bar")); } - /** - * @see DATAMONGO-235 - */ - @Test + @Test // DATAMONGO-235 public void readsMapOfObjectsListValuesCorrectly() { BasicDBList list = new BasicDBList(); @@ -668,10 +599,7 @@ public void readsMapOfObjectsListValuesCorrectly() { assertThat(result.mapOfObjects, is(not(nullValue()))); } - /** - * @see DATAMONGO-245 - */ - @Test + @Test // DATAMONGO-245 public void readsMapListNestedValuesCorrectly() { BasicDBList list = new BasicDBList(); @@ -684,10 +612,7 @@ public void readsMapListNestedValuesCorrectly() { assertThat((String) ((Map) firstObjectInFoo).get("Hello"), is(equalTo("World"))); } - /** - * @see DATAMONGO-245 - */ - @Test + @Test // DATAMONGO-245 public void readsMapDoublyNestedValuesCorrectly() { BasicDBObject nested = new BasicDBObject(); @@ -704,10 +629,7 @@ public void readsMapDoublyNestedValuesCorrectly() { assertThat((String) ((Map) doublyNestedObject).get("Hello"), is(equalTo("World"))); } - /** - * @see DATAMONGO-245 - */ - @Test + @Test // DATAMONGO-245 public void readsMapListDoublyNestedValuesCorrectly() { BasicDBList list = new BasicDBList(); @@ -726,10 +648,7 @@ public void readsMapListDoublyNestedValuesCorrectly() { assertThat((String) ((Map) doublyNestedObject).get("Hello"), is(equalTo("World"))); } - /** - * @see DATAMONGO-259 - */ - @Test + @Test // DATAMONGO-259 public void writesListOfMapsCorrectly() { Map map = Collections.singletonMap("Foo", Locale.ENGLISH); @@ -750,10 +669,7 @@ public void writesListOfMapsCorrectly() { assertThat((String) dbObject.get("Foo"), is(Locale.ENGLISH.toString())); } - /** - * @see DATAMONGO-259 - */ - @Test + @Test // DATAMONGO-259 public void readsListOfMapsCorrectly() { DBObject map = new BasicDBObject("Foo", "en"); @@ -771,10 +687,7 @@ public void readsListOfMapsCorrectly() { assertThat(wrapper.listOfMaps.get(0).get("Foo"), is(Locale.ENGLISH)); } - /** - * @see DATAMONGO-259 - */ - @Test + @Test // DATAMONGO-259 public void writesPlainMapOfCollectionsCorrectly() { Map> map = Collections.singletonMap("Foo", Arrays.asList(Locale.US)); @@ -791,10 +704,7 @@ public void writesPlainMapOfCollectionsCorrectly() { assertThat(list.get(0), is((Object) Locale.US.toString())); } - /** - * @see DATAMONGO-285 - */ - @Test + @Test // DATAMONGO-285 @SuppressWarnings({ "unchecked", "rawtypes" }) public void testSaveMapWithACollectionAsValue() { @@ -819,10 +729,7 @@ public void testSaveMapWithACollectionAsValue() { assertEquals(list.get(1), listFromMongo.get(1)); } - /** - * @see DATAMONGO-309 - */ - @Test + @Test // DATAMONGO-309 @SuppressWarnings({ "unchecked" }) public void writesArraysAsMapValuesCorrectly() { @@ -845,10 +752,7 @@ public void writesArraysAsMapValuesCorrectly() { assertThat(list, hasItem((Object) "bar")); } - /** - * @see DATAMONGO-324 - */ - @Test + @Test // DATAMONGO-324 public void writesDbObjectCorrectly() { DBObject dbObject = new BasicDBObject(); @@ -862,10 +766,7 @@ public void writesDbObjectCorrectly() { assertThat(dbObject, is(result)); } - /** - * @see DATAMONGO-324 - */ - @Test + @Test // DATAMONGO-324 public void readsDbObjectCorrectly() { DBObject dbObject = new BasicDBObject(); @@ -876,10 +777,7 @@ public void readsDbObjectCorrectly() { assertThat(result, is(dbObject)); } - /** - * @see DATAMONGO-329 - */ - @Test + @Test // DATAMONGO-329 public void writesMapAsGenericFieldCorrectly() { Map> objectToSave = new HashMap>(); @@ -915,10 +813,7 @@ public void writesIntIdCorrectly() { assertThat(result.get("_id"), is((Object) 5)); } - /** - * @see DATAMONGO-368 - */ - @Test + @Test // DATAMONGO-368 @SuppressWarnings("unchecked") public void writesNullValuesForCollection() { @@ -934,10 +829,7 @@ public void writesNullValuesForCollection() { assertThat((Collection) contacts, hasItem(nullValue())); } - /** - * @see DATAMONGO-379 - */ - @Test + @Test // DATAMONGO-379 public void considersDefaultingExpressionsAtConstructorArguments() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -947,10 +839,7 @@ public void considersDefaultingExpressionsAtConstructorArguments() { assertThat(result.bar, is(-1)); } - /** - * @see DATAMONGO-379 - */ - @Test + @Test // DATAMONGO-379 public void usesDocumentFieldIfReferencedInAtValue() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -961,10 +850,7 @@ public void usesDocumentFieldIfReferencedInAtValue() { assertThat(result.bar, is(37)); } - /** - * @see DATAMONGO-379 - */ - @Test(expected = MappingInstantiationException.class) + @Test(expected = MappingInstantiationException.class) // DATAMONGO-379 public void rejectsNotFoundConstructorParameterForPrimitiveType() { DBObject dbObject = new BasicDBObject("foo", "bar"); @@ -972,10 +858,7 @@ public void rejectsNotFoundConstructorParameterForPrimitiveType() { converter.read(DefaultedConstructorArgument.class, dbObject); } - /** - * @see DATAMONGO-358 - */ - @Test + @Test // DATAMONGO-358 public void writesListForObjectPropertyCorrectly() { Attribute attribute = new Attribute(); @@ -1001,18 +884,12 @@ public void writesListForObjectPropertyCorrectly() { assertThat(values, hasItems("1", "2")); } - /** - * @see DATAMONGO-380 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-380 public void rejectsMapWithKeyContainingDotsByDefault() { converter.write(Collections.singletonMap("foo.bar", "foobar"), new BasicDBObject()); } - /** - * @see DATAMONGO-380 - */ - @Test + @Test // DATAMONGO-380 public void escapesDotInMapKeysIfReplacementConfigured() { converter.setMapKeyDotReplacement("~"); @@ -1024,10 +901,7 @@ public void escapesDotInMapKeysIfReplacementConfigured() { assertThat(dbObject.containsField("foo.bar"), is(false)); } - /** - * @see DATAMONGO-380 - */ - @Test + @Test // DATAMONGO-380 @SuppressWarnings("unchecked") public void unescapesDotInMapKeysIfReplacementConfigured() { @@ -1040,10 +914,7 @@ public void unescapesDotInMapKeysIfReplacementConfigured() { assertThat(result.containsKey("foobar"), is(false)); } - /** - * @see DATAMONGO-382 - */ - @Test + @Test // DATAMONGO-382 public void convertsSetToBasicDBList() { Address address = new Address(); @@ -1058,10 +929,7 @@ public void convertsSetToBasicDBList() { assertThat(readResult.iterator().next(), is(instanceOf(Address.class))); } - /** - * @see DATAMONGO-402 - */ - @Test + @Test // DATAMONGO-402 public void readsMemberClassCorrectly() { DBObject dbObject = new BasicDBObject("inner", new BasicDBObject("value", "FOO!")); @@ -1072,10 +940,7 @@ public void readsMemberClassCorrectly() { assertSyntheticFieldValueOf(outer.inner, outer); } - /** - * @see DATAMONGO-458 - */ - @Test + @Test // DATAMONGO-458 public void readEmptyCollectionIsModifiable() { DBObject dbObject = new BasicDBObject("contactsSet", new BasicDBList()); @@ -1085,10 +950,7 @@ public void readEmptyCollectionIsModifiable() { wrapper.contactsSet.add(new Contact() {}); } - /** - * @see DATAMONGO-424 - */ - @Test + @Test // DATAMONGO-424 public void readsPlainDBRefObject() { DBRef dbRef = new DBRef("foo", 2); @@ -1098,10 +960,7 @@ public void readsPlainDBRefObject() { assertThat(result.ref, is(dbRef)); } - /** - * @see DATAMONGO-424 - */ - @Test + @Test // DATAMONGO-424 public void readsCollectionOfDBRefs() { DBRef dbRef = new DBRef("foo", 2); @@ -1115,10 +974,7 @@ public void readsCollectionOfDBRefs() { assertThat(result.refs, hasItem(dbRef)); } - /** - * @see DATAMONGO-424 - */ - @Test + @Test // DATAMONGO-424 public void readsDBRefMap() { DBRef dbRef = mock(DBRef.class); @@ -1131,10 +987,7 @@ public void readsDBRefMap() { assertThat(result.refMap.values(), hasItem(dbRef)); } - /** - * @see DATAMONGO-424 - */ - @Test + @Test // DATAMONGO-424 @SuppressWarnings({ "rawtypes", "unchecked" }) public void resolvesDBRefMapValue() { @@ -1152,10 +1005,7 @@ public void resolvesDBRefMapValue() { assertThat(result.personMap.values(), hasItem(isPerson)); } - /** - * @see DATAMONGO-462 - */ - @Test + @Test // DATAMONGO-462 public void writesURLsAsStringOutOfTheBox() throws Exception { URLWrapper wrapper = new URLWrapper(); @@ -1167,20 +1017,14 @@ public void writesURLsAsStringOutOfTheBox() throws Exception { assertThat(sink.get("url"), is((Object) "http://springsource.org")); } - /** - * @see DATAMONGO-462 - */ - @Test + @Test // DATAMONGO-462 public void readsURLFromStringOutOfTheBox() throws Exception { DBObject dbObject = new BasicDBObject("url", "http://springsource.org"); URLWrapper result = converter.read(URLWrapper.class, dbObject); assertThat(result.url, is(new URL("http://springsource.org"))); } - /** - * @see DATAMONGO-485 - */ - @Test + @Test // DATAMONGO-485 public void writesComplexIdCorrectly() { ComplexId id = new ComplexId(); @@ -1198,10 +1042,7 @@ public void writesComplexIdCorrectly() { assertThat(((DBObject) idField).get("innerId"), is((Object) 4711L)); } - /** - * @see DATAMONGO-485 - */ - @Test + @Test // DATAMONGO-485 public void readsComplexIdCorrectly() { DBObject innerId = new BasicDBObject("innerId", 4711L); @@ -1213,10 +1054,7 @@ public void readsComplexIdCorrectly() { assertThat(result.complexId.innerId, is(4711L)); } - /** - * @see DATAMONGO-489 - */ - @Test + @Test // DATAMONGO-489 public void readsArraysAsMapValuesCorrectly() { BasicDBList list = new BasicDBList(); @@ -1234,10 +1072,7 @@ public void readsArraysAsMapValuesCorrectly() { assertThat(values, is(arrayWithSize(2))); } - /** - * @see DATAMONGO-497 - */ - @Test + @Test // DATAMONGO-497 public void readsEmptyCollectionIntoConstructorCorrectly() { DBObject source = new BasicDBObject("attributes", new BasicDBList()); @@ -1260,10 +1095,7 @@ private static void assertSyntheticFieldValueOf(Object target, Object expected) fail(String.format("Didn't find synthetic field on %s!", target)); } - /** - * @see DATAMGONGO-508 - */ - @Test + @Test // DATAMGONGO-508 public void eagerlyReturnsDBRefObjectIfTargetAlreadyIsOne() { DBRef dbRef = new DBRef("collection", "id"); @@ -1273,10 +1105,7 @@ public void eagerlyReturnsDBRefObjectIfTargetAlreadyIsOne() { assertThat(converter.createDBRef(dbRef, property), is(dbRef)); } - /** - * @see DATAMONGO-523 - */ - @Test + @Test // DATAMONGO-523 public void considersTypeAliasAnnotation() { Aliased aliased = new Aliased(); @@ -1290,10 +1119,7 @@ public void considersTypeAliasAnnotation() { assertThat(type.toString(), is("_")); } - /** - * @see DATAMONGO-533 - */ - @Test + @Test // DATAMONGO-533 public void marshalsThrowableCorrectly() { ThrowableWrapper wrapper = new ThrowableWrapper(); @@ -1303,10 +1129,7 @@ public void marshalsThrowableCorrectly() { converter.write(wrapper, dbObject); } - /** - * @see DATAMONGO-592 - */ - @Test + @Test // DATAMONGO-592 public void recursivelyConvertsSpELReadValue() { DBObject input = (DBObject) JSON @@ -1315,10 +1138,7 @@ public void recursivelyConvertsSpELReadValue() { converter.read(ObjectContainer.class, input); } - /** - * @see DATAMONGO-724 - */ - @Test + @Test // DATAMONGO-724 @SuppressWarnings("unchecked") public void mappingConsidersCustomConvertersNotWritingTypeInformation() { @@ -1376,20 +1196,14 @@ public Person convert(DBObject source) { assertThat(((Person) value).lastname, is("converter")); } - /** - * @see DATAMONGO-743 - */ - @Test + @Test // DATAMONGO-743 public void readsIntoStringsOutOfTheBox() { DBObject dbObject = new BasicDBObject("firstname", "Dave"); assertThat(converter.read(String.class, dbObject), is("{ \"firstname\" : \"Dave\"}")); } - /** - * @see DATAMONGO-766 - */ - @Test + @Test // DATAMONGO-766 public void writesProjectingTypeCorrectly() { NestedType nested = new NestedType(); @@ -1409,11 +1223,7 @@ public void writesProjectingTypeCorrectly() { assertThat(aValue.get("c"), is((Object) "C")); } - /** - * @see DATAMONGO-812 - * @see DATAMONGO-893 - */ - @Test + @Test // DATAMONGO-812, DATAMONGO-893 public void convertsListToBasicDBListAndRetainsTypeInformationForComplexObjects() { Address address = new Address(); @@ -1430,10 +1240,7 @@ public void convertsListToBasicDBListAndRetainsTypeInformationForComplexObjects( assertThat(getTypedValue(getAsDBObject(dbList, 0), "_class", String.class), equalTo(Address.class.getName())); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void convertsListToBasicDBListWithoutTypeInformationForSimpleTypes() { Object result = converter.convertToMongoType(Collections.singletonList("foo")); @@ -1445,10 +1252,7 @@ public void convertsListToBasicDBListWithoutTypeInformationForSimpleTypes() { assertThat(dbList.get(0), instanceOf(String.class)); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void convertsArrayToBasicDBListAndRetainsTypeInformationForComplexObjects() { Address address = new Address(); @@ -1464,10 +1268,7 @@ public void convertsArrayToBasicDBListAndRetainsTypeInformationForComplexObjects assertThat(getTypedValue(getAsDBObject(dbList, 0), "_class", String.class), equalTo(Address.class.getName())); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void convertsArrayToBasicDBListWithoutTypeInformationForSimpleTypes() { Object result = converter.convertToMongoType(new String[] { "foo" }); @@ -1479,10 +1280,7 @@ public void convertsArrayToBasicDBListWithoutTypeInformationForSimpleTypes() { assertThat(dbList.get(0), instanceOf(String.class)); } - /** - * @see DATAMONGO-833 - */ - @Test + @Test // DATAMONGO-833 public void readsEnumSetCorrectly() { BasicDBList enumSet = new BasicDBList(); @@ -1496,10 +1294,7 @@ public void readsEnumSetCorrectly() { assertThat(result.enumSet, hasItem(SampleEnum.SECOND)); } - /** - * @see DATAMONGO-833 - */ - @Test + @Test // DATAMONGO-833 public void readsEnumMapCorrectly() { BasicDBObject enumMap = new BasicDBObject("FIRST", "Dave"); @@ -1510,10 +1305,7 @@ public void readsEnumMapCorrectly() { assertThat(result.enumMap.get(SampleEnum.FIRST), is("Dave")); } - /** - * @see DATAMONGO-887 - */ - @Test + @Test // DATAMONGO-887 public void readsTreeMapCorrectly() { DBObject person = new BasicDBObject("foo", "Dave"); @@ -1527,10 +1319,7 @@ public void readsTreeMapCorrectly() { assertThat(result.treeMapOfPersons.get("key").firstname, is("Dave")); } - /** - * @see DATAMONGO-887 - */ - @Test + @Test // DATAMONGO-887 public void writesTreeMapCorrectly() { Person person = new Person(); @@ -1549,10 +1338,7 @@ public void writesTreeMapCorrectly() { assertThat(entry.get("foo"), is((Object) "Dave")); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoBoxCorrectly() { ClassWithGeoBox object = new ClassWithGeoBox(); @@ -1571,10 +1357,7 @@ private static DBObject toDbObject(Point point) { return new BasicDBObject("x", point.getX()).append("y", point.getY()); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldReadEntityWithGeoBoxCorrectly() { ClassWithGeoBox object = new ClassWithGeoBox(); @@ -1589,10 +1372,7 @@ public void shouldReadEntityWithGeoBoxCorrectly() { assertThat(result.box, is(object.box)); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoPolygonCorrectly() { ClassWithGeoPolygon object = new ClassWithGeoPolygon(); @@ -1614,10 +1394,7 @@ public void shouldWriteEntityWithGeoPolygonCorrectly() { toDbObject(object.polygon.getPoints().get(1)), toDbObject(object.polygon.getPoints().get(2)))); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldReadEntityWithGeoPolygonCorrectly() { ClassWithGeoPolygon object = new ClassWithGeoPolygon(); @@ -1632,10 +1409,7 @@ public void shouldReadEntityWithGeoPolygonCorrectly() { assertThat(result.polygon, is(object.polygon)); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoCircleCorrectly() { ClassWithGeoCircle object = new ClassWithGeoCircle(); @@ -1655,10 +1429,7 @@ public void shouldWriteEntityWithGeoCircleCorrectly() { radius.getMetric().toString()))); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldReadEntityWithGeoCircleCorrectly() { ClassWithGeoCircle object = new ClassWithGeoCircle(); @@ -1673,10 +1444,7 @@ public void shouldReadEntityWithGeoCircleCorrectly() { assertThat(result.circle, is(result.circle)); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoSphereCorrectly() { ClassWithGeoSphere object = new ClassWithGeoSphere(); @@ -1696,10 +1464,7 @@ public void shouldWriteEntityWithGeoSphereCorrectly() { radius.getMetric().toString()))); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoSphereWithMetricDistanceCorrectly() { ClassWithGeoSphere object = new ClassWithGeoSphere(); @@ -1719,10 +1484,7 @@ public void shouldWriteEntityWithGeoSphereWithMetricDistanceCorrectly() { radius.getMetric().toString()))); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldReadEntityWithGeoSphereCorrectly() { ClassWithGeoSphere object = new ClassWithGeoSphere(); @@ -1737,10 +1499,7 @@ public void shouldReadEntityWithGeoSphereCorrectly() { assertThat(result.sphere, is(object.sphere)); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void shouldWriteEntityWithGeoShapeCorrectly() { ClassWithGeoShape object = new ClassWithGeoShape(); @@ -1760,10 +1519,7 @@ public void shouldWriteEntityWithGeoShapeCorrectly() { radius.getMetric().toString()))); } - /** - * @DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 @Ignore public void shouldReadEntityWithGeoShapeCorrectly() { @@ -1780,10 +1536,7 @@ public void shouldReadEntityWithGeoShapeCorrectly() { assertThat(result.shape, is((Shape) sphere)); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldIgnoreTextScorePropertyWhenWriting() { ClassWithTextScoreProperty source = new ClassWithTextScoreProperty(); @@ -1795,10 +1548,7 @@ public void shouldIgnoreTextScorePropertyWhenWriting() { assertThat(dbo.get("score"), nullValue()); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldIncludeTextScorePropertyWhenReading() { ClassWithTextScoreProperty entity = converter @@ -1806,10 +1556,7 @@ public void shouldIncludeTextScorePropertyWhenReading() { assertThat(entity.score, equalTo(5F)); } - /** - * @see DATAMONGO-1001 - */ - @Test + @Test // DATAMONGO-1001 public void shouldWriteCglibProxiedClassTypeInformationCorrectly() { ProxyFactory factory = new ProxyFactory(); @@ -1823,10 +1570,7 @@ public void shouldWriteCglibProxiedClassTypeInformationCorrectly() { assertThat(dbo.get("_class"), is((Object) GenericType.class.getName())); } - /** - * @see DATAMONGO-1001 - */ - @Test + @Test // DATAMONGO-1001 public void shouldUseTargetObjectOfLazyLoadingProxyWhenWriting() { LazyLoadingProxy mock = mock(LazyLoadingProxy.class); @@ -1837,10 +1581,7 @@ public void shouldUseTargetObjectOfLazyLoadingProxyWhenWriting() { verify(mock, times(1)).getTarget(); } - /** - * @see DATAMONGO-1034 - */ - @Test + @Test // DATAMONGO-1034 public void rejectsBasicDbListToBeConvertedIntoComplexType() { BasicDBList inner = new BasicDBList(); @@ -1860,10 +1601,7 @@ public void rejectsBasicDbListToBeConvertedIntoComplexType() { converter.read(Item.class, source); } - /** - * @see DATAMONGO-1058 - */ - @Test + @Test // DATAMONGO-1058 public void readShouldRespectExplicitFieldNameForDbRef() { BasicDBObject source = new BasicDBObject(); @@ -1875,10 +1613,7 @@ public void readShouldRespectExplicitFieldNameForDbRef() { Mockito.any(DbRefResolverCallback.class), Mockito.any(DbRefProxyHandler.class)); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void writeShouldUseExplicitFieldnameForIdPropertyWhenAnnotated() { RootForClassWithExplicitlyRenamedIdField source = new RootForClassWithExplicitlyRenamedIdField(); @@ -1893,10 +1628,7 @@ public void writeShouldUseExplicitFieldnameForIdPropertyWhenAnnotated() { assertThat((DBObject) sink.get("nested"), is(new BasicDBObjectBuilder().add("id", "nestedId").get())); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void readShouldUseExplicitFieldnameForIdPropertyWhenAnnotated() { DBObject source = new BasicDBObjectBuilder().add("_id", "rootId") @@ -1910,10 +1642,7 @@ public void readShouldUseExplicitFieldnameForIdPropertyWhenAnnotated() { assertThat(sink.nested.id, is("nestedId")); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void namedIdFieldShouldExtractValueFromUnderscoreIdField() { DBObject dbo = new BasicDBObjectBuilder().add("_id", "A").add("id", "B").get(); @@ -1923,10 +1652,7 @@ public void namedIdFieldShouldExtractValueFromUnderscoreIdField() { assertThat(withNamedIdField.id, is("A")); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void explicitlyRenamedIfFieldShouldExtractValueFromIdField() { DBObject dbo = new BasicDBObjectBuilder().add("_id", "A").add("id", "B").get(); @@ -1937,10 +1663,7 @@ public void explicitlyRenamedIfFieldShouldExtractValueFromIdField() { assertThat(withExplicitlyRenamedField.id, is("B")); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void annotatedIdFieldShouldExtractValueFromUnderscoreIdField() { DBObject dbo = new BasicDBObjectBuilder().add("_id", "A").add("id", "B").get(); @@ -1950,10 +1673,7 @@ public void annotatedIdFieldShouldExtractValueFromUnderscoreIdField() { assertThat(withAnnotatedIdField.key, is("A")); } - /** - * @see DATAMONGO-1102 - */ - @Test + @Test // DATAMONGO-1102 public void convertsJava8DateTimeTypesToDateAndBack() { TypeWithLocalDateTime source = new TypeWithLocalDateTime(); @@ -1966,10 +1686,7 @@ public void convertsJava8DateTimeTypesToDateAndBack() { assertThat(converter.read(TypeWithLocalDateTime.class, result).date, is(reference)); } - /** - * @see DATAMONGO-1128 - */ - @Test + @Test // DATAMONGO-1128 public void writesOptionalsCorrectly() { TypeWithOptional type = new TypeWithOptional(); @@ -1985,10 +1702,7 @@ public void writesOptionalsCorrectly() { assertThat(localDateTime.get("value"), is(instanceOf(Date.class))); } - /** - * @see DATAMONGO-1128 - */ - @Test + @Test // DATAMONGO-1128 public void readsOptionalsCorrectly() { LocalDateTime now = LocalDateTime.now(); @@ -2003,10 +1717,7 @@ public void readsOptionalsCorrectly() { assertThat(read.localDateTime, is(Optional.of(now))); } - /** - * @see DATAMONGO-1118 - */ - @Test + @Test // DATAMONGO-1118 @SuppressWarnings("unchecked") public void convertsMapKeyUsingCustomConverterForAndBackwards() { @@ -2025,10 +1736,7 @@ public void convertsMapKeyUsingCustomConverterForAndBackwards() { assertThat(converter.read(ClassWithMapUsingEnumAsKey.class, target).map, is(source.map)); } - /** - * @see DATAMONGO-1118 - */ - @Test + @Test // DATAMONGO-1118 public void writesMapKeyUsingCustomConverter() { MappingMongoConverter converter = new MappingMongoConverter(resolver, mappingContext); @@ -2049,10 +1757,7 @@ public void writesMapKeyUsingCustomConverter() { assertThat(map.containsField("bar-enum-value"), is(true)); } - /** - * @see DATAMONGO-1118 - */ - @Test + @Test // DATAMONGO-1118 public void readsMapKeyUsingCustomConverter() { MappingMongoConverter converter = new MappingMongoConverter(resolver, mappingContext); @@ -2066,18 +1771,12 @@ public void readsMapKeyUsingCustomConverter() { assertThat(target.map.get(FooBarEnum.FOO), is("spring")); } - /** - * @see DATAMONGO-1471 - */ - @Test + @Test // DATAMONGO-1471 public void readsDocumentWithPrimitiveIdButNoValue() { assertThat(converter.read(ClassWithIntId.class, new BasicDBObject()), is(notNullValue())); } - /** - * @see DATAMONGO-1497 - */ - @Test + @Test // DATAMONGO-1497 public void readsPropertyFromNestedFieldCorrectly() { DBObject source = new BasicDBObject("nested", new BasicDBObject("sample", "value")); @@ -2086,10 +1785,7 @@ public void readsPropertyFromNestedFieldCorrectly() { assertThat(result.sample, is("value")); } - /** - * @see DATAMONGO-1525 - */ - @Test + @Test // DATAMONGO-1525 public void readsEmptyEnumSet() { DBObject source = new BasicDBObject("enumSet", new BasicDBList()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java index 32e70e792f..407e7081e5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoConvertersUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,10 +61,7 @@ public void convertsBigDecimalToStringAndBackCorrectly() { assertThat(reference, is(bigDecimal)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsBoxToDbObjectAndBackCorrectly() { Box box = new Box(new Point(1, 2), new Point(3, 4)); @@ -75,10 +72,7 @@ public void convertsBoxToDbObjectAndBackCorrectly() { assertThat(shape, is((org.springframework.data.geo.Shape) box)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsCircleToDbObjectAndBackCorrectly() { Circle circle = new Circle(new Point(1, 2), 3); @@ -89,10 +83,7 @@ public void convertsCircleToDbObjectAndBackCorrectly() { assertThat(shape, is((org.springframework.data.geo.Shape) circle)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsPolygonToDbObjectAndBackCorrectly() { Polygon polygon = new Polygon(new Point(1, 2), new Point(2, 3), new Point(3, 4), new Point(5, 6)); @@ -103,10 +94,7 @@ public void convertsPolygonToDbObjectAndBackCorrectly() { assertThat(shape, is((org.springframework.data.geo.Shape) polygon)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsSphereToDbObjectAndBackCorrectly() { Sphere sphere = new Sphere(new Point(1, 2), 3); @@ -117,10 +105,7 @@ public void convertsSphereToDbObjectAndBackCorrectly() { assertThat(shape, is((org.springframework.data.geo.Shape) sphere)); } - /** - * @see DATAMONGO-858 - */ - @Test + @Test // DATAMONGO-858 public void convertsPointToListAndBackCorrectly() { Point point = new Point(1, 2); @@ -131,50 +116,32 @@ public void convertsPointToListAndBackCorrectly() { assertThat(converted, is((org.springframework.data.geo.Point) point)); } - /** - * @see DATAMONGO-1372 - */ - @Test + @Test // DATAMONGO-1372 public void convertsCurrencyToStringCorrectly() { assertThat(CurrencyToStringConverter.INSTANCE.convert(Currency.getInstance("USD")), is("USD")); } - /** - * @see DATAMONGO-1372 - */ - @Test + @Test // DATAMONGO-1372 public void convertsStringToCurrencyCorrectly() { assertThat(StringToCurrencyConverter.INSTANCE.convert("USD"), is(Currency.getInstance("USD"))); } - /** - * @see DATAMONGO-1416 - */ - @Test + @Test // DATAMONGO-1416 public void convertsAtomicLongToLongCorrectly() { assertThat(AtomicLongToLongConverter.INSTANCE.convert(new AtomicLong(100L)), is(100L)); } - /** - * @see DATAMONGO-1416 - */ - @Test + @Test // DATAMONGO-1416 public void convertsAtomicIntegerToIntegerCorrectly() { assertThat(AtomicIntegerToIntegerConverter.INSTANCE.convert(new AtomicInteger(100)), is(100)); } - /** - * @see DATAMONGO-1416 - */ - @Test + @Test // DATAMONGO-1416 public void convertsLongToAtomicLongCorrectly() { assertThat(LongToAtomicLongConverter.INSTANCE.convert(100L), is(instanceOf(AtomicLong.class))); } - /** - * @see DATAMONGO-1416 - */ - @Test + @Test // DATAMONGO-1416 public void convertsIntegerToAtomicIntegerCorrectly() { assertThat(IntegerToAtomicIntegerConverter.INSTANCE.convert(100), is(instanceOf(AtomicInteger.class))); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java index db4b2a4c82..b95f77a077 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MongoExampleMapperUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -76,10 +76,7 @@ public void setUp() { this.mapper = new MongoExampleMapper(converter); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsSet() { FlatDocument probe = new FlatDocument(); @@ -90,10 +87,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsSet() { assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() { FlatDocument probe = new FlatDocument(); @@ -109,10 +103,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenMultipleValuesSet() { assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() { FlatDocument probe = new FlatDocument(); @@ -126,10 +117,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIdIsNotSet() { assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() { FlatDocument probe = new FlatDocument(); @@ -144,10 +132,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenListHasValues() { assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() { FlatDocument probe = new FlatDocument(); @@ -158,10 +143,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenFieldNameIsCustomized() assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void typedExampleShouldContainTypeRestriction() { WrapperDocument probe = new WrapperDocument(); @@ -174,10 +156,7 @@ public void typedExampleShouldContainTypeRestriction() { isBsonObject().containing("_class", new BasicDBObject("$in", new String[] { probe.getClass().getName() }))); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatchMode() { WrapperDocument probe = new WrapperDocument(); @@ -189,10 +168,7 @@ public void exampleShouldBeMappedAsFlatMapWhenGivenNestedElementsWithLenientMatc assertThat(mapper.getMappedExample(of(probe), context.getPersistentEntity(WrapperDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictMatchMode() { WrapperDocument probe = new WrapperDocument(); @@ -205,10 +181,7 @@ public void exampleShouldBeMappedAsExactObjectWhenGivenNestedElementsWithStrictM isBsonObject().containing("flatDoc.stringValue", "conflux")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarting() { FlatDocument probe = new FlatDocument(); @@ -224,10 +197,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsStarti assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMatchModeIsStarting() { FlatDocument probe = new FlatDocument(); @@ -243,10 +213,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeContainingDotsWhenStringMat assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding() { FlatDocument probe = new FlatDocument(); @@ -262,10 +229,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeIsEnding assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() { FlatDocument probe = new FlatDocument(); @@ -281,10 +245,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenStringMatchModeRegex() assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMatchModeSet() { FlatDocument probe = new FlatDocument(); @@ -300,10 +261,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabledAndMat assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() { FlatDocument probe = new FlatDocument(); @@ -319,10 +277,7 @@ public void exampleShouldBeMappedCorrectlyForFlatTypeWhenIgnoreCaseEnabled() { assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedWhenContainingDBRef() { FlatDocument probe = new FlatDocument(); @@ -337,10 +292,7 @@ public void exampleShouldBeMappedWhenContainingDBRef() { assertThat(reference.getCollectionName(), is("refDoc")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedWhenDBRefIsNull() { FlatDocument probe = new FlatDocument(); @@ -351,10 +303,7 @@ public void exampleShouldBeMappedWhenDBRefIsNull() { assertThat(dbo, isBsonObject().containing("stringValue", "steelheart")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { ClassWithGeoTypes probe = new ClassWithGeoTypes(); @@ -366,10 +315,7 @@ public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { assertThat(dbo.get("legacyPoint.y"), Is.is(20D)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldExcludeFieldWithCustomNameCorrectly() { FlatDocument probe = new FlatDocument(); @@ -386,10 +332,7 @@ public void mappingShouldExcludeFieldWithCustomNameCorrectly() { assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldExcludeFieldCorrectly() { FlatDocument probe = new FlatDocument(); @@ -406,10 +349,7 @@ public void mappingShouldExcludeFieldCorrectly() { assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldExcludeNestedFieldCorrectly() { WrapperDocument probe = new WrapperDocument(); @@ -427,10 +367,7 @@ public void mappingShouldExcludeNestedFieldCorrectly() { assertThat(mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() { WrapperDocument probe = new WrapperDocument(); @@ -448,10 +385,7 @@ public void mappingShouldExcludeNestedFieldWithCustomNameCorrectly() { assertThat(mapper.getMappedExample(example, context.getPersistentEntity(WrapperDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMatcher() { FlatDocument probe = new FlatDocument(); @@ -467,10 +401,7 @@ public void mappingShouldFavorFieldSpecificationStringMatcherOverDefaultStringMa assertThat(mapper.getMappedExample(example, context.getPersistentEntity(FlatDocument.class)), is(expected)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void mappingShouldIncludePropertiesFromHierarchicalDocument() { HierachicalDocument probe = new HierachicalDocument(); @@ -483,10 +414,7 @@ public void mappingShouldIncludePropertiesFromHierarchicalDocument() { assertThat(dbo, isBsonObject().containing("anotherStringValue", "calamity")); } - /** - * @see DATAMONGO-1459 - */ - @Test + @Test // DATAMONGO-1459 public void mapsAnyMatchingExampleCorrectly() { FlatDocument probe = new FlatDocument(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NamedMongoScriptConvertsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NamedMongoScriptConvertsUnitTests.java index c7acd2f0fa..1dedb52eca 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NamedMongoScriptConvertsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NamedMongoScriptConvertsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 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. @@ -58,18 +58,12 @@ public static class NamedMongoScriptToDboConverterUnitTests { NamedMongoScriptToDBObjectConverter converter = NamedMongoScriptToDBObjectConverter.INSTANCE; - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldReturnEmptyDboWhenScriptIsNull() { assertThat(converter.convert(null), is((DBObject) new BasicDBObject())); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldConvertScriptNameCorreclty() { DBObject dbo = converter.convert(ECHO_SCRIPT); @@ -79,10 +73,7 @@ public void convertShouldConvertScriptNameCorreclty() { assertThat(id, is((Object) FUNCTION_NAME)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldConvertScriptCodeCorreclty() { DBObject dbo = converter.convert(ECHO_SCRIPT); @@ -100,18 +91,12 @@ public static class DboToNamedMongoScriptConverterUnitTests { DBObjectToNamedMongoScriptCoverter converter = DBObjectToNamedMongoScriptCoverter.INSTANCE; - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldReturnNullIfSourceIsNull() { assertThat(converter.convert(null), is(nullValue())); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldConvertIdCorreclty() { NamedMongoScript script = converter.convert(FUNCTION); @@ -119,10 +104,7 @@ public void convertShouldConvertIdCorreclty() { assertThat(script.getName(), is(FUNCTION_NAME)); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void convertShouldConvertScriptValueCorreclty() { NamedMongoScript script = converter.convert(FUNCTION); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NumberToNumberConverterFactoryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NumberToNumberConverterFactoryUnitTests.java index efce959f5e..8b7c702458 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NumberToNumberConverterFactoryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/NumberToNumberConverterFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -52,10 +52,7 @@ public static Collection parameters() { return Arrays. asList(longToInt, atomicIntToInt, atomicIntToDouble, atomicLongToInt, atomicLongToLong); } - /** - * @see DATAMONGO-1288 - */ - @Test + @Test // DATAMONGO-1288 public void convertsToTargetTypeCorrectly() { assertThat(NumberToNumberConverterFactory.INSTANCE.getConverter(expected.getClass()).convert(source), is(expected)); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index 585ed114bd..0b515a3a64 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -128,10 +128,7 @@ public void handlesObjectIdCapableBigIntegerIdsCorrectly() { assertThat(result.get("_id"), is((Object) id)); } - /** - * @see DATAMONGO-278 - */ - @Test + @Test // DATAMONGO-278 public void translates$NeCorrectly() { Criteria criteria = where("foo").ne(new ObjectId().toString()); @@ -143,10 +140,7 @@ public void handlesObjectIdCapableBigIntegerIdsCorrectly() { assertThat(dbObject.get("$ne"), is(instanceOf(ObjectId.class))); } - /** - * @see DATAMONGO-326 - */ - @Test + @Test // DATAMONGO-326 public void handlesEnumsCorrectly() { Query query = query(where("foo").is(Enum.INSTANCE)); DBObject result = mapper.getMappedObject(query.getQueryObject(), null); @@ -186,20 +180,14 @@ public void handlesEnumsInNotEqualCorrectly() { assertThat(list.get(0).toString(), is(Enum.INSTANCE.name())); } - /** - * @see DATAMONGO-373 - */ - @Test + @Test // DATAMONGO-373 public void handlesNativelyBuiltQueryCorrectly() { DBObject query = new QueryBuilder().or(new BasicDBObject("foo", "bar")).get(); mapper.getMappedObject(query, null); } - /** - * @see DATAMONGO-369 - */ - @Test + @Test // DATAMONGO-369 public void handlesAllPropertiesIfDBObject() { DBObject query = new BasicDBObject(); @@ -210,10 +198,7 @@ public void handlesAllPropertiesIfDBObject() { assertThat(result.get("bar"), is(notNullValue())); } - /** - * @see DATAMONGO-429 - */ - @Test + @Test // DATAMONGO-429 public void transformsArraysCorrectly() { Query query = new BasicQuery("{ 'tags' : { '$all' : [ 'green', 'orange']}}"); @@ -235,10 +220,7 @@ public void doesHandleNestedFieldsWithDefaultIdNames() { assertThat(((DBObject) result.get("nested")).get("_id"), is(instanceOf(ObjectId.class))); } - /** - * @see DATAMONGO-493 - */ - @Test + @Test // DATAMONGO-493 public void doesNotTranslateNonIdPropertiesFor$NeCriteria() { ObjectId accidentallyAnObjectId = new ObjectId(); @@ -254,10 +236,7 @@ public void doesHandleNestedFieldsWithDefaultIdNames() { assertThat(publishers.get("$ne"), is(instanceOf(String.class))); } - /** - * @see DATAMONGO-494 - */ - @Test + @Test // DATAMONGO-494 public void usesEntityMetadataInOr() { Query query = query(new Criteria().orOperator(where("foo").is("bar"))); @@ -356,10 +335,7 @@ public void convertsInKeywordCorrectly() { assertThat(inClause.get(1), is(instanceOf(com.mongodb.DBRef.class))); } - /** - * @see DATAMONGO-570 - */ - @Test + @Test // DATAMONGO-570 public void correctlyConvertsNullReference() { Query query = query(where("reference").is(null)); @@ -368,10 +344,7 @@ public void correctlyConvertsNullReference() { assertThat(object.get("reference"), is(nullValue())); } - /** - * @see DATAMONGO-629 - */ - @Test + @Test // DATAMONGO-629 public void doesNotMapIdIfNoEntityMetadataAvailable() { String id = new ObjectId().toString(); @@ -384,10 +357,7 @@ public void doesNotMapIdIfNoEntityMetadataAvailable() { assertThat(object.containsField("_id"), is(false)); } - /** - * @see DATAMONGO-677 - */ - @Test + @Test // DATAMONGO-677 public void handleMapWithDBRefCorrectly() { DBObject mapDbObject = new BasicDBObject(); @@ -413,10 +383,7 @@ public void convertsUnderscoreIdValueWithoutMetadata() { assertThat(mapped.get("_id"), is(instanceOf(ObjectId.class))); } - /** - * @see DATAMONGO-705 - */ - @Test + @Test // DATAMONGO-705 public void convertsDBRefWithExistsQuery() { Query query = query(where("reference").exists(false)); @@ -429,10 +396,7 @@ public void convertsDBRefWithExistsQuery() { assertThat(reference.get("$exists"), is((Object) false)); } - /** - * @see DATAMONGO-706 - */ - @Test + @Test // DATAMONGO-706 public void convertsNestedDBRefsCorrectly() { Reference reference = new Reference(); @@ -453,10 +417,7 @@ public void convertsNestedDBRefsCorrectly() { assertThat(inClause.get(0), is(instanceOf(com.mongodb.DBRef.class))); } - /** - * @see DATAMONGO-752 - */ - @Test + @Test // DATAMONGO-752 public void mapsSimpleValuesStartingWith$Correctly() { Query query = query(where("myvalue").is("$334")); @@ -467,10 +428,7 @@ public void convertsNestedDBRefsCorrectly() { assertThat(result.get("myvalue"), is((Object) "$334")); } - /** - * @see DATAMONGO-752 - */ - @Test + @Test // DATAMONGO-752 public void mapsKeywordAsSimpleValuesCorrectly() { Query query = query(where("myvalue").is("$center")); @@ -481,10 +439,7 @@ public void mapsKeywordAsSimpleValuesCorrectly() { assertThat(result.get("myvalue"), is((Object) "$center")); } - /** - * @DATAMONGO-805 - */ - @Test + @Test // DATAMONGO-805 public void shouldExcludeDBRefAssociation() { Query query = query(where("someString").is("foo")); @@ -498,10 +453,7 @@ public void shouldExcludeDBRefAssociation() { assertThat(fieldsResult.get("reference"), is((Object) 0)); } - /** - * @see DATAMONGO-686 - */ - @Test + @Test // DATAMONGO-686 public void queryMapperShouldNotChangeStateInGivenQueryObjectWhenIdConstrainedByInList() { BasicMongoPersistentEntity persistentEntity = context.getPersistentEntity(Sample.class); @@ -515,10 +467,7 @@ public void queryMapperShouldNotChangeStateInGivenQueryObjectWhenIdConstrainedBy assertThat(idValuesAfter, is(idValuesBefore)); } - /** - * @see DATAMONGO-821 - */ - @Test + @Test // DATAMONGO-821 public void queryMapperShouldNotTryToMapDBRefListPropertyIfNestedInsideDBObjectWithinDBObject() { DBObject queryObject = query( @@ -531,10 +480,7 @@ public void queryMapperShouldNotTryToMapDBRefListPropertyIfNestedInsideDBObjectW assertThat(nestedObject, is((DBObject) new BasicDBObject("$keys", 0L))); } - /** - * @see DATAMONGO-821 - */ - @Test + @Test // DATAMONGO-821 public void queryMapperShouldNotTryToMapDBRefPropertyIfNestedInsideDBObjectWithinDBObject() { DBObject queryObject = query(where("reference").is(new BasicDBObject("$nested", new BasicDBObject("$keys", 0L)))) @@ -547,10 +493,7 @@ public void queryMapperShouldNotTryToMapDBRefPropertyIfNestedInsideDBObjectWithi assertThat(nestedObject, is((DBObject) new BasicDBObject("$keys", 0L))); } - /** - * @see DATAMONGO-821 - */ - @Test + @Test // DATAMONGO-821 public void queryMapperShouldMapDBRefPropertyIfNestedInDBObject() { Reference sample = new Reference(); @@ -566,10 +509,7 @@ public void queryMapperShouldMapDBRefPropertyIfNestedInDBObject() { assertThat(inObject.get(0), is(instanceOf(com.mongodb.DBRef.class))); } - /** - * @see DATAMONGO-773 - */ - @Test + @Test // DATAMONGO-773 public void queryMapperShouldBeAbleToProcessQueriesThatIncludeDbRefFields() { BasicMongoPersistentEntity persistentEntity = context.getPersistentEntity(WithDBRef.class); @@ -581,10 +521,7 @@ public void queryMapperShouldBeAbleToProcessQueriesThatIncludeDbRefFields() { assertThat(mappedFields, is(notNullValue())); } - /** - * @see DATAMONGO-893 - */ - @Test + @Test // DATAMONGO-893 public void classInformationShouldNotBePresentInDBObjectUsedInFinderMethods() { EmbeddedClass embedded = new EmbeddedClass(); @@ -598,10 +535,7 @@ public void classInformationShouldNotBePresentInDBObjectUsedInFinderMethods() { assertThat(dbo.toString(), equalTo("{ \"embedded\" : { \"$in\" : [ { \"_id\" : \"1\"} , { \"_id\" : \"2\"}]}}")); } - /** - * @see DATAMONGO-1406 - */ - @Test + @Test // DATAMONGO-1406 public void shouldMapQueryForNestedCustomizedPropertiesUsingConfiguredFieldNames() { EmbeddedClass embeddedClass = new EmbeddedClass(); @@ -620,10 +554,7 @@ public void shouldMapQueryForNestedCustomizedPropertiesUsingConfiguredFieldNames new BasicDbListBuilder().add(new BasicDBObject("fancy_custom_name", embeddedClass.customizedField)).get())); } - /** - * @see DATAMONGO-647 - */ - @Test + @Test // DATAMONGO-647 public void customizedFieldNameShouldBeMappedCorrectlyWhenApplyingSort() { Query query = query(where("field").is("bar")).with(new Sort(Direction.DESC, "field")); @@ -631,10 +562,7 @@ public void customizedFieldNameShouldBeMappedCorrectlyWhenApplyingSort() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("foo", -1).get())); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void getMappedFieldsAppendsTextScoreFieldProperlyCorrectlyWhenNotPresent() { Query query = new Query(); @@ -645,10 +573,7 @@ public void getMappedFieldsAppendsTextScoreFieldProperlyCorrectlyWhenNotPresent( assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get())); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void getMappedFieldsReplacesTextScoreFieldProperlyCorrectlyWhenPresent() { Query query = new Query(); @@ -660,10 +585,7 @@ public void getMappedFieldsReplacesTextScoreFieldProperlyCorrectlyWhenPresent() assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get())); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void getMappedSortAppendsTextScoreProperlyWhenSortedByScore() { Query query = new Query().with(new Sort("textScore")); @@ -674,10 +596,7 @@ public void getMappedSortAppendsTextScoreProperlyWhenSortedByScore() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("score", new BasicDBObject("$meta", "textScore")).get())); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void getMappedSortIgnoresTextScoreWhenNotSortedByScore() { Query query = new Query().with(new Sort("id")); @@ -688,10 +607,7 @@ public void getMappedSortIgnoresTextScoreWhenNotSortedByScore() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("_id", 1).get())); } - /** - * @see DATAMONGO-1070 - */ - @Test + @Test // DATAMONGO-1070 public void mapsIdReferenceToDBRefCorrectly() { ObjectId id = new ObjectId(); @@ -704,10 +620,7 @@ public void mapsIdReferenceToDBRefCorrectly() { assertThat(reference.getId(), is(instanceOf(ObjectId.class))); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void shouldUseExplicitlySetFieldnameForIdPropertyCandidates() { Query query = query(where("nested.id").is("bar")); @@ -718,10 +631,7 @@ public void shouldUseExplicitlySetFieldnameForIdPropertyCandidates() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("nested.id", "bar").get())); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void shouldUseExplicitlySetFieldnameForIdPropertyCandidatesUsedInSortClause() { Query query = new Query().with(new Sort("nested.id")); @@ -732,10 +642,7 @@ public void shouldUseExplicitlySetFieldnameForIdPropertyCandidatesUsedInSortClau assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("nested.id", 1).get())); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearShouldUseGeoJsonRepresentationOnUnmappedProperty() { Query query = query(where("foo").near(new GeoJsonPoint(100, 50))); @@ -747,10 +654,7 @@ public void nearShouldUseGeoJsonRepresentationOnUnmappedProperty() { assertThat(dbo, isBsonObject().containing("foo.$near.$geometry.coordinates.[1]", 50D)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearShouldUseGeoJsonRepresentationWhenMappingToGoJsonType() { Query query = query(where("geoJsonPoint").near(new GeoJsonPoint(100, 50))); @@ -760,10 +664,7 @@ public void nearShouldUseGeoJsonRepresentationWhenMappingToGoJsonType() { assertThat(dbo, isBsonObject().containing("geoJsonPoint.$near.$geometry.type", "Point")); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearSphereShouldUseGeoJsonRepresentationWhenMappingToGoJsonType() { Query query = query(where("geoJsonPoint").nearSphere(new GeoJsonPoint(100, 50))); @@ -773,10 +674,7 @@ public void nearSphereShouldUseGeoJsonRepresentationWhenMappingToGoJsonType() { assertThat(dbo, isBsonObject().containing("geoJsonPoint.$nearSphere.$geometry.type", "Point")); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void shouldMapNameCorrectlyForGeoJsonType() { Query query = query(where("namedGeoJsonPoint").nearSphere(new GeoJsonPoint(100, 50))); @@ -787,10 +685,7 @@ public void shouldMapNameCorrectlyForGeoJsonType() { isBsonObject().containing("geoJsonPointWithNameViaFieldAnnotation.$nearSphere.$geometry.type", "Point")); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void withinShouldUseGeoJsonPolygonWhenMappingPolygonOn2DSphereIndex() { Query query = query(where("geoJsonPoint") @@ -801,10 +696,7 @@ public void withinShouldUseGeoJsonPolygonWhenMappingPolygonOn2DSphereIndex() { assertThat(dbo, isBsonObject().containing("geoJsonPoint.$geoWithin.$geometry.type", "Polygon")); } - /** - * @see DATAMONGO-1134 - */ - @Test + @Test // DATAMONGO-1134 public void intersectsShouldUseGeoJsonRepresentationCorrectly() { Query query = query(where("geoJsonPoint") @@ -816,11 +708,7 @@ public void intersectsShouldUseGeoJsonRepresentationCorrectly() { assertThat(dbo, isBsonObject().containing("geoJsonPoint.$geoIntersects.$geometry.coordinates")); } - /** - * - * @see DATAMONGO-1269 - */ - @Test + @Test // DATAMONGO-1269 public void mappingShouldRetainNumericMapKey() { Query query = query(where("map.1.stringProperty").is("ba'alzamon")); @@ -831,10 +719,7 @@ public void mappingShouldRetainNumericMapKey() { assertThat(dbo.containsField("map.1.stringProperty"), is(true)); } - /** - * @see DATAMONGO-1269 - */ - @Test + @Test // DATAMONGO-1269 public void mappingShouldRetainNumericPositionInList() { Query query = query(where("list.1.stringProperty").is("ba'alzamon")); @@ -845,10 +730,7 @@ public void mappingShouldRetainNumericPositionInList() { assertThat(dbo.containsField("list.1.stringProperty"), is(true)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectly() { Foo probe = new Foo(); @@ -862,10 +744,7 @@ public void exampleShouldBeMappedCorrectly() { assertThat(dbo, isBsonObject().containing("embedded\\._id", "conflux")); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void exampleShouldBeMappedCorrectlyWhenContainingLegacyPoint() { ClassWithGeoTypes probe = new ClassWithGeoTypes(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReflectiveDBRefResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReflectiveDBRefResolverUnitTests.java index 01b105540f..7d0c65d8c4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReflectiveDBRefResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ReflectiveDBRefResolverUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -58,10 +58,7 @@ public void setUp() { when(collectionMock.findOne(eq("id-1"))).thenReturn(new BasicDBObject("_id", "id-1")); } - /** - * @see DATAMONGO-1193 - */ - @Test + @Test // DATAMONGO-1193 public void fetchShouldNotLookUpDbWhenUsingDriverVersion2() { assumeThat(isMongo3Driver(), is(false)); @@ -72,10 +69,7 @@ public void fetchShouldNotLookUpDbWhenUsingDriverVersion2() { verify(dbFactoryMock, never()).getDb(anyString()); } - /** - * @see DATAMONGO-1193 - */ - @Test + @Test // DATAMONGO-1193 public void fetchShouldUseDbToResolveDbRefWhenUsingDriverVersion3() { assumeThat(isMongo3Driver(), is(true)); @@ -84,10 +78,7 @@ public void fetchShouldUseDbToResolveDbRefWhenUsingDriverVersion3() { verify(dbFactoryMock, times(1)).getDb(); } - /** - * @see DATAMONGO-1193 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1193 public void fetchShouldThrowExceptionWhenDbFactoryIsNullUsingDriverVersion3() { assumeThat(isMongo3Driver(), is(true)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java index c7971154df..0ce0ca4b6b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/TermToStringConverterUnitTests.java @@ -29,18 +29,12 @@ */ public class TermToStringConverterUnitTests { - /** - * @DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldNotConvertNull() { assertThat(TermToStringConverter.INSTANCE.convert(null), nullValue()); } - /** - * @DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldUseFormattedRepresentationForConversion() { Term term = spy(new Term("foo", Type.WORD)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java index adcfcb2cfc..189ec4095c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -100,10 +100,7 @@ public void setUp() { this.mapper = new UpdateMapper(converter); } - /** - * @see DATAMONGO-721 - */ - @Test + @Test // DATAMONGO-721 public void updateMapperRetainsTypeInformationForCollectionField() { Update update = new Update().push("list", new ConcreteChildClass("2", "BAR")); @@ -117,10 +114,7 @@ public void updateMapperRetainsTypeInformationForCollectionField() { assertThat(list.get("_class"), is((Object) ConcreteChildClass.class.getName())); } - /** - * @see DATAMONGO-807 - */ - @Test + @Test // DATAMONGO-807 public void updateMapperShouldRetainTypeInformationForNestedEntities() { Update update = Update.update("model", new ModelImpl(1)); @@ -134,10 +128,7 @@ public void updateMapperShouldRetainTypeInformationForNestedEntities() { assertThat(modelDbObject.get("_class"), not(nullValue())); } - /** - * @see DATAMONGO-807 - */ - @Test + @Test // DATAMONGO-807 public void updateMapperShouldNotPersistTypeInformationForKnownSimpleTypes() { Update update = Update.update("model.value", 1); @@ -150,10 +141,7 @@ public void updateMapperShouldNotPersistTypeInformationForKnownSimpleTypes() { assertThat(set.get("_class"), nullValue()); } - /** - * @see DATAMONGO-807 - */ - @Test + @Test // DATAMONGO-807 public void updateMapperShouldNotPersistTypeInformationForNullValues() { Update update = Update.update("model", null); @@ -166,10 +154,7 @@ public void updateMapperShouldNotPersistTypeInformationForNullValues() { assertThat(set.get("_class"), nullValue()); } - /** - * @see DATAMONGO-407 - */ - @Test + @Test // DATAMONGO-407 public void updateMapperShouldRetainTypeInformationForNestedCollectionElements() { Update update = Update.update("list.$", new ConcreteChildClass("42", "bubu")); @@ -183,10 +168,7 @@ public void updateMapperShouldRetainTypeInformationForNestedCollectionElements() assertThat(modelDbObject.get("_class"), is((Object) ConcreteChildClass.class.getName())); } - /** - * @see DATAMONGO-407 - */ - @Test + @Test // DATAMONGO-407 public void updateMapperShouldSupportNestedCollectionElementUpdates() { Update update = Update.update("list.$.value", "foo").set("list.$.otherValue", "bar"); @@ -200,10 +182,7 @@ public void updateMapperShouldSupportNestedCollectionElementUpdates() { assertThat(set.get("aliased.$.otherValue"), is((Object) "bar")); } - /** - * @see DATAMONGO-407 - */ - @Test + @Test // DATAMONGO-407 public void updateMapperShouldWriteTypeInformationForComplexNestedCollectionElementUpdates() { Update update = Update.update("list.$.value", "foo").set("list.$.someObject", new ConcreteChildClass("42", "bubu")); @@ -221,11 +200,8 @@ public void updateMapperShouldWriteTypeInformationForComplexNestedCollectionElem assertThat(someObject.get("value"), is((Object) "bubu")); } - /** - * @see DATAMONGO-812 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test + @Test // DATAMONGO-812 public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingSimpleTypes() { Update update = new Update().push("values").each("spring", "data", "mongodb"); @@ -241,10 +217,7 @@ public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingSimpleT assertThat(each.toMap(), (Matcher) allOf(hasValue("spring"), hasValue("data"), hasValue("mongodb"))); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void updateMapperShouldConvertPushWhithoutAddingClassInformationWhenUsedWithEvery() { Update update = new Update().push("values").each("spring", "data", "mongodb"); @@ -257,11 +230,8 @@ public void updateMapperShouldConvertPushWhithoutAddingClassInformationWhenUsedW assertThat(values.get("_class"), nullValue()); } - /** - * @see DATAMONGO-812 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test + @Test // DATAMONGO-812 public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingCustomTypes() { Update update = new Update().push("models").each(new ListModel("spring", "data", "mongodb")); @@ -276,10 +246,7 @@ public void updateMapperShouldConvertPushCorrectlyWhenCalledWithEachUsingCustomT assertThat(values.toMap(), (Matcher) allOf(hasValue("spring"), hasValue("data"), hasValue("mongodb"))); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void updateMapperShouldRetainClassInformationForPushCorrectlyWhenCalledWithEachUsingCustomTypes() { Update update = new Update().push("models").each(new ListModel("spring", "data", "mongodb")); @@ -293,10 +260,7 @@ public void updateMapperShouldRetainClassInformationForPushCorrectlyWhenCalledWi assertThat(((DBObject) each.get(0)).get("_class").toString(), equalTo(ListModel.class.getName())); } - /** - * @see DATAMONGO-812 - */ - @Test + @Test // DATAMONGO-812 public void testUpdateShouldAllowMultiplePushEachForDifferentFields() { Update update = new Update().push("category").each("spring", "data").push("type").each("mongodb"); @@ -307,10 +271,7 @@ public void testUpdateShouldAllowMultiplePushEachForDifferentFields() { assertThat(getAsDBObject(push, "type").containsField("$each"), is(true)); } - /** - * @see DATAMONGO-943 - */ - @Test + @Test // DATAMONGO-943 public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositiveIndexParameter() { Update update = new Update().push("key").atPosition(2).each(Arrays.asList("Arya", "Arry", "Weasel")); @@ -325,10 +286,7 @@ public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositiveIndexParamete assertThat(getAsDBObject(push, "key").containsField("$each"), is(true)); } - /** - * @see DATAMONGO-943 - */ - @Test + @Test // DATAMONGO-943 public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionFirst() { Update update = new Update().push("key").atPosition(Position.FIRST).each(Arrays.asList("Arya", "Arry", "Weasel")); @@ -343,10 +301,7 @@ public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionFirst() { assertThat(getAsDBObject(push, "key").containsField("$each"), is(true)); } - /** - * @see DATAMONGO-943 - */ - @Test + @Test // DATAMONGO-943 public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionLast() { Update update = new Update().push("key").atPosition(Position.LAST).each(Arrays.asList("Arya", "Arry", "Weasel")); @@ -360,10 +315,7 @@ public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionLast() { assertThat(getAsDBObject(push, "key").containsField("$each"), is(true)); } - /** - * @see DATAMONGO-943 - */ - @Test + @Test // DATAMONGO-943 public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionNull() { Update update = new Update().push("key").atPosition(null).each(Arrays.asList("Arya", "Arry", "Weasel")); @@ -377,10 +329,7 @@ public void updatePushEachAtPositionWorksCorrectlyWhenGivenPositionNull() { assertThat(getAsDBObject(push, "key").containsField("$each"), is(true)); } - /** - * @see DATAMONGO-832 - */ - @Test + @Test // DATAMONGO-832 public void updatePushEachWithSliceShouldRenderCorrectly() { Update update = new Update().push("key").slice(5).each(Arrays.asList("Arya", "Arry", "Weasel")); @@ -395,10 +344,7 @@ public void updatePushEachWithSliceShouldRenderCorrectly() { assertThat(key.containsField("$each"), is(true)); } - /** - * @see DATAMONGO-832 - */ - @Test + @Test // DATAMONGO-832 public void updatePushEachWithSliceShouldRenderWhenUsingMultiplePushCorrectly() { Update update = new Update().push("key").slice(5).each(Arrays.asList("Arya", "Arry", "Weasel")).push("key-2") @@ -420,10 +366,7 @@ public void updatePushEachWithSliceShouldRenderWhenUsingMultiplePushCorrectly() assertThat(key2.containsField("$each"), is(true)); } - /** - * @see DATAMONGO-1141 - */ - @Test + @Test // DATAMONGO-1141 public void updatePushEachWithValueSortShouldRenderCorrectly() { Update update = new Update().push("scores").sort(Direction.DESC).each(42, 23, 68); @@ -439,10 +382,7 @@ public void updatePushEachWithValueSortShouldRenderCorrectly() { assertThat(key.containsField("$each"), is(true)); } - /** - * @see DATAMONGO-1141 - */ - @Test + @Test // DATAMONGO-1141 public void updatePushEachWithDocumentSortShouldRenderCorrectly() { Update update = new Update().push("list") @@ -461,10 +401,7 @@ public void updatePushEachWithDocumentSortShouldRenderCorrectly() { assertThat(key.containsField("$each"), is(true)); } - /** - * @see DATAMONGO-1141 - */ - @Test + @Test // DATAMONGO-1141 public void updatePushEachWithSortShouldRenderCorrectlyWhenUsingMultiplePush() { Update update = new Update().push("authors").sort(Direction.ASC).each("Harry").push("chapters") @@ -486,10 +423,7 @@ public void updatePushEachWithSortShouldRenderCorrectlyWhenUsingMultiplePush() { assertThat(key2.containsField("$each"), is(true)); } - /** - * @see DATAMONGO-410 - */ - @Test + @Test // DATAMONGO-410 public void testUpdateMapperShouldConsiderCustomWriteTarget() { List someValues = Arrays.asList(new NestedEntity("spring"), new NestedEntity("data"), @@ -502,10 +436,7 @@ public void testUpdateMapperShouldConsiderCustomWriteTarget() { verify(writingConverterSpy, times(3)).convert(Mockito.any(NestedEntity.class)); } - /** - * @see DATAMONGO-404 - */ - @Test + @Test // DATAMONGO-404 public void createsDbRefForEntityIdOnPulls() { Update update = new Update().pull("dbRefAnnotatedList.id", "2"); @@ -517,10 +448,7 @@ public void createsDbRefForEntityIdOnPulls() { assertThat(pullClause.get("dbRefAnnotatedList"), is((Object) new DBRef("entity", "2"))); } - /** - * @see DATAMONGO-404 - */ - @Test + @Test // DATAMONGO-404 public void createsDbRefForEntityOnPulls() { Entity entity = new Entity(); @@ -534,20 +462,14 @@ public void createsDbRefForEntityOnPulls() { assertThat(pullClause.get("dbRefAnnotatedList"), is((Object) new DBRef("entity", entity.id))); } - /** - * @see DATAMONGO-404 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-404 public void rejectsInvalidFieldReferenceForDbRef() { Update update = new Update().pull("dbRefAnnotatedList.name", "NAME"); mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(DocumentWithDBRefCollection.class)); } - /** - * @see DATAMONGO-404 - */ - @Test + @Test // DATAMONGO-404 public void rendersNestedDbRefCorrectly() { Update update = new Update().pull("nested.dbRefAnnotatedList.id", "2"); @@ -558,10 +480,7 @@ public void rendersNestedDbRefCorrectly() { assertThat(pullClause.containsField("mapped.dbRefAnnotatedList"), is(true)); } - /** - * @see DATAMONGO-468 - */ - @Test + @Test // DATAMONGO-468 public void rendersUpdateOfDbRefPropertyWithDomainObjectCorrectly() { Entity entity = new Entity(); @@ -575,10 +494,7 @@ public void rendersUpdateOfDbRefPropertyWithDomainObjectCorrectly() { assertThat(setClause.get("dbRefProperty"), is((Object) new DBRef("entity", entity.id))); } - /** - * @see DATAMONGO-862 - */ - @Test + @Test // DATAMONGO-862 public void rendersUpdateAndPreservesKeyForPathsNotPointingToProperty() { Update update = new Update().set("listOfInterface.$.value", "expected-value"); @@ -589,10 +505,7 @@ public void rendersUpdateAndPreservesKeyForPathsNotPointingToProperty() { assertThat(setClause.containsField("listOfInterface.$.value"), is(true)); } - /** - * @see DATAMONGO-863 - */ - @Test + @Test // DATAMONGO-863 public void doesNotConvertRawDbObjects() { Update update = new Update(); @@ -610,11 +523,8 @@ public void doesNotConvertRawDbObjects() { assertThat(inClause, IsIterableContainingInOrder. contains(1L, 2L)); } - /** - * @see DATAMONG0-471 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test + @Test // DATAMONG0-471 public void testUpdateShouldApply$addToSetCorrectlyWhenUsedWith$each() { Update update = new Update().addToSet("values").each("spring", "data", "mongodb"); @@ -628,10 +538,7 @@ public void doesNotConvertRawDbObjects() { assertThat(each.toMap(), (Matcher) allOf(hasValue("spring"), hasValue("data"), hasValue("mongodb"))); } - /** - * @see DATAMONG0-471 - */ - @Test + @Test // DATAMONG0-471 public void testUpdateShouldRetainClassTypeInformationWhenUsing$addToSetWith$eachForCustomTypes() { Update update = new Update().addToSet("models").each(new ModelImpl(2014), new ModelImpl(1), new ModelImpl(28)); @@ -649,10 +556,7 @@ public void doesNotConvertRawDbObjects() { } } - /** - * @see DATAMONGO-897 - */ - @Test + @Test // DATAMONGO-897 public void updateOnDbrefPropertyOfInterfaceTypeWithoutExplicitGetterForIdShouldBeMappedCorrectly() { Update update = new Update().set("referencedDocument", new InterfaceDocumentDefinitionImpl("1", "Foo")); @@ -666,10 +570,7 @@ public void updateOnDbrefPropertyOfInterfaceTypeWithoutExplicitGetterForIdShould assertThat(model, allOf(instanceOf(DBRef.class), IsEqual. equalTo(expectedDBRef))); } - /** - * @see DATAMONGO-847 - */ - @Test + @Test // DATAMONGO-847 public void updateMapperConvertsNestedQueryCorrectly() { Update update = new Update().pull("list", Query.query(Criteria.where("value").in("foo", "bar"))); @@ -684,10 +585,7 @@ public void updateMapperConvertsNestedQueryCorrectly() { assertThat($in, IsIterableContainingInOrder. contains("foo", "bar")); } - /** - * @see DATAMONGO-847 - */ - @Test + @Test // DATAMONGO-847 public void updateMapperConvertsPullWithNestedQuerfyOnDBRefCorrectly() { Update update = new Update().pull("dbRefAnnotatedList", Query.query(Criteria.where("id").is("1"))); @@ -700,10 +598,7 @@ public void updateMapperConvertsPullWithNestedQuerfyOnDBRefCorrectly() { assertThat(list, equalTo(new BasicDBObjectBuilder().add("_id", "1").get())); } - /** - * @see DATAMONGO-1077 - */ - @Test + @Test // DATAMONGO-1077 public void shouldNotRemovePositionalParameter() { Update update = new Update(); @@ -717,10 +612,7 @@ public void shouldNotRemovePositionalParameter() { assertThat($unset, equalTo(new BasicDBObjectBuilder().add("dbRefAnnotatedList.$", 1).get())); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void mappingEachOperatorShouldNotAddTypeInfoForNonInterfaceNonAbstractTypes() { Update update = new Update().addToSet("nestedDocs").each(new NestedDocument("nested-1"), @@ -733,10 +625,7 @@ public void mappingEachOperatorShouldNotAddTypeInfoForNonInterfaceNonAbstractTyp assertThat(mappedUpdate, isBsonObject().notContaining("$addToSet.nestedDocs.$each.[1]._class")); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void mappingEachOperatorShouldAddTypeHintForInterfaceTypes() { Update update = new Update().addToSet("models").each(new ModelImpl(1), new ModelImpl(2)); @@ -748,10 +637,7 @@ public void mappingEachOperatorShouldAddTypeHintForInterfaceTypes() { assertThat(mappedUpdate, isBsonObject().containing("$addToSet.models.$each.[1]._class", ModelImpl.class.getName())); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void mappingEachOperatorShouldAddTypeHintForAbstractTypes() { Update update = new Update().addToSet("list").each(new ConcreteChildClass("foo", "one"), @@ -766,10 +652,7 @@ public void mappingEachOperatorShouldAddTypeHintForAbstractTypes() { isBsonObject().containing("$addToSet.aliased.$each.[1]._class", ConcreteChildClass.class.getName())); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void mappingShouldOnlyRemoveTypeHintFromTopLevelTypeInCaseOfNestedDocument() { WrapperAroundInterfaceType wait = new WrapperAroundInterfaceType(); @@ -787,10 +670,7 @@ public void mappingShouldOnlyRemoveTypeHintFromTopLevelTypeInCaseOfNestedDocumen ModelImpl.class.getName())); } - /** - * @see DATAMONGO-1210 - */ - @Test + @Test // DATAMONGO-1210 public void mappingShouldRetainTypeInformationOfNestedListWhenUpdatingConcreteyParentType() { ListModelWrapper lmw = new ListModelWrapper(); @@ -805,10 +685,7 @@ public void mappingShouldRetainTypeInformationOfNestedListWhenUpdatingConcreteyP .containing("$set.concreteTypeWithListAttributeOfInterfaceType.models.[0]._class", ModelImpl.class.getName())); } - /** - * @see DATAMONGO-1236 - */ - @Test + @Test // DATAMONGO-1236 public void mappingShouldRetainTypeInformationForObjectValues() { Update update = new Update().set("value", new NestedDocument("kaladin")); @@ -819,10 +696,7 @@ public void mappingShouldRetainTypeInformationForObjectValues() { assertThat(mappedUpdate, isBsonObject().containing("$set.value._class", NestedDocument.class.getName())); } - /** - * @see DATAMONGO-1236 - */ - @Test + @Test // DATAMONGO-1236 public void mappingShouldNotRetainTypeInformationForConcreteValues() { Update update = new Update().set("concreteValue", new NestedDocument("shallan")); @@ -833,10 +707,7 @@ public void mappingShouldNotRetainTypeInformationForConcreteValues() { assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteValue._class")); } - /** - * @see DATAMONGO-1236 - */ - @Test + @Test // DATAMONGO-1236 public void mappingShouldRetainTypeInformationForObjectValuesWithAlias() { Update update = new Update().set("value", new NestedDocument("adolin")); @@ -847,10 +718,7 @@ public void mappingShouldRetainTypeInformationForObjectValuesWithAlias() { assertThat(mappedUpdate, isBsonObject().containing("$set.renamed-value._class", NestedDocument.class.getName())); } - /** - * @see DATAMONGO-1236 - */ - @Test + @Test // DATAMONGO-1236 public void mappingShouldRetrainTypeInformationWhenValueTypeOfMapDoesNotMatchItsDeclaration() { Map map = Collections. singletonMap("szeth", new NestedDocument("son-son-vallano")); @@ -863,10 +731,7 @@ public void mappingShouldRetrainTypeInformationWhenValueTypeOfMapDoesNotMatchIts assertThat(mappedUpdate, isBsonObject().containing("$set.map.szeth._class", NestedDocument.class.getName())); } - /** - * @see DATAMONGO-1236 - */ - @Test + @Test // DATAMONGO-1236 public void mappingShouldNotContainTypeInformationWhenValueTypeOfMapMatchesDeclaration() { Map map = Collections. singletonMap("jasnah", @@ -880,10 +745,7 @@ public void mappingShouldNotContainTypeInformationWhenValueTypeOfMapMatchesDecla assertThat(mappedUpdate, isBsonObject().notContaining("$set.concreteMap.jasnah._class")); } - /** - * @see DATAMONGO-1250 - */ - @Test + @Test // DATAMONGO-1250 @SuppressWarnings("unchecked") public void mapsUpdateWithBothReadingAndWritingConverterRegistered() { @@ -907,10 +769,7 @@ public void mapsUpdateWithBothReadingAndWritingConverterRegistered() { assertThat(result, isBsonObject().containing("$set.allocation", Allocation.AVAILABLE.code)); } - /** - * @see DATAMONGO-1251 - */ - @Test + @Test // DATAMONGO-1251 public void mapsNullValueCorrectlyForSimpleTypes() { Update update = new Update().set("value", null); @@ -923,10 +782,7 @@ public void mapsNullValueCorrectlyForSimpleTypes() { assertThat($set.get("value"), nullValue()); } - /** - * @see DATAMONGO-1251 - */ - @Test + @Test // DATAMONGO-1251 public void mapsNullValueCorrectlyForJava8Date() { Update update = new Update().set("date", null); @@ -939,10 +795,7 @@ public void mapsNullValueCorrectlyForJava8Date() { assertThat($set.get("value"), nullValue()); } - /** - * @see DATAMONGO-1251 - */ - @Test + @Test // DATAMONGO-1251 public void mapsNullValueCorrectlyForCollectionTypes() { Update update = new Update().set("values", null); @@ -955,10 +808,7 @@ public void mapsNullValueCorrectlyForCollectionTypes() { assertThat($set.get("value"), nullValue()); } - /** - * @see DATAMONGO-1251 - */ - @Test + @Test // DATAMONGO-1251 public void mapsNullValueCorrectlyForPropertyOfNestedDocument() { Update update = new Update().set("concreteValue.name", null); @@ -971,10 +821,7 @@ public void mapsNullValueCorrectlyForPropertyOfNestedDocument() { assertThat($set.get("concreteValue.name"), nullValue()); } - /** - * @see DATAMONGO-1288 - */ - @Test + @Test // DATAMONGO-1288 public void mapsAtomicIntegerToIntegerCorrectly() { Update update = new Update().set("intValue", new AtomicInteger(10)); @@ -985,10 +832,7 @@ public void mapsAtomicIntegerToIntegerCorrectly() { assertThat($set.get("intValue"), Is. is(10)); } - /** - * @see DATAMONGO-1288 - */ - @Test + @Test // DATAMONGO-1288 public void mapsAtomicIntegerToPrimitiveIntegerCorrectly() { Update update = new Update().set("primIntValue", new AtomicInteger(10)); @@ -999,10 +843,7 @@ public void mapsAtomicIntegerToPrimitiveIntegerCorrectly() { assertThat($set.get("primIntValue"), Is. is(10)); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void mapsMinCorrectly() { Update update = new Update().min("minfield", 10); @@ -1012,10 +853,7 @@ public void mapsMinCorrectly() { assertThat(mappedUpdate, isBsonObject().containing("$min", new BasicDBObject("minfield", 10))); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void mapsMaxCorrectly() { Update update = new Update().max("maxfield", 999); @@ -1025,10 +863,7 @@ public void mapsMaxCorrectly() { assertThat(mappedUpdate, isBsonObject().containing("$max", new BasicDBObject("maxfield", 999))); } - /** - * @see DATAMONGO-1423 - */ - @Test + @Test // DATAMONGO-1423 @SuppressWarnings("unchecked") public void mappingShouldConsiderCustomConvertersForEnumMapKeys() { @@ -1052,10 +887,7 @@ public void mappingShouldConsiderCustomConvertersForEnumMapKeys() { assertThat(result, isBsonObject().containing("$set.enumAsMapKey.V", 100)); } - /** - * @see DATAMONGO-1486 - */ - @Test + @Test // DATAMONGO-1486 public void mappingShouldConvertMapKeysToString() { Update update = new Update().set("map", Collections.singletonMap(25, "#StarTrek50")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java index 9b756bf01b..59ef1c4855 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/AbstractGeoSpatialTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -175,10 +175,7 @@ public void nearSphere() { assertThat(venues.size(), is(11)); } - /** - * @see DATAMONGO-1360 - */ - @Test + @Test // DATAMONGO-1360 public void mapsQueryContainedInNearQuery() { Query query = query(where("openingDate").lt(LocalDate.now())); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java index 97b282317d..6950c5c601 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonModuleUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -43,10 +43,7 @@ public void setUp() { mapper.registerModule(new GeoJsonModule()); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void shouldDeserializeJsonPointCorrectly() throws JsonParseException, JsonMappingException, IOException { String json = "{ \"type\": \"Point\", \"coordinates\": [10.0, 20.0] }"; @@ -54,10 +51,7 @@ public void shouldDeserializeJsonPointCorrectly() throws JsonParseException, Jso assertThat(mapper.readValue(json, GeoJsonPoint.class), is(new GeoJsonPoint(10D, 20D))); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void shouldDeserializeGeoJsonLineStringCorrectly() throws JsonParseException, JsonMappingException, IOException { @@ -67,10 +61,7 @@ public void shouldDeserializeGeoJsonLineStringCorrectly() throws JsonParseExcept is(new GeoJsonLineString(Arrays.asList(new Point(10, 20), new Point(30, 40), new Point(50, 60))))); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void shouldDeserializeGeoJsonMultiPointCorrectly() throws JsonParseException, JsonMappingException, IOException { @@ -80,10 +71,7 @@ public void shouldDeserializeGeoJsonMultiPointCorrectly() throws JsonParseExcept is(new GeoJsonMultiPoint(Arrays.asList(new Point(10, 20), new Point(30, 40), new Point(50, 60))))); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 @SuppressWarnings("unchecked") public void shouldDeserializeGeoJsonMultiLineStringCorrectly() throws JsonParseException, JsonMappingException, IOException { @@ -96,10 +84,7 @@ public void shouldDeserializeGeoJsonMultiLineStringCorrectly() throws JsonParseE 60), new Point(70, 80))))); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void shouldDeserializeGeoJsonPolygonCorrectly() throws JsonParseException, JsonMappingException, IOException { String json = "{ \"type\": \"Polygon\", \"coordinates\": [ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ] ]}"; @@ -110,10 +95,7 @@ public void shouldDeserializeGeoJsonPolygonCorrectly() throws JsonParseException new Point(100, 0))))); } - /** - * @see DATAMONGO-1181 - */ - @Test + @Test // DATAMONGO-1181 public void shouldDeserializeGeoJsonMultiPolygonCorrectly() throws JsonParseException, JsonMappingException, IOException { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java index c94172774f..9a8e79e958 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoJsonTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -95,10 +95,7 @@ public void tearDown() { removeCollections(); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void geoNear() { NearQuery geoNear = NearQuery.near(new GeoJsonPoint(-73, 40), Metrics.KILOMETERS).num(10).maxDistance(150); @@ -109,10 +106,7 @@ public void geoNear() { assertThat(result.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void withinPolygon() { Point first = new Point(-73.99756, 40.73083); @@ -126,10 +120,7 @@ public void withinPolygon() { assertThat(venues.size(), is(4)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearPoint() { GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868); @@ -139,10 +130,7 @@ public void nearPoint() { assertThat(venues.size(), is(1)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearSphere() { GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868); @@ -153,10 +141,7 @@ public void nearSphere() { assertThat(venues.size(), is(1)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonPointTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -171,10 +156,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonPointTypeCorrectly() { assertThat(result.geoJsonPoint, equalTo(obj.geoJsonPoint)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonPolygonTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -190,10 +172,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonPolygonTypeCorrectly() { assertThat(result.geoJsonPolygon, equalTo(obj.geoJsonPolygon)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonLineStringTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -208,10 +187,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonLineStringTypeCorrectly() { assertThat(result.geoJsonLineString, equalTo(obj.geoJsonLineString)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiLineStringTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -227,10 +203,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiLineStringTypeCorrectly assertThat(result.geoJsonMultiLineString, equalTo(obj.geoJsonMultiLineString)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiPointTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -245,10 +218,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiPointTypeCorrectly() { assertThat(result.geoJsonMultiPoint, equalTo(obj.geoJsonMultiPoint)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiPolygonTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -264,10 +234,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonMultiPolygonTypeCorrectly() assertThat(result.geoJsonMultiPolygon, equalTo(obj.geoJsonMultiPolygon)); } - /** - * @see DATAMONGO-1137 - */ - @Test + @Test // DATAMONGO-1137 public void shouleSaveAndRetrieveDocumentWithGeoJsonGeometryCollectionTypeCorrectly() { DocumentWithPropertyUsingGeoJsonType obj = new DocumentWithPropertyUsingGeoJsonType(); @@ -284,10 +251,7 @@ public void shouleSaveAndRetrieveDocumentWithGeoJsonGeometryCollectionTypeCorrec assertThat(result.geoJsonGeometryCollection, equalTo(obj.geoJsonGeometryCollection)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void nearWithMinDistance() { Point point = new GeoJsonPoint(-73.99171, 40.738868); @@ -297,10 +261,7 @@ public void nearWithMinDistance() { assertThat(venues.size(), is(11)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void nearSphereWithMinDistance() { Point point = new GeoJsonPoint(-73.99171, 40.738868); @@ -310,10 +271,7 @@ public void nearSphereWithMinDistance() { assertThat(venues.size(), is(11)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void nearWithMinAndMaxDistance() { GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868); @@ -323,10 +281,7 @@ public void nearWithMinAndMaxDistance() { assertThat(venues.size(), is(2)); } - /** - * @see DATAMONGO-1453 - */ - @Test + @Test // DATAMONGO-1453 public void shouldConvertPointRepresentationCorrectlyWhenSourceCoordinatesUsesInteger() { this.template.execute(template.getCollectionName(DocumentWithPropertyUsingGeoJsonType.class), @@ -351,10 +306,7 @@ public Object doInCollection(DBCollection collection) throws MongoException, Dat DocumentWithPropertyUsingGeoJsonType.class).geoJsonPoint, is(equalTo(new GeoJsonPoint(0D, 0D)))); } - /** - * @see DATAMONGO-1453 - */ - @Test + @Test // DATAMONGO-1453 public void shouldConvertLineStringRepresentationCorrectlyWhenSourceCoordinatesUsesInteger() { this.template.execute(template.getCollectionName(DocumentWithPropertyUsingGeoJsonType.class), diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java index a51bd46b97..a069b183c8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DSphereTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 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. @@ -42,10 +42,7 @@ */ public class GeoSpatial2DSphereTests extends AbstractGeoSpatialTests { - /** - * @see DATAMONGO-360 - */ - @Test + @Test // DATAMONGO-360 public void indexInfoIsCorrect() { IndexOperations operations = template.indexOps(Venue.class); @@ -62,10 +59,7 @@ public void indexInfoIsCorrect() { assertThat(fields, hasItem(IndexField.geo("location"))); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void geoNearWithMinDistance() { NearQuery geoNear = NearQuery.near(-73, 40, Metrics.KILOMETERS).num(10).minDistance(1); @@ -76,10 +70,7 @@ public void geoNearWithMinDistance() { assertThat(result.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void nearSphereWithMinDistance() { Point point = new Point(-73.99171, 40.738868); List venues = template.find(query(where("location").nearSphere(point).minDistance(0.01)), Venue.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java index e9248f63b0..896871b34f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatial2DTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 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. @@ -50,10 +50,7 @@ public void nearPoint() { assertThat(venues.size(), is(7)); } - /** - * @see DATAMONGO-360 - */ - @Test + @Test // DATAMONGO-360 public void indexInfoIsCorrect() { IndexOperations operations = template.indexOps(Venue.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java index 1c2f0a6832..7341317d46 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/geo/GeoSpatialIndexTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2017 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. @@ -60,10 +60,7 @@ public void setUp() { template.setWriteResultChecking(WriteResultChecking.EXCEPTION); } - /** - * @see DATAMONGO-778 - */ - @Test + @Test // DATAMONGO-778 public void test2dIndex() { try { @@ -74,10 +71,7 @@ public void test2dIndex() { } } - /** - * @see DATAMONGO-778 - */ - @Test + @Test // DATAMONGO-778 public void test2dSphereIndex() { try { @@ -88,10 +82,7 @@ public void test2dSphereIndex() { } } - /** - * @see DATAMONGO-778 - */ - @Test + @Test // DATAMONGO-778 public void testHaystackIndex() { try { @@ -102,10 +93,7 @@ public void testHaystackIndex() { } } - /** - * @see DATAMONGO-827 - */ - @Test + @Test // DATAMONGO-827 public void useGeneratedNameShouldGenerateAnIndexName() { try { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java index 2b748fc8d9..abc4d6c6b7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,10 +62,7 @@ public void tearDown() { operations.dropCollection(IndexedPerson.class); } - /** - * @see DATAMONGO-237 - */ - @Test + @Test // DATAMONGO-237 @DirtiesContext public void createsIndexWithFieldName() { @@ -74,10 +71,7 @@ public void createsIndexWithFieldName() { assertThat(hasIndex("_firstname", IndexedPerson.class), is(true)); } - /** - * @see DATAMONGO-1163 - */ - @Test + @Test // DATAMONGO-1163 @DirtiesContext public void createsIndexFromMetaAnnotation() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java index 529828fe41..7ac592c1b9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 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. @@ -83,10 +83,7 @@ public void createsIndexForConfiguredMappingContextOnly() { assertThat(indexInfo, hasSize(0)); } - /** - * @see DATAMONGO-1202 - */ - @Test + @Test // DATAMONGO-1202 public void shouldHonorIndexedPropertiesWithRecursiveMappings() { List indexInfo = templateOne.indexOps(RecursiveConcreteType.class).getIndexInfo(); @@ -95,10 +92,7 @@ public void shouldHonorIndexedPropertiesWithRecursiveMappings() { assertThat(indexInfo, Matchers. hasItem(hasProperty("name", is("firstName")))); } - /** - * @DATAMONGO-1125 - */ - @Test + @Test // DATAMONGO-1125 public void createIndexShouldThrowMeaningfulExceptionWhenIndexCreationFails() throws UnknownHostException { expectedException.expect(DataIntegrityViolationException.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java index 093286aab7..2013e49aeb 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 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. @@ -113,10 +113,7 @@ public void doesNotCreateIndexForEntityComingFromDifferentMappingContext() { verifyZeroInteractions(collection); } - /** - * @see DATAMONGO-530 - */ - @Test + @Test // DATAMONGO-530 public void isIndexCreatorForMappingContextHandedIntoConstructor() { MongoMappingContext mappingContext = new MongoMappingContext(); @@ -127,10 +124,7 @@ public void isIndexCreatorForMappingContextHandedIntoConstructor() { assertThat(creator.isIndexCreatorFor(new MongoMappingContext()), is(false)); } - /** - * @see DATAMONGO-554 - */ - @Test + @Test // DATAMONGO-554 public void triggersBackgroundIndexingIfConfigured() { MongoMappingContext mappingContext = prepareMappingContext(AnotherPerson.class); @@ -143,10 +137,7 @@ public void triggersBackgroundIndexingIfConfigured() { assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), nullValue()); } - /** - * @see DATAMONGO-544 - */ - @Test + @Test // DATAMONGO-544 public void expireAfterSecondsIfConfigured() { MongoMappingContext mappingContext = prepareMappingContext(Milk.class); @@ -157,10 +148,7 @@ public void expireAfterSecondsIfConfigured() { assertThat(optionsCaptor.getValue().get("expireAfterSeconds"), IsEqual. equalTo(60L)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void createsNotNestedGeoSpatialIndexCorrectly() { MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class); @@ -171,10 +159,7 @@ public void createsNotNestedGeoSpatialIndexCorrectly() { .add("min", -180).add("max", 180).add("bits", 26).get())); } - /** - * @see DATAMONGO-827 - */ - @Test + @Test // DATAMONGO-827 public void autoGeneratedIndexNameShouldGenerateNoName() { MongoMappingContext mappingContext = prepareMappingContext(EntityWithGeneratedIndexName.class); @@ -185,10 +170,7 @@ public void autoGeneratedIndexNameShouldGenerateNoName() { assertThat(optionsCaptor.getValue(), is(new BasicDBObjectBuilder().get())); } - /** - * @see DATAMONGO-367 - */ - @Test + @Test // DATAMONGO-367 public void indexCreationShouldNotCreateNewCollectionForNestedGeoSpatialIndexStructures() { MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class); @@ -200,10 +182,7 @@ public void indexCreationShouldNotCreateNewCollectionForNestedGeoSpatialIndexStr assertThat(collectionNameCapturer.getValue(), equalTo("wrapper")); } - /** - * @see DATAMONGO-367 - */ - @Test + @Test // DATAMONGO-367 public void indexCreationShouldNotCreateNewCollectionForNestedIndexStructures() { MongoMappingContext mappingContext = prepareMappingContext(IndexedDocumentWrapper.class); @@ -215,10 +194,7 @@ public void indexCreationShouldNotCreateNewCollectionForNestedIndexStructures() assertThat(collectionNameCapturer.getValue(), equalTo("indexedDocumentWrapper")); } - /** - * @see DATAMONGO-1125 - */ - @Test(expected = DataAccessException.class) + @Test(expected = DataAccessException.class) // DATAMONGO-1125 public void createIndexShouldUsePersistenceExceptionTranslatorForNonDataIntegrityConcerns() { when(factory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); @@ -230,10 +206,7 @@ public void createIndexShouldUsePersistenceExceptionTranslatorForNonDataIntegrit new MongoPersistentEntityIndexCreator(mappingContext, factory); } - /** - * @see DATAMONGO-1125 - */ - @Test(expected = ClassCastException.class) + @Test(expected = ClassCastException.class) // DATAMONGO-1125 public void createIndexShouldNotConvertUnknownExceptionTypes() { when(factory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java index 97554b5582..08bb3391d8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexResolverUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -70,10 +70,7 @@ public class MongoPersistentEntityIndexResolverUnitTests { */ public static class IndexResolutionTests { - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void indexPathOnLevelZeroIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -83,10 +80,7 @@ public void indexPathOnLevelZeroIsResolvedCorrectly() { assertIndexPathAndCollection("indexedProperty", "Zero", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void indexPathOnLevelOneIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelOne.class); @@ -95,10 +89,7 @@ public void indexPathOnLevelOneIsResolvedCorrectly() { assertIndexPathAndCollection("zero.indexedProperty", "One", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void depplyNestedIndexPathIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(IndexOnLevelTwo.class); @@ -107,10 +98,7 @@ public void depplyNestedIndexPathIsResolvedCorrectly() { assertIndexPathAndCollection("one.zero.indexedProperty", "Two", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void resolvesIndexPathNameForNamedPropertiesCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -121,10 +109,7 @@ public void resolvesIndexPathNameForNamedPropertiesCorrectly() { indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void resolvesIndexDefinitionCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -135,10 +120,7 @@ public void resolvesIndexDefinitionCorrectly() { equalTo(new BasicDBObjectBuilder().add("name", "indexedProperty").get())); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void resolvesIndexDefinitionOptionsCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -150,10 +132,7 @@ public void resolvesIndexDefinitionOptionsCorrectly() { .add("sparse", true).add("background", true).add("expireAfterSeconds", 10L).get())); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void resolvesIndexCollectionNameCorrectlyWhenDefinedInAnnotation() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -161,10 +140,7 @@ public void resolvesIndexCollectionNameCorrectlyWhenDefinedInAnnotation() { assertThat(indexDefinitions.get(0).getCollection(), equalTo("CollectionOverride")); } - /** - * @see DATAMONGO-1297 - */ - @Test + @Test // DATAMONGO-1297 public void resolvesIndexOnDbrefWhenDefined() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(WithDbRef.class); @@ -175,10 +151,7 @@ public void resolvesIndexOnDbrefWhenDefined() { equalTo(new BasicDBObjectBuilder().add("indexedDbRef", 1).get())); } - /** - * @see DATAMONGO-1297 - */ - @Test + @Test // DATAMONGO-1297 public void resolvesIndexOnDbrefWhenDefinedOnNestedElement() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -190,10 +163,7 @@ public void resolvesIndexOnDbrefWhenDefinedOnNestedElement() { equalTo(new BasicDBObjectBuilder().add("nested.indexedDbRef", 1).get())); } - /** - * @see DATAMONGO-1163 - */ - @Test + @Test // DATAMONGO-1163 public void resolveIndexDefinitionInMetaAnnotatedFields() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -205,10 +175,7 @@ public void resolveIndexDefinitionInMetaAnnotatedFields() { equalTo(new BasicDBObjectBuilder().add("name", "_name").get())); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void resolveIndexDefinitionInComposedAnnotatedFields() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -223,10 +190,7 @@ public void resolveIndexDefinitionInComposedAnnotatedFields() { isBsonObject().containing("sparse", true).containing("unique", true).containing("name", "my_index_name")); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void resolveIndexDefinitionInCustomComposedAnnotatedFields() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -351,10 +315,7 @@ static class IndexOnMetaAnnotatedField { */ public static class GeoSpatialIndexResolutionTests { - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void geoSpatialIndexPathOnLevelZeroIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -364,10 +325,7 @@ public void geoSpatialIndexPathOnLevelZeroIsResolvedCorrectly() { assertIndexPathAndCollection("geoIndexedProperty", "Zero", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void geoSpatialIndexPathOnLevelOneIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -377,10 +335,7 @@ public void geoSpatialIndexPathOnLevelOneIsResolvedCorrectly() { assertIndexPathAndCollection("zero.geoIndexedProperty", "One", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void depplyNestedGeoSpatialIndexPathIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -390,10 +345,7 @@ public void depplyNestedGeoSpatialIndexPathIsResolvedCorrectly() { assertIndexPathAndCollection("one.zero.geoIndexedProperty", "Two", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void resolvesIndexDefinitionOptionsCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -405,10 +357,7 @@ public void resolvesIndexDefinitionOptionsCorrectly() { new BasicDBObjectBuilder().add("name", "location").add("min", 1).add("max", 100).add("bits", 2).get())); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void resolvesComposedAnnotationIndexDefinitionOptionsCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -479,10 +428,7 @@ static class GeoSpatialIndexedDocumentWithComposedAnnotation { */ public static class CompoundIndexResolutionTests { - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void compoundIndexPathOnLevelZeroIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -492,10 +438,7 @@ public void compoundIndexPathOnLevelZeroIsResolvedCorrectly() { assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void compoundIndexOptionsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -508,10 +451,7 @@ public void compoundIndexOptionsResolvedCorrectly() { equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get())); } - /** - * @see DATAMONGO-909 - */ - @Test + @Test // DATAMONGO-909 public void compoundIndexOnSuperClassResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -524,10 +464,7 @@ public void compoundIndexOnSuperClassResolvedCorrectly() { equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get())); } - /** - * @see DATAMONGO-827 - */ - @Test + @Test // DATAMONGO-827 public void compoundIndexDoesNotSpecifyNameWhenUsingGenerateName() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -540,10 +477,7 @@ public void compoundIndexDoesNotSpecifyNameWhenUsingGenerateName() { equalTo(new BasicDBObjectBuilder().add("foo", 1).add("bar", -1).get())); } - /** - * @see DATAMONGO-929 - */ - @Test + @Test // DATAMONGO-929 public void compoundIndexPathOnLevelOneIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -554,10 +488,7 @@ public void compoundIndexPathOnLevelOneIsResolvedCorrectly() { indexDefinitions.get(0)); } - /** - * @see DATAMONGO-929 - */ - @Test + @Test // DATAMONGO-929 public void emptyCompoundIndexPathOnLevelOneIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -568,10 +499,7 @@ public void emptyCompoundIndexPathOnLevelOneIsResolvedCorrectly() { indexDefinitions.get(0)); } - /** - * @see DATAMONGO-929 - */ - @Test + @Test // DATAMONGO-929 public void singleCompoundIndexPathOnLevelZeroIsResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -581,10 +509,7 @@ public void singleCompoundIndexPathOnLevelZeroIsResolvedCorrectly() { assertIndexPathAndCollection(new String[] { "foo", "bar" }, "CompoundIndexOnLevelZero", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void singleCompoundIndexUsingComposedAnnotationsOnTypeResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -665,10 +590,7 @@ static class CompoundIndexDocumentWithComposedAnnotation { public static class TextIndexedResolutionTests { - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldResolveSingleFieldTextIndexCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -677,10 +599,7 @@ public void shouldResolveSingleFieldTextIndexCorrectly() { assertIndexPathAndCollection("bar", "textIndexOnSinglePropertyInRoot", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldResolveMultiFieldTextIndexCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -690,10 +609,7 @@ public void shouldResolveMultiFieldTextIndexCorrectly() { indexDefinitions.get(0)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldResolveTextIndexOnElementCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -702,10 +618,7 @@ public void shouldResolveTextIndexOnElementCorrectly() { assertIndexPathAndCollection(new String[] { "nested.foo" }, "textIndexOnNestedRoot", indexDefinitions.get(0)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldResolveTextIndexOnElementWithWeightCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -718,10 +631,7 @@ public void shouldResolveTextIndexOnElementWithWeightCorrectly() { assertThat(weights.get("nested.foo"), is((Object) 5F)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldResolveTextIndexOnElementWithMostSpecificWeightCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -735,10 +645,7 @@ public void shouldResolveTextIndexOnElementWithMostSpecificWeightCorrectly() { assertThat(weights.get("nested.bar"), is((Object) 10F)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldSetDefaultLanguageCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -746,10 +653,7 @@ public void shouldSetDefaultLanguageCorrectly() { assertThat(indexDefinitions.get(0).getIndexOptions().get("default_language"), is((Object) "spanish")); } - /** - * @see DATAMONGO-937, DATAMONGO-1049 - */ - @Test + @Test // DATAMONGO-937, DATAMONGO-1049 public void shouldResolveTextIndexLanguageOverrideCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -757,10 +661,7 @@ public void shouldResolveTextIndexLanguageOverrideCorrectly() { assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is((Object) "lang")); } - /** - * @see DATAMONGO-1049 - */ - @Test + @Test // DATAMONGO-1049 public void shouldIgnoreTextIndexLanguageOverrideOnNestedElements() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -768,10 +669,7 @@ public void shouldIgnoreTextIndexLanguageOverrideOnNestedElements() { assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is(nullValue())); } - /** - * @see DATAMONGO-1049 - */ - @Test + @Test // DATAMONGO-1049 public void shouldNotCreateIndexDefinitionWhenOnlyLanguageButNoTextIndexPresent() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -779,10 +677,7 @@ public void shouldNotCreateIndexDefinitionWhenOnlyLanguageButNoTextIndexPresent( assertThat(indexDefinitions, is(empty())); } - /** - * @see DATAMONGO-1049 - */ - @Test + @Test // DATAMONGO-1049 public void shouldNotCreateIndexDefinitionWhenOnlyAnnotatedLanguageButNoTextIndexPresent() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -790,10 +685,7 @@ public void shouldNotCreateIndexDefinitionWhenOnlyAnnotatedLanguageButNoTextInde assertThat(indexDefinitions, is(empty())); } - /** - * @see DATAMONGO-1049 - */ - @Test + @Test // DATAMONGO-1049 public void shouldPreferExplicitlyAnnotatedLanguageProperty() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -801,10 +693,7 @@ public void shouldPreferExplicitlyAnnotatedLanguageProperty() { assertThat(indexDefinitions.get(0).getIndexOptions().get("language_override"), is((Object) "lang")); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void shouldResolveComposedAnnotationCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -918,10 +807,7 @@ static class TextIndexedDocumentWithComposedAnnotation { public static class MixedIndexResolutionTests { - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void multipleIndexesResolvedCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(MixedIndexRoot.class); @@ -931,10 +817,7 @@ public void multipleIndexesResolvedCorrectly() { assertThat(indexDefinitions.get(1).getIndexDefinition(), instanceOf(GeospatialIndex.class)); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void cyclicPropertyReferenceOverDBRefShouldNotBeTraversed() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(Inner.class); @@ -943,20 +826,14 @@ public void cyclicPropertyReferenceOverDBRefShouldNotBeTraversed() { equalTo(new BasicDBObjectBuilder().add("outer", 1).get())); } - /** - * @see DATAMONGO-899 - */ - @Test + @Test // DATAMONGO-899 public void associationsShouldNotBeTraversed() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(Outer.class); assertThat(indexDefinitions, empty()); } - /** - * @see DATAMONGO-926 - */ - @Test + @Test // DATAMONGO-926 public void shouldNotRunIntoStackOverflow() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -964,10 +841,7 @@ public void shouldNotRunIntoStackOverflow() { assertThat(indexDefinitions, hasSize(1)); } - /** - * @see DATAMONGO-926 - */ - @Test + @Test // DATAMONGO-926 public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelZero() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleLevelZero.class); @@ -976,10 +850,7 @@ public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelZero() { assertThat(indexDefinitions, hasSize(2)); } - /** - * @see DATAMONGO-926 - */ - @Test + @Test // DATAMONGO-926 public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelOne() { List indexDefinitions = prepareMappingContextAndResolveIndexForType(CycleOnLevelOne.class); @@ -987,10 +858,7 @@ public void indexShouldBeFoundEvenForCyclePropertyReferenceOnLevelOne() { assertThat(indexDefinitions, hasSize(1)); } - /** - * @see DATAMONGO-926 - */ - @Test + @Test // DATAMONGO-926 public void indexBeResolvedCorrectlyWhenPropertiesOfDifferentTypesAreNamedEqually() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1002,10 +870,7 @@ public void indexBeResolvedCorrectlyWhenPropertiesOfDifferentTypesAreNamedEquall assertThat(indexDefinitions, hasSize(3)); } - /** - * @see DATAMONGO-949 - */ - @Test + @Test // DATAMONGO-949 public void shouldNotDetectCycleInSimilarlyNamedProperties() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1014,10 +879,7 @@ public void shouldNotDetectCycleInSimilarlyNamedProperties() { assertThat(indexDefinitions, hasSize(1)); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 public void shouldDetectSelfCycleViaCollectionTypeCorrectly() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1025,10 +887,7 @@ public void shouldDetectSelfCycleViaCollectionTypeCorrectly() { assertThat(indexDefinitions, empty()); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 public void shouldNotDetectCycleWhenTypeIsUsedMoreThanOnce() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1036,10 +895,7 @@ public void shouldNotDetectCycleWhenTypeIsUsedMoreThanOnce() { assertThat(indexDefinitions, empty()); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 @SuppressWarnings({ "rawtypes", "unchecked" }) public void shouldCatchCyclicReferenceExceptionOnRoot() { @@ -1058,10 +914,7 @@ public void shouldCatchCyclicReferenceExceptionOnRoot() { .resolveIndexForEntity(selfCyclingEntity); } - /** - * @see DATAMONGO-1025 - */ - @Test + @Test // DATAMONGO-1025 public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedCompoundIndexFixedOnCollection() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1070,10 +923,7 @@ public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedCompoundIn equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index")); } - /** - * @see DATAMONGO-1025 - */ - @Test + @Test // DATAMONGO-1025 public void shouldUseIndexNameForNestedTypesWithNamedCompoundIndexDefinition() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1082,10 +932,7 @@ public void shouldUseIndexNameForNestedTypesWithNamedCompoundIndexDefinition() { equalTo("propertyOfTypeHavingNamedCompoundIndex.c_index")); } - /** - * @see DATAMONGO-1025 - */ - @Test + @Test // DATAMONGO-1025 public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedIndexFixedOnCollection() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1094,10 +941,7 @@ public void shouldUsePathIndexAsIndexNameForDocumentsHavingNamedNestedIndexFixed equalTo("propertyOfTypeHavingNamedIndex.property_index")); } - /** - * @see DATAMONGO-1025 - */ - @Test + @Test // DATAMONGO-1025 public void shouldUseIndexNameForNestedTypesWithNamedIndexDefinition() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1106,10 +950,7 @@ public void shouldUseIndexNameForNestedTypesWithNamedIndexDefinition() { equalTo("propertyOfTypeHavingNamedIndex.property_index")); } - /** - * @see DATAMONGO-1025 - */ - @Test + @Test // DATAMONGO-1025 public void shouldUseIndexNameOnRootLevel() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1117,10 +958,7 @@ public void shouldUseIndexNameOnRootLevel() { assertThat((String) indexDefinitions.get(0).getIndexOptions().get("name"), equalTo("property_index")); } - /** - * @see DATAMONGO-1087 - */ - @Test + @Test // DATAMONGO-1087 public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnRoot() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1131,10 +969,7 @@ public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnRoo assertThat((String) indexDefinitions.get(1).getIndexOptions().get("name"), equalTo("nameLast.component")); } - /** - * @see DATAMONGO-1087 - */ - @Test + @Test // DATAMONGO-1087 public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnNestedProperty() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1145,10 +980,7 @@ public void shouldAllowMultiplePropertiesOfSameTypeWithMatchingStartLettersOnNes assertThat((String) indexDefinitions.get(1).getIndexOptions().get("name"), equalTo("component.name")); } - /** - * @see DATAMONGO-1121 - */ - @Test + @Test // DATAMONGO-1121 public void shouldOnlyConsiderEntitiesAsPotentialCycleCandidates() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( @@ -1161,10 +993,7 @@ public void shouldOnlyConsiderEntitiesAsPotentialCycleCandidates() { } - /** - * @see DATAMONGO-1263 - */ - @Test + @Test // DATAMONGO-1263 public void shouldConsiderGenericTypeArgumentsOfCollectionElements() { List indexDefinitions = prepareMappingContextAndResolveIndexForType( diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/PathUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/PathUnitTests.java index 9e88c00c1b..52308193a4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/PathUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/PathUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -45,20 +45,14 @@ public void setUp() { when(entityMock.getType()).thenReturn((Class) Object.class); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 public void shouldIdentifyCycleForOwnerOfSameTypeAndMatchingPath() { MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo"); assertThat(new Path(property, "foo.bar").cycles(property, "foo.bar.bar"), is(true)); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 @SuppressWarnings("rawtypes") public void shouldAllowMatchingPathForDifferentOwners() { @@ -71,10 +65,7 @@ public void shouldAllowMatchingPathForDifferentOwners() { assertThat(new Path(existing, "foo.bar").cycles(toBeVerified, "foo.bar.bar"), is(false)); } - /** - * @see DATAMONGO-962 - */ - @Test + @Test // DATAMONGO-962 public void shouldAllowEqaulPropertiesOnDifferentPaths() { MongoPersistentProperty property = createPersistentPropertyMock(entityMock, "foo"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java index 8047f0fe00..4123a2bdd7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/TextIndexTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -52,10 +52,7 @@ public void setUp() throws Exception { this.indexOps = template.indexOps(TextIndexedDocumentRoot.class); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void indexInfoShouldHaveBeenCreatedCorrectly() { List indexInfos = indexOps.getIndexInfo(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasePerson.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasePerson.java index 8d2aec0655..d30605d605 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasePerson.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasePerson.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ /** * {@link QuerySupertype} is necessary for Querydsl 2.2.0-beta4 to compile the query classes directly. Can be removed as - * soon as {@link https://bugs.launchpad.net/querydsl/+bug/776219} is fixed. + * soon as https://bugs.launchpad.net/querydsl/+bug/776219 is fixed. * - * @see https://bugs.launchpad.net/querydsl/+bug/776219 + * @see https://bugs.launchpad.net/querydsl/+bug/776219 * @author Jon Brisbin * @author Oliver Gierke */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java index e5f8c87252..cdbe8aed69 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,10 +61,7 @@ public void evaluatesSpELExpression() { assertThat(entity.getCollection(), is("35")); } - /** - * @see DATAMONGO-65, DATAMONGO-1108 - */ - @Test + @Test // DATAMONGO-65, DATAMONGO-1108 public void collectionAllowsReferencingSpringBean() { CollectionProvider provider = new CollectionProvider(); @@ -83,10 +80,7 @@ public void collectionAllowsReferencingSpringBean() { assertThat(entity.getCollection(), is("otherReference")); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldDetectLanguageCorrectly() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -94,11 +88,8 @@ public void shouldDetectLanguageCorrectly() { assertThat(entity.getLanguage(), is("spanish")); } - /** - * @see DATAMONGO-1053 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-1053 public void verifyShouldThrowExceptionForInvalidTypeOfExplicitLanguageProperty() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -111,11 +102,8 @@ public void verifyShouldThrowExceptionForInvalidTypeOfExplicitLanguageProperty() entity.verify(); } - /** - * @see DATAMONGO-1053 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test + @Test // DATAMONGO-1053 public void verifyShouldPassForStringAsExplicitLanguageProperty() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -130,11 +118,8 @@ public void verifyShouldPassForStringAsExplicitLanguageProperty() { verify(propertyMock, times(1)).getActualType(); } - /** - * @see DATAMONGO-1053 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test + @Test // DATAMONGO-1053 public void verifyShouldIgnoreNonExplicitLanguageProperty() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -149,11 +134,8 @@ public void verifyShouldIgnoreNonExplicitLanguageProperty() { verify(propertyMock, never()).getActualType(); } - /** - * @see DATAMONGO-1157 - */ @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-1157 public void verifyShouldThrowErrorForLazyDBRefOnFinalClass() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -169,10 +151,7 @@ public void verifyShouldThrowErrorForLazyDBRefOnFinalClass() { entity.verify(); } - /** - * @see DATAMONGO-1157 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-1157 public void verifyShouldThrowErrorForLazyDBRefArray() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -188,10 +167,7 @@ public void verifyShouldThrowErrorForLazyDBRefArray() { entity.verify(); } - /** - * @see DATAMONGO-1157 - */ - @Test + @Test // DATAMONGO-1157 @SuppressWarnings({ "unchecked", "rawtypes" }) public void verifyShouldPassForLazyDBRefOnNonArrayNonFinalClass() { @@ -210,10 +186,7 @@ public void verifyShouldPassForLazyDBRefOnNonArrayNonFinalClass() { verify(propertyMock, times(1)).isDbReference(); } - /** - * @see DATAMONGO-1157 - */ - @Test + @Test // DATAMONGO-1157 @SuppressWarnings({ "unchecked", "rawtypes" }) public void verifyShouldPassForNonLazyDBRefOnFinalClass() { @@ -232,10 +205,7 @@ public void verifyShouldPassForNonLazyDBRefOnFinalClass() { verify(dbRefMock, times(1)).lazy(); } - /** - * @see DATAMONGO-1291 - */ - @Test + @Test // DATAMONGO-1291 public void metaInformationShouldBeReadCorrectlyFromInheritedDocumentAnnotation() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( @@ -244,10 +214,7 @@ public void metaInformationShouldBeReadCorrectlyFromInheritedDocumentAnnotation( assertThat(entity.getCollection(), is("collection-1")); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void metaInformationShouldBeReadCorrectlyFromComposedDocumentAnnotation() { BasicMongoPersistentEntity entity = new BasicMongoPersistentEntity( diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java index 5f325e7aab..953077b882 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentPropertyUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,20 +84,14 @@ public void preventsNegativeOrder() { getPropertyFor(ReflectionUtils.findField(Person.class, "ssn")); } - /** - * @see DATAMONGO-553 - */ - @Test + @Test // DATAMONGO-553 public void usesPropertyAccessForThrowableCause() { MongoPersistentProperty property = getPropertyFor(ReflectionUtils.findField(Throwable.class, "cause")); assertThat(property.usePropertyAccess(), is(true)); } - /** - * @see DATAMONGO-607 - */ - @Test + @Test // DATAMONGO-607 public void usesCustomFieldNamingStrategyByDefault() throws Exception { Field field = ReflectionUtils.findField(Person.class, "lastname"); @@ -113,10 +107,7 @@ public void usesCustomFieldNamingStrategyByDefault() throws Exception { assertThat(property.getFieldName(), is("foo")); } - /** - * @see DATAMONGO-607 - */ - @Test + @Test // DATAMONGO-607 public void rejectsInvalidValueReturnedByFieldNamingStrategy() { Field field = ReflectionUtils.findField(Person.class, "lastname"); @@ -130,60 +121,42 @@ public void rejectsInvalidValueReturnedByFieldNamingStrategy() { property.getFieldName(); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldDetectAnnotatedLanguagePropertyCorrectly() { MongoPersistentProperty property = getPropertyFor(DocumentWithLanguageProperty.class, "lang"); assertThat(property.isLanguageProperty(), is(true)); } - /** - * @see DATAMONGO-937 - */ - @Test + @Test // DATAMONGO-937 public void shouldDetectIplicitLanguagePropertyCorrectly() { MongoPersistentProperty property = getPropertyFor(DocumentWithImplicitLanguageProperty.class, "language"); assertThat(property.isLanguageProperty(), is(true)); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldDetectTextScorePropertyCorrectly() { MongoPersistentProperty property = getPropertyFor(DocumentWithTextScoreProperty.class, "score"); assertThat(property.isTextScoreProperty(), is(true)); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldDetectTextScoreAsReadOnlyProperty() { MongoPersistentProperty property = getPropertyFor(DocumentWithTextScoreProperty.class, "score"); assertThat(property.isWritable(), is(false)); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void shouldNotConsiderExplicitlyNameFieldAsIdProperty() { MongoPersistentProperty property = getPropertyFor(DocumentWithExplicitlyRenamedIdProperty.class, "id"); assertThat(property.isIdProperty(), is(false)); } - /** - * @see DATAMONGO-1050 - */ - @Test + @Test // DATAMONGO-1050 public void shouldConsiderPropertyAsIdWhenExplicitlyAnnotatedWithIdEvenWhenExplicitlyNamePresent() { MongoPersistentProperty property = getPropertyFor(DocumentWithExplicitlyRenamedIdPropertyHavingIdAnnotation.class, @@ -191,10 +164,7 @@ public void shouldConsiderPropertyAsIdWhenExplicitlyAnnotatedWithIdEvenWhenExpli assertThat(property.isIdProperty(), is(true)); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void shouldConsiderComposedAnnotationsForIdField() { MongoPersistentProperty property = getPropertyFor(DocumentWithComposedAnnotations.class, "myId"); @@ -202,10 +172,7 @@ public void shouldConsiderComposedAnnotationsForIdField() { assertThat(property.getFieldName(), is("_id")); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void shouldConsiderComposedAnnotationsForFields() { MongoPersistentProperty property = getPropertyFor(DocumentWithComposedAnnotations.class, "myField"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MappingTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MappingTests.java index b101ae6ba1..b0969ea899 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MappingTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2017 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. @@ -441,10 +441,7 @@ public void testPersonWithLongDBRef() { assertEquals(12L, p2.getPersonPojoLongId().getId()); } - /** - * @see DATADOC-275 - */ - @Test + @Test // DATADOC-275 public void readsAndWritesDBRefsCorrectly() { template.dropCollection(Item.class); @@ -467,10 +464,7 @@ public void readsAndWritesDBRefsCorrectly() { assertThat(result.items.get(0).id, is(items.id)); } - /** - * @see DATAMONGO-805 - */ - @Test + @Test // DATAMONGO-805 public void supportExcludeDbRefAssociation() { template.dropCollection(Item.class); @@ -492,10 +486,7 @@ public void supportExcludeDbRefAssociation() { assertThat(result.item, is(nullValue())); } - /** - * @see DATAMONGO-805 - */ - @Test + @Test // DATAMONGO-805 public void shouldMapFieldsOfIterableEntity() { template.dropCollection(IterableItem.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java index 99e74c523e..158e13957c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoMappingContextUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,20 +67,14 @@ public void doesNotReturnPersistentEntityForMongoSimpleType() { assertThat(context.getPersistentEntity(DBRef.class), is(nullValue())); } - /** - * @see DATAMONGO-638 - */ - @Test + @Test // DATAMONGO-638 public void doesNotCreatePersistentEntityForAbstractMap() { MongoMappingContext context = new MongoMappingContext(); assertThat(context.getPersistentEntity(AbstractMap.class), is(nullValue())); } - /** - * @see DATAMONGO-607 - */ - @Test + @Test // DATAMONGO-607 public void populatesPersistentPropertyWithCustomFieldNamingStrategy() { MongoMappingContext context = new MongoMappingContext(); @@ -96,10 +90,7 @@ public String getFieldName(PersistentProperty property) { assertThat(entity.getPersistentProperty("firstname").getFieldName(), is("FIRSTNAME")); } - /** - * @see DATAMONGO-607 - */ - @Test + @Test // DATAMONGO-607 public void rejectsClassWithAmbiguousFieldMappings() { exception.expect(MappingException.class); @@ -113,10 +104,7 @@ public void rejectsClassWithAmbiguousFieldMappings() { context.getPersistentEntity(InvalidPerson.class); } - /** - * @see DATAMONGO-694 - */ - @Test + @Test // DATAMONGO-694 public void doesNotConsiderOverrridenAccessorANewField() { MongoMappingContext context = new MongoMappingContext(); @@ -124,10 +112,7 @@ public void doesNotConsiderOverrridenAccessorANewField() { context.getPersistentEntity(Child.class); } - /** - * @see DATAMONGO-688 - */ - @Test + @Test // DATAMONGO-688 public void mappingContextShouldAcceptClassWithImplicitIdProperty() { MongoMappingContext context = new MongoMappingContext(); @@ -137,10 +122,7 @@ public void mappingContextShouldAcceptClassWithImplicitIdProperty() { assertThat(pe.isIdProperty(pe.getPersistentProperty("id")), is(true)); } - /** - * @see DATAMONGO-688 - */ - @Test + @Test // DATAMONGO-688 public void mappingContextShouldAcceptClassWithExplicitIdProperty() { MongoMappingContext context = new MongoMappingContext(); @@ -150,10 +132,7 @@ public void mappingContextShouldAcceptClassWithExplicitIdProperty() { assertThat(pe.isIdProperty(pe.getPersistentProperty("myId")), is(true)); } - /** - * @see DATAMONGO-688 - */ - @Test + @Test // DATAMONGO-688 public void mappingContextShouldAcceptClassWithExplicitAndImplicitIdPropertyByGivingPrecedenceToExplicitIdProperty() { MongoMappingContext context = new MongoMappingContext(); @@ -161,30 +140,21 @@ public void mappingContextShouldAcceptClassWithExplicitAndImplicitIdPropertyByGi assertThat(pe, is(not(nullValue()))); } - /** - * @see DATAMONGO-688 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-688 public void rejectsClassWithAmbiguousExplicitIdPropertyFieldMappings() { MongoMappingContext context = new MongoMappingContext(); context.getPersistentEntity(ClassWithMultipleExplicitIds.class); } - /** - * @see DATAMONGO-688 - */ - @Test(expected = MappingException.class) + @Test(expected = MappingException.class) // DATAMONGO-688 public void rejectsClassWithAmbiguousImplicitIdPropertyFieldMappings() { MongoMappingContext context = new MongoMappingContext(); context.getPersistentEntity(ClassWithMultipleImplicitIds.class); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldRejectClassWithInvalidTextScoreProperty() { exception.expect(MappingException.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AbstractMongoEventListenerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AbstractMongoEventListenerUnitTests.java index 27847da9c0..e5a0045450 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AbstractMongoEventListenerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AbstractMongoEventListenerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,10 +64,7 @@ public void dropsEventIfNotForCorrectDomainType() { context.close(); } - /** - * @see DATAMONGO-289 - */ - @Test + @Test // DATAMONGO-289 public void afterLoadEffectGetsHandledCorrectly() { SamplePersonEventListener listener = new SamplePersonEventListener(); @@ -75,10 +72,7 @@ public void afterLoadEffectGetsHandledCorrectly() { assertThat(listener.invokedOnAfterLoad, is(true)); } - /** - * @see DATAMONGO-289 - */ - @Test + @Test // DATAMONGO-289 public void afterLoadEventGetsFilteredForDomainType() { SamplePersonEventListener personListener = new SamplePersonEventListener(); @@ -90,10 +84,7 @@ public void afterLoadEventGetsFilteredForDomainType() { assertThat(accountListener.invokedOnAfterLoad, is(false)); } - /** - * @see DATAMONGO-289 - */ - @Test + @Test // DATAMONGO-289 public void afterLoadEventGetsFilteredForDomainTypeWorksForSubtypes() { SamplePersonEventListener personListener = new SamplePersonEventListener(); @@ -105,10 +96,7 @@ public void afterLoadEventGetsFilteredForDomainTypeWorksForSubtypes() { assertThat(contactListener.invokedOnAfterLoad, is(true)); } - /** - * @see DATAMONGO-289 - */ - @Test + @Test // DATAMONGO-289 public void afterLoadEventGetsFilteredForDomainTypeWorksForSubtypes2() { SamplePersonEventListener personListener = new SamplePersonEventListener(); @@ -120,10 +108,7 @@ public void afterLoadEventGetsFilteredForDomainTypeWorksForSubtypes2() { assertThat(contactListener.invokedOnAfterLoad, is(true)); } - /** - * @see DATAMONGO-333 - */ - @Test + @Test // DATAMONGO-333 @SuppressWarnings({ "rawtypes", "unchecked" }) public void handlesUntypedImplementations() { @@ -131,10 +116,7 @@ public void handlesUntypedImplementations() { listener.onApplicationEvent(new MongoMappingEvent(new Object(), new BasicDBObject(), "collection")); } - /** - * @see DATAMONGO-545 - */ - @Test + @Test // DATAMONGO-545 public void invokeContactCallbackForPersonEvent() { MongoMappingEvent event = new BeforeDeleteEvent(new BasicDBObject(), Person.class, @@ -145,10 +127,7 @@ public void invokeContactCallbackForPersonEvent() { assertThat(listener.invokedOnBeforeDelete, is(true)); } - /** - * @see DATAMONGO-545 - */ - @Test + @Test // DATAMONGO-545 public void invokePersonCallbackForPersonEvent() { MongoMappingEvent event = new BeforeDeleteEvent(new BasicDBObject(), Person.class, @@ -159,10 +138,7 @@ public void invokePersonCallbackForPersonEvent() { assertThat(listener.invokedOnBeforeDelete, is(true)); } - /** - * @see DATAMONGO-545 - */ - @Test + @Test // DATAMONGO-545 public void dontInvokePersonCallbackForAccountEvent() { MongoMappingEvent event = new BeforeDeleteEvent(new BasicDBObject(), Account.class, @@ -173,10 +149,7 @@ public void dontInvokePersonCallbackForAccountEvent() { assertThat(listener.invokedOnBeforeDelete, is(false)); } - /** - * @see DATAMONGO-545 - */ - @Test + @Test // DATAMONGO-545 public void donInvokePersonCallbackForUntypedEvent() { MongoMappingEvent event = new BeforeDeleteEvent(new BasicDBObject(), null, "collection-1"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java index c600911eef..a997613498 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ApplicationContextEventTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2016 by the original author(s). + * Copyright (c) 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -135,10 +135,7 @@ public void beforeSaveEvent() { comparePersonAndDbo(p, p2, dbo); } - /** - * @see DATAMONGO-1256 - */ - @Test + @Test // DATAMONGO-1256 public void loadAndConvertEvents() { PersonPojoStringId entity = new PersonPojoStringId("1", "Text"); @@ -156,10 +153,7 @@ public void loadAndConvertEvents() { assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(COLLECTION_NAME)); } - /** - * @see DATAMONGO-1256 - */ - @Test + @Test // DATAMONGO-1256 public void loadEventsOnAggregation() { template.insert(new PersonPojoStringId("1", "Text")); @@ -177,10 +171,7 @@ public void loadEventsOnAggregation() { assertThat(simpleMappingEventListener.onAfterConvertEvents.get(0).getCollectionName(), is(COLLECTION_NAME)); } - /** - * @see DATAMONGO-1256 - */ - @Test + @Test // DATAMONGO-1256 public void deleteEvents() { PersonPojoStringId entity = new PersonPojoStringId("1", "Text"); @@ -195,10 +186,7 @@ public void deleteEvents() { assertThat(simpleMappingEventListener.onAfterDeleteEvents.get(0).getCollectionName(), is(COLLECTION_NAME)); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForDBRef() throws Exception { Related ref1 = new Related(2L, "related desc1"); @@ -226,10 +214,7 @@ public void publishesAfterLoadAndAfterConvertEventsForDBRef() throws Exception { is(equalTo(ROOT_COLLECTION_NAME))); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForLazyLoadingDBRef() throws Exception { Related ref1 = new Related(2L, "related desc1"); @@ -263,10 +248,7 @@ public void publishesAfterLoadAndAfterConvertEventsForLazyLoadingDBRef() throws is(equalTo(RELATED_COLLECTION_NAME))); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForListOfDBRef() throws Exception { List references = Arrays.asList(new Related(20L, "ref 1"), new Related(30L, "ref 2")); @@ -298,10 +280,7 @@ public void publishesAfterLoadAndAfterConvertEventsForListOfDBRef() throws Excep is(equalTo(ROOT_COLLECTION_NAME))); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForLazyLoadingListOfDBRef() throws Exception { List references = Arrays.asList(new Related(20L, "ref 1"), new Related(30L, "ref 2")); @@ -338,10 +317,7 @@ public void publishesAfterLoadAndAfterConvertEventsForLazyLoadingListOfDBRef() t is(equalTo(RELATED_COLLECTION_NAME))); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForMapOfDBRef() throws Exception { Map references = new LinkedHashMap(); @@ -375,10 +351,7 @@ public void publishesAfterLoadAndAfterConvertEventsForMapOfDBRef() throws Except is(equalTo(ROOT_COLLECTION_NAME))); } - /** - * @see DATAMONGO-1271 - */ - @Test + @Test // DATAMONGO-1271 public void publishesAfterLoadAndAfterConvertEventsForLazyLoadingMapOfDBRef() throws Exception { Map references = new LinkedHashMap(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java index 6cc1c0043d..ff703cf6d8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2017 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. @@ -65,18 +65,12 @@ public IsNewAwareAuditingHandler getObject() throws BeansException { }); } - /** - * @see DATAMONGO-577 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-577 public void rejectsNullAuditingHandler() { new AuditingEventListener(null); } - /** - * @see DATAMONGO-577 - */ - @Test + @Test // DATAMONGO-577 public void triggersCreationMarkForObjectWithEmptyId() { Sample sample = new Sample(); @@ -86,10 +80,7 @@ public void triggersCreationMarkForObjectWithEmptyId() { verify(handler, times(0)).markModified(Mockito.any(Sample.class)); } - /** - * @see DATAMONGO-577 - */ - @Test + @Test // DATAMONGO-577 public void triggersModificationMarkForObjectWithSetId() { Sample sample = new Sample(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/PersonBeforeSaveListener.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/PersonBeforeSaveListener.java index eda34e756f..c5b5e36d90 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/PersonBeforeSaveListener.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/PersonBeforeSaveListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java index c638aeadc6..e5c2a78084 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/User.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 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. @@ -22,7 +22,6 @@ * Class used to test JSR-303 validation * {@link org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener} * - * @see DATAMONGO-36 * @author Maciej Walkowiak */ public class User { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java index 8a8d06cd3e..47137b8052 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/ValidatingMongoEventListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 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. @@ -33,7 +33,6 @@ /** * Integration test for {@link ValidatingMongoEventListener}. * - * @see DATAMONGO-36 * @author Maciej Walkowiak * @author Oliver Gierke * @author Christoph Strobl @@ -46,7 +45,7 @@ public class ValidatingMongoEventListenerTests { @Autowired MongoTemplate mongoTemplate; - @Test + @Test // DATAMONGO-36 public void shouldThrowConstraintViolationException() { User user = new User("john", 17); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceCountsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceCountsUnitTests.java index a0da06bc0e..9aeda12dc8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceCountsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceCountsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 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. @@ -27,10 +27,7 @@ */ public class MapReduceCountsUnitTests { - /** - * @see DATACMNS-378 - */ - @Test + @Test // DATACMNS-378 public void equalsForSameNumberValues() { MapReduceCounts left = new MapReduceCounts(1L, 1L, 1L); @@ -41,10 +38,7 @@ public void equalsForSameNumberValues() { assertThat(left.hashCode(), is(right.hashCode())); } - /** - * @see DATACMNS-378 - */ - @Test + @Test // DATACMNS-378 public void notEqualForDifferentNumberValues() { MapReduceCounts left = new MapReduceCounts(1L, 1L, 1L); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceOptionsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceOptionsTests.java index 9cc1728de4..699802ea0d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceOptionsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceOptionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 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. @@ -32,10 +32,7 @@ public void testFinalize() { new MapReduceOptions().finalizeFunction("code"); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void limitShouldBeIncludedCorrectly() { MapReduceOptions options = new MapReduceOptions(); @@ -44,10 +41,7 @@ public void limitShouldBeIncludedCorrectly() { assertThat(options.getOptionsObject(), isBsonObject().containing("limit", 10)); } - /** - * @see DATAMONGO-1334 - */ - @Test + @Test // DATAMONGO-1334 public void limitShouldNotBePresentInDboWhenNotSet() { assertThat(new MapReduceOptions().getOptionsObject(), isBsonObject().notContaining("limit")); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java index 087ee786eb..a960c67c50 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResultsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 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. @@ -32,10 +32,7 @@ */ public class MapReduceResultsUnitTests { - /** - * @see DATAMONGO-428 - */ - @Test + @Test // DATAMONGO-428 public void resolvesOutputCollectionForPlainResult() { DBObject rawResult = new BasicDBObject("result", "FOO"); @@ -44,10 +41,7 @@ public void resolvesOutputCollectionForPlainResult() { assertThat(results.getOutputCollection(), is("FOO")); } - /** - * @see DATAMONGO-428 - */ - @Test + @Test // DATAMONGO-428 public void resolvesOutputCollectionForDBObjectResult() { DBObject rawResult = new BasicDBObject("result", new BasicDBObject("collection", "FOO")); @@ -56,10 +50,7 @@ public void resolvesOutputCollectionForDBObjectResult() { assertThat(results.getOutputCollection(), is("FOO")); } - /** - * @see DATAMONGO-378 - */ - @Test + @Test // DATAMONGO-378 public void handlesLongTotalInResult() { DBObject inner = new BasicDBObject("total", 1L); @@ -70,10 +61,7 @@ public void handlesLongTotalInResult() { new MapReduceResults(Collections.emptyList(), source); } - /** - * @see DATAMONGO-378 - */ - @Test + @Test // DATAMONGO-378 public void handlesLongResultsForCounts() { DBObject inner = new BasicDBObject("input", 1L); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java index d971168334..76319ef437 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 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. @@ -278,10 +278,7 @@ public void testMapReduceExcludeQuery() { } - /** - * @see DATAMONGO-938 - */ - @Test + @Test // DATAMONGO-938 public void mapReduceShouldUseQueryMapper() { DBCollection c = mongoTemplate.getDb().getCollection("jmrWithGeo"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java index e933768b1d..da4d8ab17e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -65,10 +65,7 @@ public void overridesSortCorrectly() { assertThat(query.getSortObject(), is(sortReference)); } - /** - * @see DATAMONGO-1093 - */ - @Test + @Test // DATAMONGO-1093 public void equalsContract() { BasicQuery query1 = new BasicQuery("{ \"name\" : \"Thomas\"}", "{\"name\":1, \"age\":1}"); @@ -83,10 +80,7 @@ public void equalsContract() { .verify(); } - /** - * @see DATAMONGO-1093 - */ - @Test + @Test // DATAMONGO-1093 public void handlesEqualsAndHashCodeCorrectlyForExactCopies() { String qry = "{ \"name\" : \"Thomas\"}"; @@ -103,10 +97,7 @@ public void handlesEqualsAndHashCodeCorrectlyForExactCopies() { assertThat(query1.hashCode(), is(query2.hashCode())); } - /** - * @see DATAMONGO-1093 - */ - @Test + @Test // DATAMONGO-1093 public void handlesEqualsAndHashCodeCorrectlyWhenBasicQuerySettingsDiffer() { String qry = "{ \"name\" : \"Thomas\"}"; @@ -122,10 +113,7 @@ public void handlesEqualsAndHashCodeCorrectlyWhenBasicQuerySettingsDiffer() { assertThat(query1.hashCode(), is(not(query2.hashCode()))); } - /** - * @see DATAMONGO-1093 - */ - @Test + @Test // DATAMONGO-1093 public void handlesEqualsAndHashCodeCorrectlyWhenQuerySettingsDiffer() { String qry = "{ \"name\" : \"Thomas\"}"; @@ -141,10 +129,7 @@ public void handlesEqualsAndHashCodeCorrectlyWhenQuerySettingsDiffer() { assertThat(query1.hashCode(), is(not(query2.hashCode()))); } - /** - * @see DATAMONGO-1387 - */ - @Test + @Test // DATAMONGO-1387 public void returnsFieldsCorrectly() { String qry = "{ \"name\" : \"Thomas\"}"; @@ -155,10 +140,7 @@ public void returnsFieldsCorrectly() { assertThat(query1.getFieldsObject(), isBsonObject().containing("name").containing("age")); } - /** - * @see DATAMONGO-1387 - */ - @Test + @Test // DATAMONGO-1387 public void handlesFieldsIncludeCorrectly() { String qry = "{ \"name\" : \"Thomas\"}"; @@ -169,10 +151,7 @@ public void handlesFieldsIncludeCorrectly() { assertThat(query1.getFieldsObject(), isBsonObject().containing("name")); } - /** - * @see DATAMONGO-1387 - */ - @Test + @Test // DATAMONGO-1387 public void combinesFieldsIncludeCorrectly() { String qry = "{ \"name\" : \"Thomas\"}"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java index dc8e1ce84c..f73fbfde13 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/CriteriaTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 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. @@ -79,10 +79,7 @@ public void equalIfCriteriaMatches() { assertThat(right, is(not(left))); } - /** - * @see DATAMONGO-507 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-507 public void shouldThrowExceptionWhenTryingToNegateAndOperation() { new Criteria() // @@ -90,10 +87,7 @@ public void shouldThrowExceptionWhenTryingToNegateAndOperation() { .andOperator(Criteria.where("delete").is(true).and("_id").is(42)); // } - /** - * @see DATAMONGO-507 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-507 public void shouldThrowExceptionWhenTryingToNegateOrOperation() { new Criteria() // @@ -101,10 +95,7 @@ public void shouldThrowExceptionWhenTryingToNegateOrOperation() { .orOperator(Criteria.where("delete").is(true).and("_id").is(42)); // } - /** - * @see DATAMONGO-507 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-507 public void shouldThrowExceptionWhenTryingToNegateNorOperation() { new Criteria() // @@ -112,10 +103,7 @@ public void shouldThrowExceptionWhenTryingToNegateNorOperation() { .norOperator(Criteria.where("delete").is(true).and("_id").is(42)); // } - /** - * @see DATAMONGO-507 - */ - @Test + @Test // DATAMONGO-507 public void shouldNegateFollowingSimpleExpression() { Criteria c = Criteria.where("age").not().gt(18).and("status").is("student"); @@ -125,10 +113,7 @@ public void shouldNegateFollowingSimpleExpression() { assertThat(co.toString(), is("{ \"age\" : { \"$not\" : { \"$gt\" : 18}} , \"status\" : \"student\"}")); } - /** - * @see DATAMONGO-1068 - */ - @Test + @Test // DATAMONGO-1068 public void getCriteriaObjectShouldReturnEmptyDBOWhenNoCriteriaSpecified() { DBObject dbo = new Criteria().getCriteriaObject(); @@ -136,10 +121,7 @@ public void getCriteriaObjectShouldReturnEmptyDBOWhenNoCriteriaSpecified() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().get())); } - /** - * @see DATAMONGO-1068 - */ - @Test + @Test // DATAMONGO-1068 public void getCriteriaObjectShouldUseCritieraValuesWhenNoKeyIsPresent() { DBObject dbo = new Criteria().lt("foo").getCriteriaObject(); @@ -147,10 +129,7 @@ public void getCriteriaObjectShouldUseCritieraValuesWhenNoKeyIsPresent() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("$lt", "foo").get())); } - /** - * @see DATAMONGO-1068 - */ - @Test + @Test // DATAMONGO-1068 public void getCriteriaObjectShouldUseCritieraValuesWhenNoKeyIsPresentButMultipleCriteriasPresent() { DBObject dbo = new Criteria().lt("foo").gt("bar").getCriteriaObject(); @@ -158,10 +137,7 @@ public void getCriteriaObjectShouldUseCritieraValuesWhenNoKeyIsPresentButMultipl assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("$lt", "foo").add("$gt", "bar").get())); } - /** - * @see DATAMONGO-1068 - */ - @Test + @Test // DATAMONGO-1068 public void getCriteriaObjectShouldRespectNotWhenNoKeyPresent() { DBObject dbo = new Criteria().lt("foo").not().getCriteriaObject(); @@ -169,10 +145,7 @@ public void getCriteriaObjectShouldRespectNotWhenNoKeyPresent() { assertThat(dbo, equalTo(new BasicDBObjectBuilder().add("$not", new BasicDBObject("$lt", "foo")).get())); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void geoJsonTypesShouldBeWrappedInGeometry() { DBObject dbo = new Criteria("foo").near(new GeoJsonPoint(100, 200)).getCriteriaObject(); @@ -180,10 +153,7 @@ public void geoJsonTypesShouldBeWrappedInGeometry() { assertThat(dbo, isBsonObject().containing("foo.$near.$geometry", new GeoJsonPoint(100, 200))); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void legacyCoordinateTypesShouldNotBeWrappedInGeometry() { DBObject dbo = new Criteria("foo").near(new Point(100, 200)).getCriteriaObject(); @@ -191,10 +161,7 @@ public void legacyCoordinateTypesShouldNotBeWrappedInGeometry() { assertThat(dbo, isBsonObject().notContaining("foo.$near.$geometry")); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void maxDistanceShouldBeMappedInsideNearWhenUsedAlongWithGeoJsonType() { DBObject dbo = new Criteria("foo").near(new GeoJsonPoint(100, 200)).maxDistance(50D).getCriteriaObject(); @@ -202,10 +169,7 @@ public void maxDistanceShouldBeMappedInsideNearWhenUsedAlongWithGeoJsonType() { assertThat(dbo, isBsonObject().containing("foo.$near.$maxDistance", 50D)); } - /** - * @see DATAMONGO-1135 - */ - @Test + @Test // DATAMONGO-1135 public void maxDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonType() { DBObject dbo = new Criteria("foo").nearSphere(new GeoJsonPoint(100, 200)).maxDistance(50D).getCriteriaObject(); @@ -213,10 +177,7 @@ public void maxDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonTyp assertThat(dbo, isBsonObject().containing("foo.$nearSphere.$maxDistance", 50D)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void minDistanceShouldBeMappedInsideNearWhenUsedAlongWithGeoJsonType() { DBObject dbo = new Criteria("foo").near(new GeoJsonPoint(100, 200)).minDistance(50D).getCriteriaObject(); @@ -224,10 +185,7 @@ public void minDistanceShouldBeMappedInsideNearWhenUsedAlongWithGeoJsonType() { assertThat(dbo, isBsonObject().containing("foo.$near.$minDistance", 50D)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void minDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonType() { DBObject dbo = new Criteria("foo").nearSphere(new GeoJsonPoint(100, 200)).minDistance(50D).getCriteriaObject(); @@ -235,10 +193,7 @@ public void minDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonTyp assertThat(dbo, isBsonObject().containing("foo.$nearSphere.$minDistance", 50D)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void minAndMaxDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJsonType() { DBObject dbo = new Criteria("foo").nearSphere(new GeoJsonPoint(100, 200)).minDistance(50D).maxDistance(100D) @@ -248,18 +203,12 @@ public void minAndMaxDistanceShouldBeMappedInsideNearSphereWhenUsedAlongWithGeoJ assertThat(dbo, isBsonObject().containing("foo.$nearSphere.$maxDistance", 100D)); } - /** - * @see DATAMONGO-1134 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1134 public void intersectsShouldThrowExceptionWhenCalledWihtNullValue() { new Criteria("foo").intersects(null); } - /** - * @see DATAMONGO-1134 - */ - @Test + @Test // DATAMONGO-1134 public void intersectsShouldWrapGeoJsonTypeInGeometryCorrectly() { GeoJsonLineString lineString = new GeoJsonLineString(new Point(0, 0), new Point(10, 10)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java index 97384eb541..b4c7013f76 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/IndexUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2017 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. @@ -76,10 +76,7 @@ public void testGeospatialIndex() { assertEquals("{ \"min\" : 0}", i.getIndexOptions().toString()); } - /** - * @see DATAMONGO-778 - */ - @Test + @Test // DATAMONGO-778 public void testGeospatialIndex2DSphere() { GeospatialIndex i = new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_2DSPHERE); @@ -87,10 +84,7 @@ public void testGeospatialIndex2DSphere() { assertEquals("{ }", i.getIndexOptions().toString()); } - /** - * @see DATAMONGO-778 - */ - @Test + @Test // DATAMONGO-778 public void testGeospatialIndexGeoHaystack() { GeospatialIndex i = new GeospatialIndex("location").typed(GeoSpatialIndexType.GEO_HAYSTACK) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java index 10232f56f7..b89876ffc4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/NearQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 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. @@ -83,10 +83,7 @@ public void configuresResultMetricCorrectly() { assertThat(query.getMetric(), is((Metric) Metrics.MILES)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void shouldTakeSkipAndLimitSettingsFromGivenPageable() { Pageable pageable = new PageRequest(3, 5); @@ -96,10 +93,7 @@ public void shouldTakeSkipAndLimitSettingsFromGivenPageable() { assertThat((Integer) query.toDBObject().get("num"), is((pageable.getPageNumber() + 1) * pageable.getPageSize())); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void shouldTakeSkipAndLimitSettingsFromGivenQuery() { int limit = 10; @@ -111,10 +105,7 @@ public void shouldTakeSkipAndLimitSettingsFromGivenQuery() { assertThat((Integer) query.toDBObject().get("num"), is(limit)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void shouldTakeSkipAndLimitSettingsFromPageableEvenIfItWasSpecifiedOnQuery() { int limit = 10; @@ -127,28 +118,19 @@ public void shouldTakeSkipAndLimitSettingsFromPageableEvenIfItWasSpecifiedOnQuer assertThat((Integer) query.toDBObject().get("num"), is((pageable.getPageNumber() + 1) * pageable.getPageSize())); } - /** - * @see DATAMONGO-829 - */ - @Test + @Test // DATAMONGO-829 public void nearQueryShouldInoreZeroLimitFromQuery() { NearQuery query = NearQuery.near(new Point(1, 2)).query(Query.query(Criteria.where("foo").is("bar"))); assertThat(query.toDBObject().get("num"), nullValue()); } - /** - * @see DATAMONOGO-829 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONOGO-829 public void nearQueryShouldThrowExceptionWhenGivenANullQuery() { NearQuery.near(new Point(1, 2)).query(null); } - /** - * @see DATAMONGO-829 - */ - @Test + @Test // DATAMONGO-829 public void numShouldNotBeAlteredByQueryWithoutPageable() { int num = 100; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java index d1b4b28c90..a152374e16 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/QueryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2017 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. @@ -108,10 +108,7 @@ public void testQueryWithFieldsAndSlice() { Assert.assertEquals(expectedFields, q.getFieldsObject().toString()); } - /** - * @see DATAMONGO-652 - */ - @Test + @Test // DATAMONGO-652 public void testQueryWithFieldsElemMatchAndPositionalOperator() { Query query = query(where("name").gte("M").lte("T").and("age").not().gt(22)); @@ -179,10 +176,7 @@ public void testQueryWithRegexAndOption() { Assert.assertEquals(expected, q.getQueryObject().toString()); } - /** - * @see DATAMONGO-538 - */ - @Test + @Test // DATAMONGO-538 public void addsSortCorrectly() { Query query = new Query().with(new Sort(Direction.DESC, "foo")); @@ -198,10 +192,7 @@ public void rejectsOrderWithIgnoreCase() { new Query().with(new Sort(new Sort.Order("foo").ignoreCase())); } - /** - * @see DATAMONGO-709 - */ - @Test + @Test // DATAMONGO-709 @SuppressWarnings("unchecked") public void shouldReturnClassHierarchyOfRestrictedTypes() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/SortTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/SortTests.java index 6450ac7d62..701193bd2a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/SortTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/SortTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2017 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. @@ -41,10 +41,7 @@ public void testWithSortDescending() { assertEquals("{ \"name\" : -1}", s.getSortObject().toString()); } - /** - * @see DATADOC-177 - */ - @Test + @Test // DATADOC-177 public void preservesOrderKeysOnMultipleSorts() { Query sort = new Query().with(new Sort(Direction.DESC, "foo").and(new Sort(Direction.DESC, "bar"))); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java index 64d96ff485..8b650f5d9f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextCriteriaUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -33,50 +33,35 @@ */ public class TextCriteriaUnitTests { - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotHaveLanguageField() { TextCriteria criteria = TextCriteria.forDefaultLanguage(); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotHaveLanguageForNonDefaultLanguageField() { TextCriteria criteria = TextCriteria.forLanguage("spanish"); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ \"$language\" : \"spanish\" }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateSearchFieldForSingleTermCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("cake"); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ \"$search\" : \"cake\" }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateSearchFieldCorrectlyForMultipleTermsCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny("bake", "coffee", "cake"); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ \"$search\" : \"bake coffee cake\" }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateSearchFieldForPhraseCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingPhrase("coffee cake"); @@ -84,30 +69,21 @@ public void shouldCreateSearchFieldForPhraseCorrectly() { equalTo((DBObject) new BasicDBObject("$search", "\"coffee cake\""))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateNotFieldCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatching("cake"); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ \"$search\" : \"-cake\" }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateSearchFieldCorrectlyForNotMultipleTermsCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatchingAny("bake", "coffee", "cake"); assertThat(criteria.getCriteriaObject(), equalTo(searchObject("{ \"$search\" : \"-bake -coffee -cake\" }"))); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateSearchFieldForNotPhraseCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().notMatchingPhrase("coffee cake"); @@ -115,10 +91,7 @@ public void shouldCreateSearchFieldForNotPhraseCorrectly() { equalTo((DBObject) new BasicDBObject("$search", "-\"coffee cake\""))); } - /** - * @see DATAMONGO-1455 - */ - @Test + @Test // DATAMONGO-1455 public void caseSensitiveOperatorShouldBeSetCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("coffee").caseSensitive(true); @@ -126,10 +99,7 @@ public void caseSensitiveOperatorShouldBeSetCorrectly() { equalTo(new BasicDBObjectBuilder().add("$search", "coffee").add("$caseSensitive", true).get())); } - /** - * @see DATAMONGO-1456 - */ - @Test + @Test // DATAMONGO-1456 public void diacriticSensitiveOperatorShouldBeSetCorrectly() { TextCriteria criteria = TextCriteria.forDefaultLanguage().matching("coffee").diacriticSensitive(true); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryTests.java index 94b0637ef8..c8d28c6116 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -104,10 +104,7 @@ private DBObject weights() { }); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldOnlyFindDocumentsMatchingAnyWordOfGivenQuery() { initWithDefaultDocuments(); @@ -117,10 +114,7 @@ public void shouldOnlyFindDocumentsMatchingAnyWordOfGivenQuery() { assertThat(result, hasItems(BAKE, COFFEE, CAKE)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotFindDocumentsWhenQueryDoesNotMatchAnyDocumentInIndex() { initWithDefaultDocuments(); @@ -129,10 +123,7 @@ public void shouldNotFindDocumentsWhenQueryDoesNotMatchAnyDocumentInIndex() { assertThat(result, hasSize(0)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldApplySortByScoreCorrectly() { initWithDefaultDocuments(); @@ -147,10 +138,7 @@ public void shouldApplySortByScoreCorrectly() { assertThat(result.get(3), equalTo(CAKE)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldFindTextInAnyLanguage() { initWithDefaultDocuments(); @@ -159,10 +147,7 @@ public void shouldFindTextInAnyLanguage() { assertThat(result, hasItems(SPANISH_MILK, FRENCH_MILK)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldOnlyFindTextInSpecificLanguage() { initWithDefaultDocuments(); @@ -172,10 +157,7 @@ public void shouldOnlyFindTextInSpecificLanguage() { assertThat(result.get(0), equalTo(SPANISH_MILK)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotFindDocumentsWithNegatedTerms() { initWithDefaultDocuments(); @@ -185,10 +167,7 @@ public void shouldNotFindDocumentsWithNegatedTerms() { assertThat(result, hasItems(BAKE, COFFEE)); } - /** - * @see DATAMONGO-976 - */ - @Test + @Test // DATAMONGO-976 public void shouldInlcudeScoreCorreclty() { initWithDefaultDocuments(); @@ -202,10 +181,7 @@ public void shouldInlcudeScoreCorreclty() { } } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldApplyPhraseCorrectly() { initWithDefaultDocuments(); @@ -217,10 +193,7 @@ public void shouldApplyPhraseCorrectly() { assertThat(result, contains(MILK_AND_SUGAR)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldReturnEmptyListWhenNoDocumentsMatchGivenPhrase() { initWithDefaultDocuments(); @@ -231,10 +204,7 @@ public void shouldReturnEmptyListWhenNoDocumentsMatchGivenPhrase() { assertThat(result, empty()); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldApplyPaginationCorrectly() { initWithDefaultDocuments(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java index 2a0fa9c0bc..b71b3c4ee5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/TextQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -32,34 +32,22 @@ public class TextQueryUnitTests { private static final String QUERY = "bake coffee cake"; private static final String LANGUAGE_SPANISH = "spanish"; - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldCreateQueryObjectCorrectly() { assertThat(new TextQuery(QUERY), isTextQuery().searchingFor(QUERY)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldIncludeLanguageInQueryObjectWhenNotNull() { assertThat(new TextQuery(QUERY, LANGUAGE_SPANISH), isTextQuery().searchingFor(QUERY).inLanguage(LANGUAGE_SPANISH)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldIncludeScoreFieldCorrectly() { assertThat(new TextQuery(QUERY).includeScore(), isTextQuery().searchingFor(QUERY).returningScore()); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotOverrideExistingProjections() { TextQuery query = new TextQuery(TextCriteria.forDefaultLanguage().matching(QUERY)).includeScore(); @@ -68,18 +56,12 @@ public void shouldNotOverrideExistingProjections() { assertThat(query, isTextQuery().searchingFor(QUERY).returningScore().includingField("foo")); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldIncludeSortingByScoreCorrectly() { assertThat(new TextQuery(QUERY).sortByScore(), isTextQuery().searchingFor(QUERY).returningScore().sortingByScore()); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldNotOverrideExistingSort() { TextQuery query = new TextQuery(QUERY); @@ -90,10 +72,7 @@ public void shouldNotOverrideExistingSort() { isTextQuery().searchingFor(QUERY).returningScore().sortingByScore().sortingBy("foo", Direction.DESC)); } - /** - * @see DATAMONGO-850 - */ - @Test + @Test // DATAMONGO-850 public void shouldUseCustomFieldnameForScoring() { TextQuery query = new TextQuery(QUERY).includeScore("customFieldForScore").sortByScore(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/UpdateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/UpdateTests.java index 47cc8fee91..1325bb8001 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/UpdateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/UpdateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 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. @@ -104,10 +104,7 @@ public void testPushAll() { is("{ \"$pushAll\" : { \"authors\" : [ { \"name\" : \"Sven\"} , { \"name\" : \"Maria\"}]}}")); } - /** - * @see DATAMONGO-354 - */ - @Test + @Test // DATAMONGO-354 public void testMultiplePushAllShouldBePossibleWhenUsingDifferentFields() { Map m1 = Collections.singletonMap("name", "Sven"); @@ -177,78 +174,54 @@ public void testBasicUpdateIncAndSet() { is("{ \"$inc\" : { \"size\" : 1} , \"$set\" : { \"directory\" : \"/Users/Test/Desktop\"}}")); } - /** - * @see DATAMONGO-630 - */ - @Test + @Test // DATAMONGO-630 public void testSetOnInsert() { Update u = new Update().setOnInsert("size", 1); assertThat(u.getUpdateObject().toString(), is("{ \"$setOnInsert\" : { \"size\" : 1}}")); } - /** - * @see DATAMONGO-630 - */ - @Test + @Test // DATAMONGO-630 public void testSetOnInsertSetOnInsert() { Update u = new Update().setOnInsert("size", 1).setOnInsert("count", 1); assertThat(u.getUpdateObject().toString(), is("{ \"$setOnInsert\" : { \"size\" : 1 , \"count\" : 1}}")); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnTrueWhenMultiFieldOperationAddedForField() { Update update = new Update().set("foo", "bar"); assertThat(update.modifies("foo"), is(true)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnFalseWhenMultiFieldOperationAddedForField() { Update update = new Update().set("foo", "bar"); assertThat(update.modifies("oof"), is(false)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnTrueWhenSingleFieldOperationAddedForField() { Update update = new Update().pullAll("foo", new Object[] { "bar" }); assertThat(update.modifies("foo"), is(true)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnFalseWhenSingleFieldOperationAddedForField() { Update update = new Update().pullAll("foo", new Object[] { "bar" }); assertThat(update.modifies("oof"), is(false)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnFalseWhenCalledOnEmptyUpdate() { assertThat(new Update().modifies("foo"), is(false)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnTrueWhenUpdateWithKeyCreatedFromDbObject() { Update update = new Update().set("foo", "bar"); @@ -257,10 +230,7 @@ public void testUpdateAffectsFieldShouldReturnTrueWhenUpdateWithKeyCreatedFromDb assertThat(clone.modifies("foo"), is(true)); } - /** - * @see DATAMONGO-852 - */ - @Test + @Test // DATAMONGO-852 public void testUpdateAffectsFieldShouldReturnFalseWhenUpdateWithoutKeyCreatedFromDbObject() { Update update = new Update().set("foo", "bar"); @@ -269,34 +239,22 @@ public void testUpdateAffectsFieldShouldReturnFalseWhenUpdateWithoutKeyCreatedFr assertThat(clone.modifies("oof"), is(false)); } - /** - * @see DATAMONGO-853 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-853 public void testAddingMultiFieldOperationThrowsExceptionWhenCalledWithNullKey() { new Update().addMultiFieldOperation("$op", null, "exprected to throw IllegalArgumentException."); } - /** - * @see DATAMONGO-853 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-853 public void testAddingSingleFieldOperationThrowsExceptionWhenCalledWithNullKey() { new Update().addFieldOperation("$op", null, "exprected to throw IllegalArgumentException."); } - /** - * @see DATAMONGO-853 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-853 public void testCreatingUpdateWithNullKeyThrowsException() { Update.update(null, "value"); } - /** - * @see DATAMONGO-953 - */ - @Test + @Test // DATAMONGO-953 public void testEquality() { Update actualUpdate = new Update() // @@ -321,10 +279,7 @@ public void testEquality() { assertThat(actualUpdate.hashCode(), is(equalTo(expectedUpdate.hashCode()))); } - /** - * @see DATAMONGO-953 - */ - @Test + @Test // DATAMONGO-953 public void testToString() { Update actualUpdate = new Update() // @@ -351,10 +306,7 @@ public void testToString() { + ", \"$pop\" : { \"authors\" : -1}}")); // } - /** - * @see DATAMONGO-944 - */ - @Test + @Test // DATAMONGO-944 public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsingDate() { Update update = new Update().currentDate("foo"); @@ -362,10 +314,7 @@ public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsi equalTo(new BasicDBObjectBuilder().add("$currentDate", new BasicDBObject("foo", true)).get())); } - /** - * @see DATAMONGO-944 - */ - @Test + @Test // DATAMONGO-944 public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhenUsingDate() { Update update = new Update().currentDate("foo").currentDate("bar"); @@ -373,10 +322,7 @@ public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhen new BasicDBObjectBuilder().add("$currentDate", new BasicDBObject("foo", true).append("bar", true)).get())); } - /** - * @see DATAMONGO-944 - */ - @Test + @Test // DATAMONGO-944 public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsingTimestamp() { Update update = new Update().currentTimestamp("foo"); @@ -384,10 +330,7 @@ public void getUpdateObjectShouldReturnCurrentDateCorrectlyForSingleFieldWhenUsi .add("$currentDate", new BasicDBObject("foo", new BasicDBObject("$type", "timestamp"))).get())); } - /** - * @see DATAMONGO-944 - */ - @Test + @Test // DATAMONGO-944 public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhenUsingTimestamp() { Update update = new Update().currentTimestamp("foo").currentTimestamp("bar"); @@ -398,10 +341,7 @@ public void getUpdateObjectShouldReturnCurrentDateCorrectlyForMultipleFieldsWhen .get())); } - /** - * @see DATAMONGO-944 - */ - @Test + @Test // DATAMONGO-944 public void getUpdateObjectShouldReturnCurrentDateCorrectlyWhenUsingMixedDateAndTimestamp() { Update update = new Update().currentDate("foo").currentTimestamp("bar"); @@ -411,28 +351,19 @@ public void getUpdateObjectShouldReturnCurrentDateCorrectlyWhenUsingMixedDateAnd .get())); } - /** - * @see DATAMONGO-1002 - */ - @Test + @Test // DATAMONGO-1002 public void toStringWorksForUpdateWithComplexObject() { Update update = new Update().addToSet("key", new DateTime()); assertThat(update.toString(), is(notNullValue())); } - /** - * @see DATAMONGO-1097 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1097 public void multiplyShouldThrowExceptionForNullMultiplier() { new Update().multiply("key", null); } - /** - * @see DATAMONGO-1097 - */ - @Test + @Test // DATAMONGO-1097 public void multiplyShouldAddMultiplierAsItsDoubleValue() { Update update = new Update().multiply("key", 10); @@ -441,10 +372,7 @@ public void multiplyShouldAddMultiplierAsItsDoubleValue() { equalTo(new BasicDBObjectBuilder().add("$mul", new BasicDBObject("key", 10D)).get())); } - /** - * @see DATAMONGO-1101 - */ - @Test + @Test // DATAMONGO-1101 public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseAnd() { Update update = new Update().bitwise("key").and(10L); @@ -453,10 +381,7 @@ public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseAnd() { equalTo(new BasicDBObjectBuilder().add("$bit", new BasicDBObject("key", new BasicDBObject("and", 10L))).get())); } - /** - * @see DATAMONGO-1101 - */ - @Test + @Test // DATAMONGO-1101 public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseOr() { Update update = new Update().bitwise("key").or(10L); @@ -465,10 +390,7 @@ public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseOr() { equalTo(new BasicDBObjectBuilder().add("$bit", new BasicDBObject("key", new BasicDBObject("or", 10L))).get())); } - /** - * @see DATAMONGO-1101 - */ - @Test + @Test // DATAMONGO-1101 public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseXor() { Update update = new Update().bitwise("key").xor(10L); @@ -477,18 +399,12 @@ public void getUpdateObjectShouldReturnCorrectRepresentationForBitwiseXor() { equalTo(new BasicDBObjectBuilder().add("$bit", new BasicDBObject("key", new BasicDBObject("xor", 10L))).get())); } - /** - * @see DATAMONGO-943 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-943 public void pushShouldThrowExceptionWhenGivenNegativePosition() { new Update().push("foo").atPosition(-1).each("booh"); } - /** - * @see DATAMONGO-1346 - */ - @Test + @Test // DATAMONGO-1346 public void registersMultiplePullAllClauses() { Update update = new Update(); @@ -503,26 +419,17 @@ public void registersMultiplePullAllClauses() { assertThat(pullAll.get("field2"), is(notNullValue())); } - /** - * @see DATAMONGO-1404 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1404 public void maxShouldThrowExceptionForNullMultiplier() { new Update().max("key", null); } - /** - * @see DATAMONGO-1404 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1404 public void minShouldThrowExceptionForNullMultiplier() { new Update().min("key", null); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void getUpdateObjectShouldReturnCorrectRepresentationForMax() { Update update = new Update().max("key", 10); @@ -531,10 +438,7 @@ public void getUpdateObjectShouldReturnCorrectRepresentationForMax() { equalTo(new BasicDBObjectBuilder().add("$max", new BasicDBObject("key", 10)).get())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void getUpdateObjectShouldReturnCorrectRepresentationForMin() { Update update = new Update().min("key", 10); @@ -543,10 +447,7 @@ public void getUpdateObjectShouldReturnCorrectRepresentationForMin() { equalTo(new BasicDBObjectBuilder().add("$min", new BasicDBObject("key", 10)).get())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void shouldSuppressPreviousValueForMax() { Update update = new Update().max("key", 10); @@ -556,10 +457,7 @@ public void shouldSuppressPreviousValueForMax() { equalTo(new BasicDBObjectBuilder().add("$max", new BasicDBObject("key", 99)).get())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void shouldSuppressPreviousValueForMin() { Update update = new Update().min("key", 10); @@ -569,10 +467,7 @@ public void shouldSuppressPreviousValueForMin() { equalTo(new BasicDBObjectBuilder().add("$min", new BasicDBObject("key", 99)).get())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void getUpdateObjectShouldReturnCorrectDateRepresentationForMax() { Date date = new Date(); @@ -582,10 +477,7 @@ public void getUpdateObjectShouldReturnCorrectDateRepresentationForMax() { equalTo(new BasicDBObjectBuilder().add("$max", new BasicDBObject("key", date)).get())); } - /** - * @see DATAMONGO-1404 - */ - @Test + @Test // DATAMONGO-1404 public void getUpdateObjectShouldReturnCorrectDateRepresentationForMin() { Date date = new Date(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java index a8fda1fad1..50142e3dcf 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/ExecutableMongoScriptUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -31,10 +31,7 @@ public class ExecutableMongoScriptUnitTests { public @Rule ExpectedException expectedException = ExpectedException.none(); - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void constructorShouldThrowExceptionWhenRawScriptIsNull() { expectException(IllegalArgumentException.class, "must not be", "null"); @@ -42,10 +39,7 @@ public void constructorShouldThrowExceptionWhenRawScriptIsNull() { new ExecutableMongoScript(null); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void constructorShouldThrowExceptionWhenRawScriptIsEmpty() { expectException(IllegalArgumentException.class, "must not be", "empty"); @@ -53,10 +47,7 @@ public void constructorShouldThrowExceptionWhenRawScriptIsEmpty() { new ExecutableMongoScript(""); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void getCodeShouldReturnCodeRepresentationOfRawScript() { String jsFunction = "function(x) { return x; }"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/NamedMongoScriptUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/NamedMongoScriptUnitTests.java index 32803881dc..e22918a25a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/NamedMongoScriptUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/script/NamedMongoScriptUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2017 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. @@ -29,34 +29,22 @@ */ public class NamedMongoScriptUnitTests { - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void shouldThrowExceptionWhenScriptNameIsNull() { new NamedMongoScript(null, "return 1;"); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void shouldThrowExceptionWhenScriptNameIsEmptyString() { new NamedMongoScript("", "return 1"); } - /** - * @see DATAMONGO-479 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-479 public void shouldThrowExceptionWhenRawScriptIsEmptyString() { new NamedMongoScript("foo", ""); } - /** - * @see DATAMONGO-479 - */ - @Test + @Test // DATAMONGO-479 public void getCodeShouldReturnCodeRepresentationOfRawScript() { String jsFunction = "function(x) { return x; }"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/spel/ExpressionNodeUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/spel/ExpressionNodeUnitTests.java index 0f6d4b9b09..fce53fb054 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/spel/ExpressionNodeUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/spel/ExpressionNodeUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -36,7 +36,6 @@ /** * Unit tests for {@link ExpressionNode}. * - * @see DATAMONGO-774 * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) @@ -56,7 +55,7 @@ public void setUp() { this.operators = Arrays.asList(minus, plus, divide, multiply); } - @Test + @Test // DATAMONGO-774 public void createsOperatorNodeForOperations() { for (SpelNode operator : operators) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/gridfs/GridFsTemplateIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/gridfs/GridFsTemplateIntegrationTests.java index 6803069c6e..1f2fed1d04 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/gridfs/GridFsTemplateIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/gridfs/GridFsTemplateIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 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. @@ -61,10 +61,7 @@ public void setUp() { operations.delete(null); } - /** - * @see DATAMONGO-6 - */ - @Test + @Test // DATAMONGO-6 public void storesAndFindsSimpleDocument() throws IOException { GridFSFile reference = operations.store(resource.getInputStream(), "foo.xml"); @@ -74,10 +71,7 @@ public void storesAndFindsSimpleDocument() throws IOException { assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-6 - */ - @Test + @Test // DATAMONGO-6 public void writesMetadataCorrectly() throws IOException { DBObject metadata = new BasicDBObject("key", "value"); @@ -88,10 +82,7 @@ public void writesMetadataCorrectly() throws IOException { assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-6 - */ - @Test + @Test // DATAMONGO-6 public void marshalsComplexMetadata() throws IOException { Metadata metadata = new Metadata(); @@ -103,10 +94,7 @@ public void marshalsComplexMetadata() throws IOException { assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-6 - */ - @Test + @Test // DATAMONGO-6 public void findsFilesByResourcePattern() throws IOException { GridFSFile reference = operations.store(resource.getInputStream(), "foo.xml"); @@ -118,10 +106,7 @@ public void findsFilesByResourcePattern() throws IOException { assertThat(resources[0].getContentType(), is(reference.getContentType())); } - /** - * @see DATAMONGO-6 - */ - @Test + @Test // DATAMONGO-6 public void findsFilesByResourceLocation() throws IOException { GridFSFile reference = operations.store(resource.getInputStream(), "foo.xml"); @@ -133,10 +118,7 @@ public void findsFilesByResourceLocation() throws IOException { assertThat(resources[0].getContentType(), is(reference.getContentType())); } - /** - * @see DATAMONGO-503 - */ - @Test + @Test // DATAMONGO-503 public void storesContentType() throws IOException { GridFSFile reference = operations.store(resource.getInputStream(), "foo2.xml", "application/xml"); @@ -146,10 +128,7 @@ public void storesContentType() throws IOException { assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-534 - */ - @Test + @Test // DATAMONGO-534 public void considersSortWhenQueryingFiles() throws IOException { GridFSFile second = operations.store(resource.getInputStream(), "foo.xml"); @@ -165,10 +144,7 @@ public void considersSortWhenQueryingFiles() throws IOException { assertSame(result.get(2), third); } - /** - * @see DATAMONGO-534 - */ - @Test + @Test // DATAMONGO-534 public void queryingWithNullQueryReturnsAllFiles() throws IOException { GridFSFile reference = operations.store(resource.getInputStream(), "foo.xml"); @@ -179,18 +155,12 @@ public void queryingWithNullQueryReturnsAllFiles() throws IOException { assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-813 - */ - @Test + @Test // DATAMONGO-813 public void getResourceShouldReturnNullForNonExistingResource() { assertThat(operations.getResource("doesnotexist"), is(nullValue())); } - /** - * @see DATAMONGO-809 - */ - @Test + @Test // DATAMONGO-809 public void storesAndFindsSimpleDocumentWithMetadataDBObject() throws IOException { DBObject metadata = new BasicDBObject("key", "value"); @@ -202,10 +172,7 @@ public void storesAndFindsSimpleDocumentWithMetadataDBObject() throws IOExceptio assertSame(result.get(0), reference); } - /** - * @see DATAMONGO-809 - */ - @Test + @Test // DATAMONGO-809 public void storesAndFindsSimpleDocumentWithMetadataObject() throws IOException { Metadata metadata = new Metadata(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java index da9290058f..5d07d58bad 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 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. @@ -15,11 +15,11 @@ */ package org.springframework.data.mongodb.monitor; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -import java.net.UnknownHostException; - +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.net.UnknownHostException; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -28,19 +28,19 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import com.mongodb.Mongo; - +import com.mongodb.Mongo; + /** * This test class assumes that you are already running the MongoDB server. * * @author Mark Pollack - * @author Thomas Darimont + * @author Thomas Darimont */ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration("classpath:infrastructure.xml") +@ContextConfiguration("classpath:infrastructure.xml") public class MongoMonitorIntegrationTests { - @Autowired Mongo mongo; + @Autowired Mongo mongo; @Test public void serverInfo() { @@ -49,29 +49,25 @@ public void serverInfo() { Assert.isTrue(StringUtils.hasText("1.")); } - /** - * @throws UnknownHostException - * @see DATAMONGO-685 - */ - @Test - public void getHostNameShouldReturnServerNameReportedByMongo() throws UnknownHostException { - - ServerInfo serverInfo = new ServerInfo(mongo); - - String hostName = null; - try { - hostName = serverInfo.getHostName(); - } catch (UnknownHostException e) { - throw e; - } - - assertThat(hostName, is(notNullValue())); - assertThat(hostName, is("127.0.0.1")); - } - + @Test // DATAMONGO-685 + public void getHostNameShouldReturnServerNameReportedByMongo() throws UnknownHostException { + + ServerInfo serverInfo = new ServerInfo(mongo); + + String hostName = null; + try { + hostName = serverInfo.getHostName(); + } catch (UnknownHostException e) { + throw e; + } + + assertThat(hostName, is(notNullValue())); + assertThat(hostName, is("127.0.0.1")); + } + @Test public void operationCounters() { OperationCounters operationCounters = new OperationCounters(mongo); operationCounters.getInsertCount(); } -} +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 07ed39b7ca..7544ae64d2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -326,10 +326,7 @@ public void findsPagedPeopleByPredicate() throws Exception { assertThat(page, hasItems(carter, stefan)); } - /** - * @see DATADOC-136 - */ - @Test + @Test // DATADOC-136 public void findsPeopleBySexCorrectly() { List females = repository.findBySex(Sex.FEMALE); @@ -337,10 +334,7 @@ public void findsPeopleBySexCorrectly() { assertThat(females.get(0), is(alicia)); } - /** - * @see DATAMONGO-446 - */ - @Test + @Test // DATAMONGO-446 public void findsPeopleBySexPaginated() { List males = repository.findBySex(Sex.MALE, new PageRequest(0, 2)); @@ -354,10 +348,7 @@ public void findsPeopleByNamedQuery() { assertThat(result, hasItem(dave)); } - /** - * @see DATADOC-190 - */ - @Test + @Test // DATADOC-190 public void existsWorksCorrectly() { assertThat(repository.exists(dave.getId()), is(true)); } @@ -373,10 +364,7 @@ public void rejectsDuplicateEmailAddressOnSave() { repository.save(daveSyer); } - /** - * @see DATADOC-236 - */ - @Test + @Test // DATADOC-236 public void findsPeopleByLastnameAndOrdersCorrectly() { List result = repository.findByLastnameOrderByFirstnameAsc("Matthews"); assertThat(result.size(), is(2)); @@ -384,10 +372,7 @@ public void findsPeopleByLastnameAndOrdersCorrectly() { assertThat(result.get(1), is(oliver)); } - /** - * @see DATADOC-236 - */ - @Test + @Test // DATADOC-236 public void appliesStaticAndDynamicSorting() { List result = repository.findByFirstnameLikeOrderByLastnameAsc("*e*", new Sort("age")); assertThat(result.size(), is(5)); @@ -425,10 +410,7 @@ public void executesGeoPageQueryForResultsCorrectly() { assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-323 - */ - @Test + @Test // DATAMONGO-323 public void considersSortForAnnotatedQuery() { List result = repository.findByAgeLessThan(60, new Sort("firstname")); @@ -443,10 +425,7 @@ public void considersSortForAnnotatedQuery() { assertThat(result.get(6), is(stefan)); } - /** - * @see DATAMONGO-347 - */ - @Test + @Test // DATAMONGO-347 public void executesQueryWithDBRefReferenceCorrectly() { operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class); @@ -464,10 +443,7 @@ public void executesQueryWithDBRefReferenceCorrectly() { assertThat(result, hasItem(dave)); } - /** - * @see DATAMONGO-425 - */ - @Test + @Test // DATAMONGO-425 public void bindsDateParameterForLessThanPredicateCorrectly() { List result = repository.findByCreatedAtLessThan(boyd.createdAt); @@ -475,10 +451,7 @@ public void bindsDateParameterForLessThanPredicateCorrectly() { assertThat(result, hasItems(dave, oliver, carter)); } - /** - * @see DATAMONGO-425 - */ - @Test + @Test // DATAMONGO-425 public void bindsDateParameterForGreaterThanPredicateCorrectly() { List result = repository.findByCreatedAtGreaterThan(carter.createdAt); @@ -486,10 +459,7 @@ public void bindsDateParameterForGreaterThanPredicateCorrectly() { assertThat(result, hasItems(boyd, stefan, leroi, alicia)); } - /** - * @see DATAMONGO-427 - */ - @Test + @Test // DATAMONGO-427 public void bindsDateParameterToBeforePredicateCorrectly() { List result = repository.findByCreatedAtBefore(boyd.createdAt); @@ -497,10 +467,7 @@ public void bindsDateParameterToBeforePredicateCorrectly() { assertThat(result, hasItems(dave, oliver, carter)); } - /** - * @see DATAMONGO-427 - */ - @Test + @Test // DATAMONGO-427 public void bindsDateParameterForAfterPredicateCorrectly() { List result = repository.findByCreatedAtAfter(carter.createdAt); @@ -508,20 +475,14 @@ public void bindsDateParameterForAfterPredicateCorrectly() { assertThat(result, hasItems(boyd, stefan, leroi, alicia)); } - /** - * @see DATAMONGO-425 - */ - @Test + @Test // DATAMONGO-425 public void bindsDateParameterForManuallyDefinedQueryCorrectly() { List result = repository.findByCreatedAtLessThanManually(boyd.createdAt); assertThat(result.isEmpty(), is(false)); } - /** - * @see DATAMONGO-472 - */ - @Test + @Test // DATAMONGO-472 public void findsPeopleUsingNotPredicate() { List result = repository.findByLastnameNot("Matthews"); @@ -529,10 +490,7 @@ public void findsPeopleUsingNotPredicate() { assertThat(result, hasSize(5)); } - /** - * @see DATAMONGO-521 - */ - @Test + @Test // DATAMONGO-521 public void executesAndQueryCorrectly() { List result = repository.findByFirstnameAndLastname("Dave", "Matthews"); @@ -546,10 +504,7 @@ public void executesAndQueryCorrectly() { assertThat(result, hasItem(oliver)); } - /** - * @see DATAMONGO-600 - */ - @Test + @Test // DATAMONGO-600 public void readsDocumentsWithNestedPolymorphismCorrectly() { UsernameAndPassword usernameAndPassword = new UsernameAndPassword(); @@ -565,52 +520,34 @@ public void readsDocumentsWithNestedPolymorphismCorrectly() { assertThat(result, hasItem(dave)); } - /** - * @see DATAMONGO-636 - */ - @Test + @Test // DATAMONGO-636 public void executesDerivedCountProjection() { assertThat(repository.countByLastname("Matthews"), is(2L)); } - /** - * @see DATAMONGO-636 - */ - @Test + @Test // DATAMONGO-636 public void executesDerivedCountProjectionToInt() { assertThat(repository.countByFirstname("Oliver August"), is(1)); } - /** - * @see DATAMONGO-636 - */ - @Test + @Test // DATAMONGO-636 public void executesAnnotatedCountProjection() { assertThat(repository.someCountQuery("Matthews"), is(2L)); } - /** - * @see DATAMONGO-1454 - */ - @Test + @Test // DATAMONGO-1454 public void executesDerivedExistsProjectionToBoolean() { assertThat(repository.existsByFirstname("Oliver August"), is(true)); assertThat(repository.existsByFirstname("Hans Peter"), is(false)); } - /** - * @see DATAMONGO-1454 - */ - @Test + @Test // DATAMONGO-1454 public void executesAnnotatedExistProjection() { assertThat(repository.someExistQuery("Matthews"), is(true)); } - /** - * @see DATAMONGO-701 - */ - @Test + @Test // DATAMONGO-701 public void executesDerivedStartsWithQueryCorrectly() { List result = repository.findByLastnameStartsWith("Matt"); @@ -618,10 +555,7 @@ public void executesDerivedStartsWithQueryCorrectly() { assertThat(result, hasItems(dave, oliver)); } - /** - * @see DATAMONGO-701 - */ - @Test + @Test // DATAMONGO-701 public void executesDerivedEndsWithQueryCorrectly() { List result = repository.findByLastnameEndsWith("thews"); @@ -629,10 +563,7 @@ public void executesDerivedEndsWithQueryCorrectly() { assertThat(result, hasItems(dave, oliver)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void executesGeoPageQueryForWithPageRequestForPageInBetween() { Point farAway = new Point(-73.9, 40.7); @@ -657,10 +588,7 @@ public void executesGeoPageQueryForWithPageRequestForPageInBetween() { assertThat(results.getAverageDistance().getNormalizedValue(), is(0.0)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void executesGeoPageQueryForWithPageRequestForPageAtTheEnd() { Point point = new Point(-73.99171, 40.738868); @@ -680,10 +608,7 @@ public void executesGeoPageQueryForWithPageRequestForPageAtTheEnd() { assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void executesGeoPageQueryForWithPageRequestForJustOneElement() { Point point = new Point(-73.99171, 40.738868); @@ -700,10 +625,7 @@ public void executesGeoPageQueryForWithPageRequestForJustOneElement() { assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-445 - */ - @Test + @Test // DATAMONGO-445 public void executesGeoPageQueryForWithPageRequestForJustOneElementEmptyPage() { dave.setLocation(new Point(-73.99171, 40.738868)); @@ -719,10 +641,7 @@ public void executesGeoPageQueryForWithPageRequestForJustOneElementEmptyPage() { assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void findByFirstNameIgnoreCase() { List result = repository.findByFirstnameIgnoreCase("dave"); @@ -731,10 +650,7 @@ public void findByFirstNameIgnoreCase() { assertThat(result.get(0), is(dave)); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void findByFirstnameNotIgnoreCase() { List result = repository.findByFirstnameNotIgnoreCase("dave"); @@ -743,10 +659,7 @@ public void findByFirstnameNotIgnoreCase() { assertThat(result, not(hasItem(dave))); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void findByFirstnameStartingWithIgnoreCase() { List result = repository.findByFirstnameStartingWithIgnoreCase("da"); @@ -754,10 +667,7 @@ public void findByFirstnameStartingWithIgnoreCase() { assertThat(result.get(0), is(dave)); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void findByFirstnameEndingWithIgnoreCase() { List result = repository.findByFirstnameEndingWithIgnoreCase("VE"); @@ -765,10 +675,7 @@ public void findByFirstnameEndingWithIgnoreCase() { assertThat(result.get(0), is(dave)); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void findByFirstnameContainingIgnoreCase() { List result = repository.findByFirstnameContainingIgnoreCase("AV"); @@ -776,10 +683,7 @@ public void findByFirstnameContainingIgnoreCase() { assertThat(result.get(0), is(dave)); } - /** - * @see DATAMONGO-870 - */ - @Test + @Test // DATAMONGO-870 public void findsSliceOfPersons() { Slice result = repository.findByAgeGreaterThan(40, new PageRequest(0, 2, Direction.DESC, "firstname")); @@ -787,10 +691,7 @@ public void findsSliceOfPersons() { assertThat(result.hasNext(), is(true)); } - /** - * @see DATAMONGO-871 - */ - @Test + @Test // DATAMONGO-871 public void findsPersonsByFirstnameAsArray() { Person[] result = repository.findByThePersonsFirstnameAsArray("Leroi"); @@ -799,10 +700,7 @@ public void findsPersonsByFirstnameAsArray() { assertThat(result, is(arrayContaining(leroi))); } - /** - * @see DATAMONGO-821 - */ - @Test + @Test // DATAMONGO-821 public void findUsingAnnotatedQueryOnDBRef() { operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class); @@ -820,10 +718,7 @@ public void findUsingAnnotatedQueryOnDBRef() { assertThat(result.getContent().get(0), is(alicia)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByShouldReturnListOfDeletedElementsWhenRetunTypeIsCollectionLike() { List result = repository.deleteByLastname("Beauford"); @@ -832,10 +727,7 @@ public void deleteByShouldReturnListOfDeletedElementsWhenRetunTypeIsCollectionLi assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByShouldRemoveElementsMatchingDerivedQuery() { repository.deleteByLastname("Beauford"); @@ -843,34 +735,22 @@ public void deleteByShouldRemoveElementsMatchingDerivedQuery() { assertThat(operations.count(new BasicQuery("{'lastname':'Beauford'}"), Person.class), is(0L)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByShouldReturnNumberOfDocumentsRemovedIfReturnTypeIsLong() { assertThat(repository.deletePersonByLastname("Beauford"), is(1L)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByShouldReturnZeroInCaseNoDocumentHasBeenRemovedAndReturnTypeIsNumber() { assertThat(repository.deletePersonByLastname("dorfuaeB"), is(0L)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByShouldReturnEmptyListInCaseNoDocumentHasBeenRemovedAndReturnTypeIsCollectionLike() { assertThat(repository.deleteByLastname("dorfuaeB"), empty()); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByUsingAnnotatedQueryShouldReturnListOfDeletedElementsWhenRetunTypeIsCollectionLike() { List result = repository.removeByLastnameUsingAnnotatedQuery("Beauford"); @@ -879,10 +759,7 @@ public void deleteByUsingAnnotatedQueryShouldReturnListOfDeletedElementsWhenRetu assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByUsingAnnotatedQueryShouldRemoveElementsMatchingDerivedQuery() { repository.removeByLastnameUsingAnnotatedQuery("Beauford"); @@ -890,18 +767,12 @@ public void deleteByUsingAnnotatedQueryShouldRemoveElementsMatchingDerivedQuery( assertThat(operations.count(new BasicQuery("{'lastname':'Beauford'}"), Person.class), is(0L)); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void deleteByUsingAnnotatedQueryShouldReturnNumberOfDocumentsRemovedIfReturnTypeIsLong() { assertThat(repository.removePersonByLastnameUsingAnnotatedQuery("Beauford"), is(1L)); } - /** - * @see DATAMONGO-893 - */ - @Test + @Test // DATAMONGO-893 public void findByNestedPropertyInCollectionShouldFindMatchingDocuments() { Person p = new Person("Mary", "Poppins"); @@ -915,10 +786,7 @@ public void findByNestedPropertyInCollectionShouldFindMatchingDocuments() { assertThat(result.getContent(), hasSize(1)); } - /** - * @see DATAMONGO-745 - */ - @Test + @Test // DATAMONGO-745 public void findByCustomQueryFirstnamesInListAndLastname() { repository.save(new Person("foo", "bar")); @@ -934,10 +802,7 @@ public void findByCustomQueryFirstnamesInListAndLastname() { assertThat(result.getTotalElements(), is(3L)); } - /** - * @see DATAMONGO-745 - */ - @Test + @Test // DATAMONGO-745 public void findByCustomQueryLastnameAndStreetInList() { repository.save(new Person("foo", "bar").withAddress(new Address("street1", "1", "SB"))); @@ -954,10 +819,7 @@ public void findByCustomQueryLastnameAndStreetInList() { } - /** - * @see DATAMONGO-950 - */ - @Test + @Test // DATAMONGO-950 public void shouldLimitCollectionQueryToMaxResultsWhenPresent() { repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), @@ -966,10 +828,7 @@ public void shouldLimitCollectionQueryToMaxResultsWhenPresent() { assertThat(result.size(), is(3)); } - /** - * @see DATAMONGO-950, DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-950, DATAMONGO-1464 public void shouldNotLimitPagedQueryWhenPageRequestWithinBounds() { repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), @@ -979,10 +838,7 @@ public void shouldNotLimitPagedQueryWhenPageRequestWithinBounds() { assertThat(result.getTotalElements(), is(3L)); } - /** - * @see DATAMONGO-950 - */ - @Test + @Test // DATAMONGO-950 public void shouldLimitPagedQueryWhenPageRequestExceedsUpperBoundary() { repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), @@ -991,10 +847,7 @@ public void shouldLimitPagedQueryWhenPageRequestExceedsUpperBoundary() { assertThat(result.getContent().size(), is(1)); } - /** - * @see DATAMONGO-950, DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-950, DATAMONGO-1464 public void shouldReturnEmptyWhenPageRequestedPageIsTotallyOutOfScopeForLimit() { repository.save(Arrays.asList(new Person("Bob-1", "Dylan"), new Person("Bob-2", "Dylan"), @@ -1004,10 +857,7 @@ public void shouldReturnEmptyWhenPageRequestedPageIsTotallyOutOfScopeForLimit() assertThat(result.getTotalElements(), is(3L)); } - /** - * @see DATAMONGO-996, DATAMONGO-950, DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-996, DATAMONGO-950, DATAMONGO-1464 public void gettingNonFirstPageWorksWithoutLimitBeingSet() { Page slice = repository.findByLastnameLike("Matthews", new PageRequest(1, 1)); @@ -1018,13 +868,8 @@ public void gettingNonFirstPageWorksWithoutLimitBeingSet() { assertThat(slice.getTotalElements(), is(2L)); } - /** - * Ignored for now as this requires Querydsl 3.4.1 to succeed. - * - * @see DATAMONGO-972 - */ - @Test - @Ignore + @Test // DATAMONGO-972 + @Ignore("Ignored for now as this requires Querydsl 3.4.1 to succeed.") public void shouldExecuteFindOnDbRefCorrectly() { operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class); @@ -1040,18 +885,12 @@ public void shouldExecuteFindOnDbRefCorrectly() { assertThat(repository.findOne(QPerson.person.creator.eq(user)), is(dave)); } - /** - * @see DATAMONGO-969 - */ - @Test + @Test // DATAMONGO-969 public void shouldFindPersonsWhenUsingQueryDslPerdicatedOnIdProperty() { assertThat(repository.findAll(person.id.in(Arrays.asList(dave.id, carter.id))), containsInAnyOrder(dave, carter)); } - /** - * @see DATAMONGO-1030 - */ - @Test + @Test // DATAMONGO-1030 public void executesSingleEntityQueryWithProjectionCorrectly() { PersonSummary result = repository.findSummaryByLastname("Beauford"); @@ -1061,10 +900,7 @@ public void executesSingleEntityQueryWithProjectionCorrectly() { assertThat(result.lastname, is("Beauford")); } - /** - * @see DATAMONGO-1057 - */ - @Test + @Test // DATAMONGO-1057 public void sliceShouldTraverseElementsWithoutSkippingOnes() { repository.deleteAll(); @@ -1084,10 +920,7 @@ public void sliceShouldTraverseElementsWithoutSkippingOnes() { assertThat(slice, contains(persons.subList(20, 40).toArray())); } - /** - * @see DATAMONGO-1072 - */ - @Test + @Test // DATAMONGO-1072 public void shouldBindPlaceholdersUsedAsKeysCorrectly() { List persons = repository.findByKeyValue("firstname", alicia.getFirstname()); @@ -1096,10 +929,7 @@ public void shouldBindPlaceholdersUsedAsKeysCorrectly() { assertThat(persons, hasItem(alicia)); } - /** - * @see DATAMONGO-1105 - */ - @Test + @Test // DATAMONGO-1105 public void returnsOrderedResultsForQuerydslOrderSpecifier() { Iterable result = repository.findAll(person.firstname.asc()); @@ -1107,10 +937,7 @@ public void returnsOrderedResultsForQuerydslOrderSpecifier() { assertThat(result, contains(alicia, boyd, carter, dave, leroi, oliver, stefan)); } - /** - * @see DATAMONGO-1085 - */ - @Test + @Test // DATAMONGO-1085 public void shouldSupportSortingByQueryDslOrderSpecifier() { repository.deleteAll(); @@ -1133,10 +960,7 @@ public void shouldSupportSortingByQueryDslOrderSpecifier() { assertThat(result.iterator().next().getFirstname(), is(persons.get(2).getFirstname())); } - /** - * @see DATAMONGO-1085 - */ - @Test + @Test // DATAMONGO-1085 public void shouldSupportSortingWithQSortByQueryDslOrderSpecifier() throws Exception { repository.deleteAll(); @@ -1158,10 +982,7 @@ public void shouldSupportSortingWithQSortByQueryDslOrderSpecifier() throws Excep assertThat(result.iterator().next().getFirstname(), is("Siggi 2")); } - /** - * @see DATAMONGO-1085 - */ - @Test + @Test // DATAMONGO-1085 public void shouldSupportSortingWithQSort() throws Exception { repository.deleteAll(); @@ -1182,10 +1003,7 @@ public void shouldSupportSortingWithQSort() throws Exception { assertThat(result.iterator().next().getFirstname(), is("Siggi 2")); } - /** - * @see DATAMONGO-1165 - */ - @Test + @Test // DATAMONGO-1165 public void shouldAllowReturningJava8StreamInCustomQuery() throws Exception { Stream result = repository.findByCustomQueryWithStreamingCursorByFirstnames(Arrays.asList("Dave")); @@ -1197,10 +1015,7 @@ public void shouldAllowReturningJava8StreamInCustomQuery() throws Exception { } } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void executesGeoNearQueryForResultsCorrectlyWhenGivenMinAndMaxDistance() { Point point = new Point(-73.99171, 40.738868); @@ -1213,10 +1028,7 @@ public void executesGeoNearQueryForResultsCorrectlyWhenGivenMinAndMaxDistance() assertThat(results.getContent().isEmpty(), is(false)); } - /** - * @see DATAMONGO-990 - */ - @Test + @Test // DATAMONGO-990 public void shouldFindByFirstnameForSpELExpressionWithParameterIndexOnly() { List users = repository.findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly("Dave"); @@ -1225,10 +1037,7 @@ public void shouldFindByFirstnameForSpELExpressionWithParameterIndexOnly() { assertThat(users.get(0), is(dave)); } - /** - * @see DATAMONGO-990 - */ - @Test + @Test // DATAMONGO-990 public void shouldFindByFirstnameAndCurrentUserWithCustomQuery() { SampleSecurityContextHolder.getCurrent().setPrincipal(dave); @@ -1238,10 +1047,7 @@ public void shouldFindByFirstnameAndCurrentUserWithCustomQuery() { assertThat(users.get(0), is(dave)); } - /** - * @see DATAMONGO-990 - */ - @Test + @Test // DATAMONGO-990 public void shouldFindByFirstnameForSpELExpressionWithParameterVariableOnly() { List users = repository.findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly("Dave"); @@ -1250,10 +1056,7 @@ public void shouldFindByFirstnameForSpELExpressionWithParameterVariableOnly() { assertThat(users.get(0), is(dave)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findByExampleShouldResolveStuffCorrectly() { Person sample = new Person(); @@ -1268,10 +1071,7 @@ public void findByExampleShouldResolveStuffCorrectly() { assertThat(result.getNumberOfElements(), is(2)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldResolveStuffCorrectly() { Person sample = new Person(); @@ -1286,10 +1086,7 @@ public void findAllByExampleShouldResolveStuffCorrectly() { assertThat(result.size(), is(2)); } - /** - * @see DATAMONGO-1425 - */ - @Test + @Test // DATAMONGO-1425 public void findsPersonsByFirstnameNotContains() throws Exception { List result = repository.findByFirstnameNotContains("Boyd"); @@ -1297,10 +1094,7 @@ public void findsPersonsByFirstnameNotContains() throws Exception { assertThat(result, not(hasItem(boyd))); } - /** - * @see DATAMONGO-1425 - */ - @Test + @Test // DATAMONGO-1425 public void findBySkillsContains() throws Exception { List result = repository.findBySkillsContains(Arrays.asList("Drums")); @@ -1308,10 +1102,7 @@ public void findBySkillsContains() throws Exception { assertThat(result, hasItem(carter)); } - /** - * @see DATAMONGO-1425 - */ - @Test + @Test // DATAMONGO-1425 public void findBySkillsNotContains() throws Exception { List result = repository.findBySkillsNotContains(Arrays.asList("Drums")); @@ -1319,10 +1110,7 @@ public void findBySkillsNotContains() throws Exception { assertThat(result, not(hasItem(carter))); } - /* - * @see DATAMONGO-1424 - */ - @Test + @Test // DATAMONGO-1424 public void findsPersonsByFirstnameNotLike() throws Exception { List result = repository.findByFirstnameNotLike("Bo*"); @@ -1330,18 +1118,12 @@ public void findsPersonsByFirstnameNotLike() throws Exception { assertThat(result, not(hasItem(boyd))); } - /** - * @see DATAMONGO-1539 - */ - @Test + @Test // DATAMONGO-1539 public void countsPersonsByFirstname() { assertThat(repository.countByThePersonsFirstname("Dave"), is(1L)); } - /** - * @see DATAMONGO-1539 - */ - @Test + @Test // DATAMONGO-1539 public void deletesPersonsByFirstname() { repository.deleteByThePersonsFirstname("Dave"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java index c788e4524f..f2ee84c26b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ComplexIdRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -82,10 +82,7 @@ public void setUp() { userWithId.id = id; } - /** - * @see DATAMONGO-1078 - */ - @Test + @Test // DATAMONGO-1078 public void annotatedFindQueryShouldWorkWhenUsingComplexId() { repo.save(userWithId); @@ -93,10 +90,7 @@ public void annotatedFindQueryShouldWorkWhenUsingComplexId() { assertThat(repo.getUserByComplexId(id), is(userWithId)); } - /** - * @see DATAMONGO-1078 - */ - @Test + @Test // DATAMONGO-1078 public void annotatedFindQueryShouldWorkWhenUsingComplexIdWithinCollection() { repo.save(userWithId); @@ -107,10 +101,7 @@ public void annotatedFindQueryShouldWorkWhenUsingComplexIdWithinCollection() { assertThat(loaded, contains(userWithId)); } - /** - * @see DATAMONGO-1078 - */ - @Test + @Test // DATAMONGO-1078 public void findOneShouldWorkWhenUsingComplexId() { repo.save(userWithId); @@ -118,10 +109,7 @@ public void findOneShouldWorkWhenUsingComplexId() { assertThat(repo.findOne(id), is(userWithId)); } - /** - * @see DATAMONGO-1078 - */ - @Test + @Test // DATAMONGO-1078 public void findAllShouldWorkWhenUsingComplexId() { repo.save(userWithId); @@ -132,10 +120,7 @@ public void findAllShouldWorkWhenUsingComplexId() { assertThat(loaded, contains(userWithId)); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void composedAnnotationFindQueryShouldWorkWhenUsingComplexId() { repo.save(userWithId); @@ -143,10 +128,7 @@ public void composedAnnotationFindQueryShouldWorkWhenUsingComplexId() { assertThat(repo.getUserUsingComposedAnnotationByComplexId(id), is(userWithId)); } - /** - * @see DATAMONGO-1373 - */ - @Test + @Test // DATAMONGO-1373 public void composedAnnotationFindMetaShouldWorkWhenUsingComplexId() { repo.save(userWithId); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java index 34722d7091..e504e22543 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ContactRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 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. @@ -52,10 +52,7 @@ public void readsAndWritesContactCorrectly() { assertTrue(repository.findOne(result.getId().toString()) instanceof Person); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findsContactByTypedExample() { Person person = repository.save(new Person("Oliver", "Gierke")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java index 05305d9a90..b3b439f373 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -88,10 +88,7 @@ public void tearDown() { template.dropCollection(FullTextDocument.class); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void findAllByTextCriteriaShouldReturnMatchingDocuments() { initRepoWithDefaultDocuments(); @@ -102,10 +99,7 @@ public void findAllByTextCriteriaShouldReturnMatchingDocuments() { assertThat(result, hasItems(PASSENGER_57, DEMOLITION_MAN)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void derivedFinderWithTextCriteriaReturnsCorrectResult() { initRepoWithDefaultDocuments(); @@ -123,10 +117,7 @@ public void derivedFinderWithTextCriteriaReturnsCorrectResult() { assertThat(result, hasItems(blade)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void findByWithPaginationWorksCorrectlyWhenUsingTextCriteria() { initRepoWithDefaultDocuments(); @@ -140,10 +131,7 @@ public void findByWithPaginationWorksCorrectlyWhenUsingTextCriteria() { assertThat(page.getContent().get(0), equalTo(DEMOLITION_MAN)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void findAllByTextCriteriaWithSortWorksCorrectly() { initRepoWithDefaultDocuments(); @@ -157,10 +145,7 @@ public void findAllByTextCriteriaWithSortWorksCorrectly() { assertThat(result.get(0), equalTo(snipes)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void findByWithSortByScoreViaPageRequestTriggersSortingCorrectly() { initRepoWithDefaultDocuments(); @@ -174,10 +159,7 @@ public void findByWithSortByScoreViaPageRequestTriggersSortingCorrectly() { assertThat(page.getContent().get(0), equalTo(snipes)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void findByWithSortViaPageRequestIgnoresTextScoreWhenSortedByOtherProperty() { initRepoWithDefaultDocuments(); @@ -191,10 +173,7 @@ public void findByWithSortViaPageRequestIgnoresTextScoreWhenSortedByOtherPropert assertThat(page.getContent().get(0), equalTo(PASSENGER_57)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void derivedSortForTextScorePropertyWorksCorrectly() { initRepoWithDefaultDocuments(); @@ -206,10 +185,7 @@ public void derivedSortForTextScorePropertyWorksCorrectly() { assertThat(result.get(0), equalTo(snipes)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void derivedFinderMethodWithoutFullTextShouldNoCauseTroubleWhenHavingEntityWithTextScoreProperty() { initRepoWithDefaultDocuments(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index 849a673f19..42beb55ed5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 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. @@ -77,9 +77,7 @@ public interface PersonRepository extends MongoRepository, Query @Query(value = "{ 'firstname' : ?0 }", fields = "{ 'firstname': 1, 'lastname': 1}") List findByThePersonsFirstname(String firstname); - /** - * @see DATAMONGO-871 - */ + // DATAMONGO-871 @Query(value = "{ 'firstname' : ?0 }") Person[] findByThePersonsFirstnameAsArray(String firstname); @@ -187,197 +185,125 @@ public interface PersonRepository extends MongoRepository, Query GeoResults findByLocationNear(Point point, Distance maxDistance); - /** - * @see DATAMONGO-1110 - */ + // DATAMONGO-1110 GeoResults findPersonByLocationNear(Point point, Range distance); GeoPage findByLocationNear(Point point, Distance maxDistance, Pageable pageable); List findByCreator(User user); - /** - * @see DATAMONGO-425 - */ + // DATAMONGO-425 List findByCreatedAtLessThan(Date date); - /** - * @see DATAMONGO-425 - */ + // DATAMONGO-425 List findByCreatedAtGreaterThan(Date date); - /** - * @see DATAMONGO-425 - */ + // DATAMONGO-425 @Query("{ 'createdAt' : { '$lt' : ?0 }}") List findByCreatedAtLessThanManually(Date date); - /** - * @see DATAMONGO-427 - */ + // DATAMONGO-427 List findByCreatedAtBefore(Date date); - /** - * @see DATAMONGO-427 - */ + // DATAMONGO-427 List findByCreatedAtAfter(Date date); - /** - * @see DATAMONGO-472 - * @param lastname - * @return - */ + // DATAMONGO-472 List findByLastnameNot(String lastname); - /** - * @see DATAMONGO-600 - * @param credentials - * @return - */ + // DATAMONGO-600 List findByCredentials(Credentials credentials); - /** - * @see DATAMONGO-636 - */ + // DATAMONGO-636 long countByLastname(String lastname); - /** - * @see DATAMONGO-636 - */ + // DATAMONGO-636 int countByFirstname(String firstname); - /** - * @see DATAMONGO-636 - */ + // DATAMONGO-636 @Query(value = "{ 'lastname' : ?0 }", count = true) long someCountQuery(String lastname); - /** - * @see DATAMONGO-1454 - */ + // DATAMONGO-1454 boolean existsByFirstname(String firstname); - /** - * @see DATAMONGO-1454 - */ + // DATAMONGO-1454 @ExistsQuery(value = "{ 'lastname' : ?0 }") boolean someExistQuery(String lastname); - /** - * @see DATAMONGO-770 - */ + // DATAMONGO-770 List findByFirstnameIgnoreCase(String firstName); - /** - * @see DATAMONGO-770 - */ + // DATAMONGO-770 List findByFirstnameNotIgnoreCase(String firstName); - /** - * @see DATAMONGO-770 - */ + // DATAMONGO-770 List findByFirstnameStartingWithIgnoreCase(String firstName); - /** - * @see DATAMONGO-770 - */ + // DATAMONGO-770 List findByFirstnameEndingWithIgnoreCase(String firstName); - /** - * @see DATAMONGO-770 - */ + // DATAMONGO-770 List findByFirstnameContainingIgnoreCase(String firstName); - /** - * @see DATAMONGO-870 - */ + // DATAMONGO-870 Slice findByAgeGreaterThan(int age, Pageable pageable); - /** - * @see DATAMONGO-821 - */ + // DATAMONGO-821 @Query("{ creator : { $exists : true } }") Page findByHavingCreator(Pageable page); - /** - * @see DATAMONGO-566 - */ + // DATAMONGO-566 List deleteByLastname(String lastname); - /** - * @see DATAMONGO-566 - */ + // DATAMONGO-566 Long deletePersonByLastname(String lastname); - /** - * @see DATAMONGO-566 - */ + // DATAMONGO-566 @Query(value = "{ 'lastname' : ?0 }", delete = true) List removeByLastnameUsingAnnotatedQuery(String lastname); - /** - * @see DATAMONGO-566 - */ + // DATAMONGO-566 @Query(value = "{ 'lastname' : ?0 }", delete = true) Long removePersonByLastnameUsingAnnotatedQuery(String lastname); - /** - * @see DATAMONGO-893 - */ + // DATAMONGO-893 Page findByAddressIn(List
        address, Pageable page); - /** - * @see DATAMONGO-745 - */ + // DATAMONGO-745 @Query("{firstname:{$in:?0}, lastname:?1}") Page findByCustomQueryFirstnamesAndLastname(List firstnames, String lastname, Pageable page); - /** - * @see DATAMONGO-745 - */ + // DATAMONGO-745 @Query("{lastname:?0, address.street:{$in:?1}}") Page findByCustomQueryLastnameAndAddressStreetInList(String lastname, List streetNames, Pageable page); - /** - * @see DATAMONGO-950 - */ + // DATAMONGO-950 List findTop3ByLastnameStartingWith(String lastname); - /** - * @see DATAMONGO-950 - */ + // DATAMONGO-950 Page findTop3ByLastnameStartingWith(String lastname, Pageable pageRequest); - /** - * @see DATAMONGO-1030 - */ + // DATAMONGO-1030 PersonSummary findSummaryByLastname(String lastname); @Query("{ ?0 : ?1 }") List findByKeyValue(String key, String value); - /** - * @see DATAMONGO-1165 - */ + // DATAMONGO-1165 @Query("{ firstname : { $in : ?0 }}") Stream findByCustomQueryWithStreamingCursorByFirstnames(List firstnames); - /** - * @see DATAMONGO-990 - */ + // DATAMONGO-990 @Query("{ firstname : ?#{[0]}}") List findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly(String firstname); - /** - * @see DATAMONGO-990 - */ + // DATAMONGO-990 @Query("{ firstname : ?#{[0]}, email: ?#{principal.email} }") List findWithSpelByFirstnameAndCurrentUserWithCustomQuery(String firstname); - /** - * @see DATAMONGO-990 - */ + // DATAMONGO-990 @Query("{ firstname : :#{#firstname}}") List findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname); @@ -385,20 +311,18 @@ Page findByCustomQueryLastnameAndAddressStreetInList(String lastname, Li * Returns the count of {@link Person} with the given firstname. Uses {@link CountQuery} annotation to define the * query to be executed. * - * @see DATAMONGO-1539 * @param firstname * @return */ - @CountQuery("{ 'firstname' : ?0 }") + @CountQuery("{ 'firstname' : ?0 }") // DATAMONGO-1539 long countByThePersonsFirstname(String firstname); /** * Deletes {@link Person} entities with the given firstname. Uses {@link DeleteQuery} annotation to define the query * to be executed. * - * @see DATAMONGO-1539 * @param firstname */ - @DeleteQuery("{ 'firstname' : ?0 }") + @DeleteQuery("{ 'firstname' : ?0 }") // DATAMONGO-1539 void deleteByThePersonsFirstname(String firstname); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java index 5bba542d85..2934f9a790 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 the original author or authors. + * Copyright 2010-2017 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. @@ -51,10 +51,7 @@ public void setUp() throws InterruptedException { operations.remove(new org.springframework.data.mongodb.core.query.Query(), User.class); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void shouldLoadAssociationWithDbRefOnInterfaceAndLazyLoadingEnabled() throws Exception { User thomas = new User(); @@ -77,10 +74,7 @@ public void shouldLoadAssociationWithDbRefOnInterfaceAndLazyLoadingEnabled() thr assertThat(user.getUsername(), is(thomas.getUsername())); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void shouldLoadAssociationWithDbRefOnConcreteCollectionAndLazyLoadingEnabled() throws Exception { User thomas = new User(); @@ -108,10 +102,7 @@ public void shouldLoadAssociationWithDbRefOnConcreteCollectionAndLazyLoadingEnab assertThat(realFan.getUsername(), is(thomas.getUsername())); } - /** - * @see DATAMONGO-348 - */ - @Test + @Test // DATAMONGO-348 public void shouldLoadAssociationWithDbRefOnConcreteDomainClassAndLazyLoadingEnabled() throws Exception { User thomas = new User(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RedeclaringRepositoryMethodsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RedeclaringRepositoryMethodsTests.java index 3447d7d6a7..aced3f4122 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RedeclaringRepositoryMethodsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RedeclaringRepositoryMethodsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -34,10 +34,7 @@ public class RedeclaringRepositoryMethodsTests extends AbstractPersonRepositoryI @Autowired RedeclaringRepositoryMethodsRepository repository; - /** - * @see DATAMONGO-760 - */ - @Test + @Test // DATAMONGO-760 public void adjustedWellKnownPagedFindAllMethodShouldReturnOnlyTheUserWithFirstnameOliverAugust() { Page page = repository.findAll(new PageRequest(0, 2)); @@ -46,10 +43,7 @@ public void adjustedWellKnownPagedFindAllMethodShouldReturnOnlyTheUserWithFirstn assertThat(page.getContent().get(0).getFirstname(), is(oliver.getFirstname())); } - /** - * @see DATAMONGO-760 - */ - @Test + @Test // DATAMONGO-760 public void adjustedWllKnownFindAllMethodShouldReturnAnEmptyList() { List result = repository.findAll(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/CdiExtensionIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/CdiExtensionIntegrationTests.java index eef0448479..b21ef5a197 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/CdiExtensionIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/CdiExtensionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 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. @@ -63,10 +63,7 @@ public void bootstrapsRepositoryCorrectly() { assertThat(repository.findOne(person.getId()).getId(), is(result.getId())); } - /** - * @see DATAMONGO-1017 - */ - @Test + @Test // DATAMONGO-1017 public void returnOneFromCustomImpl() { RepositoryClient repositoryConsumer = container.getInstance(RepositoryClient.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepository.java index 224a7ed3b4..329e231fea 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -20,6 +20,5 @@ /** * @author Mark Paluch - * @see DATAMONGO-1017 */ public interface SamplePersonRepository extends Repository, SamplePersonRepositoryCustom {} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryCustom.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryCustom.java index a545d35919..d792f58a06 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryCustom.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryCustom.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -17,7 +17,6 @@ package org.springframework.data.mongodb.repository.cdi; /** - * @see DATAMONGO-1017 * @author Mark Paluch */ interface SamplePersonRepositoryCustom { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryImpl.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryImpl.java index 46a22cb6e8..aaa77b6701 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryImpl.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/cdi/SamplePersonRepositoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -17,7 +17,6 @@ package org.springframework.data.mongodb.repository.cdi; /** - * @see DATAMONGO-1017 * @author Mark Paluch */ class SamplePersonRepositoryImpl implements SamplePersonRepositoryCustom { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoNamespaceIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoNamespaceIntegrationTests.java index 8847e0abd0..6481c5c18b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoNamespaceIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoNamespaceIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 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. @@ -63,10 +63,7 @@ public void assertDefaultMappingContextIsWired() { assertThat(definition, is(notNullValue())); } - /** - * @see DATAMONGO-581 - */ - @Test + @Test // DATAMONGO-581 public void exposesPersistentEntity() { Repositories repositories = new Repositories(context); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoryConfigurationExtensionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoryConfigurationExtensionUnitTests.java index eb6fd4f40f..b75489b6ad 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoryConfigurationExtensionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/MongoRepositoryConfigurationExtensionUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 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. @@ -46,30 +46,21 @@ public class MongoRepositoryConfigurationExtensionUnitTests { RepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(metadata, EnableMongoRepositories.class, loader, environment); - /** - * @see DATAMONGO-1009 - */ - @Test + @Test // DATAMONGO-1009 public void isStrictMatchIfDomainTypeIsAnnotatedWithDocument() { MongoRepositoryConfigurationExtension extension = new MongoRepositoryConfigurationExtension(); assertHasRepo(SampleRepository.class, extension.getRepositoryConfigurations(configurationSource, loader, true)); } - /** - * @see DATAMONGO-1009 - */ - @Test + @Test // DATAMONGO-1009 public void isStrictMatchIfRepositoryExtendsStoreSpecificBase() { MongoRepositoryConfigurationExtension extension = new MongoRepositoryConfigurationExtension(); assertHasRepo(StoreRepository.class, extension.getRepositoryConfigurations(configurationSource, loader, true)); } - /** - * @see DATAMONGO-1009 - */ - @Test + @Test // DATAMONGO-1009 public void isNotStrictMatchIfDomainTypeIsNotAnnotatedWithDocument() { MongoRepositoryConfigurationExtension extension = new MongoRepositoryConfigurationExtension(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/AllowNestedMongoRepositoriesRepositoryConfigTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/AllowNestedMongoRepositoriesRepositoryConfigTests.java index bb4f61d8e9..315779c70f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/AllowNestedMongoRepositoriesRepositoryConfigTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/AllowNestedMongoRepositoriesRepositoryConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -36,10 +36,7 @@ public class AllowNestedMongoRepositoriesRepositoryConfigTests { @Autowired NestedUserRepository fooRepository; - /** - * @see DATAMONGO-780 - */ - @Test + @Test // DATAMONGO-780 public void shouldFindNestedRepository() { assertThat(fooRepository, is(notNullValue())); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/ClassWithNestedRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/ClassWithNestedRepository.java index fe9b547783..9e87ef701c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/ClassWithNestedRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/ClassWithNestedRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -19,7 +19,6 @@ import org.springframework.data.mongodb.repository.User; /** - * @see DATAMONGO-780 * @author Thomas Darimont */ public class ClassWithNestedRepository { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/NestedMongoRepositoriesJavaConfigTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/NestedMongoRepositoriesJavaConfigTests.java index e9713a3839..dbea6bad20 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/NestedMongoRepositoriesJavaConfigTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/config/lazy/NestedMongoRepositoriesJavaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -45,10 +45,7 @@ static class Config {} @Autowired NestedUserRepository nestedUserRepository; - /** - * @see DATAMONGO-780 - */ - @Test + @Test // DATAMONGO-780 public void shouldSupportNestedRepositories() { assertThat(nestedUserRepository, is(notNullValue())); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/custom/CustomRepositoryImplementationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/custom/CustomRepositoryImplementationTests.java index d375bd0e14..951b781e03 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/custom/CustomRepositoryImplementationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/custom/CustomRepositoryImplementationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2017 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. @@ -46,10 +46,7 @@ static class Config {} @Autowired CustomMongoRepository customMongoRepository; - /** - * @see DATAMONGO-804 - */ - @Test + @Test // DATAMONGO-804 public void shouldExecuteMethodOnCustomRepositoryImplementation() { String username = "bubu"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java index 83812b560f..3b2515d781 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -91,11 +91,8 @@ public void setUp() { doReturn(converter).when(mongoOperationsMock).getConverter(); } - /** - * @see DATAMONGO-566 - */ @SuppressWarnings("unchecked") - @Test + @Test // DATAMONGO-566 public void testDeleteExecutionCallsRemoveCorreclty() { createQueryForMethod("deletePersonByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" }); @@ -105,12 +102,8 @@ public void testDeleteExecutionCallsRemoveCorreclty() { Matchers.anyString()); } - /** - * @see DATAMONGO-566 - * @see DATAMONGO-1040 - */ @SuppressWarnings("unchecked") - @Test + @Test // DATAMONGO-566, DATAMONGO-1040 public void testDeleteExecutionLoadsListOfRemovedDocumentsWhenReturnTypeIsCollectionLike() { when(mongoOperationsMock.find(Matchers.any(Query.class), Matchers.any(Class.class), Matchers.anyString())) @@ -121,10 +114,7 @@ public void testDeleteExecutionLoadsListOfRemovedDocumentsWhenReturnTypeIsCollec verify(mongoOperationsMock, times(1)).findAllAndRemove(Matchers.any(Query.class), eq(Person.class), eq("persons")); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void testDeleteExecutionReturnsZeroWhenWriteResultIsNull() { MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class); @@ -133,11 +123,7 @@ public void testDeleteExecutionReturnsZeroWhenWriteResultIsNull() { assertThat(query.execute(new Object[] { "fake" }), Is. is(0L)); } - /** - * @see DATAMONGO-566 - * @see DATAMONGO-978 - */ - @Test + @Test // DATAMONGO-566, DATAMONGO-978 public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() { when(writeResultMock.getN()).thenReturn(100); @@ -151,10 +137,7 @@ public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() { verify(mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons")); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void metadataShouldNotBeAddedToQueryWhenNotPresent() { MongoQueryFake query = createQueryForMethod("findByFirstname", String.class); @@ -167,10 +150,7 @@ public void metadataShouldNotBeAddedToQueryWhenNotPresent() { assertThat(captor.getValue().getMeta().getComment(), nullValue()); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void metadataShouldBeAddedToQueryCorrectly() { MongoQueryFake query = createQueryForMethod("findByFirstname", String.class, Pageable.class); @@ -182,10 +162,7 @@ public void metadataShouldBeAddedToQueryCorrectly() { assertThat(captor.getValue().getMeta().getComment(), is("comment")); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void metadataShouldBeAddedToCountQueryCorrectly() { MongoQueryFake query = createQueryForMethod("findByFirstname", String.class, Pageable.class); @@ -197,10 +174,7 @@ public void metadataShouldBeAddedToCountQueryCorrectly() { assertThat(captor.getValue().getMeta().getComment(), is("comment")); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void metadataShouldBeAddedToStringBasedQueryCorrectly() { MongoQueryFake query = createQueryForMethod("findByAnnotatedQuery", String.class, Pageable.class); @@ -212,10 +186,7 @@ public void metadataShouldBeAddedToStringBasedQueryCorrectly() { assertThat(captor.getValue().getMeta().getComment(), is("comment")); } - /** - * @see DATAMONGO-1057 - */ - @Test + @Test // DATAMONGO-1057 public void slicedExecutionShouldRetainNrOfElementsToSkip() { MongoQueryFake query = createQueryForMethod("findByLastname", String.class, Pageable.class); @@ -233,10 +204,7 @@ public void slicedExecutionShouldRetainNrOfElementsToSkip() { assertThat(captor.getAllValues().get(1).getSkip(), is(10)); } - /** - * @see DATAMONGO-1057 - */ - @Test + @Test // DATAMONGO-1057 public void slicedExecutionShouldIncrementLimitByOne() { MongoQueryFake query = createQueryForMethod("findByLastname", String.class, Pageable.class); @@ -254,10 +222,7 @@ public void slicedExecutionShouldIncrementLimitByOne() { assertThat(captor.getAllValues().get(1).getLimit(), is(11)); } - /** - * @see DATAMONGO-1057 - */ - @Test + @Test // DATAMONGO-1057 public void slicedExecutionShouldRetainSort() { MongoQueryFake query = createQueryForMethod("findByLastname", String.class, Pageable.class); @@ -276,10 +241,7 @@ public void slicedExecutionShouldRetainSort() { assertThat(captor.getAllValues().get(1).getSortObject(), is(expectedSortObject)); } - /** - * @see DATAMONGO-1080 - */ - @Test + @Test // DATAMONGO-1080 public void doesNotTryToPostProcessQueryResultIntoWrapperType() { Person reference = new Person(); @@ -360,7 +322,7 @@ private interface Repo extends MongoRepository { @org.springframework.data.mongodb.repository.Query("{}") Page findByAnnotatedQuery(String firstnanme, Pageable pageable); - /** @see DATAMONGO-1057 */ + // DATAMONGO-1057 Slice findByLastname(String lastname, Pageable page); Optional findByLastname(String lastname); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java index 4692b0ef25..4601c0c006 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -93,10 +93,7 @@ public void convertsCollectionUponAccess() { assertThat(result, is((Object) reference)); } - /** - * @see DATAMONGO-505 - */ - @Test + @Test // DATAMONGO-505 public void convertsAssociationsToDBRef() { Property property = new Property(); @@ -110,10 +107,7 @@ public void convertsAssociationsToDBRef() { assertThat(dbRef.getId(), is((Object) 5L)); } - /** - * @see DATAMONGO-505 - */ - @Test + @Test // DATAMONGO-505 public void convertsAssociationsToDBRefForCollections() { Property property = new Property(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MappingMongoEntityInformationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MappingMongoEntityInformationUnitTests.java index 821aab557c..5cc0a4d731 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MappingMongoEntityInformationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MappingMongoEntityInformationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 by the original author(s). + * Copyright 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,20 +45,14 @@ public void setUp() { when(info.getCollection()).thenReturn("Person"); } - /** - * @see DATAMONGO-248 - */ - @Test + @Test // DATAMONGO-248 public void usesEntityCollectionIfNoCustomOneGiven() { MongoEntityInformation information = new MappingMongoEntityInformation(info); assertThat(information.getCollectionName(), is("Person")); } - /** - * @see DATAMONGO-248 - */ - @Test + @Test // DATAMONGO-248 public void usesCustomCollectionIfGiven() { MongoEntityInformation information = new MappingMongoEntityInformation(info, "foobar"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java index df6dd2715a..bae0d460ea 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -71,10 +71,7 @@ public void returnsDistanceIfAvailable() throws NoSuchMethodException, SecurityE assertThat(accessor.getDistanceRange().getUpperBound(), is(DISTANCE)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldReturnAsFullTextStringWhenNoneDefinedForMethod() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class); @@ -85,10 +82,7 @@ public void shouldReturnAsFullTextStringWhenNoneDefinedForMethod() throws NoSuch assertThat(accessor.getFullText(), IsNull.nullValue()); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldProperlyConvertTextCriteria() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByFirstname", String.class, TextCriteria.class); @@ -100,10 +94,7 @@ public void shouldProperlyConvertTextCriteria() throws NoSuchMethodException, Se equalTo("{ \"$text\" : { \"$search\" : \"data\"}}")); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldDetectMinAndMaxDistance() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Range.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java index e7468d4a10..56c63b0d3a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoParametersUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -100,10 +100,7 @@ public void findsAnnotatedDoubleArrayForGeoNearQuery() throws Exception { assertThat(parameters.getNearIndex(), is(1)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldFindTextCriteriaAtItsIndex() throws SecurityException, NoSuchMethodException { Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class); @@ -111,10 +108,7 @@ public void shouldFindTextCriteriaAtItsIndex() throws SecurityException, NoSuchM assertThat(parameters.getFullTextParameterIndex(), is(1)); } - /** - * @see DATAMONGO-973 - */ - @Test + @Test // DATAMONGO-973 public void shouldTreatTextCriteriaParameterAsSpecialParameter() throws SecurityException, NoSuchMethodException { Method method = PersonRepository.class.getMethod("findByNameAndText", String.class, TextCriteria.class); @@ -122,10 +116,7 @@ public void shouldTreatTextCriteriaParameterAsSpecialParameter() throws Security assertThat(parameters.getParameter(parameters.getFullTextParameterIndex()).isSpecialParameter(), is(true)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldFindMinAndMaxDistanceParameters() throws NoSuchMethodException, SecurityException { Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Range.class); @@ -135,10 +126,7 @@ public void shouldFindMinAndMaxDistanceParameters() throws NoSuchMethodException assertThat(parameters.getMaxDistanceIndex(), is(-1)); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldNotHaveMinDistanceIfOnlyOneDistanceParameterPresent() throws NoSuchMethodException, SecurityException { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java index 71dc7316b2..f6b8d83262 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -95,10 +95,7 @@ public void createsQueryCorrectly() throws Exception { assertThat(query, is(query(where("firstName").is("Oliver")))); } - /** - * @see DATAMONGO-469 - */ - @Test + @Test // DATAMONGO-469 public void createsAndQueryCorrectly() { Person person = new Person(); @@ -169,10 +166,7 @@ public void createsGreaterThanEqualQueryCorrectly() throws Exception { assertThat(creator.createQuery(), is(reference)); } - /** - * @see DATAMONGO-338 - */ - @Test + @Test // DATAMONGO-338 public void createsExistsClauseCorrectly() { PartTree tree = new PartTree("findByAgeExists", Person.class); @@ -181,10 +175,7 @@ public void createsExistsClauseCorrectly() { assertThat(creator.createQuery(), is(query)); } - /** - * @see DATAMONGO-338 - */ - @Test + @Test // DATAMONGO-338 public void createsRegexClauseCorrectly() { PartTree tree = new PartTree("findByFirstNameRegex", Person.class); @@ -193,10 +184,7 @@ public void createsRegexClauseCorrectly() { assertThat(creator.createQuery(), is(query)); } - /** - * @see DATAMONGO-338 - */ - @Test + @Test // DATAMONGO-338 public void createsTrueClauseCorrectly() { PartTree tree = new PartTree("findByActiveTrue", Person.class); @@ -205,10 +193,7 @@ public void createsTrueClauseCorrectly() { assertThat(creator.createQuery(), is(query)); } - /** - * @see DATAMONGO-338 - */ - @Test + @Test // DATAMONGO-338 public void createsFalseClauseCorrectly() { PartTree tree = new PartTree("findByActiveFalse", Person.class); @@ -217,10 +202,7 @@ public void createsFalseClauseCorrectly() { assertThat(creator.createQuery(), is(query)); } - /** - * @see DATAMONGO-413 - */ - @Test + @Test // DATAMONGO-413 public void createsOrQueryCorrectly() { PartTree tree = new PartTree("findByFirstNameOrAge", Person.class); @@ -230,10 +212,7 @@ public void createsOrQueryCorrectly() { assertThat(query, is(query(new Criteria().orOperator(where("firstName").is("Dave"), where("age").is(42))))); } - /** - * @see DATAMONGO-347 - */ - @Test + @Test // DATAMONGO-347 public void createsQueryReferencingADBRefCorrectly() { User user = new User(); @@ -246,10 +225,7 @@ public void createsQueryReferencingADBRefCorrectly() { assertThat(queryObject.get("creator"), is((Object) user)); } - /** - * @see DATAMONGO-418 - */ - @Test + @Test // DATAMONGO-418 public void createsQueryWithStartingWithPredicateCorrectly() { PartTree tree = new PartTree("findByUsernameStartingWith", User.class); @@ -259,10 +235,7 @@ public void createsQueryWithStartingWithPredicateCorrectly() { assertThat(query, is(query(where("username").regex("^Matt")))); } - /** - * @see DATAMONGO-418 - */ - @Test + @Test // DATAMONGO-418 public void createsQueryWithEndingWithPredicateCorrectly() { PartTree tree = new PartTree("findByUsernameEndingWith", User.class); @@ -272,10 +245,7 @@ public void createsQueryWithEndingWithPredicateCorrectly() { assertThat(query, is(query(where("username").regex("ews$")))); } - /** - * @see DATAMONGO-418 - */ - @Test + @Test // DATAMONGO-418 public void createsQueryWithContainingPredicateCorrectly() { PartTree tree = new PartTree("findByUsernameContaining", User.class); @@ -301,10 +271,7 @@ private void assertBindsDistanceToQuery(Point point, Distance distance, Query re assertThat(query, is(query)); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void createsQueryWithFindByIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByfirstNameIgnoreCase", Person.class); @@ -314,10 +281,7 @@ public void createsQueryWithFindByIgnoreCaseCorrectly() { assertThat(query, is(query(where("firstName").regex("^dave$", "i")))); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void createsQueryWithFindByNotIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameNotIgnoreCase", Person.class); @@ -327,10 +291,7 @@ public void createsQueryWithFindByNotIgnoreCaseCorrectly() { assertThat(query.toString(), is(query(where("firstName").not().regex("^dave$", "i")).toString())); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void createsQueryWithFindByStartingWithIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameStartingWithIgnoreCase", Person.class); @@ -340,10 +301,7 @@ public void createsQueryWithFindByStartingWithIgnoreCaseCorrectly() { assertThat(query, is(query(where("firstName").regex("^dave", "i")))); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void createsQueryWithFindByEndingWithIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameEndingWithIgnoreCase", Person.class); @@ -353,10 +311,7 @@ public void createsQueryWithFindByEndingWithIgnoreCaseCorrectly() { assertThat(query, is(query(where("firstName").regex("dave$", "i")))); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void createsQueryWithFindByContainingIgnoreCaseCorrectly() { PartTree tree = new PartTree("findByFirstNameContainingIgnoreCase", Person.class); @@ -366,10 +321,7 @@ public void createsQueryWithFindByContainingIgnoreCaseCorrectly() { assertThat(query, is(query(where("firstName").regex(".*dave.*", "i")))); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void shouldThrowExceptionForQueryWithFindByIgnoreCaseOnNonStringProperty() { expection.expect(IllegalArgumentException.class); @@ -381,10 +333,7 @@ public void shouldThrowExceptionForQueryWithFindByIgnoreCaseOnNonStringProperty( creator.createQuery(); } - /** - * @see DATAMONGO-770 - */ - @Test + @Test // DATAMONGO-770 public void shouldOnlyGenerateLikeExpressionsForStringPropertiesIfAllIgnoreCase() { PartTree tree = new PartTree("findByFirstNameAndAgeAllIgnoreCase", Person.class); @@ -394,10 +343,7 @@ public void shouldOnlyGenerateLikeExpressionsForStringPropertiesIfAllIgnoreCase( assertThat(query, is(query(where("firstName").regex("^dave$", "i").and("age").is(42)))); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void shouldCreateDeleteByQueryCorrectly() { PartTree tree = new PartTree("deleteByFirstName", Person.class); @@ -409,10 +355,7 @@ public void shouldCreateDeleteByQueryCorrectly() { assertThat(query, is(query(where("firstName").is("dave")))); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void shouldCreateDeleteByQueryCorrectlyForMultipleCriteriaAndCaseExpressions() { PartTree tree = new PartTree("deleteByFirstNameAndAgeAllIgnoreCase", Person.class); @@ -424,10 +367,7 @@ public void shouldCreateDeleteByQueryCorrectlyForMultipleCriteriaAndCaseExpressi assertThat(query, is(query(where("firstName").regex("^dave$", "i").and("age").is(42)))); } - /** - * @see DATAMONGO-1075 - */ - @Test + @Test // DATAMONGO-1075 public void shouldCreateInClauseWhenUsingContainsOnCollectionLikeProperty() { PartTree tree = new PartTree("findByEmailAddressesContaining", User.class); @@ -438,10 +378,7 @@ public void shouldCreateInClauseWhenUsingContainsOnCollectionLikeProperty() { assertThat(query, is(query(where("emailAddresses").in("dave")))); } - /** - * @see DATAMONGO-1075 - */ - @Test + @Test // DATAMONGO-1075 public void shouldCreateInClauseWhenUsingNotContainsOnCollectionLikeProperty() { PartTree tree = new PartTree("findByEmailAddressesNotContaining", User.class); @@ -452,11 +389,7 @@ public void shouldCreateInClauseWhenUsingNotContainsOnCollectionLikeProperty() { assertThat(query, is(query(where("emailAddresses").not().in("dave")))); } - /** - * @see DATAMONGO-1075 - * @see DATAMONGO-1425 - */ - @Test + @Test // DATAMONGO-1075, DATAMONGO-1425 public void shouldCreateRegexWhenUsingNotContainsOnStringProperty() { PartTree tree = new PartTree("findByUsernameNotContaining", User.class); @@ -466,10 +399,7 @@ public void shouldCreateRegexWhenUsingNotContainsOnStringProperty() { assertThat(query.getQueryObject(), is(query(where("username").not().regex(".*thew.*")).getQueryObject())); } - /** - * @see DATAMONGO-1139 - */ - @Test + @Test // DATAMONGO-1139 public void createsNonShericalNearForDistanceWithDefaultMetric() { Point point = new Point(1.0, 1.0); @@ -482,10 +412,7 @@ public void createsNonShericalNearForDistanceWithDefaultMetric() { assertThat(query, is(query(where("location").near(point).maxDistance(1.0)))); } - /** - * @see DATAMONGO-1136 - */ - @Test + @Test // DATAMONGO-1136 public void shouldCreateWithinQueryCorrectly() { Point first = new Point(1, 1); @@ -500,10 +427,7 @@ public void shouldCreateWithinQueryCorrectly() { assertThat(query, is(query(where("address.geo").within(shape)))); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldCreateNearSphereQueryForSphericalProperty() { Point point = new Point(10, 20); @@ -515,10 +439,7 @@ public void shouldCreateNearSphereQueryForSphericalProperty() { assertThat(query, is(query(where("address2dSphere.geo").nearSphere(point)))); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldCreateNearSphereQueryForSphericalPropertyHavingDistanceWithDefaultMetric() { Point point = new Point(1.0, 1.0); @@ -531,10 +452,7 @@ public void shouldCreateNearSphereQueryForSphericalPropertyHavingDistanceWithDef assertThat(query, is(query(where("address2dSphere.geo").nearSphere(point).maxDistance(1.0)))); } - /** - * @see DATAMONGO-1110 - */ - @Test + @Test // DATAMONGO-1110 public void shouldCreateNearQueryForMinMaxDistance() { Point point = new Point(10, 20); @@ -547,10 +465,7 @@ public void shouldCreateNearQueryForMinMaxDistance() { assertThat(query, is(query(where("address.geo").near(point).minDistance(10D).maxDistance(20D)))); } - /** - * @see DATAMONGO-1229 - */ - @Test + @Test // DATAMONGO-1229 public void appliesIgnoreCaseToLeafProperty() { PartTree tree = new PartTree("findByAddressStreetIgnoreCase", User.class); @@ -559,10 +474,7 @@ public void appliesIgnoreCaseToLeafProperty() { assertThat(new MongoQueryCreator(tree, accessor, context).createQuery(), is(notNullValue())); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void ignoreCaseShouldEscapeSource() { PartTree tree = new PartTree("findByUsernameIgnoreCase", User.class); @@ -573,10 +485,7 @@ public void ignoreCaseShouldEscapeSource() { assertThat(query, is(query(where("username").regex("^\\Qcon.flux+\\E$", "i")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void ignoreCaseShouldEscapeSourceWhenUsedForStartingWith() { PartTree tree = new PartTree("findByUsernameStartingWithIgnoreCase", User.class); @@ -587,10 +496,7 @@ public void ignoreCaseShouldEscapeSourceWhenUsedForStartingWith() { assertThat(query, is(query(where("username").regex("^\\Qdawns.light+\\E", "i")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void ignoreCaseShouldEscapeSourceWhenUsedForEndingWith() { PartTree tree = new PartTree("findByUsernameEndingWithIgnoreCase", User.class); @@ -601,10 +507,7 @@ public void ignoreCaseShouldEscapeSourceWhenUsedForEndingWith() { assertThat(query, is(query(where("username").regex("\\Qnew.ton+\\E$", "i")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void likeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { PartTree tree = new PartTree("findByUsernameLike", User.class); @@ -615,10 +518,7 @@ public void likeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { assertThat(query, is(query(where("username").regex(".*\\Qfire.fight+\\E.*")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void likeShouldEscapeSourceWhenUsedWithLeadingWildcard() { PartTree tree = new PartTree("findByUsernameLike", User.class); @@ -629,10 +529,7 @@ public void likeShouldEscapeSourceWhenUsedWithLeadingWildcard() { assertThat(query, is(query(where("username").regex(".*\\Qsteel.heart+\\E")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void likeShouldEscapeSourceWhenUsedWithTrailingWildcard() { PartTree tree = new PartTree("findByUsernameLike", User.class); @@ -642,10 +539,7 @@ public void likeShouldEscapeSourceWhenUsedWithTrailingWildcard() { assertThat(query, is(query(where("username").regex("\\Qcala.mity+\\E.*")))); } - /** - * @see DATAMONGO-1232 - */ - @Test + @Test // DATAMONGO-1232 public void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { PartTree tree = new PartTree("findByUsernameLike", User.class); @@ -655,10 +549,7 @@ public void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { assertThat(query, is(query(where("username").regex(".*")))); } - /** - * @see DATAMONGO-1342 - */ - @Test + @Test // DATAMONGO-1342 public void bindsNullValueToContainsClause() { PartTree partTree = new PartTree("emailAddressesContains", User.class); @@ -669,10 +560,7 @@ public void bindsNullValueToContainsClause() { assertThat(query, is(query(where("emailAddresses").in((Object) null)))); } - /** - * @see DATAMONGO-1424 - */ - @Test + @Test // DATAMONGO-1424 public void notLikeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); @@ -684,10 +572,7 @@ public void notLikeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() { is(query(where("username").not().regex(".*\\Qfire.fight+\\E.*")).getQueryObject())); } - /** - * @see DATAMONGO-1424 - */ - @Test + @Test // DATAMONGO-1424 public void notLikeShouldEscapeSourceWhenUsedWithLeadingWildcard() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); @@ -699,10 +584,7 @@ public void notLikeShouldEscapeSourceWhenUsedWithLeadingWildcard() { is(query(where("username").not().regex(".*\\Qsteel.heart+\\E")).getQueryObject())); } - /** - * @see DATAMONGO-1424 - */ - @Test + @Test // DATAMONGO-1424 public void notLikeShouldEscapeSourceWhenUsedWithTrailingWildcard() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); @@ -712,10 +594,7 @@ public void notLikeShouldEscapeSourceWhenUsedWithTrailingWildcard() { assertThat(query.getQueryObject(), is(query(where("username").not().regex("\\Qcala.mity+\\E.*")).getQueryObject())); } - /** - * @see DATAMONGO-1424 - */ - @Test + @Test // DATAMONGO-1424 public void notLikeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { PartTree tree = new PartTree("findByUsernameNotLike", User.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java index 133e910dd6..7ccac637b1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -82,10 +82,7 @@ public void setUp() throws Exception { when(mongoOperationsMock.getConverter()).thenReturn(converter); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void pagedExecutionShouldNotGenerateCountQueryIfQueryReportedNoResults() { when(mongoOperationsMock.find(any(Query.class), eq(Person.class), eq("person"))) @@ -98,10 +95,7 @@ public void pagedExecutionShouldNotGenerateCountQueryIfQueryReportedNoResults() verify(mongoOperationsMock, never()).count(any(Query.class), eq(Person.class), eq("person")); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void pagedExecutionShouldUseCountFromResultWithOffsetAndResultsWithinPageSize() { when(mongoOperationsMock.find(any(Query.class), eq(Person.class), eq("person"))) @@ -114,10 +108,7 @@ public void pagedExecutionShouldUseCountFromResultWithOffsetAndResultsWithinPage verify(mongoOperationsMock, never()).count(any(Query.class), eq(Person.class), eq("person")); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void pagedExecutionRetrievesObjectsForPageableOutOfRange() throws Exception { when(mongoOperationsMock.find(any(Query.class), eq(Person.class), eq("person"))) @@ -130,10 +121,7 @@ public void pagedExecutionRetrievesObjectsForPageableOutOfRange() throws Excepti verify(mongoOperationsMock).count(any(Query.class), eq(Person.class), eq("person")); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void pagingGeoExecutionShouldUseCountFromResultWithOffsetAndResultsWithinPageSize() throws Exception { MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, @@ -153,10 +141,7 @@ public void pagingGeoExecutionShouldUseCountFromResultWithOffsetAndResultsWithin verify(mongoOperationsMock, never()).count(any(Query.class), eq("person")); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void pagingGeoExecutionRetrievesObjectsForPageableOutOfRange() throws Exception { MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java index bba8388d0c..0955a30788 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryMethodUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -128,10 +128,7 @@ public void createsMongoQueryMethodObjectForMethodReturningAnInterface() throws queryMethod(SampleRepository2.class, "methodReturningAnInterface"); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void createsMongoQueryMethodWithEmptyMetaCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "emptyMetaAnnotation"); @@ -140,10 +137,7 @@ public void createsMongoQueryMethodWithEmptyMetaCorrectly() throws Exception { assertThat(method.getQueryMetaAttributes().hasValues(), is(false)); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void createsMongoQueryMethodWithMaxExecutionTimeCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithMaxExecutionTime"); @@ -152,10 +146,7 @@ public void createsMongoQueryMethodWithMaxExecutionTimeCorrectly() throws Except assertThat(method.getQueryMetaAttributes().getMaxTimeMsec(), is(100L)); } - /** - * @see DATAMONGO-1403 - */ - @Test + @Test // DATAMONGO-1403 public void createsMongoQueryMethodWithSpellFixedMaxExecutionTimeCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithSpellFixedMaxExecutionTime"); @@ -164,10 +155,7 @@ public void createsMongoQueryMethodWithSpellFixedMaxExecutionTimeCorrectly() thr assertThat(method.getQueryMetaAttributes().getMaxTimeMsec(), is(100L)); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void createsMongoQueryMethodWithMaxScanCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithMaxScan"); @@ -176,10 +164,7 @@ public void createsMongoQueryMethodWithMaxScanCorrectly() throws Exception { assertThat(method.getQueryMetaAttributes().getMaxScan(), is(10L)); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void createsMongoQueryMethodWithCommentCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithComment"); @@ -188,10 +173,7 @@ public void createsMongoQueryMethodWithCommentCorrectly() throws Exception { assertThat(method.getQueryMetaAttributes().getComment(), is("foo bar")); } - /** - * @see DATAMONGO-957 - */ - @Test + @Test // DATAMONGO-957 public void createsMongoQueryMethodWithSnapshotCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithSnapshotUsage"); @@ -200,10 +182,7 @@ public void createsMongoQueryMethodWithSnapshotCorrectly() throws Exception { assertThat(method.getQueryMetaAttributes().getSnapshot(), is(true)); } - /** - * @see DATAMONGO-1480 - */ - @Test + @Test // DATAMONGO-1480 public void createsMongoQueryMethodWithNoCursorTimeoutCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithNoCursorTimeout"); @@ -213,10 +192,7 @@ public void createsMongoQueryMethodWithNoCursorTimeoutCorrectly() throws Excepti containsInAnyOrder(org.springframework.data.mongodb.core.query.Meta.CursorOption.NO_TIMEOUT)); } - /** - * @see DATAMONGO-1480 - */ - @Test + @Test // DATAMONGO-1480 public void createsMongoQueryMethodWithMultipleFlagsCorrectly() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithMultipleFlags"); @@ -226,10 +202,7 @@ public void createsMongoQueryMethodWithMultipleFlagsCorrectly() throws Exception containsInAnyOrder(org.springframework.data.mongodb.core.query.Meta.CursorOption.NO_TIMEOUT, org.springframework.data.mongodb.core.query.Meta.CursorOption.SLAVE_OK)); } - /** - * @see DATAMONGO-1266 - */ - @Test + @Test // DATAMONGO-1266 public void fallsBackToRepositoryDomainTypeIfMethodDoesNotReturnADomainType() throws Exception { MongoQueryMethod method = queryMethod(PersonRepository.class, "deleteByUserName", String.class); @@ -281,9 +254,7 @@ interface PersonRepository extends Repository { @Meta(flags = { org.springframework.data.mongodb.core.query.Meta.CursorOption.NO_TIMEOUT, org.springframework.data.mongodb.core.query.Meta.CursorOption.SLAVE_OK }) List metaWithMultipleFlags(); - /** - * @see DATAMONGO-1266 - */ + // DATAMONGO-1266 void deleteByUserName(String userName); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java index 9e578c9dfe..45912de686 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -77,10 +77,7 @@ public void setUp() { when(mongoOperationsMock.getConverter()).thenReturn(converter); } - /** - * @see DATAMOGO-952 - */ - @Test + @Test // DATAMOGO-952 public void rejectsInvalidFieldSpecification() { exception.expect(IllegalStateException.class); @@ -89,10 +86,7 @@ public void rejectsInvalidFieldSpecification() { deriveQueryFromMethod("findByLastname", new Object[] { "foo" }); } - /** - * @see DATAMOGO-952 - */ - @Test + @Test // DATAMOGO-952 public void singleFieldJsonIncludeRestrictionShouldBeConsidered() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findByFirstname", @@ -101,10 +95,7 @@ public void singleFieldJsonIncludeRestrictionShouldBeConsidered() { assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 1).get())); } - /** - * @see DATAMOGO-952 - */ - @Test + @Test // DATAMOGO-952 public void multiFieldJsonIncludeRestrictionShouldBeConsidered() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findByFirstnameAndLastname", @@ -113,10 +104,7 @@ public void multiFieldJsonIncludeRestrictionShouldBeConsidered() { assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 1).add("lastname", 1).get())); } - /** - * @see DATAMOGO-952 - */ - @Test + @Test // DATAMOGO-952 public void multiFieldJsonExcludeRestrictionShouldBeConsidered() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findPersonByFirstnameAndLastname", @@ -125,10 +113,7 @@ public void multiFieldJsonExcludeRestrictionShouldBeConsidered() { assertThat(query.getFieldsObject(), is(new BasicDBObjectBuilder().add("firstname", 0).add("lastname", 0).get())); } - /** - * @see DATAMOGO-973 - */ - @Test + @Test // DATAMOGO-973 public void shouldAddFullTextParamCorrectlyToDerivedQuery() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findPersonByFirstname", @@ -137,10 +122,7 @@ public void shouldAddFullTextParamCorrectlyToDerivedQuery() { assertThat(query, isTextQuery().searchingFor("search").where(new Criteria("firstname").is("text"))); } - /** - * @see DATAMONGO-1180 - */ - @Test + @Test // DATAMONGO-1180 public void propagatesRootExceptionForInvalidQuery() { exception.expect(IllegalStateException.class); @@ -149,18 +131,12 @@ public void propagatesRootExceptionForInvalidQuery() { deriveQueryFromMethod("findByAge", new Object[] { 1 }); } - /** - * @see DATAMONGO-1345 - */ - @Test + @Test // DATAMONGO-1345 public void doesNotDeriveFieldSpecForNormalDomainType() { assertThat(deriveQueryFromMethod("findPersonBy", new Object[0]).getFieldsObject(), is(nullValue())); } - /** - * @see DATAMONGO-1345 - */ - @Test + @Test // DATAMONGO-1345 public void restrictsQueryToFieldsRequiredForProjection() { DBObject fieldsObject = deriveQueryFromMethod("findPersonProjectedBy", new Object[0]).getFieldsObject(); @@ -169,10 +145,7 @@ public void restrictsQueryToFieldsRequiredForProjection() { assertThat(fieldsObject.get("lastname"), is((Object) 1)); } - /** - * @see DATAMONGO-1345 - */ - @Test + @Test // DATAMONGO-1345 public void restrictsQueryToFieldsRequiredForDto() { DBObject fieldsObject = deriveQueryFromMethod("findPersonDtoByAge", new Object[] { 42 }).getFieldsObject(); @@ -181,10 +154,7 @@ public void restrictsQueryToFieldsRequiredForDto() { assertThat(fieldsObject.get("lastname"), is((Object) 1)); } - /** - * @see DATAMONGO-1345 - */ - @Test + @Test // DATAMONGO-1345 public void usesDynamicProjection() { DBObject fields = deriveQueryFromMethod("findDynamicallyProjectedBy", ExtendedProjection.class).getFieldsObject(); @@ -194,10 +164,7 @@ public void usesDynamicProjection() { assertThat(fields.get("age"), is((Object) 1)); } - /** - * @see DATAMONGO-1500 - */ - @Test + @Test // DATAMONGO-1500 public void shouldLeaveParameterConversionToQueryMapper() { org.springframework.data.mongodb.core.query.Query query = deriveQueryFromMethod("findBySex", Sex.FEMALE); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index b66056125f..f5b6ba6d68 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -144,10 +144,7 @@ public void bindsNullParametersCorrectly() throws Exception { assertThat(query.getQueryObject().get("address"), is(nullValue())); } - /** - * @see DATAMONGO-821 - */ - @Test + @Test // DATAMONGO-821 public void bindsDbrefCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByHavingSizeFansNotZero"); @@ -157,28 +154,19 @@ public void bindsDbrefCorrectly() throws Exception { assertThat(query.getQueryObject(), is(new BasicQuery("{ fans : { $not : { $size : 0 } } }").getQueryObject())); } - /** - * @see DATAMONGO-566 - */ - @Test + @Test // DATAMONGO-566 public void constructsDeleteQueryCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("removeByLastname", String.class); assertThat(mongoQuery.isDeleteQuery(), is(true)); } - /** - * @see DATAMONGO-566 - */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) // DATAMONGO-566 public void preventsDeleteAndCountFlagAtTheSameTime() throws Exception { createQueryForMethod("invalidMethod", String.class); } - /** - * @see DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-420 public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception { ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, @@ -193,10 +181,7 @@ public void shouldSupportFindByParameterizedCriteriaAndFields() throws Exception assertThat(query.getFieldsObject(), is(new BasicQuery(null, "{ \"lastname\": 1}").getFieldsObject())); } - /** - * @see DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-420 public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQuoting() throws Exception { ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "fun"); @@ -207,10 +192,7 @@ public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQu assertThat(query.getQueryObject(), is(new BasicQuery("{title: {$regex: '^fun', $options: 'i'}}").getQueryObject())); } - /** - * @see DATAMONGO-995, DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-995, DATAMONGO-420 public void shouldParseQueryWithParametersInExpression() throws Exception { ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, 1, 2, 3, 4); @@ -224,10 +206,7 @@ public void shouldParseQueryWithParametersInExpression() throws Exception { .getQueryObject())); } - /** - * @see DATAMONGO-995, DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-995, DATAMONGO-420 public void bindsSimplePropertyAlreadyQuotedCorrectly() throws Exception { ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); @@ -239,10 +218,7 @@ public void bindsSimplePropertyAlreadyQuotedCorrectly() throws Exception { assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-995, DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-995, DATAMONGO-420 public void bindsSimplePropertyAlreadyQuotedWithRegexCorrectly() throws Exception { ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "^Mat.*"); @@ -254,10 +230,7 @@ public void bindsSimplePropertyAlreadyQuotedWithRegexCorrectly() throws Exceptio assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-995, DATAMONGO-420 - */ - @Test + @Test // DATAMONGO-995, DATAMONGO-420 public void bindsSimplePropertyWithRegexCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class); @@ -269,10 +242,7 @@ public void bindsSimplePropertyWithRegexCorrectly() throws Exception { assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-1070 - */ - @Test + @Test // DATAMONGO-1070 public void parsesDbRefDeclarationsCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("methodWithManuallyDefinedDbRef", String.class); @@ -285,10 +255,7 @@ public void parsesDbRefDeclarationsCorrectly() throws Exception { assertThat(dbRef.getCollectionName(), is("reference")); } - /** - * @see DATAMONGO-1072 - */ - @Test + @Test // DATAMONGO-1072 public void shouldParseJsonKeyReplacementCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("methodWithPlaceholderInKeyOfJsonStructure", String.class, @@ -300,10 +267,7 @@ public void shouldParseJsonKeyReplacementCorrectly() throws Exception { assertThat(query.getQueryObject(), is(new BasicDBObjectBuilder().add("key", "value").get())); } - /** - * @see DATAMONGO-990 - */ - @Test + @Test // DATAMONGO-990 public void shouldSupportExpressionsInCustomQueries() throws Exception { ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews"); @@ -315,10 +279,7 @@ public void shouldSupportExpressionsInCustomQueries() throws Exception { assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-1244 - */ - @Test + @Test // DATAMONGO-1244 public void shouldSupportExpressionsInCustomQueriesWithNestedObject() throws Exception { ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); @@ -331,10 +292,7 @@ public void shouldSupportExpressionsInCustomQueriesWithNestedObject() throws Exc assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-1244 - */ - @Test + @Test // DATAMONGO-1244 public void shouldSupportExpressionsInCustomQueriesWithMultipleNestedObjects() throws Exception { ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, true, "param1", "param2"); @@ -348,10 +306,7 @@ public void shouldSupportExpressionsInCustomQueriesWithMultipleNestedObjects() t assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-1290 - */ - @Test + @Test // DATAMONGO-1290 public void shouldSupportNonQuotedBinaryDataReplacement() throws Exception { byte[] binaryData = "Matthews".getBytes("UTF-8"); @@ -365,10 +320,7 @@ public void shouldSupportNonQuotedBinaryDataReplacement() throws Exception { assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-1454 - */ - @Test + @Test // DATAMONGO-1454 public void shouldSupportExistsProjection() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("existsByLastname", String.class); @@ -376,10 +328,7 @@ public void shouldSupportExistsProjection() throws Exception { assertThat(mongoQuery.isExistsQuery(), is(true)); } - /** - * @see DATAMONGO-1565 - */ - @Test + @Test // DATAMONGO-1565 public void bindsPropertyReferenceMultipleTimesCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByAgeQuotedAndUnquoted", Integer.TYPE); @@ -396,10 +345,7 @@ public void bindsPropertyReferenceMultipleTimesCorrectly() throws Exception { assertThat(query.getQueryObject(), is(reference.getQueryObject())); } - /** - * @see DATAMONGO-1565 - */ - @Test + @Test // DATAMONGO-1565 public void shouldIgnorePlaceholderPatternInReplacementValue() throws Exception { ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "argWith?1andText", @@ -412,10 +358,7 @@ public void shouldIgnorePlaceholderPatternInReplacementValue() throws Exception is(JSON.parse("{ \"arg0\" : \"argWith?1andText\" , \"arg1\" : \"nothing-special\"}"))); } - /** - * @see DATAMONGO-1565 - */ - @Test + @Test // DATAMONGO-1565 public void shouldQuoteStringReplacementCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); @@ -427,10 +370,7 @@ public void shouldQuoteStringReplacementCorrectly() throws Exception { assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews', password: 'foo"))); } - /** - * @see DATAMONGO-1565 - */ - @Test + @Test // DATAMONGO-1565 public void shouldQuoteStringReplacementContainingQuotesCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); @@ -442,10 +382,7 @@ public void shouldQuoteStringReplacementContainingQuotesCorrectly() throws Excep assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews\", password: \"foo"))); } - /** - * @see DATAMONGO-1565 - */ - @Test + @Test // DATAMONGO-1565 public void shouldQuoteStringReplacementWithQuotationsCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); @@ -457,10 +394,7 @@ public void shouldQuoteStringReplacementWithQuotationsCorrectly() throws Excepti is((DBObject) new BasicDBObject("lastname", "\"Dave Matthews\", password: 'foo"))); } - /** - * @see DATAMONGO-1565 - */ - @Test + @Test // DATAMONGO-1565 public void shouldQuoteComplexQueryStringCorreclty() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); @@ -471,10 +405,7 @@ public void shouldQuoteComplexQueryStringCorreclty() throws Exception { is((DBObject) new BasicDBObject("lastname", new BasicDBObject("$ne", "calamity")))); } - /** - * @see DATAMONGO-1565 - */ - @Test + @Test // DATAMONGO-1565 public void shouldQuotationInQuotedComplexQueryString() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryUnitTests.java index be030e07ad..68282293b2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2017 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. @@ -75,10 +75,7 @@ public void usesMappingMongoEntityInformationIfMappingContextSet() { assertTrue(entityInformation instanceof MappingMongoEntityInformation); } - /** - * @see DATAMONGO-385 - */ - @Test + @Test // DATAMONGO-385 @SuppressWarnings("unchecked") public void createsRepositoryWithIdTypeLong() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepositoryIntegrationTests.java index 419683ccc3..de30bf44df 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QueryDslMongoRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -70,10 +70,7 @@ public void setup() { repository.save(Arrays.asList(oliver, dave, carter)); } - /** - * @see DATAMONGO-1146 - */ - @Test + @Test // DATAMONGO-1146 public void shouldSupportExistsWithPredicate() throws Exception { assertThat(repository.exists(person.firstname.eq("Dave")), is(true)); @@ -81,10 +78,7 @@ public void shouldSupportExistsWithPredicate() throws Exception { assertThat(repository.exists((Predicate) null), is(true)); } - /** - * @see DATAMONGO-1167 - */ - @Test + @Test // DATAMONGO-1167 public void shouldSupportFindAllWithPredicateAndSort() { List users = repository.findAll(person.lastname.isNotNull(), new Sort(Direction.ASC, "firstname")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java index 814a1be19f..e4b6ba694c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -65,10 +65,7 @@ public void providesMongoQuery() { assertThat(query.fetchOne(), is(person)); } - /** - * @see DATAMONGO-1063 - */ - @Test + @Test // DATAMONGO-1063 public void shouldAllowAny() { person.setSkills(Arrays.asList("vocalist", "songwriter", "guitarist")); @@ -82,10 +79,7 @@ public void shouldAllowAny() { assertThat(query.fetchOne(), is(person)); } - /** - * @see DATAMONGO-1394 - */ - @Test + @Test // DATAMONGO-1394 public void shouldAllowDbRefAgainstIdProperty() { User bart = new User(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java index 81f3661f7d..10aaa8564b 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 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. @@ -113,10 +113,7 @@ public void deleteByIdFromCustomCollectionName() { assertThat(result, not(hasItem(dave))); } - /** - * @see DATAMONGO-1054 - */ - @Test + @Test // DATAMONGO-1054 public void shouldInsertSingle() { String randomId = UUID.randomUUID().toString(); @@ -129,10 +126,7 @@ public void shouldInsertSingle() { assertThat(saved, is(equalTo(person1))); } - /** - * @see DATAMONGO-1054 - */ - @Test + @Test // DATAMONGO-1054 public void shouldInsertMultipleFromList() { String randomId = UUID.randomUUID().toString(); @@ -151,10 +145,7 @@ public void shouldInsertMultipleFromList() { assertThatAllReferencePersonsWereStoredCorrectly(idToPerson, saved); } - /** - * @see DATAMONGO-1054 - */ - @Test + @Test // DATAMONGO-1054 public void shouldInsertMutlipleFromSet() { String randomId = UUID.randomUUID().toString(); @@ -173,10 +164,7 @@ public void shouldInsertMutlipleFromSet() { assertThatAllReferencePersonsWereStoredCorrectly(idToPerson, saved); } - /** - * @see DATAMONGO-1245, DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1245, DATAMONGO-1464 public void findByExampleShouldLookUpEntriesCorrectly() { Person sample = new Person(); @@ -190,10 +178,7 @@ public void findByExampleShouldLookUpEntriesCorrectly() { assertThat(result.getTotalPages(), is(1)); } - /** - * @see DATAMONGO-1464 - */ - @Test + @Test // DATAMONGO-1464 public void findByExampleMultiplePagesShouldLookUpEntriesCorrectly() { Person sample = new Person(); @@ -206,10 +191,7 @@ public void findByExampleMultiplePagesShouldLookUpEntriesCorrectly() { assertThat(result.getTotalPages(), is(2)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldLookUpEntriesCorrectly() { Person sample = new Person(); @@ -222,10 +204,7 @@ public void findAllByExampleShouldLookUpEntriesCorrectly() { assertThat(result, hasSize(2)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingNestedObject() { dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington")); @@ -244,10 +223,7 @@ public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingNestedObject() assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingPartialNestedObject() { dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington")); @@ -266,10 +242,7 @@ public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingPartialNestedOb assertThat(result, hasSize(2)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldNotFindEntriesWhenUsingPartialNestedObjectInStrictMode() { dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington")); @@ -285,10 +258,7 @@ public void findAllByExampleShouldNotFindEntriesWhenUsingPartialNestedObjectInSt assertThat(result, empty()); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingNestedObjectInStrictMode() { dave.setAddress(new Address("1600 Pennsylvania Ave NW", "20500", "Washington")); @@ -305,10 +275,7 @@ public void findAllByExampleShouldLookUpEntriesCorrectlyWhenUsingNestedObjectInS assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldRespectStringMatchMode() { Person sample = new Person(); @@ -322,10 +289,7 @@ public void findAllByExampleShouldRespectStringMatchMode() { assertThat(result, hasSize(2)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldResolveDbRefCorrectly() { User user = new User(); @@ -348,10 +312,7 @@ public void findAllByExampleShouldResolveDbRefCorrectly() { assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldResolveLegacyCoordinatesCorrectly() { Person megan = new Person("megan", "tarash"); @@ -369,10 +330,7 @@ public void findAllByExampleShouldResolveLegacyCoordinatesCorrectly() { assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldResolveGeoJsonCoordinatesCorrectly() { Person megan = new Person("megan", "tarash"); @@ -390,10 +348,7 @@ public void findAllByExampleShouldResolveGeoJsonCoordinatesCorrectly() { assertThat(result, hasSize(1)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findAllByExampleShouldProcessInheritanceCorrectly() { PersonExtended reference = new PersonExtended(); @@ -412,10 +367,7 @@ public void findAllByExampleShouldProcessInheritanceCorrectly() { assertThat(result, hasItem(reference)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void findOneByExampleShouldLookUpEntriesCorrectly() { Person sample = new Person(); @@ -428,10 +380,7 @@ public void findOneByExampleShouldLookUpEntriesCorrectly() { assertThat(result, is(equalTo(dave))); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void existsByExampleShouldLookUpEntriesCorrectly() { Person sample = new Person(); @@ -444,10 +393,7 @@ public void existsByExampleShouldLookUpEntriesCorrectly() { assertThat(result, is(true)); } - /** - * @see DATAMONGO-1245 - */ - @Test + @Test // DATAMONGO-1245 public void countByExampleShouldLookUpEntriesCorrectly() { Person sample = new Person(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java index 986a29b4ba..bf20640b53 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SpringDataMongodbSerializerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -103,20 +103,14 @@ public void convertsComplexObjectOnSerializing() { assertThat(value, is(reference)); } - /** - * @see DATAMONGO-376 - */ - @Test + @Test // DATAMONGO-376 public void returnsEmptyStringIfNoPathExpressionIsGiven() { QAddress address = QPerson.person.shippingAddresses.any(); assertThat(serializer.getKeyForPath(address, address.getMetadata()), is("")); } - /** - * @see DATAMONGO-467 - */ - @Test + @Test // DATAMONGO-467 public void convertsIdPropertyCorrectly() { ObjectId id = new ObjectId(); @@ -130,10 +124,7 @@ public void convertsIdPropertyCorrectly() { assertThat(result.get("_id"), is((Object) id)); } - /** - * @see DATAMONGO-761 - */ - @Test + @Test // DATAMONGO-761 public void looksUpKeyForNonPropertyPath() { PathBuilder
        builder = new PathBuilder
        (Address.class, "address"); @@ -143,10 +134,7 @@ public void looksUpKeyForNonPropertyPath() { assertThat(path, is("0")); } - /** - * @see DATAMONGO-969 - */ - @Test + @Test // DATAMONGO-969 public void shouldConvertObjectIdEvenWhenNestedInOperatorDbObject() { ObjectId value = new ObjectId("53bb9fd14438765b29c2d56e"); @@ -157,10 +145,7 @@ public void shouldConvertObjectIdEvenWhenNestedInOperatorDbObject() { assertThat($ne, is(value)); } - /** - * @see DATAMONGO-969 - */ - @Test + @Test // DATAMONGO-969 public void shouldConvertCollectionOfObjectIdEvenWhenNestedInOperatorDbObject() { ObjectId firstId = new ObjectId("53bb9fd14438765b29c2d56e"); @@ -178,10 +163,7 @@ public void shouldConvertCollectionOfObjectIdEvenWhenNestedInOperatorDbObject() assertThat($in, Matchers. arrayContaining(firstId, secondId)); } - /** - * @see DATAMONGO-1485 - */ - @Test + @Test // DATAMONGO-1485 public void takesCustomConversionForEnumsIntoAccount() { MongoMappingContext context = new MongoMappingContext(); From 7ab7212edc52a5cca5b626a641f0e1c2eea8e46f Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 12 Jan 2017 16:38:34 +0100 Subject: [PATCH 075/118] DATAMONGO-1587 - Polishing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert @see http://… links to valid format using @see --- .../mongodb/core/GeoCommandStatistics.java | 4 +- .../data/mongodb/core/MongoTemplate.java | 4 +- .../mongodb/core/aggregation/Aggregation.java | 4 +- .../core/aggregation/AggregationOptions.java | 2 +- .../core/aggregation/BucketAutoOperation.java | 2 +- .../core/aggregation/BucketOperation.java | 2 +- .../aggregation/ConditionalOperators.java | 4 +- .../core/aggregation/FacetOperation.java | 4 +- .../aggregation/GraphLookupOperation.java | 2 +- .../core/aggregation/GroupOperation.java | 4 +- .../core/aggregation/LimitOperation.java | 4 +- .../core/aggregation/LookupOperation.java | 4 +- .../core/aggregation/MatchOperation.java | 4 +- .../core/aggregation/OutOperation.java | 4 +- .../core/aggregation/ProjectionOperation.java | 2 +- .../aggregation/ReplaceRootOperation.java | 5 +- .../core/aggregation/SkipOperation.java | 4 +- .../core/aggregation/SortOperation.java | 4 +- .../core/aggregation/UnwindOperation.java | 4 +- .../data/mongodb/core/geo/GeoJson.java | 6 +- .../core/geo/GeoJsonGeometryCollection.java | 4 +- .../mongodb/core/geo/GeoJsonLineString.java | 4 +- .../core/geo/GeoJsonMultiLineString.java | 4 +- .../mongodb/core/geo/GeoJsonMultiPoint.java | 4 +- .../data/mongodb/core/geo/GeoJsonPoint.java | 4 +- .../data/mongodb/core/geo/GeoJsonPolygon.java | 4 +- .../mongodb/core/index/CompoundIndex.java | 10 ++-- .../data/mongodb/core/index/Index.java | 8 +-- .../data/mongodb/core/index/Indexed.java | 12 ++-- .../core/index/TextIndexDefinition.java | 4 +- .../data/mongodb/core/query/Criteria.java | 55 +++++++++---------- .../data/mongodb/core/query/TextCriteria.java | 2 +- .../data/mongodb/core/query/TextQuery.java | 4 +- .../data/mongodb/core/query/Update.java | 42 +++++++------- .../data/mongodb/gridfs/GridFsOperations.java | 4 +- .../core/aggregation/AggregationTests.java | 6 +- .../mongodb/core/aggregation/ZipInfo.java | 2 +- 37 files changed, 122 insertions(+), 124 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java index 93cd6db533..c0aa38d2ec 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/GeoCommandStatistics.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -62,7 +62,7 @@ public static GeoCommandStatistics from(DBObject commandResult) { * didn't return any result introduced in MongoDB 3.2 RC1. * * @return - * @see https://jira.mongodb.org/browse/SERVER-21024 + * @see MongoDB Jira SERVER-21024 */ public double getAverageDistance() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index f0b4eb540a..f834253275 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 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. @@ -700,7 +700,7 @@ public GeoResults geoNear(NearQuery near, Class entityClass, String co * As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries * we skip the elements ourselves to avoid at least the document 2 object mapping overhead. * - * @see https://jira.mongodb.org/browse/SERVER-3925 + * @see MongoDB Jira: SERVER-3925 */ if (index >= elementsToSkip) { result.add(callback.doWith((DBObject) element)); 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 efe60dcb3a..4798d1e1fc 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 @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -600,7 +600,7 @@ public String toString() { * Describes the system variables available in MongoDB aggregation framework pipeline expressions. * * @author Thomas Darimont - * @see http://docs.mongodb.org/manual/reference/aggregation-variables + * @see Aggregation Variables */ enum SystemVariable { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java index c7c17b8355..05e0791f98 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java @@ -21,7 +21,7 @@ /** * Holds a set of configurable aggregation options that can be used within an aggregation pipeline. A list of support * aggregation options can be found in the MongoDB reference documentation - * http://docs.mongodb.org/manual/reference/command/aggregate/#aggregate + * https://docs.mongodb.org/manual/reference/command/aggregate/#aggregate * * @author Thomas Darimont * @author Oliver Gierke diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java index 987d859167..08d1b621f1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketAutoOperation.java @@ -31,7 +31,7 @@ * instances of this class directly. * * @see http://docs.mongodb.org/manual/reference/aggregation/bucketAuto/ + * "https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/">https://docs.mongodb.org/manual/reference/aggregation/bucketAuto/ * @see BucketOperationSupport * @author Mark Paluch * @author Christoph Strobl diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java index 4d64196bbf..af7fdc5033 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/BucketOperation.java @@ -35,7 +35,7 @@ * We recommend to use the static factory method {@link Aggregation#bucket(String)} instead of creating instances of * this class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/bucket/ + * @see https://docs.mongodb.org/manual/reference/aggregation/bucket/ * @see BucketOperationSupport * @author Mark Paluch * @since 1.10 diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java index 4289add6b9..8208059b18 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ConditionalOperators.java @@ -232,7 +232,7 @@ private boolean usesCriteriaDefinition() { * converted to a simple MongoDB type. * * @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ + * "https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/">https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/ * @author Mark Paluch */ public static class IfNull implements AggregationExpression { @@ -535,7 +535,7 @@ public interface ThenBuilder { * simple MongoDB type. * * @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/ + * "https://docs.mongodb.com/manual/reference/operator/aggregation/cond/">https://docs.mongodb.com/manual/reference/operator/aggregation/cond/ * @author Mark Paluch * @author Christoph Strobl */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java index bb1d6b8fe5..17d9e8b826 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -39,10 +39,10 @@ * We recommend to use the static factory method {@link Aggregation#facet()} instead of creating instances of this class * directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/facet/ * @author Mark Paluch * @author Christoph Strobl * @since 1.10 + * @see MongoDB Aggregation Framework: $facet */ public class FacetOperation implements FieldsExposingAggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java index 2291fed33d..d46f367638 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java @@ -38,7 +38,7 @@ * of this class directly. * * @see http://docs.mongodb.org/manual/reference/aggregation/graphLookup/ + * "https://docs.mongodb.org/manual/reference/aggregation/graphLookup/">https://docs.mongodb.org/manual/reference/aggregation/graphLookup/ * @author Mark Paluch * @author Christoph Strobl * @since 1.10 diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java index 092d88c185..49ef8d24e7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GroupOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -35,13 +35,13 @@ * We recommend to use the static factory method {@link Aggregation#group(Fields)} instead of creating instances of this * class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/group/#stage._S_group * @author Sebastian Herold * @author Thomas Darimont * @author Oliver Gierke * @author Gustavo de Geus * @author Christoph Strobl * @since 1.3 + * @see MongoDB Aggregation Framework: $group */ public class GroupOperation implements FieldsExposingAggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java index b56a59e01d..684390eb62 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LimitOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2017 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. @@ -26,10 +26,10 @@ * We recommend to use the static factory method {@link Aggregation#limit(long)} instead of creating instances of this * class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/limit/ * @author Thomas Darimont * @author Oliver Gierke * @since 1.3 + * @see MongoDB Aggregation Framework: $limit */ public class LimitOperation implements AggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java index 78860073aa..f539921be7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/LookupOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -29,8 +29,8 @@ * @author Alessio Fachechi * @author Christoph Strobl * @author Mark Paluch - * @see http://docs.mongodb.org/manual/reference/aggregation/lookup/#stage._S_lookup * @since 1.9 + * @see MongoDB Aggregation Framework: $lookup */ public class LookupOperation implements FieldsExposingAggregationOperation, InheritsFieldsAggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java index eb86fb1e5c..f925b52e8e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2017 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. @@ -28,11 +28,11 @@ * {@link Aggregation#match(org.springframework.data.mongodb.core.query.Criteria)} instead of creating instances of this * class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/match/ * @author Sebastian Herold * @author Thomas Darimont * @author Oliver Gierke * @since 1.3 + * @see MongoDB Aggregation Framework: $match */ public class MatchOperation implements AggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java index 68b357a62e..7372415281 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -25,8 +25,8 @@ * We recommend to use the static factory method {@link Aggregation#out(String)} instead of creating instances of this * class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/out/ * @author Nikolay Bogdanov + * @see MongoDB Aggregation Framework: $out */ public class OutOperation implements AggregationOperation { 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 4cc791c440..5858e5d2e8 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 @@ -41,13 +41,13 @@ * We recommend to use the static factory method {@link Aggregation#project(Fields)} instead of creating instances of * this class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/project/ * @author Tobias Trelle * @author Thomas Darimont * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch * @since 1.3 + * @see MongoDB Aggregation Framework: $project */ public class ProjectionOperation implements FieldsExposingAggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java index 95648b67b3..ce071591a9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -32,11 +32,10 @@ * We recommend to use the static factory method {@link Aggregation#replaceRoot(String)} instead of creating instances * of this class directly. * - * @see https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/ * @author Mark Paluch * @author Christoph Strobl * @since 1.10 + * @see MongoDB Aggregation Framework: $replaceRoot */ public class ReplaceRootOperation implements FieldsExposingAggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java index 8d87538e8a..d0a62b89ca 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SkipOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -26,10 +26,10 @@ * We recommend to use the static factory method {@link Aggregation#skip(int)} instead of creating instances of this * class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/skip/ * @author Thomas Darimont * @author Oliver Gierke * @since 1.3 + * @see MongoDB Aggregation Framework: $skip */ public class SkipOperation implements AggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java index 0b6f6dee2e..2089a7f806 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2017 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. @@ -30,10 +30,10 @@ * We recommend to use the static factory method {@link Aggregation#sort(Direction, String...)} instead of creating * instances of this class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/sort/#pipe._S_sort * @author Thomas Darimont * @author Oliver Gierke * @since 1.3 + * @see MongoDB Aggregation Framework: $sort */ public class SortOperation implements AggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java index 12929ca879..89b6801824 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/UnwindOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -27,12 +27,12 @@ * We recommend to use the static factory method {@link Aggregation#unwind(String)} instead of creating instances of * this class directly. * - * @see http://docs.mongodb.org/manual/reference/aggregation/unwind/#pipe._S_unwind * @author Thomas Darimont * @author Oliver Gierke * @author Mark Paluch * @author Christoph Strobl * @since 1.3 + * @see MongoDB Aggregation Framework: $unwind */ public class UnwindOperation implements AggregationOperation, FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java index 91f48672a5..3c2e817887 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJson.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -27,7 +27,7 @@ public interface GeoJson> { * String value representing the type of the {@link GeoJson} object. * * @return will never be {@literal null}. - * @see http://geojson.org/geojson-spec.html#geojson-objects + * @see http://geojson.org/geojson-spec.html#geojson-objects */ String getType(); @@ -36,7 +36,7 @@ public interface GeoJson> { * determined by {@link #getType()} of geometry. * * @return will never be {@literal null}. - * @see http://geojson.org/geojson-spec.html#geometry-objects + * @see http://geojson.org/geojson-spec.html#geometry-objects */ T getCoordinates(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java index 96cc28cae3..55ca7c8919 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonGeometryCollection.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -27,7 +27,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#geometry-collection + * @see http://geojson.org/geojson-spec.html#geometry-collection */ public class GeoJsonGeometryCollection implements GeoJson>> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java index 921a8dbf87..c6beaed31f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonLineString.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -24,7 +24,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#linestring + * @see http://geojson.org/geojson-spec.html#linestring */ public class GeoJsonLineString extends GeoJsonMultiPoint { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java index 90b046ccc2..7bd392493b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiLineString.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -28,7 +28,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#multilinestring + * @see http://geojson.org/geojson-spec.html#multilinestring */ public class GeoJsonMultiLineString implements GeoJson> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java index 0812533163..9a48fdeb2a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonMultiPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -29,7 +29,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#multipoint + * @see http://geojson.org/geojson-spec.html#multipoint */ public class GeoJsonMultiPoint implements GeoJson> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java index a44aa856ce..a48315e3b8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -25,7 +25,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#point + * @see http://geojson.org/geojson-spec.html#point */ public class GeoJsonPoint extends Point implements GeoJson> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java index a5e8b1066e..879464b858 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/geo/GeoJsonPolygon.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -31,7 +31,7 @@ * * @author Christoph Strobl * @since 1.7 - * @see http://geojson.org/geojson-spec.html#polygon + * @see http://geojson.org/geojson-spec.html#polygon */ public class GeoJsonPolygon extends Polygon implements GeoJson> { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java index 9e5c4a088a..118c04c19d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/CompoundIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -54,22 +54,22 @@ IndexDirection direction() default IndexDirection.ASCENDING; /** - * @see http://docs.mongodb.org/manual/core/index-unique/ * @return + * @see https://docs.mongodb.org/manual/core/index-unique/ */ boolean unique() default false; /** * If set to true index will skip over any document that is missing the indexed field. * - * @see http://docs.mongodb.org/manual/core/index-sparse/ * @return + * @see https://docs.mongodb.org/manual/core/index-sparse/ */ boolean sparse() default false; /** - * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping * @return + * @see https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping */ boolean dropDups() default false; @@ -139,8 +139,8 @@ /** * If {@literal true} the index will be created in the background. * - * @see http://docs.mongodb.org/manual/core/indexes/#background-construction * @return + * @see https://docs.mongodb.org/manual/core/indexes/#background-construction */ boolean background() default false; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java index 29e84f716a..dcdc4017f1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Index.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 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. @@ -109,8 +109,8 @@ public Index named(String name) { /** * Reject all documents that contain a duplicate value for the indexed field. * - * @see http://docs.mongodb.org/manual/core/index-unique/ * @return + * @see https://docs.mongodb.org/manual/core/index-unique/ */ public Index unique() { this.unique = true; @@ -120,8 +120,8 @@ public Index unique() { /** * Skip over any document that is missing the indexed field. * - * @see http://docs.mongodb.org/manual/core/index-sparse/ * @return + * @see https://docs.mongodb.org/manual/core/index-sparse/ */ public Index sparse() { this.sparse = true; @@ -167,9 +167,9 @@ public Index expire(long value, TimeUnit unit) { } /** - * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping * @param duplicates * @return + * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping */ public Index unique(Duplicates duplicates) { if (duplicates == Duplicates.DROP) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java index a322622426..2133037c94 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/Indexed.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -38,8 +38,8 @@ /** * If set to true reject all documents that contain a duplicate value for the indexed field. * - * @see http://docs.mongodb.org/manual/core/index-unique/ * @return + * @see https://docs.mongodb.org/manual/core/index-unique/ */ boolean unique() default false; @@ -48,14 +48,14 @@ /** * If set to true index will skip over any document that is missing the indexed field. * - * @see http://docs.mongodb.org/manual/core/index-sparse/ * @return + * @see https://docs.mongodb.org/manual/core/index-sparse/ */ boolean sparse() default false; /** - * @see http://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping * @return + * @see https://docs.mongodb.org/manual/core/index-creation/#index-creation-duplicate-dropping */ boolean dropDups() default false; @@ -122,16 +122,16 @@ /** * If {@literal true} the index will be created in the background. * - * @see http://docs.mongodb.org/manual/core/indexes/#background-construction * @return + * @see https://docs.mongodb.org/manual/core/indexes/#background-construction */ boolean background() default false; /** * Configures the number of seconds after which the collection should expire. Defaults to -1 for no expiry. * - * @see http://docs.mongodb.org/manual/tutorial/expire-data/ * @return + * @see https://docs.mongodb.org/manual/tutorial/expire-data/ */ int expireAfterSeconds() default -1; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java index 8e56da94f6..fed1414f7b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/TextIndexDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -305,8 +305,8 @@ public TextIndexDefinitionBuilder onField(String fieldname, Float weight) { * Define the default language to be used when indexing documents. * * @param language - * @see http://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/#specify-default-language-text-index * @return + * @see https://docs.mongodb.org/manual/tutorial/specify-language-for-text-index/#specify-default-language-text-index */ public TextIndexDefinitionBuilder withDefaultLanguage(String language) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java index 0836cdae9d..721bed0ea0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 the original author or authors. + * Copyright 2010-2017 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. @@ -150,9 +150,9 @@ private boolean lastOperatorWasNot() { /** * Creates a criterion using the {@literal $ne} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/ne/ * @param o * @return + * @see MongoDB Query operator: $ne */ public Criteria ne(Object o) { criteria.put("$ne", o); @@ -162,9 +162,9 @@ public Criteria ne(Object o) { /** * Creates a criterion using the {@literal $lt} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/lt/ * @param o * @return + * @see MongoDB Query operator: $lt */ public Criteria lt(Object o) { criteria.put("$lt", o); @@ -174,9 +174,9 @@ public Criteria lt(Object o) { /** * Creates a criterion using the {@literal $lte} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/lte/ * @param o * @return + * @see MongoDB Query operator: $lte */ public Criteria lte(Object o) { criteria.put("$lte", o); @@ -186,9 +186,9 @@ public Criteria lte(Object o) { /** * Creates a criterion using the {@literal $gt} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/gt/ * @param o * @return + * @see MongoDB Query operator: $gt */ public Criteria gt(Object o) { criteria.put("$gt", o); @@ -198,9 +198,9 @@ public Criteria gt(Object o) { /** * Creates a criterion using the {@literal $gte} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/gte/ * @param o * @return + * @see MongoDB Query operator: $gte */ public Criteria gte(Object o) { criteria.put("$gte", o); @@ -210,9 +210,9 @@ public Criteria gte(Object o) { /** * Creates a criterion using the {@literal $in} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/in/ * @param o the values to match against * @return + * @see MongoDB Query operator: $in */ public Criteria in(Object... o) { if (o.length > 1 && o[1] instanceof Collection) { @@ -226,9 +226,9 @@ public Criteria in(Object... o) { /** * Creates a criterion using the {@literal $in} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/in/ * @param c the collection containing the values to match against * @return + * @see MongoDB Query operator: $in */ public Criteria in(Collection c) { criteria.put("$in", c); @@ -238,9 +238,9 @@ public Criteria in(Collection c) { /** * Creates a criterion using the {@literal $nin} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/nin/ * @param o * @return + * @see MongoDB Query operator: $nin */ public Criteria nin(Object... o) { return nin(Arrays.asList(o)); @@ -249,9 +249,9 @@ public Criteria nin(Object... o) { /** * Creates a criterion using the {@literal $nin} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/nin/ * @param o * @return + * @see MongoDB Query operator: $nin */ public Criteria nin(Collection o) { criteria.put("$nin", o); @@ -261,10 +261,10 @@ public Criteria nin(Collection o) { /** * Creates a criterion using the {@literal $mod} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/mod/ * @param value * @param remainder * @return + * @see MongoDB Query operator: $mod */ public Criteria mod(Number value, Number remainder) { List l = new ArrayList(); @@ -277,9 +277,9 @@ public Criteria mod(Number value, Number remainder) { /** * Creates a criterion using the {@literal $all} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/all/ * @param o * @return + * @see MongoDB Query operator: $all */ public Criteria all(Object... o) { return all(Arrays.asList(o)); @@ -288,9 +288,9 @@ public Criteria all(Object... o) { /** * Creates a criterion using the {@literal $all} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/all/ * @param o * @return + * @see MongoDB Query operator: $all */ public Criteria all(Collection o) { criteria.put("$all", o); @@ -300,9 +300,9 @@ public Criteria all(Collection o) { /** * Creates a criterion using the {@literal $size} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/size/ * @param s * @return + * @see MongoDB Query operator: $size */ public Criteria size(int s) { criteria.put("$size", s); @@ -312,9 +312,9 @@ public Criteria size(int s) { /** * Creates a criterion using the {@literal $exists} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/exists/ * @param b * @return + * @see MongoDB Query operator: $exists */ public Criteria exists(boolean b) { criteria.put("$exists", b); @@ -324,9 +324,9 @@ public Criteria exists(boolean b) { /** * Creates a criterion using the {@literal $type} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/type/ * @param t * @return + * @see MongoDB Query operator: $type */ public Criteria type(int t) { criteria.put("$type", t); @@ -336,8 +336,8 @@ public Criteria type(int t) { /** * Creates a criterion using the {@literal $not} meta operator which affects the clause directly following * - * @see http://docs.mongodb.org/manual/reference/operator/query/not/ * @return + * @see MongoDB Query operator: $not */ public Criteria not() { return not(null); @@ -346,9 +346,9 @@ public Criteria not() { /** * Creates a criterion using the {@literal $not} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/not/ * @param value * @return + * @see MongoDB Query operator: $not */ private Criteria not(Object value) { criteria.put("$not", value); @@ -358,9 +358,9 @@ private Criteria not(Object value) { /** * Creates a criterion using a {@literal $regex} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/regex/ * @param re * @return + * @see MongoDB Query operator: $regex */ public Criteria regex(String re) { return regex(re, null); @@ -369,11 +369,10 @@ public Criteria regex(String re) { /** * Creates a criterion using a {@literal $regex} and {@literal $options} operator. * - * @see http://docs.mongodb.org/manual/reference/operator/query/regex/ - * @see http://docs.mongodb.org/manual/reference/operator/query/regex/#op._S_options * @param re * @param options * @return + * @see MongoDB Query operator: $regex */ public Criteria regex(String re, String options) { return regex(toPattern(re, options)); @@ -406,10 +405,10 @@ private Pattern toPattern(String regex, String options) { * Creates a geospatial criterion using a {@literal $geoWithin $centerSphere} operation. This is only available for * Mongo 2.4 and higher. * - * @see http://docs.mongodb.org/manual/reference/operator/query/geoWithin/ - * @see http://docs.mongodb.org/manual/reference/operator/query/centerSphere/ * @param circle must not be {@literal null} * @return + * @see MongoDB Query operator: $geoWithin + * @see MongoDB Query operator: $centerSphere */ public Criteria withinSphere(Circle circle) { Assert.notNull(circle); @@ -420,9 +419,9 @@ public Criteria withinSphere(Circle circle) { /** * Creates a geospatial criterion using a {@literal $geoWithin} operation. * - * @see http://docs.mongodb.org/manual/reference/operator/query/geoWithin/ * @param shape * @return + * @see MongoDB Query operator: $geoWithin */ public Criteria within(Shape shape) { @@ -434,9 +433,9 @@ public Criteria within(Shape shape) { /** * Creates a geospatial criterion using a {@literal $near} operation. * - * @see http://docs.mongodb.org/manual/reference/operator/query/near/ * @param point must not be {@literal null} * @return + * @see MongoDB Query operator: $near */ public Criteria near(Point point) { Assert.notNull(point); @@ -448,9 +447,9 @@ public Criteria near(Point point) { * Creates a geospatial criterion using a {@literal $nearSphere} operation. This is only available for Mongo 1.7 and * higher. * - * @see http://docs.mongodb.org/manual/reference/operator/query/nearSphere/ * @param point must not be {@literal null} * @return + * @see MongoDB Query operator: $nearSphere */ public Criteria nearSphere(Point point) { Assert.notNull(point); @@ -477,9 +476,9 @@ public Criteria intersects(GeoJson geoJson) { /** * Creates a geo-spatial criterion using a {@literal $maxDistance} operation, for use with $near * - * @see http://docs.mongodb.org/manual/reference/operator/query/maxDistance/ * @param maxDistance * @return + * @see MongoDB Query operator: $maxDistance */ public Criteria maxDistance(double maxDistance) { @@ -514,9 +513,9 @@ public Criteria minDistance(double minDistance) { /** * Creates a criterion using the {@literal $elemMatch} operator * - * @see http://docs.mongodb.org/manual/reference/operator/query/elemMatch/ * @param c * @return + * @see MongoDB Query operator: $elemMatch */ public Criteria elemMatch(Criteria c) { criteria.put("$elemMatch", c.getCriteriaObject()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java index 6fa51cb956..a39ac9ff31 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextCriteria.java @@ -66,7 +66,7 @@ public static TextCriteria forDefaultLanguage() { /** * For a full list of supported languages see the mongodb reference manual for - * Text Search Languages. + * Text Search Languages. * * @param language * @return diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java index 8e54122cab..8808df8bca 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/TextQuery.java @@ -48,7 +48,7 @@ public TextQuery(String wordsAndPhrases) { /** * Creates new {@link TextQuery} in {@code language}.
        * For a full list of supported languages see the mongdodb reference manual for Text Search Languages. + * href="https://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages. * * @param wordsAndPhrases * @param language @@ -62,7 +62,7 @@ public TextQuery(String wordsAndPhrases, String language) { /** * Creates new {@link TextQuery} using the {@code locale}s language.
        * For a full list of supported languages see the mongdodb reference manual for Text Search Languages. + * href="https://docs.mongodb.org/manual/reference/text-search-languages/">Text Search Languages. * * @param wordsAndPhrases * @param locale diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java index 6f5e6ad453..5cc0e4f7a8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 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. @@ -106,10 +106,10 @@ public static Update fromDBObject(DBObject object, String... exclude) { /** * Update using the {@literal $set} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/set/ * @param key * @param value * @return + * @see MongoDB Update operator: $set */ public Update set(String key, Object value) { addMultiFieldOperation("$set", key, value); @@ -119,10 +119,10 @@ public Update set(String key, Object value) { /** * Update using the {@literal $setOnInsert} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/setOnInsert/ * @param key * @param value * @return + * @see MongoDB Update operator: $setOnInsert */ public Update setOnInsert(String key, Object value) { addMultiFieldOperation("$setOnInsert", key, value); @@ -132,9 +132,9 @@ public Update setOnInsert(String key, Object value) { /** * Update using the {@literal $unset} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/unset/ * @param key * @return + * @see MongoDB Update operator: $unset */ public Update unset(String key) { addMultiFieldOperation("$unset", key, 1); @@ -144,10 +144,10 @@ public Update unset(String key) { /** * Update using the {@literal $inc} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/inc/ * @param key * @param inc * @return + * @see MongoDB Update operator: $inc */ public Update inc(String key, Number inc) { addMultiFieldOperation("$inc", key, inc); @@ -157,10 +157,10 @@ public Update inc(String key, Number inc) { /** * Update using the {@literal $push} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/push/ * @param key * @param value * @return + * @see MongoDB Update operator: $push */ public Update push(String key, Object value) { addMultiFieldOperation("$push", key, value); @@ -172,10 +172,10 @@ public Update push(String key, Object value) { * Allows creation of {@code $push} command for single or multiple (using {@code $each}) values as well as using * {@code $position}. * - * @see http://docs.mongodb.org/manual/reference/operator/update/push/ - * @see http://docs.mongodb.org/manual/reference/operator/update/each/ * @param key * @return {@link PushOperatorBuilder} for given key + * @see MongoDB Update operator: $push + * @see MongoDB Update operator: $each */ public PushOperatorBuilder push(String key) { @@ -190,10 +190,10 @@ public PushOperatorBuilder push(String key) { * Note: In mongodb 2.4 the usage of {@code $pushAll} has been deprecated in favor of {@code $push $each}. * {@link #push(String)}) returns a builder that can be used to populate the {@code $each} object. * - * @see http://docs.mongodb.org/manual/reference/operator/update/pushAll/ * @param key * @param values * @return + * @see MongoDB Update operator: $pushAll */ public Update pushAll(String key, Object[] values) { addMultiFieldOperation("$pushAll", key, Arrays.copyOf(values, values.length)); @@ -215,10 +215,10 @@ public AddToSetBuilder addToSet(String key) { /** * Update using the {@literal $addToSet} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/addToSet/ * @param key * @param value * @return + * @see MongoDB Update operator: $addToSet */ public Update addToSet(String key, Object value) { addMultiFieldOperation("$addToSet", key, value); @@ -228,10 +228,10 @@ public Update addToSet(String key, Object value) { /** * Update using the {@literal $pop} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/pop/ * @param key * @param pos * @return + * @see MongoDB Update operator: $pop */ public Update pop(String key, Position pos) { addMultiFieldOperation("$pop", key, pos == Position.FIRST ? -1 : 1); @@ -241,10 +241,10 @@ public Update pop(String key, Position pos) { /** * Update using the {@literal $pull} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/pull/ * @param key * @param value * @return + * @see MongoDB Update operator: $pull */ public Update pull(String key, Object value) { addMultiFieldOperation("$pull", key, value); @@ -254,10 +254,10 @@ public Update pull(String key, Object value) { /** * Update using the {@literal $pullAll} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/pullAll/ * @param key * @param values * @return + * @see MongoDB Update operator: $pullAll */ public Update pullAll(String key, Object[] values) { addMultiFieldOperation("$pullAll", key, Arrays.copyOf(values, values.length)); @@ -267,10 +267,10 @@ public Update pullAll(String key, Object[] values) { /** * Update using the {@literal $rename} update modifier * - * @see http://docs.mongodb.org/manual/reference/operator/update/rename/ * @param oldName * @param newName * @return + * @see MongoDB Update operator: $rename */ public Update rename(String oldName, String newName) { addMultiFieldOperation("$rename", oldName, newName); @@ -280,10 +280,10 @@ public Update rename(String oldName, String newName) { /** * Update given key to current date using {@literal $currentDate} modifier. * - * @see http://docs.mongodb.org/manual/reference/operator/update/currentDate/ * @param key * @return * @since 1.6 + * @see MongoDB Update operator: $currentDate */ public Update currentDate(String key) { @@ -294,10 +294,10 @@ public Update currentDate(String key) { /** * Update given key to current date using {@literal $currentDate : { $type : "timestamp" }} modifier. * - * @see http://docs.mongodb.org/manual/reference/operator/update/currentDate/ * @param key * @return * @since 1.6 + * @see MongoDB Update operator: $currentDate */ public Update currentTimestamp(String key) { @@ -308,11 +308,11 @@ public Update currentTimestamp(String key) { /** * Multiply the value of given key by the given number. * - * @see http://docs.mongodb.org/manual/reference/operator/update/mul/ * @param key must not be {@literal null}. * @param multiplier must not be {@literal null}. * @return * @since 1.7 + * @see MongoDB Update operator: $mul */ public Update multiply(String key, Number multiplier) { @@ -324,12 +324,12 @@ public Update multiply(String key, Number multiplier) { /** * Update given key to the {@code value} if the {@code value} is greater than the current value of the field. * - * @see http://docs.mongodb.org/manual/reference/operator/update/max/ - * @see https://docs.mongodb.org/manual/reference/bson-types/#faq-dev-compare-order-for-bson-types * @param key must not be {@literal null}. * @param value must not be {@literal null}. * @return * @since 1.10 + * @see Comparison/Sort Order + * @see MongoDB Update operator: $max */ public Update max(String key, Object value) { @@ -341,12 +341,12 @@ public Update max(String key, Object value) { /** * Update given key to the {@code value} if the {@code value} is less than the current value of the field. * - * @see http://docs.mongodb.org/manual/reference/operator/update/min/ - * @see https://docs.mongodb.org/manual/reference/bson-types/#faq-dev-compare-order-for-bson-types * @param key must not be {@literal null}. * @param value must not be {@literal null}. * @return * @since 1.10 + * @see Comparison/Sort Order + * @see MongoDB Update operator: $min */ public Update min(String key, Object value) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsOperations.java index 3c94ef21ee..c1e16b5418 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 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. @@ -121,7 +121,7 @@ public interface GridFsOperations extends ResourcePatternResolver { * Returns all files matching the given query. Note, that currently {@link Sort} criterias defined at the * {@link Query} will not be regarded as MongoDB does not support ordering for GridFS file access. * - * @see https://jira.mongodb.org/browse/JAVA-431 + * @see MongoDB Jira: JAVA-431 * @param query * @return */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 9330b3e260..9b97663705 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -152,7 +152,7 @@ private void cleanDb() { * Imports the sample dataset (zips.json) if necessary (e.g. if it doesn't exist yet). The dataset can originally be * found on the mongodb aggregation framework example website: * - * @see MongoDB Aggregation Examples + * @see MongoDB Aggregation Examples */ private void initSampleDataIfNecessary() { @@ -342,7 +342,7 @@ public void shouldDetectResultMismatch() { @Test // DATAMONGO-586 public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { /* - //complex mongodb aggregation framework example from http://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state + //complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state db.zipInfo.aggregate( { $group: { @@ -451,7 +451,7 @@ public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { public void findStatesWithPopulationOver10MillionAggregationExample() { /* //complex mongodb aggregation framework example from - http://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state + https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state db.zipcodes.aggregate( { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java index 4b63398d2c..1dd3503a1a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ZipInfo.java @@ -7,7 +7,7 @@ /** * Data model from mongodb reference data set * - * @see Aggregation Examples + * @see Aggregation Examples * @see T nextAs(Iterator iterator, Class type) { + Object parameter = iterator.next(); - if (parameter.getClass().isAssignableFrom(type)) { + + if (ClassUtils.isAssignable(type, parameter.getClass())) { return (T) parameter; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 7544ae64d2..b2d925bc61 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -51,6 +51,7 @@ import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.repository.Person.Sex; import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension.SampleSecurityContextHolder; @@ -273,6 +274,18 @@ public void findsPeopleByLocationNear() { assertThat(result, hasItem(dave)); } + @Test // DATAMONGO-1588 + public void findsPeopleByLocationNearUsingGeoJsonType() { + + GeoJsonPoint point = new GeoJsonPoint(-73.99171, 40.738868); + dave.setLocation(point); + repository.save(dave); + + List result = repository.findByLocationNear(point); + assertThat(result.size(), is(1)); + assertThat(result, hasItem(dave)); + } + @Test public void findsPeopleByLocationWithinCircle() { Point point = new Point(-73.99171, 40.738868); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java index f6b8d83262..6a70e7bfc5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java @@ -44,6 +44,8 @@ import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.geo.GeoJsonLineString; +import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.index.GeoSpatialIndexType; import org.springframework.data.mongodb.core.index.GeoSpatialIndexed; import org.springframework.data.mongodb.core.mapping.DBRef; @@ -604,6 +606,29 @@ public void notLikeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() { assertThat(query.getQueryObject(), is(query(where("username").not().regex(".*")).getQueryObject())); } + @Test // DATAMONGO-1588 + public void queryShouldAcceptSubclassOfDeclaredArgument() { + + PartTree tree = new PartTree("findByLocationNear", User.class); + ConvertingParameterAccessor accessor = getAccessor(converter, new GeoJsonPoint(-74.044502D, 40.689247D)); + + Query query = new MongoQueryCreator(tree, accessor, context).createQuery(); + assertThat(query.getQueryObject().containsField("location"), is(true)); + } + + @Test // DATAMONGO-1588 + public void queryShouldThrowExceptionWhenArgumentDoesNotMatchDeclaration() { + + expection.expect(IllegalArgumentException.class); + expection.expectMessage("Expected parameter type of " + Point.class); + + PartTree tree = new PartTree("findByLocationNear", User.class); + ConvertingParameterAccessor accessor = getAccessor(converter, + new GeoJsonLineString(new Point(-74.044502D, 40.689247D), new Point(-73.997330D, 40.730824D))); + + new MongoQueryCreator(tree, accessor, context).createQuery(); + } + interface PersonRepository extends Repository { List findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname); @@ -622,6 +647,8 @@ class User { Address address; Address2dSphere address2dSphere; + + Point location; } static class Address { From 3476c639c2957c6e4c4b579d0f7927d6a33f956d Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 16 Jan 2017 09:10:19 +0100 Subject: [PATCH 078/118] DATAMONGO-1588 - Polishing. Remove unused fields. Fix typo in method name. Reformat inner class to align formatting. Original pull request: #435. --- .../repository/query/MongoQueryCreatorUnitTests.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java index 6a70e7bfc5..567d5b9b1f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java @@ -71,8 +71,6 @@ */ public class MongoQueryCreatorUnitTests { - Method findByFirstname, findByFirstnameAndFriend, findByFirstnameNotNull; - MappingContext, MongoPersistentProperty> context; MongoConverter converter; @@ -402,7 +400,7 @@ public void shouldCreateRegexWhenUsingNotContainsOnStringProperty() { } @Test // DATAMONGO-1139 - public void createsNonShericalNearForDistanceWithDefaultMetric() { + public void createsNonSphericalNearForDistanceWithDefaultMetric() { Point point = new Point(1.0, 1.0); Distance distance = new Distance(1.0); @@ -654,11 +652,14 @@ class User { static class Address { String street; + Point geo; } static class Address2dSphere { + String street; + @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE) Point geo; } } From 822e29524d693f35571bf427c35b9c5c8c684c5a Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 16 Jan 2017 10:00:12 +0100 Subject: [PATCH 079/118] DATAMONGO-1590 - EntityInformation selected now correctly considers Persistable. We now wrap the MappingMongoEntityInformation into one that delegates the methods implemented by Persistable to the actual entity in case it implements said interface. Original pull request: #436. --- .../MongoEntityInformationSupport.java | 55 ++++++++++ .../support/MongoRepositoryFactory.java | 6 +- .../PersistableMongoEntityInformation.java | 102 ++++++++++++++++++ ...appingMongoEntityInformationUnitTests.java | 76 +++++++++++++ 4 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoEntityInformationSupport.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoEntityInformationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoEntityInformationSupport.java new file mode 100644 index 0000000000..9b2f41b879 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoEntityInformationSupport.java @@ -0,0 +1,55 @@ +/* + * Copyright 2017 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.repository.support; + +import java.io.Serializable; + +import org.springframework.data.domain.Persistable; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.repository.query.MongoEntityInformation; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Support class responsible for creating {@link MongoEntityInformation} instances for a given + * {@link MongoPersistentEntity}. + * + * @author Christoph Strobl + * @since 1.10 + */ +final class MongoEntityInformationSupport { + + private MongoEntityInformationSupport() {} + + /** + * Factory method for creating {@link MongoEntityInformation}. + * + * @param entity must not be {@literal null}. + * @param idType can be {@literal null}. + * @return never {@literal null}. + */ + static MongoEntityInformation entityInformationFor( + MongoPersistentEntity entity, Class idType) { + + Assert.notNull(entity, "Entity must not be null!"); + + MappingMongoEntityInformation entityInformation = new MappingMongoEntityInformation( + (MongoPersistentEntity) entity, (Class) idType); + return ClassUtils.isAssignable(Persistable.class, entity.getType()) + ? new PersistableMongoEntityInformation(entityInformation) : entityInformation; + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java index 03d75f2c82..93224627e7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoRepositoryFactory.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.lang.reflect.Method; +import org.springframework.data.domain.Persistable; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.core.MongoOperations; @@ -42,6 +43,7 @@ import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * Factory to create {@link MongoRepository} instances. @@ -123,8 +125,8 @@ private MongoEntityInformation getEntityInfo String.format("Could not lookup mapping metadata for domain class %s!", domainClass.getName())); } - return new MappingMongoEntityInformation((MongoPersistentEntity) entity, - information != null ? (Class) information.getIdType() : null); + return MongoEntityInformationSupport. entityInformationFor(entity, + information != null ? information.getIdType() : null); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java new file mode 100644 index 0000000000..a6c63a4051 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java @@ -0,0 +1,102 @@ +/* + * Copyright 2017 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.repository.support; + +import java.io.Serializable; + +import org.springframework.data.domain.Persistable; +import org.springframework.data.mongodb.repository.query.MongoEntityInformation; + +/** + * {@link MongoEntityInformation} implementation wrapping an existing {@link MongoEntityInformation} considering + * {@link Persistable} types by delegating {@link #isNew(Object)} and {@link #getId(Object)} to the corresponding + * {@link Persistable#isNew()} and {@link Persistable#getId()} implementations. + * + * @author Christoph Strobl + * @since 1.10 + */ +public class PersistableMongoEntityInformation implements MongoEntityInformation { + + private final MongoEntityInformation delegate; + + public PersistableMongoEntityInformation(MongoEntityInformation delegate) { + this.delegate = delegate; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.MongoEntityInformation#getCollectionName() + */ + @Override + public String getCollectionName() { + return delegate.getCollectionName(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.MongoEntityInformation#getIdAttribute() + */ + @Override + public String getIdAttribute() { + return delegate.getIdAttribute(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.EntityInformation#isNew(java.lang.Object) + */ + @Override + public boolean isNew(T t) { + + if (t instanceof Persistable) { + return ((Persistable) t).isNew(); + } + + return delegate.isNew(t); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object) + */ + @Override + public ID getId(T t) { + + if (t instanceof Persistable) { + return (ID) ((Persistable) t).getId(); + } + + return delegate.getId(t); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.PersistentEntityInformation#getIdType() + */ + @Override + public Class getIdType() { + return delegate.getIdType(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.support.EntityMetadata#getJavaType() + */ + @Override + public Class getJavaType() { + return delegate.getJavaType(); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java new file mode 100644 index 0000000000..2f03024c27 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2017 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.repository.query; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.domain.Persistable; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation; +import org.springframework.data.mongodb.repository.support.PersistableMongoEntityInformation; + +/** + * Tests for {@link PersistableMongoEntityInformation}. + * + * @author Christoph Strobl + */ +@RunWith(MockitoJUnitRunner.class) +public class PersistableMappingMongoEntityInformationUnitTests { + + @Mock MongoPersistentEntity persistableImplementingEntityTypeInfo; + + @Before + public void setUp() { + when(persistableImplementingEntityTypeInfo.getType()).thenReturn(TypeImplementingPersistable.class); + } + + @Test // DATAMONGO-1590 + public void considersPersistableIsNew() { + + PersistableMongoEntityInformation information = new PersistableMongoEntityInformation( + new MappingMongoEntityInformation(persistableImplementingEntityTypeInfo)); + + assertThat(information.isNew(new TypeImplementingPersistable(100L, false)), is(false)); + } + + static class TypeImplementingPersistable implements Persistable { + + final Long id; + final boolean isNew; + + public TypeImplementingPersistable(Long id, boolean isNew) { + this.id = id; + this.isNew = isNew; + } + + @Override + public Long getId() { + return id; + } + + @Override + public boolean isNew() { + return isNew; + } + } +} From 45818f26b3c243561cfc77a8d258fe9423f3c5df Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 18 Jan 2017 19:41:11 +0100 Subject: [PATCH 080/118] DATAMONGO-1590 - Polishing. Removed some compiler warnings. Hide newly introduced class in package scope and made use of Lombok annotations to avoid boilerplate code. Original pull request: #436. --- .../MongoEntityInformationSupport.java | 5 ++-- .../PersistableMongoEntityInformation.java | 19 +++++++------ ...appingMongoEntityInformationUnitTests.java | 27 ++++++------------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoEntityInformationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoEntityInformationSupport.java index 9b2f41b879..ccc37205c0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoEntityInformationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MongoEntityInformationSupport.java @@ -41,15 +41,16 @@ private MongoEntityInformationSupport() {} * @param idType can be {@literal null}. * @return never {@literal null}. */ + @SuppressWarnings("unchecked") static MongoEntityInformation entityInformationFor( MongoPersistentEntity entity, Class idType) { Assert.notNull(entity, "Entity must not be null!"); - MappingMongoEntityInformation entityInformation = new MappingMongoEntityInformation( + MappingMongoEntityInformation entityInformation = new MappingMongoEntityInformation( (MongoPersistentEntity) entity, (Class) idType); + return ClassUtils.isAssignable(Persistable.class, entity.getType()) ? new PersistableMongoEntityInformation(entityInformation) : entityInformation; } - } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java index a6c63a4051..99305f4b44 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/PersistableMongoEntityInformation.java @@ -15,6 +15,9 @@ */ package org.springframework.data.mongodb.repository.support; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + import java.io.Serializable; import org.springframework.data.domain.Persistable; @@ -26,15 +29,13 @@ * {@link Persistable#isNew()} and {@link Persistable#getId()} implementations. * * @author Christoph Strobl + * @author Oliver Gierke * @since 1.10 */ -public class PersistableMongoEntityInformation implements MongoEntityInformation { - - private final MongoEntityInformation delegate; +@RequiredArgsConstructor +class PersistableMongoEntityInformation implements MongoEntityInformation { - public PersistableMongoEntityInformation(MongoEntityInformation delegate) { - this.delegate = delegate; - } + private final @NonNull MongoEntityInformation delegate; /* * (non-Javadoc) @@ -59,10 +60,11 @@ public String getIdAttribute() { * @see org.springframework.data.repository.core.EntityInformation#isNew(java.lang.Object) */ @Override + @SuppressWarnings("unchecked") public boolean isNew(T t) { if (t instanceof Persistable) { - return ((Persistable) t).isNew(); + return ((Persistable) t).isNew(); } return delegate.isNew(t); @@ -73,10 +75,11 @@ public boolean isNew(T t) { * @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object) */ @Override + @SuppressWarnings("unchecked") public ID getId(T t) { if (t instanceof Persistable) { - return (ID) ((Persistable) t).getId(); + return (ID) ((Persistable) t).getId(); } return delegate.getId(t); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java index 2f03024c27..c5ebcb86fd 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/PersistableMappingMongoEntityInformationUnitTests.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.mongodb.repository.query; +package org.springframework.data.mongodb.repository.support; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import lombok.Value; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,13 +28,12 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.domain.Persistable; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; -import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation; -import org.springframework.data.mongodb.repository.support.PersistableMongoEntityInformation; /** * Tests for {@link PersistableMongoEntityInformation}. * * @author Christoph Strobl + * @author Oliver Gierke */ @RunWith(MockitoJUnitRunner.class) public class PersistableMappingMongoEntityInformationUnitTests { @@ -53,24 +54,12 @@ public void considersPersistableIsNew() { assertThat(information.isNew(new TypeImplementingPersistable(100L, false)), is(false)); } + @Value static class TypeImplementingPersistable implements Persistable { - final Long id; - final boolean isNew; - - public TypeImplementingPersistable(Long id, boolean isNew) { - this.id = id; - this.isNew = isNew; - } - - @Override - public Long getId() { - return id; - } + private static final long serialVersionUID = -1619090149320971099L; - @Override - public boolean isNew() { - return isNew; - } + Long id; + boolean isNew; } } From 98b3c1678fbc7cfe3985409030cdb7f62fac10a7 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 20 Jan 2017 16:33:59 +0100 Subject: [PATCH 081/118] DATAMONGO-1592 - Adapt AuditingEventListenerUnitTests to changes in core auditing. The core auditing implementation now skips the invocation of auditing in case the candidate aggregate doesn't need any auditing in the first place. We needed to adapt the sample class we use to actually carry the necessary auditing annotations. Related ticket: DATACMNS-957. --- .../core/mapping/event/AuditingEventListenerUnitTests.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java index ff703cf6d8..34e8bb43b6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/event/AuditingEventListenerUnitTests.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.*; import java.util.Arrays; +import java.util.Date; import org.junit.Before; import org.junit.Test; @@ -29,7 +30,9 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.Ordered; +import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.auditing.IsNewAwareAuditingHandler; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; @@ -101,5 +104,7 @@ public void hasExplicitOrder() { static class Sample { @Id String id; + @CreatedDate Date created; + @LastModifiedDate Date modified; } } From 3ed72a922a333c88be62d80d9fb4fcd8b158198e Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 20 Jan 2017 15:01:45 +0100 Subject: [PATCH 082/118] =?UTF-8?q?DATAMONGO-1594=20-=20Update=20"what?= =?UTF-8?q?=E2=80=99s=20new"=20section=20in=20reference=20documentation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/asciidoc/new-features.adoc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index dad476b2eb..1c771b996e 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -3,9 +3,15 @@ [[new-features.1-10-0]] == What's new in Spring Data MongoDB 1.10 -* Support for `$min`, `$max` and `$slice` operators via `Update`. -* Support for `$cond` and `$ifNull` operators via `Aggregation`. -* Multi-faceted aggregations using `$facet`, `$bucket` and `$bucketAuto` via `Aggregation`. +* Compatible with MongoDB Server 3.4 and the MongoDB Java Driver 3.4. +* New annotations for `@CountQuery`, `@DeleteQuery` and `@ExistsQuery`. +* Extended support for MongoDB 3.2 and MongoDB 3.4 aggregation operators (see <>). +* Support partial filter expression when creating indexes. +* Publish lifecycle events when loading/converting ``DBRef``s. +* Added any-match mode for Query By Example. +* Support for `$caseSensitive` and `$diacriticSensitive` text search. +* Support for GeoJSON Polygon with hole. +* Performance improvements by bulk fetching ``DBRef``s. [[new-features.1-9-0]] == What's new in Spring Data MongoDB 1.9 From 6f278ce838459021d98a8b2887f0b3548008a0d6 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 22 Dec 2016 11:21:30 +0100 Subject: [PATCH 083/118] DATAMONGO-1575 - Escape Strings correctly. Use regex groups and parameter index values for replacement in string based queries. --- .../ExpressionEvaluatingParameterBinder.java | 72 +++++++++++++++---- .../query/StringBasedMongoQueryUnitTests.java | 41 +++++++++-- .../reference/mongo-repositories.adoc | 62 +++++++++++++++- 3 files changed, 155 insertions(+), 20 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index a86378e1d8..279fe937c1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -15,6 +15,8 @@ */ package org.springframework.data.mongodb.repository.query; +import com.mongodb.DBObject; +import lombok.EqualsAndHashCode; import lombok.Value; import java.util.ArrayList; @@ -110,16 +112,23 @@ private String replacePlaceholders(String input, MongoParameterAccessor accessor Matcher matcher = createReplacementPattern(bindingContext.getBindings()).matcher(input); StringBuffer buffer = new StringBuffer(); + int parameterIndex = 0; while (matcher.find()) { - ParameterBinding binding = bindingContext.getBindingFor(extractPlaceholder(matcher.group())); + Placeholder placeholder = extractPlaceholder(parameterIndex++, matcher); + ParameterBinding binding = bindingContext.getBindingFor(placeholder); String valueForBinding = getParameterValueForBinding(accessor, bindingContext.getParameters(), binding); // appendReplacement does not like unescaped $ sign and others, so we need to quote that stuff first matcher.appendReplacement(buffer, Matcher.quoteReplacement(valueForBinding)); + if (StringUtils.hasText(placeholder.getSuffix())) { + buffer.append(placeholder.getSuffix()); + } - if (binding.isQuoted()) { - postProcessQuotedBinding(buffer, valueForBinding); + if (binding.isQuoted() || placeholder.isQuoted()) { + postProcessQuotedBinding(buffer, valueForBinding, + !binding.isExpression() ? accessor.getBindableValue(binding.getParameterIndex()) : null, + binding.isExpression()); } } @@ -135,7 +144,7 @@ private String replacePlaceholders(String input, MongoParameterAccessor accessor * @param buffer the {@link StringBuffer} to operate upon. * @param valueForBinding the actual binding value. */ - private void postProcessQuotedBinding(StringBuffer buffer, String valueForBinding) { + private void postProcessQuotedBinding(StringBuffer buffer, String valueForBinding, Object raw, boolean isExpression) { int quotationMarkIndex = buffer.length() - valueForBinding.length() - 1; char quotationMark = buffer.charAt(quotationMarkIndex); @@ -151,7 +160,8 @@ private void postProcessQuotedBinding(StringBuffer buffer, String valueForBindin quotationMark = buffer.charAt(quotationMarkIndex); } - if (valueForBinding.startsWith("{")) { // remove quotation char before the complex object string + if (valueForBinding.startsWith("{") && (raw instanceof DBObject || isExpression)) { // remove quotation char before + // the complex object string buffer.deleteCharAt(quotationMarkIndex); @@ -181,7 +191,12 @@ private String getParameterValueForBinding(MongoParameterAccessor accessor, Mong : accessor.getBindableValue(binding.getParameterIndex()); if (value instanceof String && binding.isQuoted()) { - return ((String) value).startsWith("{") ? (String) value : ((String) value).replace("\"", "\\\""); + + if (binding.isExpression() && ((String) value).startsWith("{")) { + return (String) value; + } + + return ((String) value).replace("\\", "\\\\").replace("\"", "\\\""); } if (value instanceof byte[]) { @@ -228,8 +243,8 @@ private Pattern createReplacementPattern(List bindings) { for (ParameterBinding binding : bindings) { regex.append("|"); - regex.append(Pattern.quote(binding.getParameter())); - regex.append("['\"]?"); // potential quotation char (as in { foo : '?0' }). + regex.append("(" + Pattern.quote(binding.getParameter()) + ")"); + regex.append("(\\W?['\"])?"); // potential quotation char (as in { foo : '?0' }). } return Pattern.compile(regex.substring(1)); @@ -239,14 +254,35 @@ private Pattern createReplacementPattern(List bindings) { * Extract the placeholder stripping any trailing trailing quotation mark that might have resulted from the * {@link #createReplacementPattern(List) pattern} used. * - * @param groupName The actual {@link Matcher#group() group}. + * @param matcher The actual {@link Matcher#group() group}. * @return */ - private Placeholder extractPlaceholder(String groupName) { + private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) { - return !groupName.endsWith("'") && !groupName.endsWith("\"") ? // - Placeholder.of(groupName, false) : // - Placeholder.of(groupName.substring(0, groupName.length() - 1), true); + if (matcher.groupCount() > 1) { + + String suffix = matcher.group(parameterIndex * 2 + 2); + String rawPlaceholder = matcher.group(parameterIndex * 2 + 1); + + if (!StringUtils.hasText(rawPlaceholder)) { + + rawPlaceholder = matcher.group(); + suffix = ""+rawPlaceholder.charAt(rawPlaceholder.length()-1); + if(rawPlaceholder.endsWith("'")) { + rawPlaceholder = rawPlaceholder.substring(0, rawPlaceholder.length()-1); + } + } + + if (StringUtils.hasText(suffix)) { + + boolean quoted= (suffix.endsWith("'") || suffix.endsWith("\"")); + + return Placeholder.of(parameterIndex, rawPlaceholder, quoted, + quoted ? suffix.substring(0, suffix.length() - 1) : suffix); + } + } + + return Placeholder.of(parameterIndex, matcher.group(), false, null); } /** @@ -317,8 +353,9 @@ private static Map mapBindings(List map = new LinkedHashMap(bindings.size(), 1); + int parameterIndex = 0; for (ParameterBinding binding : bindings) { - map.put(Placeholder.of(binding.getParameter(), binding.isQuoted()), binding); + map.put(Placeholder.of(parameterIndex++, binding.getParameter(), binding.isQuoted(), null), binding); } return map; @@ -332,10 +369,13 @@ private static Map mapBindings(List... parameters) throws Exception { @@ -437,6 +464,9 @@ private interface SampleRepository extends Repository { @Query("{ 'lastname' : '?0' }") Person findByLastnameQuoted(String lastname); + @Query("{ 'lastname' : { '$regex' : '^(?0)'} }") + Person findByLastnameRegex(String lastname); + @Query("{ 'address' : ?0 }") Person findByAddress(Address address); @@ -484,5 +514,8 @@ private interface SampleRepository extends Repository { @Query("{ 'arg0' : ?0, 'arg1' : ?1 }") List findByStringWithWildcardChar(String arg0, String arg1); + + @Query("{ 'arg0' : ?0 }") + List findByWithBsonArgument(DBObject arg0); } } diff --git a/src/main/asciidoc/reference/mongo-repositories.adoc b/src/main/asciidoc/reference/mongo-repositories.adoc index eb053f8926..e5a8a7cd7d 100644 --- a/src/main/asciidoc/reference/mongo-repositories.adoc +++ b/src/main/asciidoc/reference/mongo-repositories.adoc @@ -366,7 +366,9 @@ public interface PersonRepository extends MongoRepository } ---- -The placeholder ?0 lets you substitute the value from the method arguments into the JSON query string. +The placeholder `?0` lets you substitute the value from the method arguments into the JSON query string. + +NOTE: `String` parameter values are escaped during the binding process, which means that it is not possible to add MongoDB specific operators via the argument. You can also use the filter property to restrict the set of properties that will be mapped into the Java object. For example, @@ -382,6 +384,64 @@ public interface PersonRepository extends MongoRepository This will return only the firstname, lastname and Id properties of the Person objects. The age property, a java.lang.Integer, will not be set and its value will therefore be null. +[[mongodb.repositories.queries.json-spel]] +=== JSON based queries with SpEL expressions + +Query strings and field definitions can be used together with SpEL expressions to create dynamic queries at runtime. +SpEL expressions can provide predicate values and can be used to extend predicates with subdocuments. + +Expressions expose method arguments through an array that contains all arguments. The the following query uses `[0]` +to declare the predicate value for `lastname` that is equivalent to the `?0` parameter binding. + +[source,java] +---- +public interface PersonRepository extends MongoRepository + + @Query("{'lastname': ?#{[0]} }") + List findByQueryWithExpression(String param0); + +} +---- + +Expressions can be used to invoke functions, evaluate conditionals and construct values. SpEL expressions +reveal in conjunction with JSON a side-effect as Map-like declarations inside of SpEL read like JSON. + +[source,java] +---- +public interface PersonRepository extends MongoRepository + + @Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}") + List findByQueryWithExpressionAndNestedObject(boolean param0, String param1); + +} +---- + +SpEL in query strings can be a powerful way to enhance queries and can accept a broad range of unwanted arguments. +You should make sure to sanitize strings before passing these to the query to avoid unwanted changes to your query. + +Expression support is extensible through the Query SPI `org.springframework.data.repository.query.spi.EvaluationContextExtension` +than can contribute properties, functions and customize the root object. Extensions are retrieved from the application context +at the time of SpEL evaluation when the query is build. + +[source,java] +---- +public class SampleEvaluationContextExtension extends EvaluationContextExtensionSupport { + + @Override + public String getExtensionId() { + return "security"; + } + + @Override + public Map getProperties() { + return Collections.singletonMap("principal", SecurityContextHolder.getCurrent().getPrincipal()); + } +} +---- + +NOTE: Bootstrapping `MongoRepositoryFactory` yourself is not application context-aware and requires further configuration +to pick up Query SPI extensions. + [[mongodb.repositories.queries.type-safe]] === Type-safe Query methods From b9aa410ac176e4f051af7d79987387cf7c1f4b9a Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 2 Jan 2017 10:18:01 +0100 Subject: [PATCH 084/118] DATAMONGO-1575 - Polishing. Extend year range in license headers. Use MongoDB JSON serializer for String escaping. Move unquoting/quote checking to inner QuotedString utility class. Reformat code. --- .../ExpressionEvaluatingParameterBinder.java | 67 +++++++++++++++---- .../query/StringBasedMongoQueryUnitTests.java | 41 +++++------- .../reference/mongo-repositories.adoc | 2 - 3 files changed, 71 insertions(+), 39 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index 279fe937c1..154ecefdf8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-2017 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. @@ -15,9 +15,9 @@ */ package org.springframework.data.mongodb.repository.query; -import com.mongodb.DBObject; import lombok.EqualsAndHashCode; import lombok.Value; +import lombok.experimental.UtilityClass; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -39,10 +39,11 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import com.mongodb.DBObject; import com.mongodb.util.JSON; /** - * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placholders within a + * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a * {@link String}. * * @author Christoph Strobl @@ -143,6 +144,8 @@ private String replacePlaceholders(String input, MongoParameterAccessor accessor * * @param buffer the {@link StringBuffer} to operate upon. * @param valueForBinding the actual binding value. + * @param raw the raw binding value + * @param isExpression {@literal true} if the binding value results from a SpEL expression. */ private void postProcessQuotedBinding(StringBuffer buffer, String valueForBinding, Object raw, boolean isExpression) { @@ -160,8 +163,8 @@ private void postProcessQuotedBinding(StringBuffer buffer, String valueForBindin quotationMark = buffer.charAt(quotationMarkIndex); } - if (valueForBinding.startsWith("{") && (raw instanceof DBObject || isExpression)) { // remove quotation char before - // the complex object string + // remove quotation char before the complex object string + if (valueForBinding.startsWith("{") && (raw instanceof DBObject || isExpression)) { buffer.deleteCharAt(quotationMarkIndex); @@ -196,7 +199,7 @@ private String getParameterValueForBinding(MongoParameterAccessor accessor, Mong return (String) value; } - return ((String) value).replace("\\", "\\\\").replace("\"", "\\\""); + return QuotedString.unquote(JSON.serialize(value)); } if (value instanceof byte[]) { @@ -254,31 +257,32 @@ private Pattern createReplacementPattern(List bindings) { * Extract the placeholder stripping any trailing trailing quotation mark that might have resulted from the * {@link #createReplacementPattern(List) pattern} used. * - * @param matcher The actual {@link Matcher#group() group}. + * @param parameterIndex The actual parameter index. + * @param matcher The actual {@link Matcher}. * @return */ private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) { if (matcher.groupCount() > 1) { - String suffix = matcher.group(parameterIndex * 2 + 2); String rawPlaceholder = matcher.group(parameterIndex * 2 + 1); + String suffix = matcher.group(parameterIndex * 2 + 2); if (!StringUtils.hasText(rawPlaceholder)) { rawPlaceholder = matcher.group(); - suffix = ""+rawPlaceholder.charAt(rawPlaceholder.length()-1); - if(rawPlaceholder.endsWith("'")) { - rawPlaceholder = rawPlaceholder.substring(0, rawPlaceholder.length()-1); + suffix = "" + rawPlaceholder.charAt(rawPlaceholder.length() - 1); + if (QuotedString.endsWithQuote(rawPlaceholder)) { + rawPlaceholder = QuotedString.unquoteSuffix(rawPlaceholder); } } if (StringUtils.hasText(suffix)) { - boolean quoted= (suffix.endsWith("'") || suffix.endsWith("\"")); + boolean quoted = QuotedString.endsWithQuote(suffix); return Placeholder.of(parameterIndex, rawPlaceholder, quoted, - quoted ? suffix.substring(0, suffix.length() - 1) : suffix); + quoted ? QuotedString.unquoteSuffix(suffix) : suffix); } } @@ -388,4 +392,41 @@ public String toString() { } } + + /** + * Utility to handle quoted strings using single/double quotes. + * + * @author Mark Paluch + */ + @UtilityClass + static class QuotedString { + + /** + * @param string + * @return {@literal true} if {@literal string} ends with a single/double quote. + */ + static boolean endsWithQuote(String string) { + return string.endsWith("'") || string.endsWith("\""); + } + + /** + * Remove trailing quoting from {@literal quoted}. + * + * @param quoted + * @return {@literal quoted} with removed quotes. + */ + public static String unquoteSuffix(String quoted) { + return quoted.substring(0, quoted.length() - 1); + } + + /** + * Remove leading and trailing quoting from {@literal quoted}. + * + * @param quoted + * @return {@literal quoted} with removed quotes. + */ + public static String unquote(String quoted) { + return quoted.substring(1, quoted.length() - 1); + } + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index d7ed7737c1..8d9237bcbe 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -363,9 +363,9 @@ public void shouldIgnorePlaceholderPatternInReplacementValue() throws Exception public void shouldQuoteStringReplacementCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews', password: 'foo"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews', password: 'foo"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(not(new BasicDBObjectBuilder().add("lastname", "Matthews").add("password", "foo").get()))); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews', password: 'foo"))); @@ -375,9 +375,9 @@ public void shouldQuoteStringReplacementCorrectly() throws Exception { public void shouldQuoteStringReplacementContainingQuotesCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews\", password: \"foo"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews\", password: \"foo"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(not(new BasicDBObjectBuilder().add("lastname", "Matthews").add("password", "foo").get()))); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews\", password: \"foo"))); @@ -387,54 +387,47 @@ public void shouldQuoteStringReplacementContainingQuotesCorrectly() throws Excep public void shouldQuoteStringReplacementWithQuotationsCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "\"Dave Matthews\", password: 'foo"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "\"Dave Matthews\", password: 'foo"))); } @Test // DATAMONGO-1565, DATAMONGO-1575 - public void shouldQuoteComplexQueryStringCorreclty() throws Exception { + public void shouldQuoteComplexQueryStringCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"calamity\" }"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"calamity\" }"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); - assertThat(query.getQueryObject(), - is((DBObject) new BasicDBObject("lastname", "{ $ne : \"calamity\" }"))); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "{ $ne : \"calamity\" }"))); } @Test // DATAMONGO-1565, DATAMONGO-1575 public void shouldQuotationInQuotedComplexQueryString() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"\\\"calamity\\\"\" }"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); - assertThat(query.getQueryObject(), - is((DBObject) new BasicDBObject("lastname", "{ $ne : \"\\\"calamity\\\"\" }"))); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "{ $ne : \"\\\"calamity\\\"\" }"))); } - /** - * @see DATAMONGO-1575 - */ - @Test + @Test // DATAMONGO-1575 public void shouldTakeBsonParameterAsIs() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByWithBsonArgument", DBObject.class); - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new BasicDBObject("$regex", "^calamity$")); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, + new BasicDBObject("$regex", "^calamity$")); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("arg0", Pattern.compile("^calamity$")))); } - /** - * @see DATAMONGO-1575 - */ - @Test + @Test // DATAMONGO-1575 public void shouldReplaceParametersInInQuotedExpressionOfNestedQueryOperator() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameRegex", String.class); diff --git a/src/main/asciidoc/reference/mongo-repositories.adoc b/src/main/asciidoc/reference/mongo-repositories.adoc index e5a8a7cd7d..40d12e2ac6 100644 --- a/src/main/asciidoc/reference/mongo-repositories.adoc +++ b/src/main/asciidoc/reference/mongo-repositories.adoc @@ -399,7 +399,6 @@ public interface PersonRepository extends MongoRepository @Query("{'lastname': ?#{[0]} }") List findByQueryWithExpression(String param0); - } ---- @@ -412,7 +411,6 @@ public interface PersonRepository extends MongoRepository @Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}") List findByQueryWithExpressionAndNestedObject(boolean param0, String param1); - } ---- From d2d162dee6776cd74f855d17e180405a996e8662 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 25 Jan 2017 16:50:02 +0100 Subject: [PATCH 085/118] DATAMONGO-1596 - Fix typo in JavaDoc. Use correct @RelatedDocument annotation in MongoDB cross store reference documentation. --- src/main/asciidoc/reference/cross-store.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/asciidoc/reference/cross-store.adoc b/src/main/asciidoc/reference/cross-store.adoc index 01a5991616..fda6aa1244 100644 --- a/src/main/asciidoc/reference/cross-store.adoc +++ b/src/main/asciidoc/reference/cross-store.adoc @@ -157,7 +157,7 @@ Finally, you need to configure your project to use MongoDB and also configure th [[mongodb_cross-store-application]] == Writing the Cross Store Application -We are assuming that you have a working JPA application so we will only cover the additional steps needed to persist part of your Entity in your Mongo database. First you need to identify the field you want persisted. It should be a domain class and follow the general rules for the Mongo mapping support covered in previous chapters. The field you want persisted in MongoDB should be annotated using the `@RelatedDocument` annotation. That is really all you need to do!. The cross-store aspects take care of the rest. This includes marking the field with `@Transient` so it won't be persisted using JPA, keeping track of any changes made to the field value and writing them to the database on successful transaction completion, loading the document from MongoDB the first time the value is used in your application. Here is an example of a simple Entity that has a field annotated with `@RelatedEntity`. +We are assuming that you have a working JPA application so we will only cover the additional steps needed to persist part of your Entity in your Mongo database. First you need to identify the field you want persisted. It should be a domain class and follow the general rules for the Mongo mapping support covered in previous chapters. The field you want persisted in MongoDB should be annotated using the `@RelatedDocument` annotation. That is really all you need to do!. The cross-store aspects take care of the rest. This includes marking the field with `@Transient` so it won't be persisted using JPA, keeping track of any changes made to the field value and writing them to the database on successful transaction completion, loading the document from MongoDB the first time the value is used in your application. Here is an example of a simple Entity that has a field annotated with `@RelatedDocument`. .Example of Entity with @RelatedDocument ==== From 8e3d7f96c4d38d5510eac7ac28a033bb3b0ac8b6 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 3 Jan 2017 14:37:47 +0100 Subject: [PATCH 086/118] DATAMONGO-1517 - Add support for Decimal128 BSON type. Support Decimal128 as Mongo simple type if present. Decimal128 is stored as NumberDecimal. class Person { String id; Decimal128 decimal128; Person(String id, Decimal128 decimal128) { this.id = id; this.decimal128 = decimal128; } } mongoTemplate.save(new Person("foo", new Decimal128(new BigDecimal("123.456")))); is represented as: { "_id" : "foo", "decimal128" : NumberDecimal("123.456") } --- .../core/convert/CustomConversions.java | 5 +- .../core/convert/ReflectiveSimpleTypes.java | 53 +++++++++++++++++++ src/main/asciidoc/reference/mapping.adoc | 4 ++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReflectiveSimpleTypes.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java index 14054d84cd..6f9daebb33 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -56,6 +56,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public class CustomConversions { @@ -92,7 +93,7 @@ public CustomConversions(List converters) { this.readingPairs = new LinkedHashSet(); this.writingPairs = new LinkedHashSet(); - this.customSimpleTypes = new HashSet>(); + this.customSimpleTypes = new HashSet>(ReflectiveSimpleTypes.getSupportedSimpleTypes()); this.customReadTargetTypes = new ConcurrentHashMap>>(); this.customWriteTargetTypes = new ConcurrentHashMap>>(); this.rawWriteTargetTypes = new ConcurrentHashMap, CacheValue>>(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReflectiveSimpleTypes.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReflectiveSimpleTypes.java new file mode 100644 index 0000000000..f4991c3883 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReflectiveSimpleTypes.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017 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.convert; + +import java.util.Collection; +import java.util.Collections; + +import org.springframework.util.ClassUtils; + +/** + * {@link ReflectiveSimpleTypes} provides reflective access to MongoDB types that are not consistently available for + * various driver versions. + * + * @author Mark Paluch + * @since 1.10 + */ +class ReflectiveSimpleTypes { + + private static final boolean HAS_DECIMAL_128 = ClassUtils.isPresent("org.bson.types.Decimal128", + ReflectiveSimpleTypes.class.getClassLoader()); + + /** + * Returns a {@link Collection} of simple MongoDB types (i.e. natively supported by the MongoDB driver) that are not + * consistently available for various driver versions. + * + * @return a {@link Collection} of simple MongoDB types. + */ + public static Collection> getSupportedSimpleTypes() { + + if (HAS_DECIMAL_128) { + return Collections.> singleton(getDecimal128Class()); + } + + return Collections.emptySet(); + } + + private static Class getDecimal128Class() { + return ClassUtils.resolveClassName("org.bson.types.Decimal128", ReflectiveSimpleTypes.class.getClassLoader()); + } +} diff --git a/src/main/asciidoc/reference/mapping.adoc b/src/main/asciidoc/reference/mapping.adoc index d0184a638a..af758f77c5 100644 --- a/src/main/asciidoc/reference/mapping.adoc +++ b/src/main/asciidoc/reference/mapping.adoc @@ -126,6 +126,10 @@ In addition to these types, Spring Data MongoDB provides a set of built-in conve | native | `{"value" : { … }}` +| `Decimal128` +| native +| `{"value" : NumberDecimal(…)}` + | `AtomicInteger` + calling `get()` before the actual conversion | converter + From 7413a031c1422a32ca443f42e04c9eaf22611b9a Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 12 Jan 2017 13:04:50 +0100 Subject: [PATCH 087/118] DATAMONGO-1517 - Polishing. Remove ReflectiveSimpleTypes in favor of MongoSimpleTypes. Add add integration test. --- .../core/convert/CustomConversions.java | 2 +- .../core/convert/ReflectiveSimpleTypes.java | 53 ------------------- .../core/mapping/MongoSimpleTypes.java | 14 +++-- .../data/mongodb/util/MongoClientVersion.java | 16 +++++- .../data/mongodb/core/MongoTemplateTests.java | 43 +++++++++++++-- 5 files changed, 65 insertions(+), 63 deletions(-) delete mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReflectiveSimpleTypes.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java index 6f9daebb33..e57c92a6c7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java @@ -93,7 +93,7 @@ public CustomConversions(List converters) { this.readingPairs = new LinkedHashSet(); this.writingPairs = new LinkedHashSet(); - this.customSimpleTypes = new HashSet>(ReflectiveSimpleTypes.getSupportedSimpleTypes()); + this.customSimpleTypes = new HashSet>(); this.customReadTargetTypes = new ConcurrentHashMap>>(); this.customWriteTargetTypes = new ConcurrentHashMap>>(); this.rawWriteTargetTypes = new ConcurrentHashMap, CacheValue>>(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReflectiveSimpleTypes.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReflectiveSimpleTypes.java deleted file mode 100644 index f4991c3883..0000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReflectiveSimpleTypes.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2017 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.convert; - -import java.util.Collection; -import java.util.Collections; - -import org.springframework.util.ClassUtils; - -/** - * {@link ReflectiveSimpleTypes} provides reflective access to MongoDB types that are not consistently available for - * various driver versions. - * - * @author Mark Paluch - * @since 1.10 - */ -class ReflectiveSimpleTypes { - - private static final boolean HAS_DECIMAL_128 = ClassUtils.isPresent("org.bson.types.Decimal128", - ReflectiveSimpleTypes.class.getClassLoader()); - - /** - * Returns a {@link Collection} of simple MongoDB types (i.e. natively supported by the MongoDB driver) that are not - * consistently available for various driver versions. - * - * @return a {@link Collection} of simple MongoDB types. - */ - public static Collection> getSupportedSimpleTypes() { - - if (HAS_DECIMAL_128) { - return Collections.> singleton(getDecimal128Class()); - } - - return Collections.emptySet(); - } - - private static Class getDecimal128Class() { - return ClassUtils.resolveClassName("org.bson.types.Decimal128", ReflectiveSimpleTypes.class.getClassLoader()); - } -} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java index c26f125164..e2892b4d05 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoSimpleTypes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright (c) 2011-2017 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ import org.bson.types.CodeWScope; import org.bson.types.ObjectId; import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.mongodb.util.MongoClientVersion; +import org.springframework.util.ClassUtils; import com.mongodb.DBObject; import com.mongodb.DBRef; @@ -34,6 +36,7 @@ * Simple constant holder for a {@link SimpleTypeHolder} enriched with Mongo specific simple types. * * @author Oliver Gierke + * @author Christoph Strobl */ public abstract class MongoSimpleTypes { @@ -54,12 +57,17 @@ public abstract class MongoSimpleTypes { simpleTypes.add(Pattern.class); simpleTypes.add(Binary.class); simpleTypes.add(UUID.class); + + if (MongoClientVersion.isMongo34Driver()) { + simpleTypes + .add(ClassUtils.resolveClassName("org.bson.types.Decimal128", MongoSimpleTypes.class.getClassLoader())); + } + MONGO_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes); } private static final Set> MONGO_SIMPLE_TYPES; public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(MONGO_SIMPLE_TYPES, true); - private MongoSimpleTypes() { - } + private MongoSimpleTypes() {} } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoClientVersion.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoClientVersion.java index d230574911..272f885a7c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoClientVersion.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/MongoClientVersion.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 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. @@ -28,16 +28,28 @@ public class MongoClientVersion { private static final boolean IS_MONGO_30 = ClassUtils.isPresent("com.mongodb.binding.SingleServerBinding", MongoClientVersion.class.getClassLoader()); + + private static final boolean IS_MONGO_34 = ClassUtils.isPresent("org.bson.types.Decimal128", + MongoClientVersion.class.getClassLoader()); + private static final boolean IS_ASYNC_CLIENT = ClassUtils.isPresent("com.mongodb.async.client.MongoClient", MongoClientVersion.class.getClassLoader()); /** - * @return |literal true} if MongoDB Java driver version 3.0 or later is on classpath. + * @return {@literal true} if MongoDB Java driver version 3.0 or later is on classpath. */ public static boolean isMongo3Driver() { return IS_MONGO_30; } + /** + * @return {@literal true} if MongoDB Java driver version 3.4 or later is on classpath. + * @since 1.10 + */ + public static boolean isMongo34Driver() { + return IS_MONGO_34; + } + /** * @return {lliteral true} if MongoDB Java driver is on classpath. */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index c68cc8e41e..a281b9d627 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -24,6 +24,11 @@ import static org.springframework.data.mongodb.core.query.Query.*; import static org.springframework.data.mongodb.core.query.Update.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; @@ -82,10 +87,12 @@ import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; +import org.springframework.data.mongodb.util.MongoClientVersion; import org.springframework.data.util.CloseableIterator; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -101,10 +108,6 @@ import com.mongodb.WriteConcern; import com.mongodb.WriteResult; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - /** * Integration test for {@link MongoTemplate}. * @@ -125,6 +128,9 @@ public class MongoTemplateTests { .parse("2.4"); private static final org.springframework.data.util.Version TWO_DOT_EIGHT = org.springframework.data.util.Version .parse("2.8"); + private static final org.springframework.data.util.Version THREE_DOT_FOUR= org.springframework.data.util.Version + .parse("3.4"); + @Autowired MongoTemplate template; @Autowired MongoDbFactory factory; @@ -3141,6 +3147,28 @@ public void onBeforeSave(BeforeSaveEvent event) { assertThat(document.id, is(notNullValue())); } + /** + * @see DATAMONGO-1517 + */ + @Test + public void decimal128TypeShouldBeSavedAndLoadedCorrectly() + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + + assumeThat(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR), is(true)); + assumeThat(MongoClientVersion.isMongo34Driver(), is(true)); + + Class decimal128Type = ClassUtils.resolveClassName("org.bson.types.Decimal128", null); + + WithObjectTypeProperty source = new WithObjectTypeProperty(); + source.id = "decimal128-property-value"; + source.value = decimal128Type.getConstructor(BigDecimal.class).newInstance(new BigDecimal(100)); + + template.save(source); + + WithObjectTypeProperty loaded = template.findOne(query(where("id").is(source.id)), WithObjectTypeProperty.class); + assertThat(loaded.getValue(), instanceOf(decimal128Type)); + } + static class TypeWithNumbers { @Id String id; @@ -3510,4 +3538,11 @@ static class WithGeoJson { String description; GeoJsonPoint point; } + + @Data + static class WithObjectTypeProperty { + + @Id String id; + Object value; + } } From d046f30862a086d9b9eb456c9f2ea0c23bdb7560 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 26 Jan 2017 10:26:33 +0100 Subject: [PATCH 088/118] DATAMONGO-1574 - Updated changelog. --- src/main/resources/changelog.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 017db12244..5512f0b26a 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,24 @@ Spring Data MongoDB Changelog ============================= +Changes in version 1.10.0.RELEASE (2017-01-26) +---------------------------------------------- +* DATAMONGO-1596 - Reference to wrong annotation in cross-store reference documentation. +* DATAMONGO-1594 - Update "what’s new" section in reference documentation. +* DATAMONGO-1590 - Entity new detection doesn't consider Persistable.isNew(). +* DATAMONGO-1589 - Update project documentation with the CLA tool integration. +* DATAMONGO-1588 - Repository will not accept Point subclass in spatial query. +* DATAMONGO-1587 - Migrate ticket references in test code to Spring Framework style. +* DATAMONGO-1586 - TypeBasedAggregationOperationContext.getReferenceFor(…) exposes fields with their leaf property name. +* DATAMONGO-1585 - Aggregation sort references target field of projected and aliased fields. +* DATAMONGO-1578 - Add missing @Test annotation to ProjectionOperationUnitTests. +* DATAMONGO-1577 - Fix Reference and JavaDoc spelling issues. +* DATAMONGO-1576 - AbstractMongoEventListener methods not called when working with member fields. +* DATAMONGO-1575 - Treat String replacement values in StringBased queries as such unless they are SpEL expressions. +* DATAMONGO-1574 - Release 1.10 GA (Ingalls). +* DATAMONGO-1508 - Documentation lacking for db-factory authentication-dbname. + + Changes in version 1.9.6.RELEASE (2016-12-21) --------------------------------------------- * DATAMONGO-1565 - Placeholders in manually defined queries not escaped properly. From 52b52dba93ae1ad35255cde8484bf70e118819ac Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 26 Jan 2017 10:26:35 +0100 Subject: [PATCH 089/118] DATAMONGO-1574 - Prepare 1.10 GA (Ingalls). --- pom.xml | 8 ++++---- src/main/resources/notice.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index ed9720988d..62cf287e10 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ org.springframework.data.build spring-data-parent - 1.9.0.BUILD-SNAPSHOT + 1.9.0.RELEASE @@ -28,7 +28,7 @@ multi spring-data-mongodb - 1.13.0.BUILD-SNAPSHOT + 1.13.0.RELEASE 2.14.3 2.13.0 @@ -229,8 +229,8 @@ - spring-libs-snapshot - https://repo.spring.io/libs-snapshot + spring-libs-release + https://repo.spring.io/libs-release diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt index b9dbc9eb86..0e749a8efa 100644 --- a/src/main/resources/notice.txt +++ b/src/main/resources/notice.txt @@ -1,4 +1,4 @@ -Spring Data MongoDB 1.10 RC1 +Spring Data MongoDB 1.10 GA Copyright (c) [2010-2015] Pivotal Software, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License"). From d5fec0989c0aa4ce31054258e56de5e30542a865 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 26 Jan 2017 10:27:10 +0100 Subject: [PATCH 090/118] DATAMONGO-1574 - Release version 1.10 GA (Ingalls). --- 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 62cf287e10..7c5a0d396d 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.RELEASE pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index ae0a5d6c8f..0ad075f44e 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.RELEASE ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.10.0.BUILD-SNAPSHOT + 1.10.0.RELEASE diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 2d02722262..39e7c8ae40 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.RELEASE ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index ee5e3336db..f62981493a 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.RELEASE ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 8072d3f665..f0b825c3a5 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.RELEASE ../pom.xml From 3e4ce05e4311543ce593b2bb3b415cd72d7017dd Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 26 Jan 2017 10:56:54 +0100 Subject: [PATCH 091/118] DATAMONGO-1574 - Prepare next development iteration. --- 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 7c5a0d396d..7248977dba 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.RELEASE + 1.11.0.BUILD-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index 0ad075f44e..dc5a04a7ad 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.RELEASE + 1.11.0.BUILD-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.10.0.RELEASE + 1.11.0.BUILD-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 39e7c8ae40..9876e11ee3 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.RELEASE + 1.11.0.BUILD-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index f62981493a..c0436bac90 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.RELEASE + 1.11.0.BUILD-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index f0b825c3a5..ea1b594b99 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.RELEASE + 1.11.0.BUILD-SNAPSHOT ../pom.xml From b79474c92c231ee4f2b3bd77fdde12878002acef Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 26 Jan 2017 10:56:58 +0100 Subject: [PATCH 092/118] DATAMONGO-1574 - After release cleanups. --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 7248977dba..75fa63795e 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ org.springframework.data.build spring-data-parent - 1.9.0.RELEASE + 1.10.0.BUILD-SNAPSHOT @@ -28,7 +28,7 @@ multi spring-data-mongodb - 1.13.0.RELEASE + 1.14.0.BUILD-SNAPSHOT 2.14.3 2.13.0 @@ -229,8 +229,8 @@ - spring-libs-release - https://repo.spring.io/libs-release + spring-libs-snapshot + https://repo.spring.io/libs-snapshot From 18e00a91f905755e8211472050eed3e038093e31 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 31 Jan 2017 10:45:38 +0100 Subject: [PATCH 093/118] DATAMONGO-1602 - Remove references to Assert single-arg methods. Replace references to Assert single-arg methods with references to methods accepting the test object and message. Related ticket: SPR-15196. --- .../config/MappingMongoConverterParser.java | 6 ++- .../mongodb/core/DefaultIndexOperations.java | 2 +- .../data/mongodb/core/MongoAdmin.java | 30 +++++++------- .../data/mongodb/core/MongoTemplate.java | 33 ++++++++------- .../core/aggregation/AggregationResults.java | 7 ++-- .../core/convert/ConverterRegistration.java | 5 ++- .../core/convert/CustomConversions.java | 6 +-- .../core/convert/MappingMongoConverter.java | 22 +++++----- .../mongodb/core/convert/QueryMapper.java | 4 +- .../data/mongodb/core/geo/Sphere.java | 7 ++-- .../data/mongodb/core/index/IndexField.java | 27 ++++++++---- .../data/mongodb/core/index/IndexInfo.java | 6 ++- .../index/MongoMappingEventPublisher.java | 7 ++-- .../MongoPersistentEntityIndexCreator.java | 8 ++-- .../mapping/BasicMongoPersistentEntity.java | 7 ++-- .../core/mapreduce/GroupByResults.java | 11 +++-- .../core/mapreduce/MapReduceResults.java | 9 ++-- .../data/mongodb/core/query/Criteria.java | 26 ++++++++---- .../data/mongodb/core/query/NearQuery.java | 19 +++++---- .../mongodb/core/spel/ExpressionNode.java | 7 ++-- .../data/mongodb/gridfs/AntPath.java | 7 +++- .../data/mongodb/gridfs/GridFsTemplate.java | 9 ++-- .../repository/cdi/MongoRepositoryBean.java | 6 +-- .../query/ConvertingParameterAccessor.java | 41 ++++++++++--------- .../repository/query/MongoQueryCreator.java | 2 +- .../IndexEnsuringQueryCreationListener.java | 4 +- .../support/MongoRepositoryFactory.java | 9 ++-- .../support/QueryDslMongoRepository.java | 9 ++-- .../support/QuerydslRepositorySupport.java | 13 +++--- .../support/SimpleMongoRepository.java | 12 +++--- .../monitor/MongoMonitorIntegrationTests.java | 4 +- .../mongodb/performance/PerformanceTests.java | 11 ++--- 32 files changed, 213 insertions(+), 163 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java index 3aae756891..fa4ac4946e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MappingMongoConverterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 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. @@ -361,7 +361,9 @@ private static class NegatingFilter implements TypeFilter { * @param filters */ public NegatingFilter(TypeFilter... filters) { - Assert.notNull(filters); + + Assert.notNull(filters, "TypeFilters must not be null"); + this.delegates = new HashSet(Arrays.asList(filters)); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java index f82371c742..ac4c24998d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java index 804ad5e593..aaa64ff274 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAdmin.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2017 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. @@ -27,7 +27,8 @@ * Mongo server administration exposed via JMX annotations * * @author Mark Pollack - * @author Thomas Darimont + * @author Thomas Darimont + * @author Mark Paluch */ @ManagedResource(description = "Mongo Admin Operations") public class MongoAdmin implements MongoAdminOperations { @@ -35,10 +36,11 @@ public class MongoAdmin implements MongoAdminOperations { private final Mongo mongo; private String username; private String password; - private String authenticationDatabaseName; + private String authenticationDatabaseName; public MongoAdmin(Mongo mongo) { - Assert.notNull(mongo); + + Assert.notNull(mongo, "Mongo must not be null!"); this.mongo = mongo; } @@ -84,16 +86,16 @@ public void setPassword(String password) { this.password = password; } - /** - * Sets the authenticationDatabaseName to use to authenticate with the Mongo database. - * - * @param authenticationDatabaseName The authenticationDatabaseName to use. - */ - public void setAuthenticationDatabaseName(String authenticationDatabaseName) { - this.authenticationDatabaseName = authenticationDatabaseName; - } - + /** + * Sets the authenticationDatabaseName to use to authenticate with the Mongo database. + * + * @param authenticationDatabaseName The authenticationDatabaseName to use. + */ + public void setAuthenticationDatabaseName(String authenticationDatabaseName) { + this.authenticationDatabaseName = authenticationDatabaseName; + } + DB getDB(String databaseName) { - return MongoDbUtils.getDB(mongo, databaseName, new UserCredentials(username, password), authenticationDatabaseName); + return MongoDbUtils.getDB(mongo, databaseName, new UserCredentials(username, password), authenticationDatabaseName); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index f834253275..f8708cb388 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -214,7 +214,7 @@ public MongoTemplate(MongoDbFactory mongoDbFactory) { */ public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { - Assert.notNull(mongoDbFactory); + Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!"); this.mongoDbFactory = mongoDbFactory; this.exceptionTranslator = mongoDbFactory.getExceptionTranslator(); @@ -439,7 +439,7 @@ public void executeQuery(Query query, String collectionName, DocumentCallbackHan protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, CursorPreparer preparer) { - Assert.notNull(query); + Assert.notNull(query, "Query must not be null!"); DBObject queryObject = queryMapper.getMappedObject(query.getQueryObject(), null); DBObject sortObject = query.getSortObject(); @@ -455,7 +455,7 @@ protected void executeQuery(Query query, String collectionName, DocumentCallback public T execute(DbCallback action) { - Assert.notNull(action); + Assert.notNull(action, "DbCallbackmust not be null!"); try { DB db = this.getDb(); @@ -471,7 +471,7 @@ public T execute(Class entityClass, CollectionCallback callback) { public T execute(String collectionName, CollectionCallback callback) { - Assert.notNull(callback); + Assert.notNull(callback, "CollectionCallback must not be null!"); try { DBCollection collection = getAndPrepareCollection(getDb(), collectionName); @@ -749,7 +749,8 @@ public T findAndRemove(Query query, Class entityClass, String collectionN } public long count(Query query, Class entityClass) { - Assert.notNull(entityClass); + + Assert.notNull(entityClass, "Entity class must not be null!"); return count(query, entityClass, determineCollectionName(entityClass)); } @@ -763,7 +764,8 @@ public long count(final Query query, String collectionName) { */ public long count(Query query, Class entityClass, String collectionName) { - Assert.hasText(collectionName); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + final DBObject dbObject = query == null ? null : queryMapper.getMappedObject(query.getQueryObject(), entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); @@ -934,7 +936,7 @@ protected void doInsertAll(Collection listToSave, MongoWriter void doInsertBatch(String collectionName, Collection batchToSave, MongoWriter writer) { - Assert.notNull(writer); + Assert.notNull(writer, "MongoWriter must not be null!"); List dbObjectList = new ArrayList(); for (T o : batchToSave) { @@ -963,14 +965,14 @@ protected void doInsertBatch(String collectionName, Collection public void save(Object objectToSave) { - Assert.notNull(objectToSave); + Assert.notNull(objectToSave, "Object to save must not be null!"); save(objectToSave, determineEntityCollectionName(objectToSave)); } public void save(Object objectToSave, String collectionName) { - Assert.notNull(objectToSave); - Assert.hasText(collectionName); + Assert.notNull(objectToSave, "Object to save must not be null!"); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); MongoPersistentEntity mongoPersistentEntity = getPersistentEntity(objectToSave.getClass()); @@ -1216,7 +1218,7 @@ public WriteResult remove(Object object) { public WriteResult remove(Object object, String collection) { - Assert.hasText(collection); + Assert.hasText(collection, "Collection name must not be null or empty!"); if (object == null) { return null; @@ -2297,8 +2299,9 @@ private class ReadDbObjectCallback implements DbObjectCallback { public ReadDbObjectCallback(EntityReader reader, Class type, String collectionName) { - Assert.notNull(reader); - Assert.notNull(type); + Assert.notNull(reader, "EntityReader must not be null!"); + Assert.notNull(type, "Entity type must not be null!"); + this.reader = reader; this.type = type; this.collectionName = collectionName; @@ -2446,7 +2449,9 @@ static class GeoNearResultDbObjectCallback implements DbObjectCallback delegate, Metric metric) { - Assert.notNull(delegate); + + Assert.notNull(delegate, "DocumentCallback must not be null!"); + this.delegate = delegate; this.metric = metric; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java index 2fbf96c4cb..8223358873 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2014 the original author or authors. + * Copyright 2013-2017 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. @@ -29,6 +29,7 @@ * @author Tobias Trelle * @author Oliver Gierke * @author Thomas Darimont + * @author Mark Paluch * @param The class in which the results are mapped onto. * @since 1.3 */ @@ -46,8 +47,8 @@ public class AggregationResults implements Iterable { */ public AggregationResults(List mappedResults, DBObject rawResults) { - Assert.notNull(mappedResults); - Assert.notNull(rawResults); + Assert.notNull(mappedResults, "List of mapped results must not be null!"); + Assert.notNull(rawResults, "Raw results must not be null!"); this.mappedResults = Collections.unmodifiableList(mappedResults); this.rawResults = rawResults; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConverterRegistration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConverterRegistration.java index 3365c2359f..133d778a5c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConverterRegistration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConverterRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2017 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. @@ -23,6 +23,7 @@ * Conversion registration information. * * @author Oliver Gierke + * @author Mark Paluch */ class ConverterRegistration { @@ -39,7 +40,7 @@ class ConverterRegistration { */ public ConverterRegistration(ConvertiblePair convertiblePair, boolean isReading, boolean isWriting) { - Assert.notNull(convertiblePair); + Assert.notNull(convertiblePair, "ConvertiblePair must not be null!"); this.convertiblePair = convertiblePair; this.reading = isReading; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java index e57c92a6c7..ffea59ed5f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/CustomConversions.java @@ -89,7 +89,7 @@ public class CustomConversions { */ public CustomConversions(List converters) { - Assert.notNull(converters); + Assert.notNull(converters, "List of converters must not be null!"); this.readingPairs = new LinkedHashSet(); this.writingPairs = new LinkedHashSet(); @@ -346,8 +346,8 @@ public Class get() { private static Class getCustomTarget(Class sourceType, Class requestedTargetType, Collection pairs) { - Assert.notNull(sourceType); - Assert.notNull(pairs); + Assert.notNull(sourceType, "Source Class must not be null!"); + Assert.notNull(pairs, "Collection of ConvertiblePair must not be null!"); if (requestedTargetType != null && pairs.contains(new ConvertiblePair(sourceType, requestedTargetType))) { return requestedTargetType; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index bb78d33fe7..4d41b65ebe 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -1,11 +1,11 @@ /* - * Copyright 2011-2017 by the original author(s). + * Copyright 2011-2017 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 + * 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, @@ -315,7 +315,7 @@ public void doWithAssociation(Association association) return result; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.convert.MongoWriter#toDBRef(java.lang.Object, org.springframework.data.mongodb.core.mapping.MongoPersistentProperty) */ @@ -483,7 +483,7 @@ protected void writePropertyInternal(Object obj, DBObject dbo, MongoPersistentPr DBRef dbRefObj = null; /* - * If we already have a LazyLoadingProxy, we use it's cached DBRef value instead of + * If we already have a LazyLoadingProxy, we use it's cached DBRef value instead of * unnecessarily initializing it only to convert it to a DBRef a few instructions later. */ if (obj instanceof LazyLoadingProxy) { @@ -830,7 +830,7 @@ private Object getPotentiallyConvertedSimpleRead(Object value, Class target) protected DBRef createDBRef(Object target, MongoPersistentProperty property) { - Assert.notNull(target); + Assert.notNull(target, "Target object must not be null!"); if (target instanceof DBRef) { return (DBRef) target; @@ -1070,7 +1070,7 @@ public BasicDBList maybeConvertList(Iterable source, TypeInformation typeI /** * Removes the type information from the entire conversion result. - * + * * @param object * @param recursively whether to apply the removal recursively * @return @@ -1131,22 +1131,22 @@ private class MongoDbPropertyValueProvider implements PropertyValueProvider= 0, "Radius must not be negative!"); this.center = center; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java index 83bf354369..0e433aa21b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexField.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 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. @@ -44,8 +44,13 @@ private IndexField(String key, Direction direction, Type type) { private IndexField(String key, Direction direction, Type type, Float weight) { - Assert.hasText(key); - Assert.isTrue(direction != null ^ (Type.GEO.equals(type) || Type.TEXT.equals(type))); + Assert.hasText(key, "Key must not be null or empty"); + + if (Type.GEO.equals(type) || Type.TEXT.equals(type)) { + Assert.isTrue(direction == null, "Geo/Text indexes must not have a direction!"); + } else { + Assert.notNull(direction, "Default indexes require a direction"); + } this.key = key; this.direction = direction; @@ -58,17 +63,21 @@ private IndexField(String key, Direction direction, Type type, Float weight) { * * @deprecated use {@link #create(String, Direction)}. * @param key must not be {@literal null} or emtpy. - * @param direction must not be {@literal null}. + * @param order must not be {@literal null}. * @return */ @Deprecated public static IndexField create(String key, Order order) { - Assert.notNull(order); + + Assert.notNull(order, "Order must not be null!"); + return new IndexField(key, order.toDirection(), Type.DEFAULT); } public static IndexField create(String key, Direction order) { - Assert.notNull(order); + + Assert.notNull(order, "Direction must not be null!"); + return new IndexField(key, order, Type.DEFAULT); } @@ -128,7 +137,7 @@ public boolean isGeo() { } /** - * Returns wheter the {@link IndexField} is a text index field. + * Returns whether the {@link IndexField} is a text index field. * * @return true if type is {@link Type#TEXT} * @since 1.6 @@ -158,7 +167,7 @@ public boolean equals(Object obj) { && this.type == that.type; } - /* + /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ @@ -173,7 +182,7 @@ public int hashCode() { return result; } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java index f4d4d6700d..4d9e5a1821 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -32,6 +32,7 @@ * @author Mark Pollack * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch */ public class IndexInfo { @@ -147,7 +148,8 @@ public List getIndexFields() { */ public boolean isIndexForFields(Collection keys) { - Assert.notNull(keys); + Assert.notNull(keys, "Collection of keys must not be null!"); + List indexKeys = new ArrayList(indexFields.size()); for (IndexField field : indexFields) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoMappingEventPublisher.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoMappingEventPublisher.java index 26ce216809..4f19ebd6cd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoMappingEventPublisher.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoMappingEventPublisher.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -34,6 +34,7 @@ * * @author Jon Brisbin * @author Oliver Gierke + * @author Mark Paluch */ public class MongoMappingEventPublisher implements ApplicationEventPublisher { @@ -46,7 +47,7 @@ public class MongoMappingEventPublisher implements ApplicationEventPublisher { */ public MongoMappingEventPublisher(MongoPersistentEntityIndexCreator indexCreator) { - Assert.notNull(indexCreator); + Assert.notNull(indexCreator, "MongoPersistentEntityIndexCreator must not be null!"); this.indexCreator = indexCreator; } @@ -61,7 +62,7 @@ public void publishEvent(ApplicationEvent event) { } } - /* + /* * (non-Javadoc) * @see org.springframework.context.ApplicationEventPublisher#publishEvent(java.lang.Object) */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java index 41018a8c8c..7febac08a9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -79,9 +79,9 @@ public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, Mon public MongoPersistentEntityIndexCreator(MongoMappingContext mappingContext, MongoDbFactory mongoDbFactory, IndexResolver indexResolver) { - Assert.notNull(mongoDbFactory); - Assert.notNull(mappingContext); - Assert.notNull(indexResolver); + Assert.notNull(mappingContext, "MongoMappingContext must not be null!"); + Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!"); + Assert.notNull(indexResolver, "IndexResolver must not be null!"); this.mongoDbFactory = mongoDbFactory; this.mappingContext = mappingContext; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java index a1196fca46..71734ecc48 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2014 the original author or authors. + * Copyright 2011-2017 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. @@ -51,6 +51,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public class BasicMongoPersistentEntity extends BasicPersistentEntity implements MongoPersistentEntity, ApplicationContextAware { @@ -138,7 +139,7 @@ public boolean hasTextScoreProperty() { return getTextScoreProperty() != null; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mapping.model.BasicPersistentEntity#verify() */ @@ -200,7 +201,7 @@ public int compare(MongoPersistentProperty o1, MongoPersistentProperty o2) { @Override protected MongoPersistentProperty returnPropertyIfBetterIdPropertyCandidateOrNull(MongoPersistentProperty property) { - Assert.notNull(property); + Assert.notNull(property, "MongoPersistentProperty must not be null!"); if (!property.isIdProperty()) { return null; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java index a3dc65ee80..9b090b77f6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/GroupByResults.java @@ -1,11 +1,11 @@ /* - * Copyright 2011 - 2014 the original author or authors. + * Copyright 2011-2017 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 + * 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, @@ -27,6 +27,7 @@ * * @author Mark Pollack * @author Christoph Strobl + * @author Mark Paluch * @param The class in which the results are mapped onto, accessible via an {@link Iterator}. */ public class GroupByResults implements Iterable { @@ -40,10 +41,12 @@ public class GroupByResults implements Iterable { public GroupByResults(List mappedResults, DBObject rawResults) { - Assert.notNull(mappedResults); - Assert.notNull(rawResults); + Assert.notNull(mappedResults, "List of mapped results must not be null!"); + Assert.notNull(rawResults, "Raw results must not be null!"); + this.mappedResults = mappedResults; this.rawResults = rawResults; + parseKeys(); parseCount(); parseServerUsed(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java index 4d7c1407eb..2827917528 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceResults.java @@ -1,11 +1,11 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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 + * 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, @@ -29,6 +29,7 @@ * @author Mark Pollack * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch * @param The class in which the results are mapped onto, accessible via an iterator. */ public class MapReduceResults implements Iterable { @@ -49,8 +50,8 @@ public class MapReduceResults implements Iterable { @Deprecated public MapReduceResults(List mappedResults, DBObject rawResults) { - Assert.notNull(mappedResults); - Assert.notNull(rawResults); + Assert.notNull(mappedResults, "List of mapped results must not be null!"); + Assert.notNull(rawResults, "Raw results must not be null!"); this.mappedResults = mappedResults; this.rawResults = rawResults; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java index 721bed0ea0..df4dd65566 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Criteria.java @@ -50,6 +50,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch */ public class Criteria implements CriteriaDefinition { @@ -386,7 +387,7 @@ public Criteria regex(String re, String options) { */ public Criteria regex(Pattern pattern) { - Assert.notNull(pattern); + Assert.notNull(pattern, "Pattern must not be null!"); if (lastOperatorWasNot()) { return not(pattern); @@ -397,7 +398,9 @@ public Criteria regex(Pattern pattern) { } private Pattern toPattern(String regex, String options) { - Assert.notNull(regex); + + Assert.notNull(regex, "Regex string must not be null!"); + return Pattern.compile(regex, options == null ? 0 : BSON.regexFlags(options)); } @@ -411,7 +414,9 @@ private Pattern toPattern(String regex, String options) { * @see MongoDB Query operator: $centerSphere */ public Criteria withinSphere(Circle circle) { - Assert.notNull(circle); + + Assert.notNull(circle, "Circle must not be null!"); + criteria.put("$geoWithin", new GeoCommand(new Sphere(circle))); return this; } @@ -425,7 +430,8 @@ public Criteria withinSphere(Circle circle) { */ public Criteria within(Shape shape) { - Assert.notNull(shape); + Assert.notNull(shape, "Shape must not be null!"); + criteria.put("$geoWithin", new GeoCommand(shape)); return this; } @@ -438,7 +444,9 @@ public Criteria within(Shape shape) { * @see MongoDB Query operator: $near */ public Criteria near(Point point) { - Assert.notNull(point); + + Assert.notNull(point, "Point must not be null!"); + criteria.put("$near", point); return this; } @@ -452,7 +460,9 @@ public Criteria near(Point point) { * @see MongoDB Query operator: $nearSphere */ public Criteria nearSphere(Point point) { - Assert.notNull(point); + + Assert.notNull(point, "Point must not be null!"); + criteria.put("$nearSphere", point); return this; } @@ -706,7 +716,7 @@ private boolean createNearCriteriaForCommand(String command, String operation, d return false; } - /* + /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @@ -769,7 +779,7 @@ private boolean isEqual(Object left, Object right) { return ObjectUtils.nullSafeEquals(left, right); } - /* + /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java index b847d192e6..22a571e90c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/NearQuery.java @@ -1,11 +1,11 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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 + * 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, @@ -49,11 +49,11 @@ public final class NearQuery { /** * Creates a new {@link NearQuery}. * - * @param point + * @param point must not be {@literal null}. */ private NearQuery(Point point, Metric metric) { - Assert.notNull(point); + Assert.notNull(point, "Point must not be null!"); this.point = point; this.spherical = false; @@ -108,7 +108,6 @@ public static NearQuery near(Point point) { * @return */ public static NearQuery near(Point point, Metric metric) { - Assert.notNull(point); return new NearQuery(point, metric); } @@ -185,7 +184,8 @@ public NearQuery maxDistance(double maxDistance) { */ public NearQuery maxDistance(double maxDistance, Metric metric) { - Assert.notNull(metric); + Assert.notNull(metric, "Metric must not be null!"); + return maxDistance(new Distance(maxDistance, metric)); } @@ -198,7 +198,7 @@ public NearQuery maxDistance(double maxDistance, Metric metric) { */ public NearQuery maxDistance(Distance distance) { - Assert.notNull(distance); + Assert.notNull(distance, "Distance must not be null!"); if (distance.getMetric() != Metrics.NEUTRAL) { this.spherical(true); @@ -241,7 +241,8 @@ public NearQuery minDistance(double minDistance) { */ public NearQuery minDistance(double minDistance, Metric metric) { - Assert.notNull(metric); + Assert.notNull(metric, "Metric must not be null!"); + return minDistance(new Distance(minDistance, metric)); } @@ -255,7 +256,7 @@ public NearQuery minDistance(double minDistance, Metric metric) { */ public NearQuery minDistance(Distance distance) { - Assert.notNull(distance); + Assert.notNull(distance, "Distance must not be null!"); if (distance.getMetric() != Metrics.NEUTRAL) { this.spherical(true); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java index 01c7c18100..9fb3f5b891 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 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. @@ -31,6 +31,7 @@ * * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch */ public class ExpressionNode implements Iterable { @@ -173,7 +174,7 @@ public boolean hasChildren() { */ public ExpressionNode getChild(int index) { - Assert.isTrue(index >= 0); + Assert.isTrue(index >= 0, "Index must be greater or equal to zero!"); return from(node.getChild(index), state); } @@ -199,7 +200,7 @@ protected ExpressionNode from(SpelNode node) { return from(node, state); } - /* + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/AntPath.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/AntPath.java index de20bc35db..a9fa8f84bd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/AntPath.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/AntPath.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2017 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. @@ -24,6 +24,7 @@ * Value object to abstract Ant paths. * * @author Oliver Gierke + * @author Mark Paluch */ class AntPath { @@ -38,7 +39,9 @@ class AntPath { * @param path must not be {@literal null}. */ public AntPath(String path) { - Assert.notNull(path); + + Assert.notNull(path, "Path must not be null!"); + this.path = path; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsTemplate.java index e523e8855a..602cc7b5ba 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/gridfs/GridFsTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -46,6 +46,7 @@ * @author Thomas Darimont * @author Martin Baumgartner * @author Christoph Strobl + * @author Mark Paluch */ public class GridFsTemplate implements GridFsOperations, ResourcePatternResolver { @@ -73,8 +74,8 @@ public GridFsTemplate(MongoDbFactory dbFactory, MongoConverter converter) { */ public GridFsTemplate(MongoDbFactory dbFactory, MongoConverter converter, String bucket) { - Assert.notNull(dbFactory); - Assert.notNull(converter); + Assert.notNull(dbFactory, "MongoDbFactory must not be null!"); + Assert.notNull(converter, "MongoConverter must not be null!"); this.dbFactory = dbFactory; this.converter = converter; @@ -156,7 +157,7 @@ public GridFSFile store(InputStream content, String filename, DBObject metadata) */ public GridFSFile store(InputStream content, String filename, String contentType, DBObject metadata) { - Assert.notNull(content); + Assert.notNull(content, "InputStream must not be null!"); GridFSInputFile file = getGridFs().createFile(content); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/cdi/MongoRepositoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/cdi/MongoRepositoryBean.java index 0a43c72b2a..6a43fa025d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/cdi/MongoRepositoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/cdi/MongoRepositoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2017 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. @@ -53,11 +53,11 @@ public MongoRepositoryBean(Bean operations, Set qua super(qualifiers, repositoryType, beanManager, detector); - Assert.notNull(operations); + Assert.notNull(operations, "MongoOperations bean must not be null!"); this.operations = operations; } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class) */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java index 15d0cd25e6..f49f4d69d6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -42,6 +42,7 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ public class ConvertingParameterAccessor implements MongoParameterAccessor { @@ -56,41 +57,41 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { */ public ConvertingParameterAccessor(MongoWriter writer, MongoParameterAccessor delegate) { - Assert.notNull(writer); - Assert.notNull(delegate); + Assert.notNull(writer, "MongoWriter must not be null!"); + Assert.notNull(delegate, "MongoParameterAccessor must not be null!"); this.writer = writer; this.delegate = delegate; } /* - * (non-Javadoc) - * - * @see java.lang.Iterable#iterator() - */ + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ public PotentiallyConvertingIterator iterator() { return new ConvertingIterator(delegate.iterator()); } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.query.ParameterAccessor#getPageable() - */ + * (non-Javadoc) + * + * @see org.springframework.data.repository.query.ParameterAccessor#getPageable() + */ public Pageable getPageable() { return delegate.getPageable(); } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.query.ParameterAccessor#getSort() - */ + * (non-Javadoc) + * + * @see org.springframework.data.repository.query.ParameterAccessor#getSort() + */ public Sort getSort() { return delegate.getSort(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.query.ParameterAccessor#getDynamicProjection() */ @@ -107,7 +108,7 @@ public Object getBindableValue(int index) { return getConvertedValue(delegate.getBindableValue(index), null); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getDistanceRange() */ @@ -116,7 +117,7 @@ public Range getDistanceRange() { return delegate.getDistanceRange(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.MongoParameterAccessor#getGeoNearLocation() */ @@ -143,7 +144,7 @@ private Object getConvertedValue(Object value, TypeInformation typeInformatio return writer.convertToMongoType(value, typeInformation == null ? null : typeInformation.getActualType()); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.query.ParameterAccessor#hasBindableNullValue() */ @@ -185,7 +186,7 @@ public Object next() { return delegate.next(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.ConvertingParameterAccessor.PotentiallConvertingIterator#nextConverted() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index 694593ce51..0d40ed4d28 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -92,7 +92,7 @@ public MongoQueryCreator(PartTree tree, ConvertingParameterAccessor accessor, super(tree, accessor); - Assert.notNull(context); + Assert.notNull(context, "MappingContext must not be null!"); this.accessor = accessor; this.isGeoNearQuery = isGeoNearQuery; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java index 4ae998db9d..2b6ddec1bd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/IndexEnsuringQueryCreationListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-2017 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. @@ -53,7 +53,7 @@ class IndexEnsuringQueryCreationListener implements QueryCreationListener entityInformation, super(entityInformation, mongoOperations); - Assert.notNull(resolver); + Assert.notNull(resolver, "EntityPathResolver must not be null!"); + EntityPath path = resolver.createPath(entityInformation.getJavaType()); this.builder = new PathBuilder(path.getType(), path.getMetadata()); @@ -124,7 +125,7 @@ public List findAll(Predicate predicate, Sort sort) { return applySorting(createQueryFor(predicate), sort).fetchResults().getResults(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#findAll(com.mysema.query.types.OrderSpecifier[]) */ @@ -187,7 +188,7 @@ public long count(Predicate predicate) { return createQueryFor(predicate).fetchCount(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.querydsl.QueryDslPredicateExecutor#exists(com.mysema.query.types.Predicate) */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java index bce77cd8b8..bbfe8dd9b7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/QuerydslRepositorySupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright 2011-2017 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. @@ -27,6 +27,7 @@ * Base class to create repository implementations based on Querydsl. * * @author Oliver Gierke + * @author Mark Paluch */ public abstract class QuerydslRepositorySupport { @@ -40,7 +41,7 @@ public abstract class QuerydslRepositorySupport { */ public QuerydslRepositorySupport(MongoOperations operations) { - Assert.notNull(operations); + Assert.notNull(operations, "MongoOperations must not be null!"); this.template = operations; this.context = operations.getConverter().getMappingContext(); @@ -54,7 +55,9 @@ public QuerydslRepositorySupport(MongoOperations operations) { * @return */ protected AbstractMongodbQuery> from(final EntityPath path) { - Assert.notNull(path); + + Assert.notNull(path, "EntityPath must not be null!"); + MongoPersistentEntity entity = context.getPersistentEntity(path.getType()); return from(path, entity.getCollection()); } @@ -68,8 +71,8 @@ protected AbstractMongodbQuery> from(final Enti */ protected AbstractMongodbQuery> from(final EntityPath path, String collection) { - Assert.notNull(path); - Assert.hasText(collection); + Assert.notNull(path, "EntityPath must not be null!"); + Assert.hasText(collection, "Collection name must not be null or empty!"); return new SpringDataMongodbQuery(template, path.getType(), collection); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java index 83665535c9..c6549dff61 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2017 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. @@ -61,8 +61,8 @@ public class SimpleMongoRepository implements MongoR */ public SimpleMongoRepository(MongoEntityInformation metadata, MongoOperations mongoOperations) { - Assert.notNull(mongoOperations); - Assert.notNull(metadata); + Assert.notNull(metadata, "MongoEntityInformation must not be null!"); + Assert.notNull(mongoOperations, "MongoOperations must not be null!"); this.entityInformation = metadata; this.mongoOperations = mongoOperations; @@ -197,7 +197,7 @@ public List findAll() { return findAll(new Query()); } - /* + /* * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable) */ @@ -231,7 +231,7 @@ public List findAll(Sort sort) { return findAll(new Query().with(sort)); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.MongoRepository#insert(java.lang.Object) */ @@ -244,7 +244,7 @@ public S insert(S entity) { return entity; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.MongoRepository#insert(java.lang.Iterable) */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java index 5d07d58bad..d9d6e66d6c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/monitor/MongoMonitorIntegrationTests.java @@ -25,8 +25,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import com.mongodb.Mongo; @@ -35,6 +33,7 @@ * * @author Mark Pollack * @author Thomas Darimont + * @author Mark Paluch */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") @@ -46,7 +45,6 @@ public class MongoMonitorIntegrationTests { public void serverInfo() { ServerInfo serverInfo = new ServerInfo(mongo); serverInfo.getVersion(); - Assert.isTrue(StringUtils.hasText("1.")); } @Test // DATAMONGO-685 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java index a135a05ae4..adf5325ee6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/PerformanceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -17,7 +17,6 @@ import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Query.*; -import static org.springframework.util.Assert.*; import java.text.DecimalFormat; import java.util.ArrayList; @@ -47,6 +46,7 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean; +import org.springframework.util.Assert; import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; @@ -66,6 +66,7 @@ * * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch */ public class PerformanceTests { @@ -622,7 +623,7 @@ public DBObject toDBObject() { private static List pickRandomNumerOfItemsFrom(List source) { - isTrue(!source.isEmpty()); + Assert.isTrue(!source.isEmpty(), "Source must not be empty!"); Random random = new Random(); int numberOfItems = random.nextInt(source.size()); @@ -836,7 +837,7 @@ public String print(double referenceAverage, double referenceMedian) { String.format(" %s%%", DEVIATION_FORMAT.format(getMediaDeviationFrom(referenceMedian)))) + '\n'; } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @@ -895,7 +896,7 @@ public String print() { return builder.toString(); } - /* + /* * (non-Javadoc) * @see java.lang.Object#toString() */ From 7b5233c4683804c54887ddd4c3435951cac7e629 Mon Sep 17 00:00:00 2001 From: Thiago Diniz da Silveira Date: Thu, 2 Feb 2017 22:12:52 -0200 Subject: [PATCH 094/118] DATAMONGO-1607 - Fix ClassCastException in Circle, Point and Sphere when coordinates are not Double. Original Pull Request: #438 --- .../data/mongodb/core/convert/GeoConverters.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java index f4d3cdc663..b1c3cac2c7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java @@ -53,6 +53,7 @@ * @author Thomas Darimont * @author Oliver Gierke * @author Christoph Strobl + * @author Thiago Diniz da Silveira * @since 1.5 */ abstract class GeoConverters { @@ -120,8 +121,11 @@ public Point convert(DBObject source) { if (source.containsField("type")) { return DbObjectToGeoJsonPointConverter.INSTANCE.convert(source); } + + Number x = (Number) source.get("x"); + Number y = (Number) source.get("y"); - return new Point((Double) source.get("x"), (Double) source.get("y")); + return new Point(x.doubleValue(), y.doubleValue()); } } @@ -255,7 +259,8 @@ public Circle convert(DBObject source) { } DBObject center = (DBObject) source.get("center"); - Double radius = (Double) source.get("radius"); + Number radiusNumber = (Number) source.get("radius"); + Double radius = radiusNumber.doubleValue(); Distance distance = new Distance(radius); @@ -326,7 +331,9 @@ public Sphere convert(DBObject source) { } DBObject center = (DBObject) source.get("center"); - Double radius = (Double) source.get("radius"); + Number radiusNumber = (Number) source.get("radius"); + Double radius = radiusNumber.doubleValue(); + Distance distance = new Distance(radius); From f6efd690be61559379e185d6f1bf256dfdab289c Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 10 Feb 2017 10:19:49 +0100 Subject: [PATCH 095/118] DATAMONGO-1607 - Polishing. Move coordinate conversion to dedicated method. Additionally fix issue with assertions applied to late in the chain and added some tests. Original Pull Request: #438 --- .../mongodb/core/convert/GeoConverters.java | 40 ++++++++++--------- .../core/convert/GeoConvertersUnitTests.java | 30 ++++++++++++++ 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java index b1c3cac2c7..7644912b01 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/GeoConverters.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 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. @@ -41,6 +41,7 @@ import org.springframework.data.mongodb.core.geo.Sphere; import org.springframework.data.mongodb.core.query.GeoCommand; import org.springframework.util.Assert; +import org.springframework.util.NumberUtils; import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBList; @@ -121,11 +122,8 @@ public Point convert(DBObject source) { if (source.containsField("type")) { return DbObjectToGeoJsonPointConverter.INSTANCE.convert(source); } - - Number x = (Number) source.get("x"); - Number y = (Number) source.get("y"); - return new Point(x.doubleValue(), y.doubleValue()); + return new Point(toPrimitiveDoubleValue(source.get("x")), toPrimitiveDoubleValue(source.get("y"))); } } @@ -259,10 +257,12 @@ public Circle convert(DBObject source) { } DBObject center = (DBObject) source.get("center"); - Number radiusNumber = (Number) source.get("radius"); - Double radius = radiusNumber.doubleValue(); + Number radius = (Number) source.get("radius"); - Distance distance = new Distance(radius); + Assert.notNull(center, "Center must not be null!"); + Assert.notNull(radius, "Radius must not be null!"); + + Distance distance = new Distance(toPrimitiveDoubleValue(radius)); if (source.containsField("metric")) { @@ -272,9 +272,6 @@ public Circle convert(DBObject source) { distance = distance.in(Metrics.valueOf(metricString)); } - Assert.notNull(center, "Center must not be null!"); - Assert.notNull(radius, "Radius must not be null!"); - return new Circle(DbObjectToPointConverter.INSTANCE.convert(center), distance); } } @@ -331,11 +328,12 @@ public Sphere convert(DBObject source) { } DBObject center = (DBObject) source.get("center"); - Number radiusNumber = (Number) source.get("radius"); - Double radius = radiusNumber.doubleValue(); + Number radius = (Number) source.get("radius"); + Assert.notNull(center, "Center must not be null!"); + Assert.notNull(radius, "Radius must not be null!"); - Distance distance = new Distance(radius); + Distance distance = new Distance(toPrimitiveDoubleValue(radius)); if (source.containsField("metric")) { @@ -345,9 +343,6 @@ public Sphere convert(DBObject source) { distance = distance.in(Metrics.valueOf(metricString)); } - Assert.notNull(center, "Center must not be null!"); - Assert.notNull(radius, "Radius must not be null!"); - return new Sphere(DbObjectToPointConverter.INSTANCE.convert(center), distance); } } @@ -607,7 +602,7 @@ public GeoJsonPoint convert(DBObject source) { String.format("Cannot convert type '%s' to Point.", source.get("type"))); List dbl = (List) source.get("coordinates"); - return new GeoJsonPoint(dbl.get(0).doubleValue(), dbl.get(1).doubleValue()); + return new GeoJsonPoint(toPrimitiveDoubleValue(dbl.get(0)), toPrimitiveDoubleValue(dbl.get(1))); } } @@ -841,7 +836,8 @@ static List toListOfPoint(BasicDBList listOfCoordinatePairs) { List coordinatesList = (List) point; - points.add(new GeoJsonPoint(coordinatesList.get(0).doubleValue(), coordinatesList.get(1).doubleValue())); + points.add(new GeoJsonPoint(toPrimitiveDoubleValue(coordinatesList.get(0)), + toPrimitiveDoubleValue(coordinatesList.get(1)))); } return points; } @@ -856,4 +852,10 @@ static List toListOfPoint(BasicDBList listOfCoordinatePairs) { static GeoJsonPolygon toGeoJsonPolygon(BasicDBList dbList) { return new GeoJsonPolygon(toListOfPoint((BasicDBList) dbList.get(0))); } + + private static double toPrimitiveDoubleValue(Object value) { + + Assert.isInstanceOf(Number.class, value, "Argument must be a Number."); + return NumberUtils.convertNumberToTargetClass((Number) value, Double.class).doubleValue(); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java index 2c31331166..6181d9cfd1 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/GeoConvertersUnitTests.java @@ -41,6 +41,7 @@ import org.springframework.data.mongodb.core.geo.Sphere; import org.springframework.data.mongodb.core.query.GeoCommand; +import com.mongodb.BasicDBObject; import com.mongodb.DBObject; /** @@ -48,6 +49,7 @@ * * @author Thomas Darimont * @author Oliver Gierke + * @author Christoph Strobl * @since 1.5 */ public class GeoConvertersUnitTests { @@ -153,4 +155,32 @@ public void convertsGeoCommandToDbObjectCorrectly() { assertThat(boxObject, is((Object) Arrays.asList(GeoConverters.toList(box.getFirst()), GeoConverters.toList(box.getSecond())))); } + + @Test // DATAMONGO-1607 + public void convertsPointCorrectlyWhenUsingNonDoubleForCoordinates() { + + assertThat(DbObjectToPointConverter.INSTANCE.convert(new BasicDBObject().append("x", 1L).append("y", 2L)), + is(new Point(1, 2))); + } + + @Test // DATAMONGO-1607 + public void convertsCircleCorrectlyWhenUsingNonDoubleForCoordinates() { + + DBObject circle = new BasicDBObject(); + circle.put("center", new BasicDBObject().append("x", 1).append("y", 2)); + circle.put("radius", 3L); + + assertThat(DbObjectToCircleConverter.INSTANCE.convert(circle), is(new Circle(new Point(1, 2), new Distance(3)))); + } + + @Test // DATAMONGO-1607 + public void convertsSphereCorrectlyWhenUsingNonDoubleForCoordinates() { + + DBObject sphere = new BasicDBObject(); + sphere.put("center", new BasicDBObject().append("x", 1).append("y", 2)); + sphere.put("radius", 3L); + + assertThat(DbObjectToSphereConverter.INSTANCE.convert(sphere), is(new Sphere(new Point(1, 2), new Distance(3)))); + } + } From fb3789be35a6928efeaac0471d5225c6f67cf791 Mon Sep 17 00:00:00 2001 From: Edward Prentice Date: Tue, 7 Feb 2017 21:27:40 +0000 Subject: [PATCH 096/118] DATAMONGO-1608 - Add guard against NPE in MongoQueryCreator when using IgnoreCase. Original Pull Request: #439 --- .../data/mongodb/repository/query/MongoQueryCreator.java | 5 ++++- .../AbstractPersonRepositoryIntegrationTests.java | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index 0d40ed4d28..276de5d2ca 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -55,6 +55,7 @@ * @author Oliver Gierke * @author Thomas Darimont * @author Christoph Strobl + * @author Edward Prentice */ class MongoQueryCreator extends AbstractQueryCreator { @@ -299,7 +300,9 @@ private Criteria createLikeRegexCriteriaOrThrow(Part part, MongoPersistentProper criteria = criteria.not(); } - return addAppropriateLikeRegexTo(criteria, part, parameters.next().toString()); + Object next = parameters.next(); + + return addAppropriateLikeRegexTo(criteria, part, next != null ? next.toString() : ""); case NEVER: // intentional no-op diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index b2d925bc61..3bb60ff396 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -67,6 +67,7 @@ * @author Christoph Strobl * @author Mark Paluch * @author Fırat KÜÇÜK + * @author Edward Prentice */ @RunWith(SpringJUnit4ClassRunner.class) public abstract class AbstractPersonRepositoryIntegrationTests { @@ -654,6 +655,14 @@ public void executesGeoPageQueryForWithPageRequestForJustOneElementEmptyPage() { assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS)); } + @Test // DATAMONGO-1608 + public void findByFirstNameIgnoreCaseWithNull() { + + List result = repository.findByFirstnameIgnoreCase(null); + + assertThat(result.size(), is(0)); + } + @Test // DATAMONGO-770 public void findByFirstNameIgnoreCase() { From 71fbb910ec30443aae4ef5d08c16a6b76ef2b2dd Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 10 Feb 2017 09:36:32 +0100 Subject: [PATCH 097/118] DATAMONGO-1608 - Polishing. Throw an IllegalArgumentException when trying to create a query using 'null' as an argument for queries resulting in a $regex query operator. Original Pull Request: #439 --- .../repository/query/MongoQueryCreator.java | 16 ++++++++++------ ...stractPersonRepositoryIntegrationTests.java | 18 ++++++++++++++++-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index 276de5d2ca..769b2da371 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -300,9 +300,7 @@ private Criteria createLikeRegexCriteriaOrThrow(Part part, MongoPersistentProper criteria = criteria.not(); } - Object next = parameters.next(); - - return addAppropriateLikeRegexTo(criteria, part, next != null ? next.toString() : ""); + return addAppropriateLikeRegexTo(criteria, part, parameters.next()); case NEVER: // intentional no-op @@ -330,7 +328,7 @@ private Criteria createContainingCriteria(Part part, MongoPersistentProperty pro return criteria.in(nextAsArray(parameters)); } - return addAppropriateLikeRegexTo(criteria, part, parameters.next().toString()); + return addAppropriateLikeRegexTo(criteria, part, parameters.next()); } /** @@ -341,9 +339,15 @@ private Criteria createContainingCriteria(Part part, MongoPersistentProperty pro * @param value * @return the criteria extended with the regex. */ - private Criteria addAppropriateLikeRegexTo(Criteria criteria, Part part, String value) { + private Criteria addAppropriateLikeRegexTo(Criteria criteria, Part part, Object value) { + + if (value == null) { + + throw new IllegalArgumentException(String.format( + "Argument for creating $regex pattern for property '%s' must not be null!", part.getProperty().getSegment())); + } - return criteria.regex(toLikeRegex(value, part), toRegexOptions(part)); + return criteria.regex(toLikeRegex(value.toString(), part), toRegexOptions(part)); } /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 3bb60ff396..319f2aa47d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -30,7 +30,9 @@ import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DuplicateKeyException; @@ -72,6 +74,8 @@ @RunWith(SpringJUnit4ClassRunner.class) public abstract class AbstractPersonRepositoryIntegrationTests { + public @Rule ExpectedException expectedException = ExpectedException.none(); + @Autowired protected PersonRepository repository; @Autowired MongoOperations operations; @@ -171,6 +175,15 @@ public void findsPersonsByFirstnameLike() throws Exception { assertThat(result, hasItem(boyd)); } + @Test // DATAMONGO-1608 + public void findByFirstnameLikeWithNull() { + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("property 'firstname'"); + + repository.findByFirstnameLike(null); + } + @Test public void findsPagedPersons() throws Exception { @@ -658,9 +671,10 @@ public void executesGeoPageQueryForWithPageRequestForJustOneElementEmptyPage() { @Test // DATAMONGO-1608 public void findByFirstNameIgnoreCaseWithNull() { - List result = repository.findByFirstnameIgnoreCase(null); + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("property 'firstname'"); - assertThat(result.size(), is(0)); + repository.findByFirstnameIgnoreCase(null); } @Test // DATAMONGO-770 From 541f99011d97be589e6f44c290ac40cad1fa860a Mon Sep 17 00:00:00 2001 From: Laszlo Csontos Date: Wed, 22 Feb 2017 06:51:14 +0100 Subject: [PATCH 098/118] DATAMONGO-1617 - BeforeConvertEvent is now emitted before updatable idendifier assertion. We now make sure the BeforeConvertEvent is published before we check for identifier types that can potentially be auto-generated. That allows the event listeners to populate identifiers. Previously the identifier check kicked in before that and thus caused the listener not being able to populate the property. Original pull request: #443. --- .../data/mongodb/core/MongoTemplate.java | 9 ++- .../data/mongodb/core/MongoTemplateTests.java | 57 ++++++++++++++++++- .../core/PersonWithIdPropertyOfTypeUUID.java | 57 +++++++++++++++++++ 3 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/PersonWithIdPropertyOfTypeUUID.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index f8708cb388..8cea8fe3b5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -141,6 +141,7 @@ * @author Doménique Tilleuil * @author Niko Schmuck * @author Mark Paluch + * @author Laszlo Csontos */ @SuppressWarnings("deprecation") public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -844,12 +845,11 @@ private WriteConcern potentiallyForceAcknowledgedWrite(WriteConcern wc) { protected void doInsert(String collectionName, T objectToSave, MongoWriter writer) { + maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); assertUpdateableIdIfNotSet(objectToSave); initializeVersionProperty(objectToSave); - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); - DBObject dbDoc = toDbObject(objectToSave, writer); maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName)); @@ -1001,6 +1001,7 @@ private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity doInsert(collectionName, objectToSave, this.mongoConverter); } else { + maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); assertUpdateableIdIfNotSet(objectToSave); // Create query for entity with the id and old version @@ -1012,7 +1013,6 @@ private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity BasicDBObject dbObject = new BasicDBObject(); - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); this.mongoConverter.write(objectToSave, dbObject); maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbObject, collectionName)); @@ -1025,9 +1025,8 @@ private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity protected void doSave(String collectionName, T objectToSave, MongoWriter writer) { - assertUpdateableIdIfNotSet(objectToSave); - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); + assertUpdateableIdIfNotSet(objectToSave); DBObject dbDoc = toDbObject(objectToSave, writer); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index a281b9d627..b59328f3aa 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -82,6 +82,7 @@ import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; +import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Criteria; @@ -119,6 +120,7 @@ * @author Komi Innocent * @author Christoph Strobl * @author Mark Paluch + * @author Laszlo Csontos */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") @@ -134,13 +136,19 @@ public class MongoTemplateTests { @Autowired MongoTemplate template; @Autowired MongoDbFactory factory; - @Autowired ConfigurableApplicationContext context; + ConfigurableApplicationContext context; MongoTemplate mappingTemplate; org.springframework.data.util.Version mongoVersion; @Rule public ExpectedException thrown = ExpectedException.none(); + @Autowired + public void setApplicationContext(ConfigurableApplicationContext context) { + context.addApplicationListener(new PersonWithIdPropertyOfTypeUUIDListener()); + this.context = context; + } + @Autowired public void setMongo(Mongo mongo) throws Exception { @@ -152,7 +160,8 @@ public void setMongo(Mongo mongo) throws Exception { PersonWith_idPropertyOfTypeString.class, PersonWithIdPropertyOfTypeObjectId.class, PersonWithIdPropertyOfTypeString.class, PersonWithIdPropertyOfTypeInteger.class, PersonWithIdPropertyOfTypeBigInteger.class, PersonWithIdPropertyOfPrimitiveInt.class, - PersonWithIdPropertyOfTypeLong.class, PersonWithIdPropertyOfPrimitiveLong.class))); + PersonWithIdPropertyOfTypeLong.class, PersonWithIdPropertyOfPrimitiveLong.class, + PersonWithIdPropertyOfTypeUUID.class))); mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); mappingContext.initialize(); @@ -195,6 +204,7 @@ protected void cleanDb() { template.dropCollection(PersonWithIdPropertyOfPrimitiveInt.class); template.dropCollection(PersonWithIdPropertyOfTypeLong.class); template.dropCollection(PersonWithIdPropertyOfPrimitiveLong.class); + template.dropCollection(PersonWithIdPropertyOfTypeUUID.class); template.dropCollection(PersonWithVersionPropertyOfTypeInteger.class); template.dropCollection(TestClass.class); template.dropCollection(Sample.class); @@ -632,6 +642,23 @@ private void testProperHandlingOfDifferentIdTypes(MongoTemplate mongoTemplate) t assertThat(p12q, notNullValue()); assertThat(p12q.getId(), is(p12.getId())); checkCollectionContents(PersonWithIdPropertyOfPrimitiveLong.class, 1); + + // DATAMONGO-1617 + // UUID id - provided + PersonWithIdPropertyOfTypeUUID p13 = new PersonWithIdPropertyOfTypeUUID(); + p13.setFirstName("Sven_10"); + p13.setAge(22); + p13.setId(UUID.randomUUID()); + // insert + mongoTemplate.insert(p13); + // also try save + mongoTemplate.save(p13); + assertThat(p13.getId(), notNullValue()); + PersonWithIdPropertyOfTypeUUID p13q = mongoTemplate.findOne(new Query(where("id").in(p13.getId())), + PersonWithIdPropertyOfTypeUUID.class); + assertThat(p13q, notNullValue()); + assertThat(p13q.getId(), is(p13.getId())); + checkCollectionContents(PersonWithIdPropertyOfTypeUUID.class, 1); } private void checkCollectionContents(Class entityClass, int count) { @@ -1429,6 +1456,17 @@ public void doesNotFailOnVersionInitForUnversionedEntity() { template.insert(dbObject, template.determineCollectionName(PersonWithVersionPropertyOfTypeInteger.class)); } + @Test // DATAMONGO-1617 + public void doesNotFailOnInsertForEntityWithNonAutogeneratableId() { + + PersonWithIdPropertyOfTypeUUID person = new PersonWithIdPropertyOfTypeUUID(); + person.setFirstName("Laszlo"); + person.setAge(33); + + template.insert(person); + assertThat(person.getId(), is(notNullValue())); + } + @Test // DATAMONGO-539 public void removesObjectFromExplicitCollection() { @@ -3545,4 +3583,19 @@ static class WithObjectTypeProperty { @Id String id; Object value; } + + static class PersonWithIdPropertyOfTypeUUIDListener extends AbstractMongoEventListener { + + @Override + public void onBeforeConvert(BeforeConvertEvent event) { + PersonWithIdPropertyOfTypeUUID person = event.getSource(); + + if (person.getId() != null) { + return; + } + + person.setId(UUID.randomUUID()); + } + + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/PersonWithIdPropertyOfTypeUUID.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/PersonWithIdPropertyOfTypeUUID.java new file mode 100644 index 0000000000..46a5a6f87f --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/PersonWithIdPropertyOfTypeUUID.java @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2017 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; + +import java.util.UUID; + +public class PersonWithIdPropertyOfTypeUUID { + + private UUID id; + + private String firstName; + + private int age; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "PersonWithIdPropertyOfTypeUUID [id=" + id + ", firstName=" + firstName + ", age=" + age + "]"; + } + +} From 506b7705b457c7757178a61e96b06483cf1f927b Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 28 Feb 2017 18:03:28 +0100 Subject: [PATCH 099/118] DATAMONGO-1617 - Polishing. Some cleanups in MongoTemplateTests. Removed manual ID assignment in general id handling test to make sure we use the id generation. Removed unneccessary code from domain type in favor of Lombok. Original pull request: #443. --- .../data/mongodb/core/MongoTemplateTests.java | 48 ++++++++++--------- .../core/PersonWithIdPropertyOfTypeUUID.java | 37 ++------------ 2 files changed, 30 insertions(+), 55 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index b59328f3aa..08bf971c6a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -130,10 +130,9 @@ public class MongoTemplateTests { .parse("2.4"); private static final org.springframework.data.util.Version TWO_DOT_EIGHT = org.springframework.data.util.Version .parse("2.8"); - private static final org.springframework.data.util.Version THREE_DOT_FOUR= org.springframework.data.util.Version + private static final org.springframework.data.util.Version THREE_DOT_FOUR = org.springframework.data.util.Version .parse("3.4"); - @Autowired MongoTemplate template; @Autowired MongoDbFactory factory; @@ -145,23 +144,25 @@ public class MongoTemplateTests { @Autowired public void setApplicationContext(ConfigurableApplicationContext context) { - context.addApplicationListener(new PersonWithIdPropertyOfTypeUUIDListener()); + this.context = context; + + context.addApplicationListener(new PersonWithIdPropertyOfTypeUUIDListener()); } @Autowired public void setMongo(Mongo mongo) throws Exception { - CustomConversions conversions = new CustomConversions(Arrays.asList(DateToDateTimeConverter.INSTANCE, - DateTimeToDateConverter.INSTANCE)); + CustomConversions conversions = new CustomConversions( + Arrays.asList(DateToDateTimeConverter.INSTANCE, DateTimeToDateConverter.INSTANCE)); MongoMappingContext mappingContext = new MongoMappingContext(); - mappingContext.setInitialEntitySet(new HashSet>(Arrays.asList(PersonWith_idPropertyOfTypeObjectId.class, - PersonWith_idPropertyOfTypeString.class, PersonWithIdPropertyOfTypeObjectId.class, - PersonWithIdPropertyOfTypeString.class, PersonWithIdPropertyOfTypeInteger.class, - PersonWithIdPropertyOfTypeBigInteger.class, PersonWithIdPropertyOfPrimitiveInt.class, - PersonWithIdPropertyOfTypeLong.class, PersonWithIdPropertyOfPrimitiveLong.class, - PersonWithIdPropertyOfTypeUUID.class))); + mappingContext.setInitialEntitySet(new HashSet>( + Arrays.asList(PersonWith_idPropertyOfTypeObjectId.class, PersonWith_idPropertyOfTypeString.class, + PersonWithIdPropertyOfTypeObjectId.class, PersonWithIdPropertyOfTypeString.class, + PersonWithIdPropertyOfTypeInteger.class, PersonWithIdPropertyOfTypeBigInteger.class, + PersonWithIdPropertyOfPrimitiveInt.class, PersonWithIdPropertyOfTypeLong.class, + PersonWithIdPropertyOfPrimitiveLong.class, PersonWithIdPropertyOfTypeUUID.class))); mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder()); mappingContext.initialize(); @@ -175,8 +176,11 @@ public void setMongo(Mongo mongo) throws Exception { @Before public void setUp() { + cleanDb(); queryMongoVersionIfNecessary(); + + this.mappingTemplate.setApplicationContext(context); } @After @@ -648,7 +652,6 @@ private void testProperHandlingOfDifferentIdTypes(MongoTemplate mongoTemplate) t PersonWithIdPropertyOfTypeUUID p13 = new PersonWithIdPropertyOfTypeUUID(); p13.setFirstName("Sven_10"); p13.setAge(22); - p13.setId(UUID.randomUUID()); // insert mongoTemplate.insert(p13); // also try save @@ -2406,7 +2409,7 @@ public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollection() { doc.dbRefAnnotatedList = Arrays.asList( // sample1, // sample2 // - ); + ); template.save(doc); Update update = new Update().pull("dbRefAnnotatedList", doc.dbRefAnnotatedList.get(1)); @@ -2435,7 +2438,7 @@ public void updateWithPullShouldRemoveNestedItemFromDbRefAnnotatedCollectionWhen doc.dbRefAnnotatedList = Arrays.asList( // sample1, // sample2 // - ); + ); template.save(doc); Update update = new Update().pull("dbRefAnnotatedList.id", "2"); @@ -2500,8 +2503,8 @@ public void shouldBeAbleToUpdateDbRefPropertyWithDomainObject() { @Test // DATAMONGO-862 public void testUpdateShouldWorkForPathsOnInterfaceMethods() { - DocumentWithCollection document = new DocumentWithCollection(Arrays. asList(new ModelA("spring"), - new ModelA("data"))); + DocumentWithCollection document = new DocumentWithCollection( + Arrays. asList(new ModelA("spring"), new ModelA("data"))); template.save(document); @@ -2967,7 +2970,7 @@ public void updatesNumericValueCorrectlyWhenUsingMinOperator() { .min("longVal", 490) // .min("bigIntegerVal", new BigInteger("590")) // .min("bigDeciamVal", new BigDecimal("690")) // - ; + ; template.updateFirst(query(where("id").is(twn.id)), update, TypeWithNumbers.class); @@ -3023,7 +3026,7 @@ public void updatesNumericValueCorrectlyWhenUsingMaxOperator() { .max("longVal", 590) // .max("bigIntegerVal", new BigInteger("690")) // .max("bigDeciamVal", new BigDecimal("790")) // - ; + ; template.updateFirst(query(where("id").is(twn.id)), update, TypeWithNumbers.class); @@ -3052,7 +3055,7 @@ public void updatesBigNumberValueUsingStringComparisonWhenUsingMaxOperator() { Update update = new Update()// .max("bigIntegerVal", new BigInteger("70")) // .max("bigDeciamVal", new BigDecimal("80")) // - ; + ; template.updateFirst(query(where("id").is(twn.id)), update, TypeWithNumbers.class); @@ -3076,7 +3079,7 @@ public void updatesBigNumberValueUsingStringComparisonWhenUsingMinOperator() { Update update = new Update()// .min("bigIntegerVal", new BigInteger("700")) // .min("bigDeciamVal", new BigDecimal("800")) // - ; + ; template.updateFirst(query(where("id").is(twn.id)), update, TypeWithNumbers.class); @@ -3584,10 +3587,12 @@ static class WithObjectTypeProperty { Object value; } - static class PersonWithIdPropertyOfTypeUUIDListener extends AbstractMongoEventListener { + static class PersonWithIdPropertyOfTypeUUIDListener + extends AbstractMongoEventListener { @Override public void onBeforeConvert(BeforeConvertEvent event) { + PersonWithIdPropertyOfTypeUUID person = event.getSource(); if (person.getId() != null) { @@ -3596,6 +3601,5 @@ public void onBeforeConvert(BeforeConvertEvent e person.setId(UUID.randomUUID()); } - } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/PersonWithIdPropertyOfTypeUUID.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/PersonWithIdPropertyOfTypeUUID.java index 46a5a6f87f..e50fd3f28b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/PersonWithIdPropertyOfTypeUUID.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/PersonWithIdPropertyOfTypeUUID.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2017 the original author or authors. + * Copyright 2017 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. @@ -15,43 +15,14 @@ */ package org.springframework.data.mongodb.core; +import lombok.Data; + import java.util.UUID; +@Data public class PersonWithIdPropertyOfTypeUUID { private UUID id; - private String firstName; - private int age; - - public UUID getId() { - return id; - } - - public void setId(UUID id) { - this.id = id; - } - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - @Override - public String toString() { - return "PersonWithIdPropertyOfTypeUUID [id=" + id + ", firstName=" + firstName + ", age=" + age + "]"; - } - } From c03e3a0491ea1197e1617dab516f24558fe49791 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 28 Feb 2017 20:05:35 +0100 Subject: [PATCH 100/118] DATAMONGO-1617 - Reinstantiate version property initialization before BeforeConvertEvent publication. Related pull request: #443. --- .../data/mongodb/core/MongoTemplate.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 8cea8fe3b5..1f3fd76fe8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -766,7 +766,7 @@ public long count(final Query query, String collectionName) { public long count(Query query, Class entityClass, String collectionName) { Assert.hasText(collectionName, "Collection name must not be null or empty!"); - + final DBObject dbObject = query == null ? null : queryMapper.getMappedObject(query.getQueryObject(), entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); @@ -845,11 +845,10 @@ private WriteConcern potentiallyForceAcknowledgedWrite(WriteConcern wc) { protected void doInsert(String collectionName, T objectToSave, MongoWriter writer) { + initializeVersionProperty(objectToSave); maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); assertUpdateableIdIfNotSet(objectToSave); - initializeVersionProperty(objectToSave); - DBObject dbDoc = toDbObject(objectToSave, writer); maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName)); @@ -939,12 +938,13 @@ protected void doInsertBatch(String collectionName, Collection Assert.notNull(writer, "MongoWriter must not be null!"); List dbObjectList = new ArrayList(); + for (T o : batchToSave) { initializeVersionProperty(o); - BasicDBObject dbDoc = new BasicDBObject(); - maybeEmitEvent(new BeforeConvertEvent(o, collectionName)); + + BasicDBObject dbDoc = new BasicDBObject(); writer.write(o, dbDoc); maybeEmitEvent(new BeforeSaveEvent(o, dbDoc, collectionName)); From a19e67ed99f5321218da660d3568a57dadee3277 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 8 Feb 2017 15:11:13 +0100 Subject: [PATCH 101/118] DATAMONGO-1603 - Fix Placeholder not replaced correctly in @Query. Fix issues when placeholders are appended with other chars eg. '?0xyz' or have been reused multiple times within the query. Additional tests and fixes for complex quoted replacements eg. in regex query. Rely on placeholder quotation indication instead of binding one. Might be misleading when placeholder is used more than once. Original pull request: #441. --- .../ExpressionEvaluatingParameterBinder.java | 21 +++-- .../query/StringBasedMongoQuery.java | 3 +- .../query/StringBasedMongoQueryUnitTests.java | 90 +++++++++++++++++++ 3 files changed, 106 insertions(+), 8 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index 154ecefdf8..7c5d6c08a4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -126,7 +126,7 @@ private String replacePlaceholders(String input, MongoParameterAccessor accessor buffer.append(placeholder.getSuffix()); } - if (binding.isQuoted() || placeholder.isQuoted()) { + if (placeholder.isQuoted()) { postProcessQuotedBinding(buffer, valueForBinding, !binding.isExpression() ? accessor.getBindableValue(binding.getParameterIndex()) : null, binding.isExpression()); @@ -247,7 +247,8 @@ private Pattern createReplacementPattern(List bindings) { regex.append("|"); regex.append("(" + Pattern.quote(binding.getParameter()) + ")"); - regex.append("(\\W?['\"])?"); // potential quotation char (as in { foo : '?0' }). + regex.append("([\\w.]*"); + regex.append("(\\W?['\"]|\\w*')?)"); } return Pattern.compile(regex.substring(1)); @@ -265,15 +266,22 @@ private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) { if (matcher.groupCount() > 1) { - String rawPlaceholder = matcher.group(parameterIndex * 2 + 1); - String suffix = matcher.group(parameterIndex * 2 + 2); + String rawPlaceholder = matcher.group(parameterIndex * 3 + 1); + String suffix = matcher.group(parameterIndex * 3 + 2); if (!StringUtils.hasText(rawPlaceholder)) { rawPlaceholder = matcher.group(); - suffix = "" + rawPlaceholder.charAt(rawPlaceholder.length() - 1); + if(rawPlaceholder.matches(".*\\d$")) { + suffix = ""; + } else { + int index = rawPlaceholder.replaceAll("[^\\?0-9]*$", "").length() - 1; + if (index > 0 && rawPlaceholder.length() > index) { + suffix = rawPlaceholder.substring(index+1); + } + } if (QuotedString.endsWithQuote(rawPlaceholder)) { - rawPlaceholder = QuotedString.unquoteSuffix(rawPlaceholder); + rawPlaceholder = rawPlaceholder.substring(0, rawPlaceholder.length() - (StringUtils.hasText(suffix) ? suffix.length() : 1)); } } @@ -284,6 +292,7 @@ private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) { return Placeholder.of(parameterIndex, rawPlaceholder, quoted, quoted ? QuotedString.unquoteSuffix(suffix) : suffix); } + return Placeholder.of(parameterIndex, rawPlaceholder, false, null); } return Placeholder.of(parameterIndex, matcher.group(), false, null); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java index c79f872ad1..9fb3dd782a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java @@ -339,8 +339,7 @@ private static void potentiallyAddBinding(String source, List while (valueMatcher.find()) { int paramIndex = Integer.parseInt(valueMatcher.group(PARAMETER_INDEX_GROUP)); - boolean quoted = (source.startsWith("'") && source.endsWith("'")) - || (source.startsWith("\"") && source.endsWith("\"")); + boolean quoted = source.startsWith("'") || source.startsWith("\""); bindings.add(new ParameterBinding(paramIndex, quoted)); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index 8d9237bcbe..eae8070662 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -437,6 +437,78 @@ public void shouldReplaceParametersInInQuotedExpressionOfNestedQueryOperator() t assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", Pattern.compile("^(calamity)")))); } + @Test // DATAMONGO-1603 + public void shouldAllowReuseOfPlaceholderWithinQuery() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByReusingPlaceholdersMultipleTimes", String.class, + String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", "calamity") + .append("arg1", "regalia").append("arg2", "calamity"))); + } + + @Test // DATAMONGO-1603 + public void shouldAllowReuseOfQuotedPlaceholderWithinQuery() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByReusingPlaceholdersMultipleTimesWhenQuoted", + String.class, String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", "calamity") + .append("arg1", "regalia").append("arg2", "calamity"))); + } + + @Test // DATAMONGO-1603 + public void shouldAllowReuseOfQuotedPlaceholderWithinQueryAndIncludeSuffixCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod( + "findByReusingPlaceholdersMultipleTimesWhenQuotedAndSomeStuffAppended", String.class, String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", "calamity") + .append("arg1", "regalia").append("arg2", "calamitys"))); + } + + @Test // DATAMONGO-1603 + public void shouldAllowQuotedParameterWithSuffixAppended() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByWhenQuotedAndSomeStuffAppended", String.class, + String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), + is((DBObject) new BasicDBObject().append("arg0", "calamity").append("arg1", "regalias"))); + } + + @Test // DATAMONGO-1603 + public void shouldCaptureReplacementWithComplexSuffixCorrectly() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByMultiRegex", String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + + assertThat(query.getQueryObject(), is((DBObject) JSON.parse( + "{ \"$or\" : [ { \"firstname\" : { \"$regex\" : \".*calamity.*\" , \"$options\" : \"i\"}} , { \"lastname\" : { \"$regex\" : \".*calamityxyz.*\" , \"$options\" : \"i\"}}]}"))); + } + + @Test // DATAMONGO-1603 + public void shouldAllowPlaceholderReuseInQuotedValue() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameRegex", String.class, String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + + assertThat(query.getQueryObject(), + is((DBObject) JSON.parse("{ 'lastname' : { '$regex' : '^(calamity|John regalia|regalia)'} }"))); + } + private StringBasedMongoQuery createQueryForMethod(String name, Class... parameters) throws Exception { Method method = SampleRepository.class.getMethod(name, parameters); @@ -460,6 +532,9 @@ private interface SampleRepository extends Repository { @Query("{ 'lastname' : { '$regex' : '^(?0)'} }") Person findByLastnameRegex(String lastname); + @Query("{'$or' : [{'firstname': {'$regex': '.*?0.*', '$options': 'i'}}, {'lastname' : {'$regex': '.*?0xyz.*', '$options': 'i'}} ]}") + Person findByMultiRegex(String arg0); + @Query("{ 'address' : ?0 }") Person findByAddress(Address address); @@ -510,5 +585,20 @@ private interface SampleRepository extends Repository { @Query("{ 'arg0' : ?0 }") List findByWithBsonArgument(DBObject arg0); + + @Query("{ 'arg0' : ?0, 'arg1' : ?1, 'arg2' : ?0 }") + List findByReusingPlaceholdersMultipleTimes(String arg0, String arg1); + + @Query("{ 'arg0' : ?0, 'arg1' : ?1, 'arg2' : '?0' }") + List findByReusingPlaceholdersMultipleTimesWhenQuoted(String arg0, String arg1); + + @Query("{ 'arg0' : '?0', 'arg1' : ?1, 'arg2' : '?0s' }") + List findByReusingPlaceholdersMultipleTimesWhenQuotedAndSomeStuffAppended(String arg0, String arg1); + + @Query("{ 'arg0' : '?0', 'arg1' : '?1s' }") + List findByWhenQuotedAndSomeStuffAppended(String arg0, String arg1); + + @Query("{ 'lastname' : { '$regex' : '^(?0|John ?1|?1)'} }") // use spel or some regex string this is fucking bad + Person findByLastnameRegex(String lastname, String alternative); } } From 45456f890fe8441e63a48301893147c1fd8a12a5 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 1 Mar 2017 08:17:57 +0100 Subject: [PATCH 102/118] DATAMONGO-1603 - Polishing. Remove code that became unused. Reformat code. Extend years in copyright header. Original pull request: #441. --- .../ExpressionEvaluatingParameterBinder.java | 52 +++++++++---------- .../query/StringBasedMongoQuery.java | 30 +++++------ .../query/StringBasedMongoQueryUnitTests.java | 2 +- 3 files changed, 40 insertions(+), 44 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index 7c5d6c08a4..83489e61b7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -45,7 +45,7 @@ /** * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a * {@link String}. - * + * * @author Christoph Strobl * @author Thomas Darimont * @author Oliver Gierke @@ -59,7 +59,7 @@ class ExpressionEvaluatingParameterBinder { /** * Creates new {@link ExpressionEvaluatingParameterBinder} - * + * * @param expressionParser must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. */ @@ -76,7 +76,7 @@ public ExpressionEvaluatingParameterBinder(SpelExpressionParser expressionParser /** * Bind values provided by {@link MongoParameterAccessor} to placeholders in {@literal raw} while considering * potential conversions and parameter types. - * + * * @param raw can be {@literal null} or empty. * @param accessor must not be {@literal null}. * @param bindingContext must not be {@literal null}. @@ -93,7 +93,7 @@ public String bind(String raw, MongoParameterAccessor accessor, BindingContext b /** * Replaced the parameter placeholders with the actual parameter values from the given {@link ParameterBinding}s. - * + * * @param input must not be {@literal null} or empty. * @param accessor must not be {@literal null}. * @param bindingContext must not be {@literal null}. @@ -264,38 +264,34 @@ private Pattern createReplacementPattern(List bindings) { */ private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) { - if (matcher.groupCount() > 1) { - - String rawPlaceholder = matcher.group(parameterIndex * 3 + 1); - String suffix = matcher.group(parameterIndex * 3 + 2); + String rawPlaceholder = matcher.group(parameterIndex * 3 + 1); + String suffix = matcher.group(parameterIndex * 3 + 2); - if (!StringUtils.hasText(rawPlaceholder)) { + if (!StringUtils.hasText(rawPlaceholder)) { - rawPlaceholder = matcher.group(); - if(rawPlaceholder.matches(".*\\d$")) { - suffix = ""; - } else { - int index = rawPlaceholder.replaceAll("[^\\?0-9]*$", "").length() - 1; - if (index > 0 && rawPlaceholder.length() > index) { - suffix = rawPlaceholder.substring(index+1); - } - } - if (QuotedString.endsWithQuote(rawPlaceholder)) { - rawPlaceholder = rawPlaceholder.substring(0, rawPlaceholder.length() - (StringUtils.hasText(suffix) ? suffix.length() : 1)); + rawPlaceholder = matcher.group(); + if (rawPlaceholder.matches(".*\\d$")) { + suffix = ""; + } else { + int index = rawPlaceholder.replaceAll("[^\\?0-9]*$", "").length() - 1; + if (index > 0 && rawPlaceholder.length() > index) { + suffix = rawPlaceholder.substring(index + 1); } } + if (QuotedString.endsWithQuote(rawPlaceholder)) { + rawPlaceholder = rawPlaceholder.substring(0, + rawPlaceholder.length() - (StringUtils.hasText(suffix) ? suffix.length() : 1)); + } + } - if (StringUtils.hasText(suffix)) { + if (StringUtils.hasText(suffix)) { - boolean quoted = QuotedString.endsWithQuote(suffix); + boolean quoted = QuotedString.endsWithQuote(suffix); - return Placeholder.of(parameterIndex, rawPlaceholder, quoted, - quoted ? QuotedString.unquoteSuffix(suffix) : suffix); - } - return Placeholder.of(parameterIndex, rawPlaceholder, false, null); + return Placeholder.of(parameterIndex, rawPlaceholder, quoted, + quoted ? QuotedString.unquoteSuffix(suffix) : suffix); } - - return Placeholder.of(parameterIndex, matcher.group(), false, null); + return Placeholder.of(parameterIndex, rawPlaceholder, false, null); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java index 9fb3dd782a..2ea13d1a08 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2016 the original author or authors. + * Copyright 2011-2017 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. @@ -38,7 +38,7 @@ /** * Query to use a plain JSON String to create the {@link Query} to actually execute. - * + * * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont @@ -61,7 +61,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { /** * Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod} and {@link MongoOperations}. - * + * * @param method must not be {@literal null}. * @param mongoOperations must not be {@literal null}. * @param expressionParser must not be {@literal null}. @@ -99,7 +99,6 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider); - if (method.hasAnnotatedQuery()) { org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation(); @@ -127,10 +126,10 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati @Override protected Query createQuery(ConvertingParameterAccessor accessor) { - String queryString = parameterBinder.bind(this.query, accessor, new BindingContext(getQueryMethod() - .getParameters(), queryParameterBindings)); - String fieldsString = parameterBinder.bind(this.fieldSpec, accessor, new BindingContext(getQueryMethod() - .getParameters(), fieldSpecParameterBindings)); + String queryString = parameterBinder.bind(this.query, accessor, + new BindingContext(getQueryMethod().getParameters(), queryParameterBindings)); + String fieldsString = parameterBinder.bind(this.fieldSpec, accessor, + new BindingContext(getQueryMethod().getParameters(), fieldSpecParameterBindings)); Query query = new BasicQuery(queryString, fieldsString).with(accessor.getSort()); @@ -141,7 +140,7 @@ protected Query createQuery(ConvertingParameterAccessor accessor) { return query; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isCountQuery() */ @@ -168,7 +167,8 @@ protected boolean isDeleteQuery() { return this.isDeleteQuery; } - private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, boolean isDeleteQuery) { + private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, + boolean isDeleteQuery) { return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1; } @@ -188,7 +188,7 @@ private static int countBooleanValues(boolean... values) { /** * A parser that extracts the parameter bindings from a given query string. - * + * * @author Thomas Darimont */ private enum ParameterBindingParser { @@ -211,7 +211,7 @@ private enum ParameterBindingParser { /** * Returns a list of {@link ParameterBinding}s found in the given {@code input} or an * {@link Collections#emptyList()}. - * + * * @param input can be {@literal null} or empty. * @param bindings must not be {@literal null}. * @return @@ -306,7 +306,7 @@ private static void collectParameterReferencesIntoBindings(List Date: Mon, 30 Jan 2017 10:30:18 +0100 Subject: [PATCH 103/118] DATAMONGO-1600 - Make GraphLookupOperationBuilder public. Make GraphLookupOperationBuilder public so it can be used in types outside the aggregation package. Original Pull Request: #437 --- .../data/mongodb/core/aggregation/GraphLookupOperation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java index d46f367638..15612501b2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2016-2017 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. @@ -332,7 +332,7 @@ public GraphLookupOperationBuilder connectTo(String fieldName) { /** * @author Mark Paluch */ - static final class GraphLookupOperationBuilder { + public static final class GraphLookupOperationBuilder { private final String from; private final List startWith; From 5d8f10fb2db3e9b98cc450f0dda33c6a41134889 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 9 Feb 2017 13:57:39 +0100 Subject: [PATCH 104/118] DATAMONGO-1605 - Retain type of SpEL expression result when used in @Query. Fix issue where any result of a SpEL expression had been treated as quoted String within the resulting MongoDB query. --- .../ExpressionEvaluatingParameterBinder.java | 12 +++++++++- .../query/StringBasedMongoQueryUnitTests.java | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index 83489e61b7..51de1e455f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -170,6 +170,12 @@ private void postProcessQuotedBinding(StringBuffer buffer, String valueForBindin } else { + if (isExpression) { + + buffer.deleteCharAt(quotationMarkIndex); + return; + } + if (quotationMark == '\'') { buffer.replace(quotationMarkIndex, quotationMarkIndex + 1, "\""); } @@ -199,7 +205,7 @@ private String getParameterValueForBinding(MongoParameterAccessor accessor, Mong return (String) value; } - return QuotedString.unquote(JSON.serialize(value)); + return binding.isExpression() ? JSON.serialize(value) : QuotedString.unquote(JSON.serialize(value)); } if (value instanceof byte[]) { @@ -213,6 +219,10 @@ private String getParameterValueForBinding(MongoParameterAccessor accessor, Mong return base64representation; } + if (binding.isExpression() && value instanceof String) { + return "\"" + JSON.serialize(value) + "\""; + } + return JSON.serialize(value); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index 919c584750..8c18b1ed8e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -509,6 +509,26 @@ public void shouldAllowPlaceholderReuseInQuotedValue() throws Exception { is((DBObject) JSON.parse("{ 'lastname' : { '$regex' : '^(calamity|John regalia|regalia)'} }"))); } + @Test // DATAMONGO-1605 + public void findUsingSpelShouldRetainParameterType() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByUsingSpel", Object.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, 100.01D); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", 100.01D))); + } + + @Test // DATAMONGO-1605 + public void findUsingSpelShouldRetainNullValues() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByUsingSpel", Object.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[]{null}); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", null))); + } + private StringBasedMongoQuery createQueryForMethod(String name, Class... parameters) throws Exception { Method method = SampleRepository.class.getMethod(name, parameters); @@ -600,5 +620,8 @@ private interface SampleRepository extends Repository { @Query("{ 'lastname' : { '$regex' : '^(?0|John ?1|?1)'} }") // use spel or some regex string this is fucking bad Person findByLastnameRegex(String lastname, String alternative); + + @Query("{ arg0 : ?#{[0]} }") + List findByUsingSpel(Object arg0); } } From 2b58f707b509e273f1ee362b116cc11822b94e50 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 1 Mar 2017 15:47:38 +0100 Subject: [PATCH 105/118] DATAMONGO-1605 - Polishing. Remove additional quoting around JSON serialization because JSON serialization adds quotes to a string. Reformat code. --- .../repository/query/ExpressionEvaluatingParameterBinder.java | 4 ---- .../repository/query/StringBasedMongoQueryUnitTests.java | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java index 51de1e455f..414ab99acf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java @@ -219,10 +219,6 @@ private String getParameterValueForBinding(MongoParameterAccessor accessor, Mong return base64representation; } - if (binding.isExpression() && value instanceof String) { - return "\"" + JSON.serialize(value) + "\""; - } - return JSON.serialize(value); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index 8c18b1ed8e..8e467a5085 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -523,7 +523,7 @@ public void findUsingSpelShouldRetainParameterType() throws Exception { public void findUsingSpelShouldRetainNullValues() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByUsingSpel", Object.class); - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[]{null}); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { null }); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", null))); From 2ec2cf6e7d112fa95c2060edf2b8ae006b3886bd Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 26 Jan 2017 11:33:00 +0100 Subject: [PATCH 106/118] DATAMONGO-1573 - Updated changelog. --- src/main/resources/changelog.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 5512f0b26a..c24f2eed94 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,20 @@ Spring Data MongoDB Changelog ============================= +Changes in version 1.9.7.RELEASE (2017-01-26) +--------------------------------------------- +* DATAMONGO-1596 - Reference to wrong annotation in cross-store reference documentation. +* DATAMONGO-1590 - Entity new detection doesn't consider Persistable.isNew(). +* DATAMONGO-1588 - Repository will not accept Point subclass in spatial query. +* DATAMONGO-1586 - TypeBasedAggregationOperationContext.getReferenceFor(…) exposes fields with their leaf property name. +* DATAMONGO-1585 - Aggregation sort references target field of projected and aliased fields. +* DATAMONGO-1578 - Add missing @Test annotation to ProjectionOperationUnitTests. +* DATAMONGO-1577 - Fix Reference and JavaDoc spelling issues. +* DATAMONGO-1576 - AbstractMongoEventListener methods not called when working with member fields. +* DATAMONGO-1573 - Release 1.9.7 (Hopper SR7). +* DATAMONGO-1508 - Documentation lacking for db-factory authentication-dbname. + + Changes in version 1.10.0.RELEASE (2017-01-26) ---------------------------------------------- * DATAMONGO-1596 - Reference to wrong annotation in cross-store reference documentation. From 0a3e5d00764f9e8d088b638d708db071aed9ddf1 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 2 Mar 2017 09:32:50 +0100 Subject: [PATCH 107/118] DATAMONGO-1598 - Updated changelog. --- src/main/resources/changelog.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index c24f2eed94..637544f723 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,17 @@ Spring Data MongoDB Changelog ============================= +Changes in version 1.10.1.RELEASE (2017-03-02) +---------------------------------------------- +* DATAMONGO-1608 - NullPointerException with null argument when using IgnoreCase. +* DATAMONGO-1607 - Class Cast Exception when retrieve data Point with value equal integer. +* DATAMONGO-1605 - All SPEL values are converted to String. +* DATAMONGO-1603 - @Query Annotation Placeholder Issue. +* DATAMONGO-1602 - Remove references to single-argument assertion methods of Spring. +* DATAMONGO-1600 - GraphLookupOperationBuilder is not visible. +* DATAMONGO-1598 - Release 1.10.1 (Ingalls SR1). + + Changes in version 1.9.7.RELEASE (2017-01-26) --------------------------------------------- * DATAMONGO-1596 - Reference to wrong annotation in cross-store reference documentation. From efc1ce43545a4493adfe73a22586e2c0ecd54413 Mon Sep 17 00:00:00 2001 From: manindr Date: Fri, 3 Mar 2017 19:45:15 +0530 Subject: [PATCH 108/118] stream support for result of aggregation operations --- .../data/mongodb/core/MongoOperations.java | 22 + .../data/mongodb/core/MongoTemplate.java | 4600 +++++++++-------- .../core/aggregation/AggregationOptions.java | 6 +- .../core/aggregation/AggregationTests.java | 3235 ++++++------ 4 files changed, 4050 insertions(+), 3813 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index 6d3e13d4ae..31f63dada6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -394,6 +394,7 @@ public interface MongoOperations { */ GroupByResults group(Criteria criteria, String inputCollectionName, GroupBy groupBy, Class entityClass); + /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. The name of the * inputCollection is derived from the inputType of the aggregation. @@ -419,6 +420,23 @@ public interface MongoOperations { */ AggregationResults aggregate(TypedAggregation aggregation, Class outputType); + + /** + * Execute an aggregation operation. The raw results will be mapped to the given entity class and are returned as stream. The name of the + * inputCollection is derived from the inputType of the aggregation. + * + * @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be + * {@literal null}. + * @param outputType The parameterized type of the returned list, must not be {@literal null}. + * @return The results of the aggregation operation. + * @since 1.11.0 + */ + CloseableIterator aggregateStream(TypedAggregation aggregation, Class outputType); + + + CloseableIterator aggregateStream(TypedAggregation aggregation, String inputCollectionName, + Class outputType); + /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. * @@ -432,6 +450,8 @@ public interface MongoOperations { */ AggregationResults aggregate(Aggregation aggregation, Class inputType, Class outputType); + CloseableIterator aggregateStream(Aggregation aggregation, Class inputType, Class outputType); + /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. * @@ -1007,6 +1027,8 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl */ WriteResult remove(Query query, String collectionName); + CloseableIterator aggregateStream(Aggregation aggregation, String collectionName, Class outputType); + /** * Returns and removes all documents form the specified collection that match the provided query. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 1f3fd76fe8..6b569de3c6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -15,6 +15,9 @@ */ package org.springframework.data.mongodb.core; +import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.ALLOW_DISK_USE; +import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.CURSOR; +import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.EXPLAIN; import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.SerializationUtils.*; @@ -31,6 +34,8 @@ import java.util.Scanner; import java.util.Set; +import com.mongodb.*; +import com.mongodb.AggregationOptions; import org.bson.types.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,12 +66,7 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.BulkOperations.BulkMode; -import org.springframework.data.mongodb.core.aggregation.Aggregation; -import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; -import org.springframework.data.mongodb.core.aggregation.AggregationResults; -import org.springframework.data.mongodb.core.aggregation.Fields; -import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext; -import org.springframework.data.mongodb.core.aggregation.TypedAggregation; +import org.springframework.data.mongodb.core.aggregation.*; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; @@ -92,11 +92,7 @@ import org.springframework.data.mongodb.core.mapreduce.GroupByResults; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; import org.springframework.data.mongodb.core.mapreduce.MapReduceResults; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Meta; -import org.springframework.data.mongodb.core.query.NearQuery; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; +import org.springframework.data.mongodb.core.query.*; import org.springframework.data.mongodb.util.MongoClientVersion; import org.springframework.data.util.CloseableIterator; import org.springframework.jca.cci.core.ConnectionCallback; @@ -106,21 +102,6 @@ import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; -import com.mongodb.BasicDBObject; -import com.mongodb.Bytes; -import com.mongodb.CommandResult; -import com.mongodb.Cursor; -import com.mongodb.DB; -import com.mongodb.DBCollection; -import com.mongodb.DBCursor; -import com.mongodb.DBObject; -import com.mongodb.MapReduceCommand; -import com.mongodb.MapReduceOutput; -import com.mongodb.Mongo; -import com.mongodb.MongoException; -import com.mongodb.ReadPreference; -import com.mongodb.WriteConcern; -import com.mongodb.WriteResult; import com.mongodb.util.JSON; import com.mongodb.util.JSONParseException; @@ -146,2396 +127,2471 @@ @SuppressWarnings("deprecation") public class MongoTemplate implements MongoOperations, ApplicationContextAware { - private static final Logger LOGGER = LoggerFactory.getLogger(MongoTemplate.class); - private static final String ID_FIELD = "_id"; - private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE; - private static final Collection ITERABLE_CLASSES; - - static { - - Set iterableClasses = new HashSet(); - iterableClasses.add(List.class.getName()); - iterableClasses.add(Collection.class.getName()); - iterableClasses.add(Iterator.class.getName()); - - ITERABLE_CLASSES = Collections.unmodifiableCollection(iterableClasses); - } - - private final MongoConverter mongoConverter; - private final MappingContext, MongoPersistentProperty> mappingContext; - private final MongoDbFactory mongoDbFactory; - private final PersistenceExceptionTranslator exceptionTranslator; - private final QueryMapper queryMapper; - private final UpdateMapper updateMapper; - - private WriteConcern writeConcern; - private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE; - private WriteResultChecking writeResultChecking = WriteResultChecking.NONE; - private ReadPreference readPreference; - private ApplicationEventPublisher eventPublisher; - private ResourceLoader resourceLoader; - private MongoPersistentEntityIndexCreator indexCreator; - - /** - * Constructor used for a basic template configuration - * - * @param mongo must not be {@literal null}. - * @param databaseName must not be {@literal null} or empty. - */ - public MongoTemplate(Mongo mongo, String databaseName) { - this(new SimpleMongoDbFactory(mongo, databaseName), null); - } - - /** - * Constructor used for a template configuration with user credentials in the form of - * {@link org.springframework.data.authentication.UserCredentials} - * - * @param mongo must not be {@literal null}. - * @param databaseName must not be {@literal null} or empty. - * @param userCredentials - */ - public MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCredentials) { - this(new SimpleMongoDbFactory(mongo, databaseName, userCredentials)); - } - - /** - * Constructor used for a basic template configuration. - * - * @param mongoDbFactory must not be {@literal null}. - */ - public MongoTemplate(MongoDbFactory mongoDbFactory) { - this(mongoDbFactory, null); - } - - /** - * Constructor used for a basic template configuration. - * - * @param mongoDbFactory must not be {@literal null}. - * @param mongoConverter - */ - public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { - - Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!"); - - this.mongoDbFactory = mongoDbFactory; - this.exceptionTranslator = mongoDbFactory.getExceptionTranslator(); - this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter; - this.queryMapper = new QueryMapper(this.mongoConverter); - this.updateMapper = new UpdateMapper(this.mongoConverter); - - // We always have a mapping context in the converter, whether it's a simple one or not - mappingContext = this.mongoConverter.getMappingContext(); - // We create indexes based on mapping events - if (null != mappingContext && mappingContext instanceof MongoMappingContext) { - indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext, mongoDbFactory); - eventPublisher = new MongoMappingEventPublisher(indexCreator); - if (mappingContext instanceof ApplicationEventPublisherAware) { - ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); - } - } - } - - /** - * Configures the {@link WriteResultChecking} to be used with the template. Setting {@literal null} will reset the - * default of {@value #DEFAULT_WRITE_RESULT_CHECKING}. - * - * @param resultChecking - */ - public void setWriteResultChecking(WriteResultChecking resultChecking) { - this.writeResultChecking = resultChecking == null ? DEFAULT_WRITE_RESULT_CHECKING : resultChecking; - } - - /** - * Configures the {@link WriteConcern} to be used with the template. If none is configured the {@link WriteConcern} - * configured on the {@link MongoDbFactory} will apply. If you configured a {@link Mongo} instance no - * {@link WriteConcern} will be used. - * - * @param writeConcern - */ - public void setWriteConcern(WriteConcern writeConcern) { - this.writeConcern = writeConcern; - } - - /** - * Configures the {@link WriteConcernResolver} to be used with the template. - * - * @param writeConcernResolver - */ - public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) { - this.writeConcernResolver = writeConcernResolver; - } - - /** - * Used by @{link {@link #prepareCollection(DBCollection)} to set the {@link ReadPreference} before any operations are - * performed. - * - * @param readPreference - */ - public void setReadPreference(ReadPreference readPreference) { - this.readPreference = readPreference; - } - - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) - */ - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - - prepareIndexCreator(applicationContext); - - eventPublisher = applicationContext; - if (mappingContext instanceof ApplicationEventPublisherAware) { - ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); - } - resourceLoader = applicationContext; - } - - /** - * Inspects the given {@link ApplicationContext} for {@link MongoPersistentEntityIndexCreator} and those in turn if - * they were registered for the current {@link MappingContext}. If no creator for the current {@link MappingContext} - * can be found we manually add the internally created one as {@link ApplicationListener} to make sure indexes get - * created appropriately for entity types persisted through this {@link MongoTemplate} instance. - * - * @param context must not be {@literal null}. - */ - private void prepareIndexCreator(ApplicationContext context) { - - String[] indexCreators = context.getBeanNamesForType(MongoPersistentEntityIndexCreator.class); - - for (String creator : indexCreators) { - MongoPersistentEntityIndexCreator creatorBean = context.getBean(creator, MongoPersistentEntityIndexCreator.class); - if (creatorBean.isIndexCreatorFor(mappingContext)) { - return; - } - } - - if (context instanceof ConfigurableApplicationContext) { - ((ConfigurableApplicationContext) context).addApplicationListener(indexCreator); - } - } - - /** - * Returns the default {@link org.springframework.data.mongodb.core.convert.MongoConverter}. - * - * @return - */ - public MongoConverter getConverter() { - return this.mongoConverter; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#executeAsStream(org.springframework.data.mongodb.core.query.Query, java.lang.Class) - */ - @Override - public CloseableIterator stream(final Query query, final Class entityType) { - - return stream(query, entityType, determineCollectionName(entityType)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#stream(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) - */ - @Override - public CloseableIterator stream(final Query query, final Class entityType, final String collectionName) { - - Assert.notNull(query, "Query must not be null!"); - Assert.notNull(entityType, "Entity type must not be null!"); - Assert.hasText(collectionName, "Collection name must not be null or empty!"); - - return execute(collectionName, new CollectionCallback>() { - - @Override - public CloseableIterator doInCollection(DBCollection collection) throws MongoException, DataAccessException { - - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityType); - - DBObject mappedFields = queryMapper.getMappedFields(query.getFieldsObject(), persistentEntity); - DBObject mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity); - - DBCursor cursor = collection.find(mappedQuery, mappedFields); - QueryCursorPreparer cursorPreparer = new QueryCursorPreparer(query, entityType); - - ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, entityType, collectionName); - - return new CloseableIterableCursorAdapter(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback); - } - }); - } - - public String getCollectionName(Class entityClass) { - return this.determineCollectionName(entityClass); - } - - public CommandResult executeCommand(String jsonCommand) { - return executeCommand((DBObject) JSON.parse(jsonCommand)); - } - - public CommandResult executeCommand(final DBObject command) { - - CommandResult result = execute(new DbCallback() { - public CommandResult doInDB(DB db) throws MongoException, DataAccessException { - return db.command(command); - } - }); - - logCommandExecutionError(command, result); - return result; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(com.mongodb.DBObject, int) - */ - @Deprecated - public CommandResult executeCommand(final DBObject command, final int options) { - return executeCommand(command, - (options & Bytes.QUERYOPTION_SLAVEOK) != 0 ? ReadPreference.secondaryPreferred() : ReadPreference.primary()); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(com.mongodb.DBObject, com.mongodb.ReadPreference) - */ - public CommandResult executeCommand(final DBObject command, final ReadPreference readPreference) { - - Assert.notNull(command, "Command must not be null!"); - - CommandResult result = execute(new DbCallback() { - public CommandResult doInDB(DB db) throws MongoException, DataAccessException { - return readPreference != null ? db.command(command, readPreference) : db.command(command); - } - }); - - logCommandExecutionError(command, result); - - return result; - } - - protected void logCommandExecutionError(final DBObject command, CommandResult result) { - - String error = result.getErrorMessage(); - - if (error != null) { - LOGGER.warn("Command execution of {} failed: {}", command.toString(), error); - } - } - - public void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch) { - executeQuery(query, collectionName, dch, new QueryCursorPreparer(query, null)); - } - - /** - * Execute a MongoDB query and iterate over the query results on a per-document basis with a - * {@link DocumentCallbackHandler} using the provided CursorPreparer. - * - * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification, must not be {@literal null}. - * @param collectionName name of the collection to retrieve the objects from - * @param dch the handler that will extract results, one document at a time - * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply - * limits, skips and so on). - */ - protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, - CursorPreparer preparer) { - - Assert.notNull(query, "Query must not be null!"); - - DBObject queryObject = queryMapper.getMappedObject(query.getQueryObject(), null); - DBObject sortObject = query.getSortObject(); - DBObject fieldsObject = query.getFieldsObject(); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing query: {} sort: {} fields: {} in collection: {}", serializeToJsonSafely(queryObject), - sortObject, fieldsObject, collectionName); - } - - this.executeQueryInternal(new FindCallback(queryObject, fieldsObject), preparer, dch, collectionName); - } - - public T execute(DbCallback action) { + private static final Logger LOGGER = LoggerFactory.getLogger(MongoTemplate.class); + private static final String ID_FIELD = "_id"; + private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE; + private static final Collection ITERABLE_CLASSES; + + static { + + Set iterableClasses = new HashSet(); + iterableClasses.add(List.class.getName()); + iterableClasses.add(Collection.class.getName()); + iterableClasses.add(Iterator.class.getName()); + + ITERABLE_CLASSES = Collections.unmodifiableCollection(iterableClasses); + } + + private final MongoConverter mongoConverter; + private final MappingContext, MongoPersistentProperty> mappingContext; + private final MongoDbFactory mongoDbFactory; + private final PersistenceExceptionTranslator exceptionTranslator; + private final QueryMapper queryMapper; + private final UpdateMapper updateMapper; + + private WriteConcern writeConcern; + private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE; + private WriteResultChecking writeResultChecking = WriteResultChecking.NONE; + private ReadPreference readPreference; + private ApplicationEventPublisher eventPublisher; + private ResourceLoader resourceLoader; + private MongoPersistentEntityIndexCreator indexCreator; + + /** + * Constructor used for a basic template configuration + * + * @param mongo must not be {@literal null}. + * @param databaseName must not be {@literal null} or empty. + */ + public MongoTemplate(Mongo mongo, String databaseName) { + this(new SimpleMongoDbFactory(mongo, databaseName), null); + } + + /** + * Constructor used for a template configuration with user credentials in the form of + * {@link org.springframework.data.authentication.UserCredentials} + * + * @param mongo must not be {@literal null}. + * @param databaseName must not be {@literal null} or empty. + * @param userCredentials + */ + public MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCredentials) { + this(new SimpleMongoDbFactory(mongo, databaseName, userCredentials)); + } + + /** + * Constructor used for a basic template configuration. + * + * @param mongoDbFactory must not be {@literal null}. + */ + public MongoTemplate(MongoDbFactory mongoDbFactory) { + this(mongoDbFactory, null); + } + + /** + * Constructor used for a basic template configuration. + * + * @param mongoDbFactory must not be {@literal null}. + * @param mongoConverter + */ + public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { + + Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!"); + + this.mongoDbFactory = mongoDbFactory; + this.exceptionTranslator = mongoDbFactory.getExceptionTranslator(); + this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter; + this.queryMapper = new QueryMapper(this.mongoConverter); + this.updateMapper = new UpdateMapper(this.mongoConverter); + + // We always have a mapping context in the converter, whether it's a simple one or not + mappingContext = this.mongoConverter.getMappingContext(); + // We create indexes based on mapping events + if (null != mappingContext && mappingContext instanceof MongoMappingContext) { + indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext, mongoDbFactory); + eventPublisher = new MongoMappingEventPublisher(indexCreator); + if (mappingContext instanceof ApplicationEventPublisherAware) { + ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); + } + } + } + + /** + * Configures the {@link WriteResultChecking} to be used with the template. Setting {@literal null} will reset the + * default of {@value #DEFAULT_WRITE_RESULT_CHECKING}. + * + * @param resultChecking + */ + public void setWriteResultChecking(WriteResultChecking resultChecking) { + this.writeResultChecking = resultChecking == null ? DEFAULT_WRITE_RESULT_CHECKING : resultChecking; + } + + /** + * Configures the {@link WriteConcern} to be used with the template. If none is configured the {@link WriteConcern} + * configured on the {@link MongoDbFactory} will apply. If you configured a {@link Mongo} instance no + * {@link WriteConcern} will be used. + * + * @param writeConcern + */ + public void setWriteConcern(WriteConcern writeConcern) { + this.writeConcern = writeConcern; + } + + /** + * Configures the {@link WriteConcernResolver} to be used with the template. + * + * @param writeConcernResolver + */ + public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) { + this.writeConcernResolver = writeConcernResolver; + } + + /** + * Used by @{link {@link #prepareCollection(DBCollection)} to set the {@link ReadPreference} before any operations are + * performed. + * + * @param readPreference + */ + public void setReadPreference(ReadPreference readPreference) { + this.readPreference = readPreference; + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + + prepareIndexCreator(applicationContext); + + eventPublisher = applicationContext; + if (mappingContext instanceof ApplicationEventPublisherAware) { + ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); + } + resourceLoader = applicationContext; + } + + /** + * Inspects the given {@link ApplicationContext} for {@link MongoPersistentEntityIndexCreator} and those in turn if + * they were registered for the current {@link MappingContext}. If no creator for the current {@link MappingContext} + * can be found we manually add the internally created one as {@link ApplicationListener} to make sure indexes get + * created appropriately for entity types persisted through this {@link MongoTemplate} instance. + * + * @param context must not be {@literal null}. + */ + private void prepareIndexCreator(ApplicationContext context) { + + String[] indexCreators = context.getBeanNamesForType(MongoPersistentEntityIndexCreator.class); + + for (String creator : indexCreators) { + MongoPersistentEntityIndexCreator creatorBean = context.getBean(creator, MongoPersistentEntityIndexCreator.class); + if (creatorBean.isIndexCreatorFor(mappingContext)) { + return; + } + } + + if (context instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) context).addApplicationListener(indexCreator); + } + } + + /** + * Returns the default {@link org.springframework.data.mongodb.core.convert.MongoConverter}. + * + * @return + */ + public MongoConverter getConverter() { + return this.mongoConverter; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#executeAsStream(org.springframework.data.mongodb.core.query.Query, java.lang.Class) + */ + @Override + public CloseableIterator stream(final Query query, final Class entityType) { + + return stream(query, entityType, determineCollectionName(entityType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#stream(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + */ + @Override + public CloseableIterator stream(final Query query, final Class entityType, final String collectionName) { + + Assert.notNull(query, "Query must not be null!"); + Assert.notNull(entityType, "Entity type must not be null!"); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + + return execute(collectionName, new CollectionCallback>() { + + @Override + public CloseableIterator doInCollection(DBCollection collection) throws MongoException, DataAccessException { + + MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityType); + + DBObject mappedFields = queryMapper.getMappedFields(query.getFieldsObject(), persistentEntity); + DBObject mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity); + + DBCursor cursor = collection.find(mappedQuery, mappedFields); + QueryCursorPreparer cursorPreparer = new QueryCursorPreparer(query, entityType); + + ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, entityType, collectionName); + + return new CloseableIterableCursorAdapter(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback); + } + }); + } + + public String getCollectionName(Class entityClass) { + return this.determineCollectionName(entityClass); + } + + public CommandResult executeCommand(String jsonCommand) { + return executeCommand((DBObject) JSON.parse(jsonCommand)); + } + + public CommandResult executeCommand(final DBObject command) { + + CommandResult result = execute(new DbCallback() { + public CommandResult doInDB(DB db) throws MongoException, DataAccessException { + return db.command(command); + } + }); + + logCommandExecutionError(command, result); + return result; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(com.mongodb.DBObject, int) + */ + @Deprecated + public CommandResult executeCommand(final DBObject command, final int options) { + return executeCommand(command, + (options & Bytes.QUERYOPTION_SLAVEOK) != 0 ? ReadPreference.secondaryPreferred() : ReadPreference.primary()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(com.mongodb.DBObject, com.mongodb.ReadPreference) + */ + public CommandResult executeCommand(final DBObject command, final ReadPreference readPreference) { + + Assert.notNull(command, "Command must not be null!"); + + CommandResult result = execute(new DbCallback() { + public CommandResult doInDB(DB db) throws MongoException, DataAccessException { + return readPreference != null ? db.command(command, readPreference) : db.command(command); + } + }); + + logCommandExecutionError(command, result); + + return result; + } + + protected void logCommandExecutionError(final DBObject command, CommandResult result) { + + String error = result.getErrorMessage(); + + if (error != null) { + LOGGER.warn("Command execution of {} failed: {}", command.toString(), error); + } + } + + public void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch) { + executeQuery(query, collectionName, dch, new QueryCursorPreparer(query, null)); + } + + /** + * Execute a MongoDB query and iterate over the query results on a per-document basis with a + * {@link DocumentCallbackHandler} using the provided CursorPreparer. + * + * @param query the query class that specifies the criteria used to find a record and also an optional fields + * specification, must not be {@literal null}. + * @param collectionName name of the collection to retrieve the objects from + * @param dch the handler that will extract results, one document at a time + * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply + * limits, skips and so on). + */ + protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, + CursorPreparer preparer) { + + Assert.notNull(query, "Query must not be null!"); + + DBObject queryObject = queryMapper.getMappedObject(query.getQueryObject(), null); + DBObject sortObject = query.getSortObject(); + DBObject fieldsObject = query.getFieldsObject(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing query: {} sort: {} fields: {} in collection: {}", serializeToJsonSafely(queryObject), + sortObject, fieldsObject, collectionName); + } + + this.executeQueryInternal(new FindCallback(queryObject, fieldsObject), preparer, dch, collectionName); + } + + public T execute(DbCallback action) { + + Assert.notNull(action, "DbCallbackmust not be null!"); + + try { + DB db = this.getDb(); + return action.doInDB(db); + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + public T execute(Class entityClass, CollectionCallback callback) { + return execute(determineCollectionName(entityClass), callback); + } + + public T execute(String collectionName, CollectionCallback callback) { + + Assert.notNull(callback, "CollectionCallback must not be null!"); + + try { + DBCollection collection = getAndPrepareCollection(getDb(), collectionName); + return callback.doInCollection(collection); + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#executeInSession(org.springframework.data.mongodb.core.DbCallback) + */ + @Deprecated + public T executeInSession(final DbCallback action) { + + return execute(new DbCallback() { + public T doInDB(DB db) throws MongoException, DataAccessException { + try { + ReflectiveDbInvoker.requestStart(db); + return action.doInDB(db); + } finally { + ReflectiveDbInvoker.requestDone(db); + } + } + }); + } + + public DBCollection createCollection(Class entityClass) { + return createCollection(determineCollectionName(entityClass)); + } + + public DBCollection createCollection(Class entityClass, CollectionOptions collectionOptions) { + return createCollection(determineCollectionName(entityClass), collectionOptions); + } + + public DBCollection createCollection(final String collectionName) { + return doCreateCollection(collectionName, new BasicDBObject()); + } + + public DBCollection createCollection(final String collectionName, final CollectionOptions collectionOptions) { + return doCreateCollection(collectionName, convertToDbObject(collectionOptions)); + } + + public DBCollection getCollection(final String collectionName) { + return execute(new DbCallback() { + public DBCollection doInDB(DB db) throws MongoException, DataAccessException { + return db.getCollection(collectionName); + } + }); + } + + public boolean collectionExists(Class entityClass) { + return collectionExists(determineCollectionName(entityClass)); + } + + public boolean collectionExists(final String collectionName) { + return execute(new DbCallback() { + public Boolean doInDB(DB db) throws MongoException, DataAccessException { + return db.collectionExists(collectionName); + } + }); + } + + public void dropCollection(Class entityClass) { + dropCollection(determineCollectionName(entityClass)); + } + + public void dropCollection(String collectionName) { + execute(collectionName, new CollectionCallback() { + public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { + collection.drop(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Dropped collection [{}]", collection.getFullName()); + } + return null; + } + }); + } + + public IndexOperations indexOps(String collectionName) { + return new DefaultIndexOperations(this, collectionName); + } + + public IndexOperations indexOps(Class entityClass) { + return new DefaultIndexOperations(this, determineCollectionName(entityClass), entityClass); + } + + public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) { + return bulkOps(bulkMode, null, collectionName); + } + + public BulkOperations bulkOps(BulkMode bulkMode, Class entityClass) { + return bulkOps(bulkMode, entityClass, determineCollectionName(entityClass)); + } + + public BulkOperations bulkOps(BulkMode mode, Class entityType, String collectionName) { + + Assert.notNull(mode, "BulkMode must not be null!"); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + + DefaultBulkOperations operations = new DefaultBulkOperations(this, mode, collectionName, entityType); + + operations.setExceptionTranslator(exceptionTranslator); + operations.setWriteConcernResolver(writeConcernResolver); + operations.setDefaultWriteConcern(writeConcern); + + return operations; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#scriptOps() + */ + @Override + public ScriptOperations scriptOps() { + return new DefaultScriptOperations(this); + } + + // Find methods that take a Query to express the query and that return a single object. + + public T findOne(Query query, Class entityClass) { + return findOne(query, entityClass, determineCollectionName(entityClass)); + } + + public T findOne(Query query, Class entityClass, String collectionName) { + if (query.getSortObject() == null) { + return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass); + } else { + query.limit(1); + List results = find(query, entityClass, collectionName); + return results.isEmpty() ? null : results.get(0); + } + } + + public boolean exists(Query query, Class entityClass) { + return exists(query, entityClass, determineCollectionName(entityClass)); + } + + public boolean exists(Query query, String collectionName) { + return exists(query, null, collectionName); + } + + public boolean exists(Query query, Class entityClass, String collectionName) { + + if (query == null) { + throw new InvalidDataAccessApiUsageException("Query passed in to exist can't be null"); + } + + DBObject mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass)); + return execute(collectionName, new FindCallback(mappedQuery)).hasNext(); + } + + // Find methods that take a Query to express the query and that return a List of objects. + + public List find(Query query, Class entityClass) { + return find(query, entityClass, determineCollectionName(entityClass)); + } + + public List find(final Query query, Class entityClass, String collectionName) { + + if (query == null) { + return findAll(entityClass, collectionName); + } + + return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass, + new QueryCursorPreparer(query, entityClass)); + } + + public T findById(Object id, Class entityClass) { + return findById(id, entityClass, determineCollectionName(entityClass)); + } - Assert.notNull(action, "DbCallbackmust not be null!"); + public T findById(Object id, Class entityClass, String collectionName) { + MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityClass); + MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty(); + String idKey = idProperty == null ? ID_FIELD : idProperty.getName(); + return doFindOne(collectionName, new BasicDBObject(idKey, id), null, entityClass); + } - try { - DB db = this.getDb(); - return action.doInDB(db); - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } + public GeoResults geoNear(NearQuery near, Class entityClass) { + return geoNear(near, entityClass, determineCollectionName(entityClass)); + } - public T execute(Class entityClass, CollectionCallback callback) { - return execute(determineCollectionName(entityClass), callback); - } + @SuppressWarnings("unchecked") + public GeoResults geoNear(NearQuery near, Class entityClass, String collectionName) { - public T execute(String collectionName, CollectionCallback callback) { + if (near == null) { + throw new InvalidDataAccessApiUsageException("NearQuery must not be null!"); + } - Assert.notNull(callback, "CollectionCallback must not be null!"); + if (entityClass == null) { + throw new InvalidDataAccessApiUsageException("Entity class must not be null!"); + } - try { - DBCollection collection = getAndPrepareCollection(getDb(), collectionName); - return callback.doInCollection(collection); - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } + String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(entityClass); + DBObject nearDbObject = near.toDBObject(); - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#executeInSession(org.springframework.data.mongodb.core.DbCallback) - */ - @Deprecated - public T executeInSession(final DbCallback action) { - - return execute(new DbCallback() { - public T doInDB(DB db) throws MongoException, DataAccessException { - try { - ReflectiveDbInvoker.requestStart(db); - return action.doInDB(db); - } finally { - ReflectiveDbInvoker.requestDone(db); - } - } - }); - } - - public DBCollection createCollection(Class entityClass) { - return createCollection(determineCollectionName(entityClass)); - } - - public DBCollection createCollection(Class entityClass, CollectionOptions collectionOptions) { - return createCollection(determineCollectionName(entityClass), collectionOptions); - } - - public DBCollection createCollection(final String collectionName) { - return doCreateCollection(collectionName, new BasicDBObject()); - } - - public DBCollection createCollection(final String collectionName, final CollectionOptions collectionOptions) { - return doCreateCollection(collectionName, convertToDbObject(collectionOptions)); - } - - public DBCollection getCollection(final String collectionName) { - return execute(new DbCallback() { - public DBCollection doInDB(DB db) throws MongoException, DataAccessException { - return db.getCollection(collectionName); - } - }); - } - - public boolean collectionExists(Class entityClass) { - return collectionExists(determineCollectionName(entityClass)); - } - - public boolean collectionExists(final String collectionName) { - return execute(new DbCallback() { - public Boolean doInDB(DB db) throws MongoException, DataAccessException { - return db.collectionExists(collectionName); - } - }); - } - - public void dropCollection(Class entityClass) { - dropCollection(determineCollectionName(entityClass)); - } - - public void dropCollection(String collectionName) { - execute(collectionName, new CollectionCallback() { - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - collection.drop(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Dropped collection [{}]", collection.getFullName()); - } - return null; - } - }); - } - - public IndexOperations indexOps(String collectionName) { - return new DefaultIndexOperations(this, collectionName); - } - - public IndexOperations indexOps(Class entityClass) { - return new DefaultIndexOperations(this, determineCollectionName(entityClass), entityClass); - } - - public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) { - return bulkOps(bulkMode, null, collectionName); - } - - public BulkOperations bulkOps(BulkMode bulkMode, Class entityClass) { - return bulkOps(bulkMode, entityClass, determineCollectionName(entityClass)); - } - - public BulkOperations bulkOps(BulkMode mode, Class entityType, String collectionName) { - - Assert.notNull(mode, "BulkMode must not be null!"); - Assert.hasText(collectionName, "Collection name must not be null or empty!"); - - DefaultBulkOperations operations = new DefaultBulkOperations(this, mode, collectionName, entityType); - - operations.setExceptionTranslator(exceptionTranslator); - operations.setWriteConcernResolver(writeConcernResolver); - operations.setDefaultWriteConcern(writeConcern); - - return operations; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#scriptOps() - */ - @Override - public ScriptOperations scriptOps() { - return new DefaultScriptOperations(this); - } - - // Find methods that take a Query to express the query and that return a single object. - - public T findOne(Query query, Class entityClass) { - return findOne(query, entityClass, determineCollectionName(entityClass)); - } - - public T findOne(Query query, Class entityClass, String collectionName) { - if (query.getSortObject() == null) { - return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass); - } else { - query.limit(1); - List results = find(query, entityClass, collectionName); - return results.isEmpty() ? null : results.get(0); - } - } - - public boolean exists(Query query, Class entityClass) { - return exists(query, entityClass, determineCollectionName(entityClass)); - } - - public boolean exists(Query query, String collectionName) { - return exists(query, null, collectionName); - } - - public boolean exists(Query query, Class entityClass, String collectionName) { - - if (query == null) { - throw new InvalidDataAccessApiUsageException("Query passed in to exist can't be null"); - } - - DBObject mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass)); - return execute(collectionName, new FindCallback(mappedQuery)).hasNext(); - } - - // Find methods that take a Query to express the query and that return a List of objects. - - public List find(Query query, Class entityClass) { - return find(query, entityClass, determineCollectionName(entityClass)); - } + BasicDBObject command = new BasicDBObject("geoNear", collection); + command.putAll(nearDbObject); - public List find(final Query query, Class entityClass, String collectionName) { + if (nearDbObject.containsField("query")) { + DBObject query = (DBObject) nearDbObject.get("query"); + command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(entityClass))); + } - if (query == null) { - return findAll(entityClass, collectionName); - } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing geoNear using: {} for class: {} in collection: {}", serializeToJsonSafely(command), + entityClass, collectionName); + } - return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass, - new QueryCursorPreparer(query, entityClass)); - } + CommandResult commandResult = executeCommand(command, this.readPreference); + List results = (List) commandResult.get("results"); + results = results == null ? Collections.emptyList() : results; - public T findById(Object id, Class entityClass) { - return findById(id, entityClass, determineCollectionName(entityClass)); - } + DbObjectCallback> callback = new GeoNearResultDbObjectCallback( + new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), near.getMetric()); + List> result = new ArrayList>(results.size()); - public T findById(Object id, Class entityClass, String collectionName) { - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityClass); - MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty(); - String idKey = idProperty == null ? ID_FIELD : idProperty.getName(); - return doFindOne(collectionName, new BasicDBObject(idKey, id), null, entityClass); - } + int index = 0; + int elementsToSkip = near.getSkip() != null ? near.getSkip() : 0; - public GeoResults geoNear(NearQuery near, Class entityClass) { - return geoNear(near, entityClass, determineCollectionName(entityClass)); - } - - @SuppressWarnings("unchecked") - public GeoResults geoNear(NearQuery near, Class entityClass, String collectionName) { - - if (near == null) { - throw new InvalidDataAccessApiUsageException("NearQuery must not be null!"); - } - - if (entityClass == null) { - throw new InvalidDataAccessApiUsageException("Entity class must not be null!"); - } - - String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(entityClass); - DBObject nearDbObject = near.toDBObject(); - - BasicDBObject command = new BasicDBObject("geoNear", collection); - command.putAll(nearDbObject); - - if (nearDbObject.containsField("query")) { - DBObject query = (DBObject) nearDbObject.get("query"); - command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(entityClass))); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing geoNear using: {} for class: {} in collection: {}", serializeToJsonSafely(command), - entityClass, collectionName); - } - - CommandResult commandResult = executeCommand(command, this.readPreference); - List results = (List) commandResult.get("results"); - results = results == null ? Collections.emptyList() : results; - - DbObjectCallback> callback = new GeoNearResultDbObjectCallback( - new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), near.getMetric()); - List> result = new ArrayList>(results.size()); - - int index = 0; - int elementsToSkip = near.getSkip() != null ? near.getSkip() : 0; - - for (Object element : results) { + for (Object element : results) { /* - * As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries + * As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries * we skip the elements ourselves to avoid at least the document 2 object mapping overhead. * * @see MongoDB Jira: SERVER-3925 */ - if (index >= elementsToSkip) { - result.add(callback.doWith((DBObject) element)); - } - index++; - } - - if (elementsToSkip > 0) { - // as we skipped some elements we have to calculate the averageDistance ourselves: - return new GeoResults(result, near.getMetric()); - } + if (index >= elementsToSkip) { + result.add(callback.doWith((DBObject) element)); + } + index++; + } + + if (elementsToSkip > 0) { + // as we skipped some elements we have to calculate the averageDistance ourselves: + return new GeoResults(result, near.getMetric()); + } + + GeoCommandStatistics stats = GeoCommandStatistics.from(commandResult); + return new GeoResults(result, new Distance(stats.getAverageDistance(), near.getMetric())); + } + + public T findAndModify(Query query, Update update, Class entityClass) { + return findAndModify(query, update, new FindAndModifyOptions(), entityClass, determineCollectionName(entityClass)); + } + + public T findAndModify(Query query, Update update, Class entityClass, String collectionName) { + return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName); + } + + public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass) { + return findAndModify(query, update, options, entityClass, determineCollectionName(entityClass)); + } + + public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass, + String collectionName) { + return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(), + getMappedSortObject(query, entityClass), entityClass, update, options); + } + + // Find methods that take a Query to express the query and that return a single object that is also removed from the + // collection in the database. + + public T findAndRemove(Query query, Class entityClass) { + return findAndRemove(query, entityClass, determineCollectionName(entityClass)); + } + + public T findAndRemove(Query query, Class entityClass, String collectionName) { + + return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(), + getMappedSortObject(query, entityClass), entityClass); + } + + public long count(Query query, Class entityClass) { + + Assert.notNull(entityClass, "Entity class must not be null!"); + return count(query, entityClass, determineCollectionName(entityClass)); + } + + public long count(final Query query, String collectionName) { + return count(query, null, collectionName); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + */ + public long count(Query query, Class entityClass, String collectionName) { + + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + + final DBObject dbObject = query == null ? null + : queryMapper.getMappedObject(query.getQueryObject(), + entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); + + return execute(collectionName, new CollectionCallback() { + public Long doInCollection(DBCollection collection) throws MongoException, DataAccessException { + return collection.count(dbObject); + } + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object) + */ + public void insert(Object objectToSave) { + ensureNotIterable(objectToSave); + insert(objectToSave, determineEntityCollectionName(objectToSave)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object, java.lang.String) + */ + public void insert(Object objectToSave, String collectionName) { + ensureNotIterable(objectToSave); + doInsert(collectionName, objectToSave, this.mongoConverter); + } + + protected void ensureNotIterable(Object o) { + if (null != o) { + if (o.getClass().isArray() || ITERABLE_CLASSES.contains(o.getClass().getName())) { + throw new IllegalArgumentException("Cannot use a collection here."); + } + } + } + + /** + * Prepare the collection before any processing is done using it. This allows a convenient way to apply settings like + * slaveOk() etc. Can be overridden in sub-classes. + * + * @param collection + */ + protected void prepareCollection(DBCollection collection) { + if (this.readPreference != null) { + collection.setReadPreference(readPreference); + } + } + + /** + * Prepare the WriteConcern before any processing is done using it. This allows a convenient way to apply custom + * settings in sub-classes.
        + * In case of using MongoDB Java driver version 3 the returned {@link WriteConcern} will be defaulted to + * {@link WriteConcern#ACKNOWLEDGED} when {@link WriteResultChecking} is set to {@link WriteResultChecking#EXCEPTION}. + * + * @param writeConcern any WriteConcern already configured or null + * @return The prepared WriteConcern or null + */ + protected WriteConcern prepareWriteConcern(MongoAction mongoAction) { + + WriteConcern wc = writeConcernResolver.resolve(mongoAction); + return potentiallyForceAcknowledgedWrite(wc); + } + + private WriteConcern potentiallyForceAcknowledgedWrite(WriteConcern wc) { + + if (ObjectUtils.nullSafeEquals(WriteResultChecking.EXCEPTION, writeResultChecking) + && MongoClientVersion.isMongo3Driver()) { + if (wc == null || wc.getWObject() == null + || (wc.getWObject() instanceof Number && ((Number) wc.getWObject()).intValue() < 1)) { + return WriteConcern.ACKNOWLEDGED; + } + } + return wc; + } + + protected void doInsert(String collectionName, T objectToSave, MongoWriter writer) { + + initializeVersionProperty(objectToSave); + maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); + assertUpdateableIdIfNotSet(objectToSave); + + DBObject dbDoc = toDbObject(objectToSave, writer); + + maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName)); + Object id = insertDBObject(collectionName, dbDoc, objectToSave.getClass()); + + populateIdIfNecessary(objectToSave, id); + maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc, collectionName)); + } + + /** + * @param objectToSave + * @param writer + * @return + */ + private DBObject toDbObject(T objectToSave, MongoWriter writer) { + + if (!(objectToSave instanceof String)) { + DBObject dbDoc = new BasicDBObject(); + writer.write(objectToSave, dbDoc); + return dbDoc; + } else { + try { + return (DBObject) JSON.parse((String) objectToSave); + } catch (JSONParseException e) { + throw new MappingException("Could not parse given String to save into a JSON document!", e); + } + } + } + + private void initializeVersionProperty(Object entity) { + + MongoPersistentEntity mongoPersistentEntity = getPersistentEntity(entity.getClass()); + + if (mongoPersistentEntity != null && mongoPersistentEntity.hasVersionProperty()) { + ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor( + mongoPersistentEntity.getPropertyAccessor(entity), mongoConverter.getConversionService()); + accessor.setProperty(mongoPersistentEntity.getVersionProperty(), 0); + } + } + + public void insert(Collection batchToSave, Class entityClass) { + doInsertBatch(determineCollectionName(entityClass), batchToSave, this.mongoConverter); + } + + public void insert(Collection batchToSave, String collectionName) { + doInsertBatch(collectionName, batchToSave, this.mongoConverter); + } + + public void insertAll(Collection objectsToSave) { + doInsertAll(objectsToSave, this.mongoConverter); + } - GeoCommandStatistics stats = GeoCommandStatistics.from(commandResult); - return new GeoResults(result, new Distance(stats.getAverageDistance(), near.getMetric())); - } + protected void doInsertAll(Collection listToSave, MongoWriter writer) { - public T findAndModify(Query query, Update update, Class entityClass) { - return findAndModify(query, update, new FindAndModifyOptions(), entityClass, determineCollectionName(entityClass)); - } + Map> elementsByCollection = new HashMap>(); - public T findAndModify(Query query, Update update, Class entityClass, String collectionName) { - return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName); - } + for (T element : listToSave) { - public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass) { - return findAndModify(query, update, options, entityClass, determineCollectionName(entityClass)); - } + if (element == null) { + continue; + } - public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass, - String collectionName) { - return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(), - getMappedSortObject(query, entityClass), entityClass, update, options); - } + MongoPersistentEntity entity = mappingContext.getPersistentEntity(element.getClass()); - // Find methods that take a Query to express the query and that return a single object that is also removed from the - // collection in the database. + if (entity == null) { + throw new InvalidDataAccessApiUsageException("No PersistentEntity information found for " + element.getClass()); + } - public T findAndRemove(Query query, Class entityClass) { - return findAndRemove(query, entityClass, determineCollectionName(entityClass)); - } + String collection = entity.getCollection(); + List collectionElements = elementsByCollection.get(collection); - public T findAndRemove(Query query, Class entityClass, String collectionName) { + if (null == collectionElements) { + collectionElements = new ArrayList(); + elementsByCollection.put(collection, collectionElements); + } - return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(), - getMappedSortObject(query, entityClass), entityClass); - } + collectionElements.add(element); + } - public long count(Query query, Class entityClass) { + for (Map.Entry> entry : elementsByCollection.entrySet()) { + doInsertBatch(entry.getKey(), entry.getValue(), this.mongoConverter); + } + } - Assert.notNull(entityClass, "Entity class must not be null!"); - return count(query, entityClass, determineCollectionName(entityClass)); - } + protected void doInsertBatch(String collectionName, Collection batchToSave, MongoWriter writer) { - public long count(final Query query, String collectionName) { - return count(query, null, collectionName); - } + Assert.notNull(writer, "MongoWriter must not be null!"); - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) - */ - public long count(Query query, Class entityClass, String collectionName) { - - Assert.hasText(collectionName, "Collection name must not be null or empty!"); - - final DBObject dbObject = query == null ? null - : queryMapper.getMappedObject(query.getQueryObject(), - entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); - - return execute(collectionName, new CollectionCallback() { - public Long doInCollection(DBCollection collection) throws MongoException, DataAccessException { - return collection.count(dbObject); - } - }); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object) - */ - public void insert(Object objectToSave) { - ensureNotIterable(objectToSave); - insert(objectToSave, determineEntityCollectionName(objectToSave)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object, java.lang.String) - */ - public void insert(Object objectToSave, String collectionName) { - ensureNotIterable(objectToSave); - doInsert(collectionName, objectToSave, this.mongoConverter); - } - - protected void ensureNotIterable(Object o) { - if (null != o) { - if (o.getClass().isArray() || ITERABLE_CLASSES.contains(o.getClass().getName())) { - throw new IllegalArgumentException("Cannot use a collection here."); - } - } - } - - /** - * Prepare the collection before any processing is done using it. This allows a convenient way to apply settings like - * slaveOk() etc. Can be overridden in sub-classes. - * - * @param collection - */ - protected void prepareCollection(DBCollection collection) { - if (this.readPreference != null) { - collection.setReadPreference(readPreference); - } - } - - /** - * Prepare the WriteConcern before any processing is done using it. This allows a convenient way to apply custom - * settings in sub-classes.
        - * In case of using MongoDB Java driver version 3 the returned {@link WriteConcern} will be defaulted to - * {@link WriteConcern#ACKNOWLEDGED} when {@link WriteResultChecking} is set to {@link WriteResultChecking#EXCEPTION}. - * - * @param writeConcern any WriteConcern already configured or null - * @return The prepared WriteConcern or null - */ - protected WriteConcern prepareWriteConcern(MongoAction mongoAction) { + List dbObjectList = new ArrayList(); - WriteConcern wc = writeConcernResolver.resolve(mongoAction); - return potentiallyForceAcknowledgedWrite(wc); - } + for (T o : batchToSave) { - private WriteConcern potentiallyForceAcknowledgedWrite(WriteConcern wc) { + initializeVersionProperty(o); + maybeEmitEvent(new BeforeConvertEvent(o, collectionName)); - if (ObjectUtils.nullSafeEquals(WriteResultChecking.EXCEPTION, writeResultChecking) - && MongoClientVersion.isMongo3Driver()) { - if (wc == null || wc.getWObject() == null - || (wc.getWObject() instanceof Number && ((Number) wc.getWObject()).intValue() < 1)) { - return WriteConcern.ACKNOWLEDGED; - } - } - return wc; - } + BasicDBObject dbDoc = new BasicDBObject(); + writer.write(o, dbDoc); - protected void doInsert(String collectionName, T objectToSave, MongoWriter writer) { + maybeEmitEvent(new BeforeSaveEvent(o, dbDoc, collectionName)); + dbObjectList.add(dbDoc); + } - initializeVersionProperty(objectToSave); - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); - assertUpdateableIdIfNotSet(objectToSave); + List ids = consolidateIdentifiers(insertDBObjectList(collectionName, dbObjectList), dbObjectList); - DBObject dbDoc = toDbObject(objectToSave, writer); + int i = 0; + for (T obj : batchToSave) { + if (i < ids.size()) { + populateIdIfNecessary(obj, ids.get(i)); + maybeEmitEvent(new AfterSaveEvent(obj, dbObjectList.get(i), collectionName)); + } + i++; + } + } - maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName)); - Object id = insertDBObject(collectionName, dbDoc, objectToSave.getClass()); - - populateIdIfNecessary(objectToSave, id); - maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc, collectionName)); - } - - /** - * @param objectToSave - * @param writer - * @return - */ - private DBObject toDbObject(T objectToSave, MongoWriter writer) { + public void save(Object objectToSave) { - if (!(objectToSave instanceof String)) { - DBObject dbDoc = new BasicDBObject(); - writer.write(objectToSave, dbDoc); - return dbDoc; - } else { - try { - return (DBObject) JSON.parse((String) objectToSave); - } catch (JSONParseException e) { - throw new MappingException("Could not parse given String to save into a JSON document!", e); - } - } - } + Assert.notNull(objectToSave, "Object to save must not be null!"); + save(objectToSave, determineEntityCollectionName(objectToSave)); + } - private void initializeVersionProperty(Object entity) { + public void save(Object objectToSave, String collectionName) { - MongoPersistentEntity mongoPersistentEntity = getPersistentEntity(entity.getClass()); + Assert.notNull(objectToSave, "Object to save must not be null!"); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); - if (mongoPersistentEntity != null && mongoPersistentEntity.hasVersionProperty()) { - ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor( - mongoPersistentEntity.getPropertyAccessor(entity), mongoConverter.getConversionService()); - accessor.setProperty(mongoPersistentEntity.getVersionProperty(), 0); - } - } + MongoPersistentEntity mongoPersistentEntity = getPersistentEntity(objectToSave.getClass()); - public void insert(Collection batchToSave, Class entityClass) { - doInsertBatch(determineCollectionName(entityClass), batchToSave, this.mongoConverter); - } + // No optimistic locking -> simple save + if (mongoPersistentEntity == null || !mongoPersistentEntity.hasVersionProperty()) { + doSave(collectionName, objectToSave, this.mongoConverter); + return; + } - public void insert(Collection batchToSave, String collectionName) { - doInsertBatch(collectionName, batchToSave, this.mongoConverter); - } + doSaveVersioned(objectToSave, mongoPersistentEntity, collectionName); + } - public void insertAll(Collection objectsToSave) { - doInsertAll(objectsToSave, this.mongoConverter); - } + private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity, String collectionName) { - protected void doInsertAll(Collection listToSave, MongoWriter writer) { + ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor( + entity.getPropertyAccessor(objectToSave), mongoConverter.getConversionService()); - Map> elementsByCollection = new HashMap>(); + MongoPersistentProperty idProperty = entity.getIdProperty(); + MongoPersistentProperty versionProperty = entity.getVersionProperty(); - for (T element : listToSave) { + Object version = convertingAccessor.getProperty(versionProperty); + Number versionNumber = convertingAccessor.getProperty(versionProperty, Number.class); - if (element == null) { - continue; - } + // Fresh instance -> initialize version property + if (version == null) { + doInsert(collectionName, objectToSave, this.mongoConverter); + } else { - MongoPersistentEntity entity = mappingContext.getPersistentEntity(element.getClass()); + maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); + assertUpdateableIdIfNotSet(objectToSave); - if (entity == null) { - throw new InvalidDataAccessApiUsageException("No PersistentEntity information found for " + element.getClass()); - } + // Create query for entity with the id and old version + Object id = convertingAccessor.getProperty(idProperty); + Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version)); + + // Bump version number + convertingAccessor.setProperty(versionProperty, versionNumber.longValue() + 1); - String collection = entity.getCollection(); - List collectionElements = elementsByCollection.get(collection); + BasicDBObject dbObject = new BasicDBObject(); + + this.mongoConverter.write(objectToSave, dbObject); + + maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbObject, collectionName)); + Update update = Update.fromDBObject(dbObject, ID_FIELD); + + doUpdate(collectionName, query, update, objectToSave.getClass(), false, false); + maybeEmitEvent(new AfterSaveEvent(objectToSave, dbObject, collectionName)); + } + } + + protected void doSave(String collectionName, T objectToSave, MongoWriter writer) { + + maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); + assertUpdateableIdIfNotSet(objectToSave); + + DBObject dbDoc = toDbObject(objectToSave, writer); + + maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName)); + Object id = saveDBObject(collectionName, dbDoc, objectToSave.getClass()); + + populateIdIfNecessary(objectToSave, id); + maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc, collectionName)); + } + + protected Object insertDBObject(final String collectionName, final DBObject dbDoc, final Class entityClass) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Inserting DBObject containing fields: {} in collection: {}", dbDoc.keySet(), collectionName); + } + + return execute(collectionName, new CollectionCallback() { + public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, + entityClass, dbDoc, null); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDoc) + : collection.insert(dbDoc, writeConcernToUse); + handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.INSERT); + return dbDoc.get(ID_FIELD); + } + }); + } + + // TODO: 2.0 - Change method signature to return List and return all identifiers (DATAMONGO-1513, + // DATAMONGO-1519) + protected List insertDBObjectList(final String collectionName, final List dbDocList) { + if (dbDocList.isEmpty()) { + return Collections.emptyList(); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Inserting list of DBObjects containing {} items", dbDocList.size()); + } + + execute(collectionName, new CollectionCallback() { + public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null, + null, null); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDocList) + : collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse); + handleAnyWriteResultErrors(writeResult, null, MongoActionOperation.INSERT_LIST); + return null; + } + }); + + List ids = new ArrayList(); + for (DBObject dbo : dbDocList) { + Object id = dbo.get(ID_FIELD); + if (id instanceof ObjectId) { + ids.add((ObjectId) id); + } else { + // no id was generated + ids.add(null); + } + } + return ids; + } + + protected Object saveDBObject(final String collectionName, final DBObject dbDoc, final Class entityClass) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Saving DBObject containing fields: {}", dbDoc.keySet()); + } + + return execute(collectionName, new CollectionCallback() { + public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass, + dbDoc, null); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult writeResult = writeConcernToUse == null ? collection.save(dbDoc) + : collection.save(dbDoc, writeConcernToUse); + handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.SAVE); + return dbDoc.get(ID_FIELD); + } + }); + } + + public WriteResult upsert(Query query, Update update, Class entityClass) { + return doUpdate(determineCollectionName(entityClass), query, update, entityClass, true, false); + } + + public WriteResult upsert(Query query, Update update, String collectionName) { + return doUpdate(collectionName, query, update, null, true, false); + } + + public WriteResult upsert(Query query, Update update, Class entityClass, String collectionName) { + return doUpdate(collectionName, query, update, entityClass, true, false); + } + + public WriteResult updateFirst(Query query, Update update, Class entityClass) { + return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, false); + } + + public WriteResult updateFirst(final Query query, final Update update, final String collectionName) { + return doUpdate(collectionName, query, update, null, false, false); + } + + public WriteResult updateFirst(Query query, Update update, Class entityClass, String collectionName) { + return doUpdate(collectionName, query, update, entityClass, false, false); + } + + public WriteResult updateMulti(Query query, Update update, Class entityClass) { + return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, true); + } + + public WriteResult updateMulti(final Query query, final Update update, String collectionName) { + return doUpdate(collectionName, query, update, null, false, true); + } + + public WriteResult updateMulti(final Query query, final Update update, Class entityClass, String collectionName) { + return doUpdate(collectionName, query, update, entityClass, false, true); + } + + protected WriteResult doUpdate(final String collectionName, final Query query, final Update update, + final Class entityClass, final boolean upsert, final boolean multi) { + + return execute(collectionName, new CollectionCallback() { + public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { + + MongoPersistentEntity entity = entityClass == null ? null : getPersistentEntity(entityClass); + + increaseVersionForUpdateIfNecessary(entity, update); + + DBObject queryObj = query == null ? new BasicDBObject() + : queryMapper.getMappedObject(query.getQueryObject(), entity); + DBObject updateObj = update == null ? new BasicDBObject() + : updateMapper.getMappedObject(update.getUpdateObject(), entity); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", + serializeToJsonSafely(queryObj), serializeToJsonSafely(updateObj), collectionName); + } + + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, + entityClass, updateObj, queryObj); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult writeResult = writeConcernToUse == null ? collection.update(queryObj, updateObj, upsert, multi) + : collection.update(queryObj, updateObj, upsert, multi, writeConcernToUse); + + if (entity != null && entity.hasVersionProperty() && !multi) { + if (ReflectiveWriteResultInvoker.wasAcknowledged(writeResult) && writeResult.getN() == 0 + && dbObjectContainsVersionProperty(queryObj, entity)) { + throw new OptimisticLockingFailureException("Optimistic lock exception on saving entity: " + + updateObj.toMap().toString() + " to collection " + collectionName); + } + } + + handleAnyWriteResultErrors(writeResult, queryObj, MongoActionOperation.UPDATE); + return writeResult; + } + }); + } + + private void increaseVersionForUpdateIfNecessary(MongoPersistentEntity persistentEntity, Update update) { + + if (persistentEntity != null && persistentEntity.hasVersionProperty()) { + String versionFieldName = persistentEntity.getVersionProperty().getFieldName(); + if (!update.modifies(versionFieldName)) { + update.inc(versionFieldName, 1L); + } + } + } + + private boolean dbObjectContainsVersionProperty(DBObject dbObject, MongoPersistentEntity persistentEntity) { + + if (persistentEntity == null || !persistentEntity.hasVersionProperty()) { + return false; + } - if (null == collectionElements) { - collectionElements = new ArrayList(); - elementsByCollection.put(collection, collectionElements); - } + return dbObject.containsField(persistentEntity.getVersionProperty().getFieldName()); + } - collectionElements.add(element); - } + public WriteResult remove(Object object) { - for (Map.Entry> entry : elementsByCollection.entrySet()) { - doInsertBatch(entry.getKey(), entry.getValue(), this.mongoConverter); - } - } + if (object == null) { + return null; + } - protected void doInsertBatch(String collectionName, Collection batchToSave, MongoWriter writer) { + return remove(getIdQueryFor(object), object.getClass()); + } - Assert.notNull(writer, "MongoWriter must not be null!"); + public WriteResult remove(Object object, String collection) { - List dbObjectList = new ArrayList(); + Assert.hasText(collection, "Collection name must not be null or empty!"); - for (T o : batchToSave) { + if (object == null) { + return null; + } - initializeVersionProperty(o); - maybeEmitEvent(new BeforeConvertEvent(o, collectionName)); + return doRemove(collection, getIdQueryFor(object), object.getClass()); + } - BasicDBObject dbDoc = new BasicDBObject(); - writer.write(o, dbDoc); + /** + * Returns {@link Entry} containing the field name of the id property as {@link Entry#getKey()} and the {@link Id}s + * property value as its {@link Entry#getValue()}. + * + * @param object + * @return + */ + private Entry extractIdPropertyAndValue(Object object) { - maybeEmitEvent(new BeforeSaveEvent(o, dbDoc, collectionName)); - dbObjectList.add(dbDoc); - } + Assert.notNull(object, "Id cannot be extracted from 'null'."); - List ids = consolidateIdentifiers(insertDBObjectList(collectionName, dbObjectList), dbObjectList); + Class objectType = object.getClass(); - int i = 0; - for (T obj : batchToSave) { - if (i < ids.size()) { - populateIdIfNecessary(obj, ids.get(i)); - maybeEmitEvent(new AfterSaveEvent(obj, dbObjectList.get(i), collectionName)); - } - i++; - } - } + if (object instanceof DBObject) { + return Collections.singletonMap(ID_FIELD, ((DBObject) object).get(ID_FIELD)).entrySet().iterator().next(); + } - public void save(Object objectToSave) { + MongoPersistentEntity entity = mappingContext.getPersistentEntity(objectType); + MongoPersistentProperty idProp = entity == null ? null : entity.getIdProperty(); - Assert.notNull(objectToSave, "Object to save must not be null!"); - save(objectToSave, determineEntityCollectionName(objectToSave)); - } + if (idProp == null || entity == null) { + throw new MappingException("No id property found for object of type " + objectType); + } - public void save(Object objectToSave, String collectionName) { + Object idValue = entity.getPropertyAccessor(object).getProperty(idProp); + return Collections.singletonMap(idProp.getFieldName(), idValue).entrySet().iterator().next(); + } - Assert.notNull(objectToSave, "Object to save must not be null!"); - Assert.hasText(collectionName, "Collection name must not be null or empty!"); + /** + * Returns a {@link Query} for the given entity by its id. + * + * @param object must not be {@literal null}. + * @return + */ + private Query getIdQueryFor(Object object) { - MongoPersistentEntity mongoPersistentEntity = getPersistentEntity(objectToSave.getClass()); + Entry id = extractIdPropertyAndValue(object); + return new Query(where(id.getKey()).is(id.getValue())); + } - // No optimistic locking -> simple save - if (mongoPersistentEntity == null || !mongoPersistentEntity.hasVersionProperty()) { - doSave(collectionName, objectToSave, this.mongoConverter); - return; - } + /** + * Returns a {@link Query} for the given entities by their ids. + * + * @param objects must not be {@literal null} or {@literal empty}. + * @return + */ + private Query getIdInQueryFor(Collection objects) { - doSaveVersioned(objectToSave, mongoPersistentEntity, collectionName); - } + Assert.notEmpty(objects, "Cannot create Query for empty collection."); - private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity, String collectionName) { + Iterator it = objects.iterator(); + Entry firstEntry = extractIdPropertyAndValue(it.next()); - ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor( - entity.getPropertyAccessor(objectToSave), mongoConverter.getConversionService()); + ArrayList ids = new ArrayList(objects.size()); + ids.add(firstEntry.getValue()); - MongoPersistentProperty idProperty = entity.getIdProperty(); - MongoPersistentProperty versionProperty = entity.getVersionProperty(); + while (it.hasNext()) { + ids.add(extractIdPropertyAndValue(it.next()).getValue()); + } - Object version = convertingAccessor.getProperty(versionProperty); - Number versionNumber = convertingAccessor.getProperty(versionProperty, Number.class); + return new Query(where(firstEntry.getKey()).in(ids)); + } - // Fresh instance -> initialize version property - if (version == null) { - doInsert(collectionName, objectToSave, this.mongoConverter); - } else { + private void assertUpdateableIdIfNotSet(Object entity) { - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); - assertUpdateableIdIfNotSet(objectToSave); + MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entity.getClass()); + MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty(); - // Create query for entity with the id and old version - Object id = convertingAccessor.getProperty(idProperty); - Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version)); - - // Bump version number - convertingAccessor.setProperty(versionProperty, versionNumber.longValue() + 1); + if (idProperty == null || persistentEntity == null) { + return; + } - BasicDBObject dbObject = new BasicDBObject(); - - this.mongoConverter.write(objectToSave, dbObject); - - maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbObject, collectionName)); - Update update = Update.fromDBObject(dbObject, ID_FIELD); - - doUpdate(collectionName, query, update, objectToSave.getClass(), false, false); - maybeEmitEvent(new AfterSaveEvent(objectToSave, dbObject, collectionName)); - } - } - - protected void doSave(String collectionName, T objectToSave, MongoWriter writer) { - - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); - assertUpdateableIdIfNotSet(objectToSave); - - DBObject dbDoc = toDbObject(objectToSave, writer); - - maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName)); - Object id = saveDBObject(collectionName, dbDoc, objectToSave.getClass()); - - populateIdIfNecessary(objectToSave, id); - maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc, collectionName)); - } - - protected Object insertDBObject(final String collectionName, final DBObject dbDoc, final Class entityClass) { - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Inserting DBObject containing fields: {} in collection: {}", dbDoc.keySet(), collectionName); - } - - return execute(collectionName, new CollectionCallback() { - public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, - entityClass, dbDoc, null); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDoc) - : collection.insert(dbDoc, writeConcernToUse); - handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.INSERT); - return dbDoc.get(ID_FIELD); - } - }); - } - - // TODO: 2.0 - Change method signature to return List and return all identifiers (DATAMONGO-1513, - // DATAMONGO-1519) - protected List insertDBObjectList(final String collectionName, final List dbDocList) { - if (dbDocList.isEmpty()) { - return Collections.emptyList(); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Inserting list of DBObjects containing {} items", dbDocList.size()); - } - - execute(collectionName, new CollectionCallback() { - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null, - null, null); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDocList) - : collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse); - handleAnyWriteResultErrors(writeResult, null, MongoActionOperation.INSERT_LIST); - return null; - } - }); - - List ids = new ArrayList(); - for (DBObject dbo : dbDocList) { - Object id = dbo.get(ID_FIELD); - if (id instanceof ObjectId) { - ids.add((ObjectId) id); - } else { - // no id was generated - ids.add(null); - } - } - return ids; - } - - protected Object saveDBObject(final String collectionName, final DBObject dbDoc, final Class entityClass) { - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Saving DBObject containing fields: {}", dbDoc.keySet()); - } - - return execute(collectionName, new CollectionCallback() { - public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass, - dbDoc, null); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.save(dbDoc) - : collection.save(dbDoc, writeConcernToUse); - handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.SAVE); - return dbDoc.get(ID_FIELD); - } - }); - } - - public WriteResult upsert(Query query, Update update, Class entityClass) { - return doUpdate(determineCollectionName(entityClass), query, update, entityClass, true, false); - } - - public WriteResult upsert(Query query, Update update, String collectionName) { - return doUpdate(collectionName, query, update, null, true, false); - } - - public WriteResult upsert(Query query, Update update, Class entityClass, String collectionName) { - return doUpdate(collectionName, query, update, entityClass, true, false); - } - - public WriteResult updateFirst(Query query, Update update, Class entityClass) { - return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, false); - } - - public WriteResult updateFirst(final Query query, final Update update, final String collectionName) { - return doUpdate(collectionName, query, update, null, false, false); - } - - public WriteResult updateFirst(Query query, Update update, Class entityClass, String collectionName) { - return doUpdate(collectionName, query, update, entityClass, false, false); - } - - public WriteResult updateMulti(Query query, Update update, Class entityClass) { - return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, true); - } - - public WriteResult updateMulti(final Query query, final Update update, String collectionName) { - return doUpdate(collectionName, query, update, null, false, true); - } - - public WriteResult updateMulti(final Query query, final Update update, Class entityClass, String collectionName) { - return doUpdate(collectionName, query, update, entityClass, false, true); - } - - protected WriteResult doUpdate(final String collectionName, final Query query, final Update update, - final Class entityClass, final boolean upsert, final boolean multi) { - - return execute(collectionName, new CollectionCallback() { - public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { - - MongoPersistentEntity entity = entityClass == null ? null : getPersistentEntity(entityClass); - - increaseVersionForUpdateIfNecessary(entity, update); - - DBObject queryObj = query == null ? new BasicDBObject() - : queryMapper.getMappedObject(query.getQueryObject(), entity); - DBObject updateObj = update == null ? new BasicDBObject() - : updateMapper.getMappedObject(update.getUpdateObject(), entity); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", - serializeToJsonSafely(queryObj), serializeToJsonSafely(updateObj), collectionName); - } - - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, - entityClass, updateObj, queryObj); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.update(queryObj, updateObj, upsert, multi) - : collection.update(queryObj, updateObj, upsert, multi, writeConcernToUse); - - if (entity != null && entity.hasVersionProperty() && !multi) { - if (ReflectiveWriteResultInvoker.wasAcknowledged(writeResult) && writeResult.getN() == 0 - && dbObjectContainsVersionProperty(queryObj, entity)) { - throw new OptimisticLockingFailureException("Optimistic lock exception on saving entity: " - + updateObj.toMap().toString() + " to collection " + collectionName); - } - } - - handleAnyWriteResultErrors(writeResult, queryObj, MongoActionOperation.UPDATE); - return writeResult; - } - }); - } - - private void increaseVersionForUpdateIfNecessary(MongoPersistentEntity persistentEntity, Update update) { - - if (persistentEntity != null && persistentEntity.hasVersionProperty()) { - String versionFieldName = persistentEntity.getVersionProperty().getFieldName(); - if (!update.modifies(versionFieldName)) { - update.inc(versionFieldName, 1L); - } - } - } - - private boolean dbObjectContainsVersionProperty(DBObject dbObject, MongoPersistentEntity persistentEntity) { - - if (persistentEntity == null || !persistentEntity.hasVersionProperty()) { - return false; - } - - return dbObject.containsField(persistentEntity.getVersionProperty().getFieldName()); - } + Object idValue = persistentEntity.getPropertyAccessor(entity).getProperty(idProperty); - public WriteResult remove(Object object) { + if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) { + throw new InvalidDataAccessApiUsageException( + String.format("Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(), + entity.getClass().getName())); + } + } - if (object == null) { - return null; - } + public WriteResult remove(Query query, String collectionName) { + return remove(query, null, collectionName); + } - return remove(getIdQueryFor(object), object.getClass()); - } + public WriteResult remove(Query query, Class entityClass) { + return remove(query, entityClass, determineCollectionName(entityClass)); + } - public WriteResult remove(Object object, String collection) { + public WriteResult remove(Query query, Class entityClass, String collectionName) { + return doRemove(collectionName, query, entityClass); + } - Assert.hasText(collection, "Collection name must not be null or empty!"); + protected WriteResult doRemove(final String collectionName, final Query query, final Class entityClass) { - if (object == null) { - return null; - } + if (query == null) { + throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null!"); + } - return doRemove(collection, getIdQueryFor(object), object.getClass()); - } + Assert.hasText(collectionName, "Collection name must not be null or empty!"); - /** - * Returns {@link Entry} containing the field name of the id property as {@link Entry#getKey()} and the {@link Id}s - * property value as its {@link Entry#getValue()}. - * - * @param object - * @return - */ - private Entry extractIdPropertyAndValue(Object object) { + final DBObject queryObject = query.getQueryObject(); + final MongoPersistentEntity entity = getPersistentEntity(entityClass); - Assert.notNull(object, "Id cannot be extracted from 'null'."); + return execute(collectionName, new CollectionCallback() { + public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { - Class objectType = object.getClass(); + maybeEmitEvent(new BeforeDeleteEvent(queryObject, entityClass, collectionName)); - if (object instanceof DBObject) { - return Collections.singletonMap(ID_FIELD, ((DBObject) object).get(ID_FIELD)).entrySet().iterator().next(); - } + DBObject dboq = queryMapper.getMappedObject(queryObject, entity); - MongoPersistentEntity entity = mappingContext.getPersistentEntity(objectType); - MongoPersistentProperty idProp = entity == null ? null : entity.getIdProperty(); + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, + entityClass, null, queryObject); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - if (idProp == null || entity == null) { - throw new MappingException("No id property found for object of type " + objectType); - } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Remove using query: {} in collection: {}.", + new Object[]{serializeToJsonSafely(dboq), collectionName}); + } - Object idValue = entity.getPropertyAccessor(object).getProperty(idProp); - return Collections.singletonMap(idProp.getFieldName(), idValue).entrySet().iterator().next(); - } + WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) + : collection.remove(dboq, writeConcernToUse); - /** - * Returns a {@link Query} for the given entity by its id. - * - * @param object must not be {@literal null}. - * @return - */ - private Query getIdQueryFor(Object object) { + handleAnyWriteResultErrors(wr, dboq, MongoActionOperation.REMOVE); - Entry id = extractIdPropertyAndValue(object); - return new Query(where(id.getKey()).is(id.getValue())); - } + maybeEmitEvent(new AfterDeleteEvent(queryObject, entityClass, collectionName)); - /** - * Returns a {@link Query} for the given entities by their ids. - * - * @param objects must not be {@literal null} or {@literal empty}. - * @return - */ - private Query getIdInQueryFor(Collection objects) { + return wr; + } + }); + } - Assert.notEmpty(objects, "Cannot create Query for empty collection."); + public List findAll(Class entityClass) { + return findAll(entityClass, determineCollectionName(entityClass)); + } - Iterator it = objects.iterator(); - Entry firstEntry = extractIdPropertyAndValue(it.next()); + public List findAll(Class entityClass, String collectionName) { + return executeFindMultiInternal(new FindCallback(null), null, + new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), collectionName); + } - ArrayList ids = new ArrayList(objects.size()); - ids.add(firstEntry.getValue()); + public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, + Class entityClass) { + return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), + entityClass); + } - while (it.hasNext()) { - ids.add(extractIdPropertyAndValue(it.next()).getValue()); - } + public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, + MapReduceOptions mapReduceOptions, Class entityClass) { + return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, mapReduceOptions, entityClass); + } - return new Query(where(firstEntry.getKey()).in(ids)); - } + public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, + String reduceFunction, Class entityClass) { + return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), + entityClass); + } - private void assertUpdateableIdIfNotSet(Object entity) { + public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, + String reduceFunction, MapReduceOptions mapReduceOptions, Class entityClass) { - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entity.getClass()); - MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty(); + String mapFunc = replaceWithResourceIfNecessary(mapFunction); + String reduceFunc = replaceWithResourceIfNecessary(reduceFunction); + DBCollection inputCollection = getCollection(inputCollectionName); - if (idProperty == null || persistentEntity == null) { - return; - } + MapReduceCommand command = new MapReduceCommand(inputCollection, mapFunc, reduceFunc, + mapReduceOptions.getOutputCollection(), mapReduceOptions.getOutputType(), + query == null || query.getQueryObject() == null ? null + : queryMapper.getMappedObject(query.getQueryObject(), null)); - Object idValue = persistentEntity.getPropertyAccessor(entity).getProperty(idProperty); + copyMapReduceOptionsToCommand(query, mapReduceOptions, command); - if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) { - throw new InvalidDataAccessApiUsageException( - String.format("Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(), - entity.getClass().getName())); - } - } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing MapReduce on collection [{}], mapFunction [{}], reduceFunction [{}]", command.getInput(), + mapFunc, reduceFunc); + } - public WriteResult remove(Query query, String collectionName) { - return remove(query, null, collectionName); - } + MapReduceOutput mapReduceOutput = inputCollection.mapReduce(command); - public WriteResult remove(Query query, Class entityClass) { - return remove(query, entityClass, determineCollectionName(entityClass)); - } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("MapReduce command result = [{}]", serializeToJsonSafely(mapReduceOutput.results())); + } - public WriteResult remove(Query query, Class entityClass, String collectionName) { - return doRemove(collectionName, query, entityClass); - } + List mappedResults = new ArrayList(); + DbObjectCallback callback = new ReadDbObjectCallback(mongoConverter, entityClass, inputCollectionName); - protected WriteResult doRemove(final String collectionName, final Query query, final Class entityClass) { + for (DBObject dbObject : mapReduceOutput.results()) { + mappedResults.add(callback.doWith(dbObject)); + } - if (query == null) { - throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null!"); - } + return new MapReduceResults(mappedResults, mapReduceOutput); + } - Assert.hasText(collectionName, "Collection name must not be null or empty!"); + public GroupByResults group(String inputCollectionName, GroupBy groupBy, Class entityClass) { + return group(null, inputCollectionName, groupBy, entityClass); + } - final DBObject queryObject = query.getQueryObject(); - final MongoPersistentEntity entity = getPersistentEntity(entityClass); + public GroupByResults group(Criteria criteria, String inputCollectionName, GroupBy groupBy, + Class entityClass) { - return execute(collectionName, new CollectionCallback() { - public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { + DBObject dbo = groupBy.getGroupByObject(); + dbo.put("ns", inputCollectionName); - maybeEmitEvent(new BeforeDeleteEvent(queryObject, entityClass, collectionName)); + if (criteria == null) { + dbo.put("cond", null); + } else { + dbo.put("cond", queryMapper.getMappedObject(criteria.getCriteriaObject(), null)); + } + // If initial document was a JavaScript string, potentially loaded by Spring's Resource abstraction, load it and + // convert to DBObject - DBObject dboq = queryMapper.getMappedObject(queryObject, entity); + if (dbo.containsField("initial")) { + Object initialObj = dbo.get("initial"); + if (initialObj instanceof String) { + String initialAsString = replaceWithResourceIfNecessary((String) initialObj); + dbo.put("initial", JSON.parse(initialAsString)); + } + } - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, - entityClass, null, queryObject); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + if (dbo.containsField("$reduce")) { + dbo.put("$reduce", replaceWithResourceIfNecessary(dbo.get("$reduce").toString())); + } + if (dbo.containsField("$keyf")) { + dbo.put("$keyf", replaceWithResourceIfNecessary(dbo.get("$keyf").toString())); + } + if (dbo.containsField("finalize")) { + dbo.put("finalize", replaceWithResourceIfNecessary(dbo.get("finalize").toString())); + } - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Remove using query: {} in collection: {}.", - new Object[] { serializeToJsonSafely(dboq), collectionName }); - } + DBObject commandObject = new BasicDBObject("group", dbo); - WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) - : collection.remove(dboq, writeConcernToUse); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing Group with DBObject [{}]", serializeToJsonSafely(commandObject)); + } - handleAnyWriteResultErrors(wr, dboq, MongoActionOperation.REMOVE); + CommandResult commandResult = executeCommand(commandObject, getDb().getOptions()); + handleCommandError(commandResult, commandObject); - maybeEmitEvent(new AfterDeleteEvent(queryObject, entityClass, collectionName)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Group command result = [{}]", commandResult); + } - return wr; - } - }); - } + @SuppressWarnings("unchecked") + Iterable resultSet = (Iterable) commandResult.get("retval"); + List mappedResults = new ArrayList(); + DbObjectCallback callback = new ReadDbObjectCallback(mongoConverter, entityClass, inputCollectionName); - public List findAll(Class entityClass) { - return findAll(entityClass, determineCollectionName(entityClass)); - } + for (DBObject dbObject : resultSet) { + mappedResults.add(callback.doWith(dbObject)); + } - public List findAll(Class entityClass, String collectionName) { - return executeFindMultiInternal(new FindCallback(null), null, - new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), collectionName); - } + return new GroupByResults(mappedResults, commandResult); + } - public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - Class entityClass) { - return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), - entityClass); - } + @Override + public AggregationResults aggregate(TypedAggregation aggregation, Class outputType) { + return aggregate(aggregation, determineCollectionName(aggregation.getInputType()), outputType); + } - public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - MapReduceOptions mapReduceOptions, Class entityClass) { - return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, mapReduceOptions, entityClass); - } + @Override + public CloseableIterator aggregateStream(TypedAggregation aggregation, Class outputType) { + return aggregateStream(aggregation, determineCollectionName(aggregation.getInputType()), outputType); + } - public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, - String reduceFunction, Class entityClass) { - return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), - entityClass); - } + @Override + public AggregationResults aggregate(TypedAggregation aggregation, String inputCollectionName, + Class outputType) { - public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, - String reduceFunction, MapReduceOptions mapReduceOptions, Class entityClass) { + Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - String mapFunc = replaceWithResourceIfNecessary(mapFunction); - String reduceFunc = replaceWithResourceIfNecessary(reduceFunction); - DBCollection inputCollection = getCollection(inputCollectionName); + AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), + mappingContext, queryMapper); + return aggregate(aggregation, inputCollectionName, outputType, context); + } - MapReduceCommand command = new MapReduceCommand(inputCollection, mapFunc, reduceFunc, - mapReduceOptions.getOutputCollection(), mapReduceOptions.getOutputType(), - query == null || query.getQueryObject() == null ? null - : queryMapper.getMappedObject(query.getQueryObject(), null)); + @Override + public CloseableIterator aggregateStream(TypedAggregation aggregation, String inputCollectionName, + Class outputType) { - copyMapReduceOptionsToCommand(query, mapReduceOptions, command); + Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing MapReduce on collection [{}], mapFunction [{}], reduceFunction [{}]", command.getInput(), - mapFunc, reduceFunc); - } + AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), + mappingContext, queryMapper); + return aggregateStream(aggregation, inputCollectionName, outputType, context); + } - MapReduceOutput mapReduceOutput = inputCollection.mapReduce(command); + @Override + public AggregationResults aggregate(Aggregation aggregation, Class inputType, Class outputType) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("MapReduce command result = [{}]", serializeToJsonSafely(mapReduceOutput.results())); - } + return aggregate(aggregation, determineCollectionName(inputType), outputType, + new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); + } - List mappedResults = new ArrayList(); - DbObjectCallback callback = new ReadDbObjectCallback(mongoConverter, entityClass, inputCollectionName); + @Override + public CloseableIterator aggregateStream(Aggregation aggregation, Class inputType, Class outputType) { - for (DBObject dbObject : mapReduceOutput.results()) { - mappedResults.add(callback.doWith(dbObject)); - } + return aggregateStream(aggregation, determineCollectionName(inputType), outputType, + new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); + } - return new MapReduceResults(mappedResults, mapReduceOutput); - } - public GroupByResults group(String inputCollectionName, GroupBy groupBy, Class entityClass) { - return group(null, inputCollectionName, groupBy, entityClass); - } + @Override + public AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType) { + return aggregate(aggregation, collectionName, outputType, null); + } - public GroupByResults group(Criteria criteria, String inputCollectionName, GroupBy groupBy, - Class entityClass) { - DBObject dbo = groupBy.getGroupByObject(); - dbo.put("ns", inputCollectionName); + @Override + public CloseableIterator aggregateStream(Aggregation aggregation, String collectionName, Class outputType) { + return aggregateStream(aggregation, collectionName, outputType, null); + } - if (criteria == null) { - dbo.put("cond", null); - } else { - dbo.put("cond", queryMapper.getMappedObject(criteria.getCriteriaObject(), null)); - } - // If initial document was a JavaScript string, potentially loaded by Spring's Resource abstraction, load it and - // convert to DBObject - - if (dbo.containsField("initial")) { - Object initialObj = dbo.get("initial"); - if (initialObj instanceof String) { - String initialAsString = replaceWithResourceIfNecessary((String) initialObj); - dbo.put("initial", JSON.parse(initialAsString)); - } - } - - if (dbo.containsField("$reduce")) { - dbo.put("$reduce", replaceWithResourceIfNecessary(dbo.get("$reduce").toString())); - } - if (dbo.containsField("$keyf")) { - dbo.put("$keyf", replaceWithResourceIfNecessary(dbo.get("$keyf").toString())); - } - if (dbo.containsField("finalize")) { - dbo.put("finalize", replaceWithResourceIfNecessary(dbo.get("finalize").toString())); - } - - DBObject commandObject = new BasicDBObject("group", dbo); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing Group with DBObject [{}]", serializeToJsonSafely(commandObject)); - } - - CommandResult commandResult = executeCommand(commandObject, getDb().getOptions()); - handleCommandError(commandResult, commandObject); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Group command result = [{}]", commandResult); - } - - @SuppressWarnings("unchecked") - Iterable resultSet = (Iterable) commandResult.get("retval"); - List mappedResults = new ArrayList(); - DbObjectCallback callback = new ReadDbObjectCallback(mongoConverter, entityClass, inputCollectionName); - - for (DBObject dbObject : resultSet) { - mappedResults.add(callback.doWith(dbObject)); - } - - return new GroupByResults(mappedResults, commandResult); - } - - @Override - public AggregationResults aggregate(TypedAggregation aggregation, Class outputType) { - return aggregate(aggregation, determineCollectionName(aggregation.getInputType()), outputType); - } - - @Override - public AggregationResults aggregate(TypedAggregation aggregation, String inputCollectionName, - Class outputType) { - - Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - - AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), - mappingContext, queryMapper); - return aggregate(aggregation, inputCollectionName, outputType, context); - } - @Override - public AggregationResults aggregate(Aggregation aggregation, Class inputType, Class outputType) { - - return aggregate(aggregation, determineCollectionName(inputType), outputType, - new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); - } - - @Override - public AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType) { - return aggregate(aggregation, collectionName, outputType, null); - } - - /* - * (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String) */ - @Override - public List findAllAndRemove(Query query, String collectionName) { - return findAndRemove(query, null, collectionName); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class) - */ - @Override - public List findAllAndRemove(Query query, Class entityClass) { - return findAllAndRemove(query, entityClass, determineCollectionName(entityClass)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) - */ - @Override - public List findAllAndRemove(Query query, Class entityClass, String collectionName) { - return doFindAndDelete(collectionName, query, entityClass); - } - - /** - * Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)} - * and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is - * constructed out of the find result. - * - * @param collectionName - * @param query - * @param entityClass - * @return - */ - protected List doFindAndDelete(String collectionName, Query query, Class entityClass) { - - List result = find(query, entityClass, collectionName); - - if (!CollectionUtils.isEmpty(result)) { - remove(getIdInQueryFor(result), entityClass, collectionName); - } - - return result; - } - - protected AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType, - AggregationOperationContext context) { - - Assert.hasText(collectionName, "Collection name must not be null or empty!"); - Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - Assert.notNull(outputType, "Output type must not be null!"); - - AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; - DBObject command = aggregation.toDbObject(collectionName, rootContext); + @Override + public List findAllAndRemove(Query query, String collectionName) { + return findAndRemove(query, null, collectionName); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class) + */ + @Override + public List findAllAndRemove(Query query, Class entityClass) { + return findAllAndRemove(query, entityClass, determineCollectionName(entityClass)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + */ + @Override + public List findAllAndRemove(Query query, Class entityClass, String collectionName) { + return doFindAndDelete(collectionName, query, entityClass); + } + + /** + * Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)} + * and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is + * constructed out of the find result. + * + * @param collectionName + * @param query + * @param entityClass + * @return + */ + protected List doFindAndDelete(String collectionName, Query query, Class entityClass) { + + List result = find(query, entityClass, collectionName); + + if (!CollectionUtils.isEmpty(result)) { + remove(getIdInQueryFor(result), entityClass, collectionName); + } + + return result; + } + + protected AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType, + AggregationOperationContext context) { + + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); + Assert.notNull(outputType, "Output type must not be null!"); + + AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; + DBObject command = aggregation.toDbObject(collectionName, rootContext); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing aggregation: {}", serializeToJsonSafely(command)); + } + + CommandResult commandResult = executeCommand(command, this.readPreference); + handleCommandError(commandResult, command); + + return new AggregationResults(returnPotentiallyMappedResults(outputType, commandResult, collectionName), + commandResult); + } + + protected CloseableIterator aggregateStream(final Aggregation aggregation, final String collectionName, final Class outputType, + AggregationOperationContext context) { + + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); + Assert.notNull(outputType, "Output type must not be null!"); + + AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; + + final DBObject command = aggregation.toDbObject(collectionName, rootContext); + + Assert.isNull(command.get(CURSOR), "Custom options not allowed while streaming"); + Assert.isNull(command.get(EXPLAIN), "Explain option can't be used while streaming"); + + return execute(collectionName, new CollectionCallback>() { + + @Override + public CloseableIterator doInCollection(DBCollection collection) throws MongoException, DataAccessException { + + List pipeline = (List) command.get("pipeline"); + Cursor cursor = collection.aggregate(pipeline, getNativeAggregationOptionsFromCommand(command)); + + ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, outputType, collectionName); + + return new CloseableIterableCursorAdapter(cursor, exceptionTranslator, readCallback); + } + + private AggregationOptions getNativeAggregationOptionsFromCommand(DBObject command) { + AggregationOptions.Builder builder = AggregationOptions.builder(); + Object allowDiskUse = command.get(ALLOW_DISK_USE); + if(allowDiskUse !=null && String.valueOf(allowDiskUse).equals("true")){ + builder.allowDiskUse(true); + } + return builder.build(); + } + }); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing aggregation: {}", serializeToJsonSafely(command)); - } - - CommandResult commandResult = executeCommand(command, this.readPreference); - handleCommandError(commandResult, command); - - return new AggregationResults(returnPotentiallyMappedResults(outputType, commandResult, collectionName), - commandResult); - } - - /** - * Returns the potentially mapped results of the given {@commandResult} contained some. - * - * @param outputType - * @param commandResult - * @return - */ - private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, - String collectionName) { - - @SuppressWarnings("unchecked") - Iterable resultSet = (Iterable) commandResult.get("result"); - if (resultSet == null) { - return Collections.emptyList(); - } - - DbObjectCallback callback = new UnwrapAndReadDbObjectCallback(mongoConverter, outputType, collectionName); - - List mappedResults = new ArrayList(); - for (DBObject dbObject : resultSet) { - mappedResults.add(callback.doWith(dbObject)); - } - - return mappedResults; - } - - protected String replaceWithResourceIfNecessary(String function) { - - String func = function; - - if (this.resourceLoader != null && ResourceUtils.isUrl(function)) { - - Resource functionResource = resourceLoader.getResource(func); - - if (!functionResource.exists()) { - throw new InvalidDataAccessApiUsageException(String.format("Resource %s not found!", function)); - } - - Scanner scanner = null; - - try { - scanner = new Scanner(functionResource.getInputStream()); - return scanner.useDelimiter("\\A").next(); - } catch (IOException e) { - throw new InvalidDataAccessApiUsageException(String.format("Cannot read map-reduce file %s!", function), e); - } finally { - if (scanner != null) { - scanner.close(); - } - } - } - - return func; - } - - private void copyMapReduceOptionsToCommand(Query query, MapReduceOptions mapReduceOptions, - MapReduceCommand mapReduceCommand) { - - if (query != null) { - if (query.getSkip() != 0 || query.getFieldsObject() != null) { - throw new InvalidDataAccessApiUsageException( - "Can not use skip or field specification with map reduce operations"); - } - if (query.getLimit() > 0 && mapReduceOptions.getLimit() == null) { - mapReduceCommand.setLimit(query.getLimit()); - } - if (query.getSortObject() != null) { - mapReduceCommand.setSort(queryMapper.getMappedObject(query.getSortObject(), null)); - } - } - - if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit().intValue() > 0) { - mapReduceCommand.setLimit(mapReduceOptions.getLimit()); - } - - if (mapReduceOptions.getJavaScriptMode() != null) { - mapReduceCommand.setJsMode(true); - } - if (!mapReduceOptions.getExtraOptions().isEmpty()) { - for (Map.Entry entry : mapReduceOptions.getExtraOptions().entrySet()) { - ReflectiveMapReduceInvoker.addExtraOption(mapReduceCommand, entry.getKey(), entry.getValue()); - } - } - if (mapReduceOptions.getFinalizeFunction() != null) { - mapReduceCommand.setFinalize(this.replaceWithResourceIfNecessary(mapReduceOptions.getFinalizeFunction())); - } - if (mapReduceOptions.getOutputDatabase() != null) { - mapReduceCommand.setOutputDB(mapReduceOptions.getOutputDatabase()); - } - if (!mapReduceOptions.getScopeVariables().isEmpty()) { - mapReduceCommand.setScope(mapReduceOptions.getScopeVariables()); - } - } - - public Set getCollectionNames() { - return execute(new DbCallback>() { - public Set doInDB(DB db) throws MongoException, DataAccessException { - return db.getCollectionNames(); - } - }); - } - - public DB getDb() { - return mongoDbFactory.getDb(); - } - - protected void maybeEmitEvent(MongoMappingEvent event) { - if (null != eventPublisher) { - eventPublisher.publishEvent(event); - } - } - - /** - * Create the specified collection using the provided options - * - * @param collectionName - * @param collectionOptions - * @return the collection that was created - */ - protected DBCollection doCreateCollection(final String collectionName, final DBObject collectionOptions) { - return execute(new DbCallback() { - public DBCollection doInDB(DB db) throws MongoException, DataAccessException { - DBCollection coll = db.createCollection(collectionName, collectionOptions); - // TODO: Emit a collection created event - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Created collection [{}]", coll.getFullName()); - } - return coll; - } - }); - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. - * The query document is specified as a standard {@link DBObject} and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from. - * @param query the query document that specifies the criteria used to find a record. - * @param fields the document that specifies the fields to be returned. - * @param entityClass the parameterized type of the returned list. - * @return the {@link List} of converted objects. - */ - protected T doFindOne(String collectionName, DBObject query, DBObject fields, Class entityClass) { - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - DBObject mappedQuery = queryMapper.getMappedObject(query, entity); - DBObject mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findOne using query: {} fields: {} for class: {} in collection: {}", serializeToJsonSafely(query), - mappedFields, entityClass, collectionName); - } - - return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields), - new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName), collectionName); - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to a List using the template's converter. The - * query document is specified as a standard DBObject and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from - * @param query the query document that specifies the criteria used to find a record - * @param fields the document that specifies the fields to be returned - * @param entityClass the parameterized type of the returned list. - * @return the List of converted objects. - */ - protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass) { - return doFind(collectionName, query, fields, entityClass, null, - new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName)); - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified type. The object is - * converted from the MongoDB native representation using an instance of {@see MongoConverter}. The query document is - * specified as a standard DBObject and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from. - * @param query the query document that specifies the criteria used to find a record. - * @param fields the document that specifies the fields to be returned. - * @param entityClass the parameterized type of the returned list. - * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply - * limits, skips and so on). - * @return the {@link List} of converted objects. - */ - protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, - CursorPreparer preparer) { - return doFind(collectionName, query, fields, entityClass, preparer, - new ReadDbObjectCallback(mongoConverter, entityClass, collectionName)); - } - - protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, - CursorPreparer preparer, DbObjectCallback objectCallback) { - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - - DBObject mappedFields = queryMapper.getMappedFields(fields, entity); - DBObject mappedQuery = queryMapper.getMappedObject(query, entity); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}", - serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName); - } - - return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer, objectCallback, - collectionName); - } - - protected DBObject convertToDbObject(CollectionOptions collectionOptions) { - DBObject dbo = new BasicDBObject(); - if (collectionOptions != null) { - if (collectionOptions.getCapped() != null) { - dbo.put("capped", collectionOptions.getCapped().booleanValue()); - } - if (collectionOptions.getSize() != null) { - dbo.put("size", collectionOptions.getSize().intValue()); - } - if (collectionOptions.getMaxDocuments() != null) { - dbo.put("max", collectionOptions.getMaxDocuments().intValue()); - } - } - return dbo; - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. - * The first document that matches the query is returned and also removed from the collection in the database. - *

        - * The query document is specified as a standard DBObject and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from - * @param query the query document that specifies the criteria used to find a record - * @param entityClass the parameterized type of the returned list. - * @return the List of converted objects. - */ - protected T doFindAndRemove(String collectionName, DBObject query, DBObject fields, DBObject sort, - Class entityClass) { - - EntityReader readerToUse = this.mongoConverter; - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findAndRemove using query: {} fields: {} sort: {} for class: {} in collection: {}", - serializeToJsonSafely(query), fields, sort, entityClass, collectionName); - } - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - - return executeFindOneInternal(new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort), - new ReadDbObjectCallback(readerToUse, entityClass, collectionName), collectionName); - } - - protected T doFindAndModify(String collectionName, DBObject query, DBObject fields, DBObject sort, - Class entityClass, Update update, FindAndModifyOptions options) { - - EntityReader readerToUse = this.mongoConverter; - - if (options == null) { - options = new FindAndModifyOptions(); - } - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - - increaseVersionForUpdateIfNecessary(entity, update); - - DBObject mappedQuery = queryMapper.getMappedObject(query, entity); - DBObject mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - "findAndModify using query: {} fields: {} sort: {} for class: {} and update: {} " + "in collection: {}", - serializeToJsonSafely(mappedQuery), fields, sort, entityClass, serializeToJsonSafely(mappedUpdate), - collectionName); - } - - return executeFindOneInternal(new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate, options), - new ReadDbObjectCallback(readerToUse, entityClass, collectionName), collectionName); - } - - /** - * Populates the id property of the saved object, if it's not set already. - * - * @param savedObject - * @param id - */ - protected void populateIdIfNecessary(Object savedObject, Object id) { - - if (id == null) { - return; - } - - if (savedObject instanceof BasicDBObject) { - DBObject dbObject = (DBObject) savedObject; - dbObject.put(ID_FIELD, id); - return; - } - - MongoPersistentProperty idProp = getIdPropertyFor(savedObject.getClass()); - - if (idProp == null) { - return; - } - - ConversionService conversionService = mongoConverter.getConversionService(); - MongoPersistentEntity entity = mappingContext.getPersistentEntity(savedObject.getClass()); - PersistentPropertyAccessor accessor = entity.getPropertyAccessor(savedObject); - - if (accessor.getProperty(idProp) != null) { - return; - } - - new ConvertingPropertyAccessor(accessor, conversionService).setProperty(idProp, id); - } - - private DBCollection getAndPrepareCollection(DB db, String collectionName) { - try { - DBCollection collection = db.getCollection(collectionName); - prepareCollection(collection); - return collection; - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } - - /** - * Internal method using callbacks to do queries against the datastore that requires reading a single object from a - * collection of objects. It will take the following steps - *

          - *
        1. Execute the given {@link ConnectionCallback} for a {@link DBObject}.
        2. - *
        3. Apply the given {@link DbObjectCallback} to each of the {@link DBObject}s to obtain the result.
        4. - *
            - * - * @param - * @param collectionCallback the callback to retrieve the {@link DBObject} with - * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type - * @param collectionName the collection to be queried - * @return - */ - private T executeFindOneInternal(CollectionCallback collectionCallback, - DbObjectCallback objectCallback, String collectionName) { - - try { - T result = objectCallback - .doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName))); - return result; - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } - - /** - * Internal method using callback to do queries against the datastore that requires reading a collection of objects. - * It will take the following steps - *
              - *
            1. Execute the given {@link ConnectionCallback} for a {@link DBCursor}.
            2. - *
            3. Prepare that {@link DBCursor} with the given {@link CursorPreparer} (will be skipped if {@link CursorPreparer} - * is {@literal null}
            4. - *
            5. Iterate over the {@link DBCursor} and applies the given {@link DbObjectCallback} to each of the - * {@link DBObject}s collecting the actual result {@link List}.
            6. - *
                - * - * @param - * @param collectionCallback the callback to retrieve the {@link DBCursor} with - * @param preparer the {@link CursorPreparer} to potentially modify the {@link DBCursor} before ireating over it - * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type - * @param collectionName the collection to be queried - * @return - */ - private List executeFindMultiInternal(CollectionCallback collectionCallback, CursorPreparer preparer, - DbObjectCallback objectCallback, String collectionName) { - - try { - - DBCursor cursor = null; - - try { - - cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)); - - if (preparer != null) { - cursor = preparer.prepare(cursor); - } - - List result = new ArrayList(); - - while (cursor.hasNext()) { - DBObject object = cursor.next(); - result.add(objectCallback.doWith(object)); - } - - return result; - - } finally { - - if (cursor != null) { - cursor.close(); - } - } - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } - - private void executeQueryInternal(CollectionCallback collectionCallback, CursorPreparer preparer, - DocumentCallbackHandler callbackHandler, String collectionName) { - - try { - - DBCursor cursor = null; - - try { - cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)); - - if (preparer != null) { - cursor = preparer.prepare(cursor); - } - - while (cursor.hasNext()) { - DBObject dbobject = cursor.next(); - callbackHandler.processDocument(dbobject); - } - - } finally { - if (cursor != null) { - cursor.close(); - } - } - - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } - - private MongoPersistentEntity getPersistentEntity(Class type) { - return type == null ? null : mappingContext.getPersistentEntity(type); - } - - private MongoPersistentProperty getIdPropertyFor(Class type) { - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(type); - return persistentEntity == null ? null : persistentEntity.getIdProperty(); - } - - private String determineEntityCollectionName(T obj) { - if (null != obj) { - return determineCollectionName(obj.getClass()); - } - - return null; - } - - String determineCollectionName(Class entityClass) { - - if (entityClass == null) { - throw new InvalidDataAccessApiUsageException( - "No class parameter provided, entity collection can't be determined!"); - } - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - if (entity == null) { - throw new InvalidDataAccessApiUsageException( - "No Persistent Entity information found for the class " + entityClass.getName()); - } - return entity.getCollection(); - } - - /** - * Handles {@link WriteResult} errors based on the configured {@link WriteResultChecking}. - * - * @param writeResult - * @param query - * @param operation - */ - protected void handleAnyWriteResultErrors(WriteResult writeResult, DBObject query, MongoActionOperation operation) { - - if (writeResultChecking == WriteResultChecking.NONE) { - return; - } - - String error = ReflectiveWriteResultInvoker.getError(writeResult); - - if (error == null) { - return; - } - - String message; - - switch (operation) { - - case INSERT: - case SAVE: - message = String.format("Insert/Save for %s failed: %s", query, error); - break; - case INSERT_LIST: - message = String.format("Insert list failed: %s", error); - break; - default: - message = String.format("Execution of %s%s failed: %s", operation, - query == null ? "" : " using query " + query.toString(), error); - } - - if (writeResultChecking == WriteResultChecking.EXCEPTION) { - throw new MongoDataIntegrityViolationException(message, writeResult, operation); - } else { - LOGGER.error(message); - return; - } - } - - /** - * Inspects the given {@link CommandResult} for erros and potentially throws an - * {@link InvalidDataAccessApiUsageException} for that error. - * - * @param result must not be {@literal null}. - * @param source must not be {@literal null}. - */ - private void handleCommandError(CommandResult result, DBObject source) { - - try { - result.throwOnError(); - } catch (MongoException ex) { - - String error = result.getErrorMessage(); - error = error == null ? "NO MESSAGE" : error; - - throw new InvalidDataAccessApiUsageException( - "Command execution failed: Error [" + error + "], Command = " + source, ex); - } - } - - private static final MongoConverter getDefaultMongoConverter(MongoDbFactory factory) { - - DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory); - MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext()); - converter.afterPropertiesSet(); - return converter; - } - - private DBObject getMappedSortObject(Query query, Class type) { - - if (query == null || query.getSortObject() == null) { - return null; - } - - return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type)); - } - - /** - * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original - * exception if the conversation failed. Thus allows safe re-throwing of the return value. - * - * @param ex the exception to translate - * @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation - * @return - */ - private static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex, - PersistenceExceptionTranslator exceptionTranslator) { - RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex); - return resolved == null ? ex : resolved; - } - - /** - * Returns all identifiers for the given documents. Will augment the given identifiers and fill in only the ones that - * are {@literal null} currently. This would've been better solved in {@link #insertDBObjectList(String, List)} - * directly but would require a signature change of that method. - * - * @param ids - * @param documents - * @return TODO: Remove for 2.0 and change method signature of {@link #insertDBObjectList(String, List)}. - */ - private static List consolidateIdentifiers(List ids, List documents) { - - List result = new ArrayList(ids.size()); - - for (int i = 0; i < ids.size(); i++) { - - ObjectId objectId = ids.get(i); - result.add(objectId == null ? documents.get(i).get(ID_FIELD) : objectId); - } - - return result; - } - - // Callback implementations - - /** - * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification - * {@link DBObject} and executes that against the {@link DBCollection}. - * - * @author Oliver Gierke - * @author Thomas Risberg - */ - private static class FindOneCallback implements CollectionCallback { - - private final DBObject query; - private final DBObject fields; - - public FindOneCallback(DBObject query, DBObject fields) { - this.query = query; - this.fields = fields; - } - - public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { - if (fields == null) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findOne using query: {} in db.collection: {}", serializeToJsonSafely(query), - collection.getFullName()); - } - return collection.findOne(query); - } else { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query), fields, - collection.getFullName()); - } - return collection.findOne(query, fields); - } - } - } - - /** - * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification - * {@link DBObject} and executes that against the {@link DBCollection}. - * - * @author Oliver Gierke - * @author Thomas Risberg - */ - private static class FindCallback implements CollectionCallback { - - private final DBObject query; - private final DBObject fields; - - public FindCallback(DBObject query) { - this(query, null); - } - - public FindCallback(DBObject query, DBObject fields) { - this.query = query == null ? new BasicDBObject() : query; - this.fields = fields; - } - - public DBCursor doInCollection(DBCollection collection) throws MongoException, DataAccessException { - - if (fields == null || fields.toMap().isEmpty()) { - return collection.find(query); - } else { - return collection.find(query, fields); - } - } - } - - /** - * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification - * {@link DBObject} and executes that against the {@link DBCollection}. - * - * @author Thomas Risberg - */ - private static class FindAndRemoveCallback implements CollectionCallback { - - private final DBObject query; - private final DBObject fields; - private final DBObject sort; - - public FindAndRemoveCallback(DBObject query, DBObject fields, DBObject sort) { - this.query = query; - this.fields = fields; - this.sort = sort; - } - - public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { - return collection.findAndModify(query, fields, sort, true, null, false, false); - } - } - - private static class FindAndModifyCallback implements CollectionCallback { - - private final DBObject query; - private final DBObject fields; - private final DBObject sort; - private final DBObject update; - private final FindAndModifyOptions options; - - public FindAndModifyCallback(DBObject query, DBObject fields, DBObject sort, DBObject update, - FindAndModifyOptions options) { - this.query = query; - this.fields = fields; - this.sort = sort; - this.update = update; - this.options = options; - } - - public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { - return collection.findAndModify(query, fields, sort, options.isRemove(), update, options.isReturnNew(), - options.isUpsert()); - } - } - - /** - * Simple internal callback to allow operations on a {@link DBObject}. - * - * @author Oliver Gierke - * @author Thomas Darimont - */ - - interface DbObjectCallback { - - T doWith(DBObject object); - } - - /** - * Simple {@link DbObjectCallback} that will transform {@link DBObject} into the given target type using the given - * {@link MongoReader}. - * - * @author Oliver Gierke - * @author Christoph Strobl - */ - private class ReadDbObjectCallback implements DbObjectCallback { - - private final EntityReader reader; - private final Class type; - private final String collectionName; - - public ReadDbObjectCallback(EntityReader reader, Class type, String collectionName) { - - Assert.notNull(reader, "EntityReader must not be null!"); - Assert.notNull(type, "Entity type must not be null!"); - - this.reader = reader; - this.type = type; - this.collectionName = collectionName; - } - - public T doWith(DBObject object) { - if (null != object) { - maybeEmitEvent(new AfterLoadEvent(object, type, collectionName)); - } - T source = reader.read(type, object); - if (null != source) { - maybeEmitEvent(new AfterConvertEvent(object, source, collectionName)); - } - return source; - } - } - - class UnwrapAndReadDbObjectCallback extends ReadDbObjectCallback { - - public UnwrapAndReadDbObjectCallback(EntityReader reader, Class type, - String collectionName) { - super(reader, type, collectionName); - } - - @Override - public T doWith(DBObject object) { - - Object idField = object.get(Fields.UNDERSCORE_ID); - - if (!(idField instanceof DBObject)) { - return super.doWith(object); - } - - DBObject toMap = new BasicDBObject(); - DBObject nested = (DBObject) idField; - toMap.putAll(nested); - - for (String key : object.keySet()) { - if (!Fields.UNDERSCORE_ID.equals(key)) { - toMap.put(key, object.get(key)); - } - } - - return super.doWith(toMap); - } - } - - class QueryCursorPreparer implements CursorPreparer { - - private final Query query; - private final Class type; - - public QueryCursorPreparer(Query query, Class type) { - - this.query = query; - this.type = type; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.CursorPreparer#prepare(com.mongodb.DBCursor) - */ - public DBCursor prepare(DBCursor cursor) { - - if (query == null) { - return cursor; - } - - if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null - && !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) { - return cursor; - } - - DBCursor cursorToUse = cursor.copy(); - - try { - - if (query.getSkip() > 0) { - cursorToUse = cursorToUse.skip(query.getSkip()); - } - - if (query.getLimit() > 0) { - cursorToUse = cursorToUse.limit(query.getLimit()); - } - - if (query.getSortObject() != null) { - DBObject sortDbo = type != null ? getMappedSortObject(query, type) : query.getSortObject(); - cursorToUse = cursorToUse.sort(sortDbo); - } - - if (StringUtils.hasText(query.getHint())) { - cursorToUse = cursorToUse.hint(query.getHint()); - } - - if (query.getMeta().hasValues()) { - - for (Entry entry : query.getMeta().values()) { - cursorToUse = cursorToUse.addSpecial(entry.getKey(), entry.getValue()); - } - - for (Meta.CursorOption option : query.getMeta().getFlags()) { - - switch (option) { - case EXHAUST: - cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_EXHAUST); - break; - case NO_TIMEOUT: - cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_NOTIMEOUT); - break; - case PARTIAL: - cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_PARTIAL); - break; - case SLAVE_OK: - cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_SLAVEOK); - break; - default: - throw new IllegalArgumentException(String.format("%s is no supported flag.", option)); - } - } - } - - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - - return cursorToUse; - } - } - - /** - * {@link DbObjectCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to - * a delegate and creates a {@link GeoResult} from the result. - * - * @author Oliver Gierke - */ - static class GeoNearResultDbObjectCallback implements DbObjectCallback> { - - private final DbObjectCallback delegate; - private final Metric metric; - - /** - * Creates a new {@link GeoNearResultDbObjectCallback} using the given {@link DbObjectCallback} delegate for - * {@link GeoResult} content unmarshalling. - * - * @param delegate must not be {@literal null}. - */ - public GeoNearResultDbObjectCallback(DbObjectCallback delegate, Metric metric) { - - Assert.notNull(delegate, "DocumentCallback must not be null!"); - - this.delegate = delegate; - this.metric = metric; - } - - public GeoResult doWith(DBObject object) { - - double distance = ((Double) object.get("dis")).doubleValue(); - DBObject content = (DBObject) object.get("obj"); - - T doWith = delegate.doWith(content); - - return new GeoResult(doWith, new Distance(distance, metric)); - } - } - - /** - * A {@link CloseableIterator} that is backed by a MongoDB {@link Cursor}. - * - * @since 1.7 - * @author Thomas Darimont - */ - static class CloseableIterableCursorAdapter implements CloseableIterator { - - private volatile Cursor cursor; - private PersistenceExceptionTranslator exceptionTranslator; - private DbObjectCallback objectReadCallback; - - /** - * Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link Cursor}. - * - * @param cursor - * @param exceptionTranslator - * @param objectReadCallback - */ - public CloseableIterableCursorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator, - DbObjectCallback objectReadCallback) { - - this.cursor = cursor; - this.exceptionTranslator = exceptionTranslator; - this.objectReadCallback = objectReadCallback; - } - - @Override - public boolean hasNext() { - - if (cursor == null) { - return false; - } - - try { - return cursor.hasNext(); - } catch (RuntimeException ex) { - throw potentiallyConvertRuntimeException(ex, exceptionTranslator); - } - } - - @Override - public T next() { - - if (cursor == null) { - return null; - } - - try { - DBObject item = cursor.next(); - T converted = objectReadCallback.doWith(item); - return converted; - } catch (RuntimeException ex) { - throw potentiallyConvertRuntimeException(ex, exceptionTranslator); - } - } - - @Override - public void close() { - - Cursor c = cursor; - try { - c.close(); - } catch (RuntimeException ex) { - throw potentiallyConvertRuntimeException(ex, exceptionTranslator); - } finally { - cursor = null; - exceptionTranslator = null; - objectReadCallback = null; - } - } - } +/* + Query query = new BasicQuery(command); + return stream(query, outputType); +*/ + } + + + /** + * Returns the potentially mapped results of the given {@commandResult} contained some. + * + * @param outputType + * @param commandResult + * @return + */ + private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, + String collectionName) { + + @SuppressWarnings("unchecked") + Iterable resultSet = (Iterable) commandResult.get("result"); + if (resultSet == null) { + return Collections.emptyList(); + } + + DbObjectCallback callback = new UnwrapAndReadDbObjectCallback(mongoConverter, outputType, collectionName); + + List mappedResults = new ArrayList(); + for (DBObject dbObject : resultSet) { + mappedResults.add(callback.doWith(dbObject)); + } + + return mappedResults; + } + + protected String replaceWithResourceIfNecessary(String function) { + + String func = function; + + if (this.resourceLoader != null && ResourceUtils.isUrl(function)) { + + Resource functionResource = resourceLoader.getResource(func); + + if (!functionResource.exists()) { + throw new InvalidDataAccessApiUsageException(String.format("Resource %s not found!", function)); + } + + Scanner scanner = null; + + try { + scanner = new Scanner(functionResource.getInputStream()); + return scanner.useDelimiter("\\A").next(); + } catch (IOException e) { + throw new InvalidDataAccessApiUsageException(String.format("Cannot read map-reduce file %s!", function), e); + } finally { + if (scanner != null) { + scanner.close(); + } + } + } + + return func; + } + + private void copyMapReduceOptionsToCommand(Query query, MapReduceOptions mapReduceOptions, + MapReduceCommand mapReduceCommand) { + + if (query != null) { + if (query.getSkip() != 0 || query.getFieldsObject() != null) { + throw new InvalidDataAccessApiUsageException( + "Can not use skip or field specification with map reduce operations"); + } + if (query.getLimit() > 0 && mapReduceOptions.getLimit() == null) { + mapReduceCommand.setLimit(query.getLimit()); + } + if (query.getSortObject() != null) { + mapReduceCommand.setSort(queryMapper.getMappedObject(query.getSortObject(), null)); + } + } + + if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit().intValue() > 0) { + mapReduceCommand.setLimit(mapReduceOptions.getLimit()); + } + + if (mapReduceOptions.getJavaScriptMode() != null) { + mapReduceCommand.setJsMode(true); + } + if (!mapReduceOptions.getExtraOptions().isEmpty()) { + for (Map.Entry entry : mapReduceOptions.getExtraOptions().entrySet()) { + ReflectiveMapReduceInvoker.addExtraOption(mapReduceCommand, entry.getKey(), entry.getValue()); + } + } + if (mapReduceOptions.getFinalizeFunction() != null) { + mapReduceCommand.setFinalize(this.replaceWithResourceIfNecessary(mapReduceOptions.getFinalizeFunction())); + } + if (mapReduceOptions.getOutputDatabase() != null) { + mapReduceCommand.setOutputDB(mapReduceOptions.getOutputDatabase()); + } + if (!mapReduceOptions.getScopeVariables().isEmpty()) { + mapReduceCommand.setScope(mapReduceOptions.getScopeVariables()); + } + } + + public Set getCollectionNames() { + return execute(new DbCallback>() { + public Set doInDB(DB db) throws MongoException, DataAccessException { + return db.getCollectionNames(); + } + }); + } + + public DB getDb() { + return mongoDbFactory.getDb(); + } + + protected void maybeEmitEvent(MongoMappingEvent event) { + if (null != eventPublisher) { + eventPublisher.publishEvent(event); + } + } + + /** + * Create the specified collection using the provided options + * + * @param collectionName + * @param collectionOptions + * @return the collection that was created + */ + protected DBCollection doCreateCollection(final String collectionName, final DBObject collectionOptions) { + return execute(new DbCallback() { + public DBCollection doInDB(DB db) throws MongoException, DataAccessException { + DBCollection coll = db.createCollection(collectionName, collectionOptions); + // TODO: Emit a collection created event + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Created collection [{}]", coll.getFullName()); + } + return coll; + } + }); + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. + * The query document is specified as a standard {@link DBObject} and so is the fields specification. + * + * @param collectionName name of the collection to retrieve the objects from. + * @param query the query document that specifies the criteria used to find a record. + * @param fields the document that specifies the fields to be returned. + * @param entityClass the parameterized type of the returned list. + * @return the {@link List} of converted objects. + */ + protected T doFindOne(String collectionName, DBObject query, DBObject fields, Class entityClass) { + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + DBObject mappedQuery = queryMapper.getMappedObject(query, entity); + DBObject mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findOne using query: {} fields: {} for class: {} in collection: {}", serializeToJsonSafely(query), + mappedFields, entityClass, collectionName); + } + + return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields), + new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName), collectionName); + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to a List using the template's converter. The + * query document is specified as a standard DBObject and so is the fields specification. + * + * @param collectionName name of the collection to retrieve the objects from + * @param query the query document that specifies the criteria used to find a record + * @param fields the document that specifies the fields to be returned + * @param entityClass the parameterized type of the returned list. + * @return the List of converted objects. + */ + protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass) { + return doFind(collectionName, query, fields, entityClass, null, + new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName)); + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified type. The object is + * converted from the MongoDB native representation using an instance of {@see MongoConverter}. The query document is + * specified as a standard DBObject and so is the fields specification. + * + * @param collectionName name of the collection to retrieve the objects from. + * @param query the query document that specifies the criteria used to find a record. + * @param fields the document that specifies the fields to be returned. + * @param entityClass the parameterized type of the returned list. + * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply + * limits, skips and so on). + * @return the {@link List} of converted objects. + */ + protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, + CursorPreparer preparer) { + return doFind(collectionName, query, fields, entityClass, preparer, + new ReadDbObjectCallback(mongoConverter, entityClass, collectionName)); + } + + protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, + CursorPreparer preparer, DbObjectCallback objectCallback) { + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + + DBObject mappedFields = queryMapper.getMappedFields(fields, entity); + DBObject mappedQuery = queryMapper.getMappedObject(query, entity); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}", + serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName); + } + + return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer, objectCallback, + collectionName); + } + + protected DBObject convertToDbObject(CollectionOptions collectionOptions) { + DBObject dbo = new BasicDBObject(); + if (collectionOptions != null) { + if (collectionOptions.getCapped() != null) { + dbo.put("capped", collectionOptions.getCapped().booleanValue()); + } + if (collectionOptions.getSize() != null) { + dbo.put("size", collectionOptions.getSize().intValue()); + } + if (collectionOptions.getMaxDocuments() != null) { + dbo.put("max", collectionOptions.getMaxDocuments().intValue()); + } + } + return dbo; + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. + * The first document that matches the query is returned and also removed from the collection in the database. + *

                + * The query document is specified as a standard DBObject and so is the fields specification. + * + * @param collectionName name of the collection to retrieve the objects from + * @param query the query document that specifies the criteria used to find a record + * @param entityClass the parameterized type of the returned list. + * @return the List of converted objects. + */ + protected T doFindAndRemove(String collectionName, DBObject query, DBObject fields, DBObject sort, + Class entityClass) { + + EntityReader readerToUse = this.mongoConverter; + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findAndRemove using query: {} fields: {} sort: {} for class: {} in collection: {}", + serializeToJsonSafely(query), fields, sort, entityClass, collectionName); + } + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + + return executeFindOneInternal(new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort), + new ReadDbObjectCallback(readerToUse, entityClass, collectionName), collectionName); + } + + protected T doFindAndModify(String collectionName, DBObject query, DBObject fields, DBObject sort, + Class entityClass, Update update, FindAndModifyOptions options) { + + EntityReader readerToUse = this.mongoConverter; + + if (options == null) { + options = new FindAndModifyOptions(); + } + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + + increaseVersionForUpdateIfNecessary(entity, update); + + DBObject mappedQuery = queryMapper.getMappedObject(query, entity); + DBObject mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "findAndModify using query: {} fields: {} sort: {} for class: {} and update: {} " + "in collection: {}", + serializeToJsonSafely(mappedQuery), fields, sort, entityClass, serializeToJsonSafely(mappedUpdate), + collectionName); + } + + return executeFindOneInternal(new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate, options), + new ReadDbObjectCallback(readerToUse, entityClass, collectionName), collectionName); + } + + /** + * Populates the id property of the saved object, if it's not set already. + * + * @param savedObject + * @param id + */ + protected void populateIdIfNecessary(Object savedObject, Object id) { + + if (id == null) { + return; + } + + if (savedObject instanceof BasicDBObject) { + DBObject dbObject = (DBObject) savedObject; + dbObject.put(ID_FIELD, id); + return; + } + + MongoPersistentProperty idProp = getIdPropertyFor(savedObject.getClass()); + + if (idProp == null) { + return; + } + + ConversionService conversionService = mongoConverter.getConversionService(); + MongoPersistentEntity entity = mappingContext.getPersistentEntity(savedObject.getClass()); + PersistentPropertyAccessor accessor = entity.getPropertyAccessor(savedObject); + + if (accessor.getProperty(idProp) != null) { + return; + } + + new ConvertingPropertyAccessor(accessor, conversionService).setProperty(idProp, id); + } + + private DBCollection getAndPrepareCollection(DB db, String collectionName) { + try { + DBCollection collection = db.getCollection(collectionName); + prepareCollection(collection); + return collection; + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + /** + * Internal method using callbacks to do queries against the datastore that requires reading a single object from a + * collection of objects. It will take the following steps + *

                  + *
                1. Execute the given {@link ConnectionCallback} for a {@link DBObject}.
                2. + *
                3. Apply the given {@link DbObjectCallback} to each of the {@link DBObject}s to obtain the result.
                4. + *
                    + * + * @param + * @param collectionCallback the callback to retrieve the {@link DBObject} with + * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type + * @param collectionName the collection to be queried + * @return + */ + private T executeFindOneInternal(CollectionCallback collectionCallback, + DbObjectCallback objectCallback, String collectionName) { + + try { + T result = objectCallback + .doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName))); + return result; + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + /** + * Internal method using callback to do queries against the datastore that requires reading a collection of objects. + * It will take the following steps + *
                      + *
                    1. Execute the given {@link ConnectionCallback} for a {@link DBCursor}.
                    2. + *
                    3. Prepare that {@link DBCursor} with the given {@link CursorPreparer} (will be skipped if {@link CursorPreparer} + * is {@literal null}
                    4. + *
                    5. Iterate over the {@link DBCursor} and applies the given {@link DbObjectCallback} to each of the + * {@link DBObject}s collecting the actual result {@link List}.
                    6. + *
                        + * + * @param + * @param collectionCallback the callback to retrieve the {@link DBCursor} with + * @param preparer the {@link CursorPreparer} to potentially modify the {@link DBCursor} before ireating over it + * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type + * @param collectionName the collection to be queried + * @return + */ + private List executeFindMultiInternal(CollectionCallback collectionCallback, CursorPreparer preparer, + DbObjectCallback objectCallback, String collectionName) { + + try { + + DBCursor cursor = null; + + try { + + cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)); + + if (preparer != null) { + cursor = preparer.prepare(cursor); + } + + List result = new ArrayList(); + + while (cursor.hasNext()) { + DBObject object = cursor.next(); + result.add(objectCallback.doWith(object)); + } + + return result; + + } finally { + + if (cursor != null) { + cursor.close(); + } + } + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + private void executeQueryInternal(CollectionCallback collectionCallback, CursorPreparer preparer, + DocumentCallbackHandler callbackHandler, String collectionName) { + + try { + + DBCursor cursor = null; + + try { + cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)); + + if (preparer != null) { + cursor = preparer.prepare(cursor); + } + + while (cursor.hasNext()) { + DBObject dbobject = cursor.next(); + callbackHandler.processDocument(dbobject); + } + + } finally { + if (cursor != null) { + cursor.close(); + } + } + + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + private MongoPersistentEntity getPersistentEntity(Class type) { + return type == null ? null : mappingContext.getPersistentEntity(type); + } + + private MongoPersistentProperty getIdPropertyFor(Class type) { + MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(type); + return persistentEntity == null ? null : persistentEntity.getIdProperty(); + } + + private String determineEntityCollectionName(T obj) { + if (null != obj) { + return determineCollectionName(obj.getClass()); + } + + return null; + } + + String determineCollectionName(Class entityClass) { + + if (entityClass == null) { + throw new InvalidDataAccessApiUsageException( + "No class parameter provided, entity collection can't be determined!"); + } + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + if (entity == null) { + throw new InvalidDataAccessApiUsageException( + "No Persistent Entity information found for the class " + entityClass.getName()); + } + return entity.getCollection(); + } + + /** + * Handles {@link WriteResult} errors based on the configured {@link WriteResultChecking}. + * + * @param writeResult + * @param query + * @param operation + */ + protected void handleAnyWriteResultErrors(WriteResult writeResult, DBObject query, MongoActionOperation operation) { + + if (writeResultChecking == WriteResultChecking.NONE) { + return; + } + + String error = ReflectiveWriteResultInvoker.getError(writeResult); + + if (error == null) { + return; + } + + String message; + + switch (operation) { + + case INSERT: + case SAVE: + message = String.format("Insert/Save for %s failed: %s", query, error); + break; + case INSERT_LIST: + message = String.format("Insert list failed: %s", error); + break; + default: + message = String.format("Execution of %s%s failed: %s", operation, + query == null ? "" : " using query " + query.toString(), error); + } + + if (writeResultChecking == WriteResultChecking.EXCEPTION) { + throw new MongoDataIntegrityViolationException(message, writeResult, operation); + } else { + LOGGER.error(message); + return; + } + } + + /** + * Inspects the given {@link CommandResult} for erros and potentially throws an + * {@link InvalidDataAccessApiUsageException} for that error. + * + * @param result must not be {@literal null}. + * @param source must not be {@literal null}. + */ + private void handleCommandError(CommandResult result, DBObject source) { + + try { + result.throwOnError(); + } catch (MongoException ex) { + + String error = result.getErrorMessage(); + error = error == null ? "NO MESSAGE" : error; + + throw new InvalidDataAccessApiUsageException( + "Command execution failed: Error [" + error + "], Command = " + source, ex); + } + } + + private static final MongoConverter getDefaultMongoConverter(MongoDbFactory factory) { + + DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory); + MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext()); + converter.afterPropertiesSet(); + return converter; + } + + private DBObject getMappedSortObject(Query query, Class type) { + + if (query == null || query.getSortObject() == null) { + return null; + } + + return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type)); + } + + /** + * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original + * exception if the conversation failed. Thus allows safe re-throwing of the return value. + * + * @param ex the exception to translate + * @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation + * @return + */ + private static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex, + PersistenceExceptionTranslator exceptionTranslator) { + RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex); + return resolved == null ? ex : resolved; + } + + /** + * Returns all identifiers for the given documents. Will augment the given identifiers and fill in only the ones that + * are {@literal null} currently. This would've been better solved in {@link #insertDBObjectList(String, List)} + * directly but would require a signature change of that method. + * + * @param ids + * @param documents + * @return TODO: Remove for 2.0 and change method signature of {@link #insertDBObjectList(String, List)}. + */ + private static List consolidateIdentifiers(List ids, List documents) { + + List result = new ArrayList(ids.size()); + + for (int i = 0; i < ids.size(); i++) { + + ObjectId objectId = ids.get(i); + result.add(objectId == null ? documents.get(i).get(ID_FIELD) : objectId); + } + + return result; + } + + // Callback implementations + + /** + * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification + * {@link DBObject} and executes that against the {@link DBCollection}. + * + * @author Oliver Gierke + * @author Thomas Risberg + */ + private static class FindOneCallback implements CollectionCallback { + + private final DBObject query; + private final DBObject fields; + + public FindOneCallback(DBObject query, DBObject fields) { + this.query = query; + this.fields = fields; + } + + public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { + if (fields == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findOne using query: {} in db.collection: {}", serializeToJsonSafely(query), + collection.getFullName()); + } + return collection.findOne(query); + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query), fields, + collection.getFullName()); + } + return collection.findOne(query, fields); + } + } + } + + /** + * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification + * {@link DBObject} and executes that against the {@link DBCollection}. + * + * @author Oliver Gierke + * @author Thomas Risberg + */ + private static class FindCallback implements CollectionCallback { + + private final DBObject query; + private final DBObject fields; + + public FindCallback(DBObject query) { + this(query, null); + } + + public FindCallback(DBObject query, DBObject fields) { + this.query = query == null ? new BasicDBObject() : query; + this.fields = fields; + } + + public DBCursor doInCollection(DBCollection collection) throws MongoException, DataAccessException { + + if (fields == null || fields.toMap().isEmpty()) { + return collection.find(query); + } else { + return collection.find(query, fields); + } + } + } + + /** + * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification + * {@link DBObject} and executes that against the {@link DBCollection}. + * + * @author Thomas Risberg + */ + private static class FindAndRemoveCallback implements CollectionCallback { + + private final DBObject query; + private final DBObject fields; + private final DBObject sort; + + public FindAndRemoveCallback(DBObject query, DBObject fields, DBObject sort) { + this.query = query; + this.fields = fields; + this.sort = sort; + } + + public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { + return collection.findAndModify(query, fields, sort, true, null, false, false); + } + } + + private static class FindAndModifyCallback implements CollectionCallback { + + private final DBObject query; + private final DBObject fields; + private final DBObject sort; + private final DBObject update; + private final FindAndModifyOptions options; + + public FindAndModifyCallback(DBObject query, DBObject fields, DBObject sort, DBObject update, + FindAndModifyOptions options) { + this.query = query; + this.fields = fields; + this.sort = sort; + this.update = update; + this.options = options; + } + + public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { + return collection.findAndModify(query, fields, sort, options.isRemove(), update, options.isReturnNew(), + options.isUpsert()); + } + } + + /** + * Simple internal callback to allow operations on a {@link DBObject}. + * + * @author Oliver Gierke + * @author Thomas Darimont + */ + + interface DbObjectCallback { + + T doWith(DBObject object); + } + + /** + * Simple {@link DbObjectCallback} that will transform {@link DBObject} into the given target type using the given + * {@link MongoReader}. + * + * @author Oliver Gierke + * @author Christoph Strobl + */ + private class ReadDbObjectCallback implements DbObjectCallback { + + private final EntityReader reader; + private final Class type; + private final String collectionName; + + public ReadDbObjectCallback(EntityReader reader, Class type, String collectionName) { + + Assert.notNull(reader, "EntityReader must not be null!"); + Assert.notNull(type, "Entity type must not be null!"); + + this.reader = reader; + this.type = type; + this.collectionName = collectionName; + } + + public T doWith(DBObject object) { + if (null != object) { + maybeEmitEvent(new AfterLoadEvent(object, type, collectionName)); + } + T source = reader.read(type, object); + if (null != source) { + maybeEmitEvent(new AfterConvertEvent(object, source, collectionName)); + } + return source; + } + } + + class UnwrapAndReadDbObjectCallback extends ReadDbObjectCallback { + + public UnwrapAndReadDbObjectCallback(EntityReader reader, Class type, + String collectionName) { + super(reader, type, collectionName); + } + + @Override + public T doWith(DBObject object) { + + Object idField = object.get(Fields.UNDERSCORE_ID); + + if (!(idField instanceof DBObject)) { + return super.doWith(object); + } + + DBObject toMap = new BasicDBObject(); + DBObject nested = (DBObject) idField; + toMap.putAll(nested); + + for (String key : object.keySet()) { + if (!Fields.UNDERSCORE_ID.equals(key)) { + toMap.put(key, object.get(key)); + } + } + + return super.doWith(toMap); + } + } + + class QueryCursorPreparer implements CursorPreparer { + + private final Query query; + private final Class type; + + public QueryCursorPreparer(Query query, Class type) { + + this.query = query; + this.type = type; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.CursorPreparer#prepare(com.mongodb.DBCursor) + */ + public DBCursor prepare(DBCursor cursor) { + + if (query == null) { + return cursor; + } + + if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null + && !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) { + return cursor; + } + + DBCursor cursorToUse = cursor.copy(); + + try { + + if (query.getSkip() > 0) { + cursorToUse = cursorToUse.skip(query.getSkip()); + } + + if (query.getLimit() > 0) { + cursorToUse = cursorToUse.limit(query.getLimit()); + } + + if (query.getSortObject() != null) { + DBObject sortDbo = type != null ? getMappedSortObject(query, type) : query.getSortObject(); + cursorToUse = cursorToUse.sort(sortDbo); + } + + if (StringUtils.hasText(query.getHint())) { + cursorToUse = cursorToUse.hint(query.getHint()); + } + + if (query.getMeta().hasValues()) { + + for (Entry entry : query.getMeta().values()) { + cursorToUse = cursorToUse.addSpecial(entry.getKey(), entry.getValue()); + } + + for (Meta.CursorOption option : query.getMeta().getFlags()) { + + switch (option) { + case EXHAUST: + cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_EXHAUST); + break; + case NO_TIMEOUT: + cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_NOTIMEOUT); + break; + case PARTIAL: + cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_PARTIAL); + break; + case SLAVE_OK: + cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_SLAVEOK); + break; + default: + throw new IllegalArgumentException(String.format("%s is no supported flag.", option)); + } + } + } + + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + + return cursorToUse; + } + } + + /** + * {@link DbObjectCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to + * a delegate and creates a {@link GeoResult} from the result. + * + * @author Oliver Gierke + */ + static class GeoNearResultDbObjectCallback implements DbObjectCallback> { + + private final DbObjectCallback delegate; + private final Metric metric; + + /** + * Creates a new {@link GeoNearResultDbObjectCallback} using the given {@link DbObjectCallback} delegate for + * {@link GeoResult} content unmarshalling. + * + * @param delegate must not be {@literal null}. + */ + public GeoNearResultDbObjectCallback(DbObjectCallback delegate, Metric metric) { + + Assert.notNull(delegate, "DocumentCallback must not be null!"); + + this.delegate = delegate; + this.metric = metric; + } + + public GeoResult doWith(DBObject object) { + + double distance = ((Double) object.get("dis")).doubleValue(); + DBObject content = (DBObject) object.get("obj"); + + T doWith = delegate.doWith(content); + + return new GeoResult(doWith, new Distance(distance, metric)); + } + } + + /** + * A {@link CloseableIterator} that is backed by a MongoDB {@link Cursor}. + * + * @author Thomas Darimont + * @since 1.7 + */ + static class CloseableIterableCursorAdapter implements CloseableIterator { + + private volatile Cursor cursor; + private PersistenceExceptionTranslator exceptionTranslator; + private DbObjectCallback objectReadCallback; + + /** + * Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link Cursor}. + * + * @param cursor + * @param exceptionTranslator + * @param objectReadCallback + */ + public CloseableIterableCursorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator, + DbObjectCallback objectReadCallback) { + + this.cursor = cursor; + this.exceptionTranslator = exceptionTranslator; + this.objectReadCallback = objectReadCallback; + } + + @Override + public boolean hasNext() { + + if (cursor == null) { + return false; + } + + try { + return cursor.hasNext(); + } catch (RuntimeException ex) { + throw potentiallyConvertRuntimeException(ex, exceptionTranslator); + } + } + + @Override + public T next() { + + if (cursor == null) { + return null; + } + + try { + DBObject item = cursor.next(); + T converted = objectReadCallback.doWith(item); + return converted; + } catch (RuntimeException ex) { + throw potentiallyConvertRuntimeException(ex, exceptionTranslator); + } + } + + @Override + public void close() { + + Cursor c = cursor; + try { + c.close(); + } catch (RuntimeException ex) { + throw potentiallyConvertRuntimeException(ex, exceptionTranslator); + } finally { + cursor = null; + exceptionTranslator = null; + objectReadCallback = null; + } + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java index 05e0791f98..901c926d21 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java @@ -31,9 +31,9 @@ */ public class AggregationOptions { - private static final String CURSOR = "cursor"; - private static final String EXPLAIN = "explain"; - private static final String ALLOW_DISK_USE = "allowDiskUse"; + public static final String CURSOR = "cursor"; + public static final String EXPLAIN = "explain"; + public static final String ALLOW_DISK_USE = "allowDiskUse"; private final boolean allowDiskUse; private final boolean explain; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 9b97663705..73077994dc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -66,6 +66,7 @@ import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.Person; +import org.springframework.data.util.CloseableIterator; import org.springframework.data.util.Version; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -93,255 +94,343 @@ @ContextConfiguration("classpath:infrastructure.xml") public class AggregationTests { - private static final String INPUT_COLLECTION = "aggregation_test_collection"; - private static final Logger LOGGER = LoggerFactory.getLogger(AggregationTests.class); - private static final Version TWO_DOT_FOUR = new Version(2, 4); - private static final Version TWO_DOT_SIX = new Version(2, 6); - private static final Version THREE_DOT_TWO = new Version(3, 2); - private static final Version THREE_DOT_FOUR = new Version(3, 4); - - private static boolean initialized = false; - - @Autowired MongoTemplate mongoTemplate; - - @Rule public ExpectedException exception = ExpectedException.none(); - private static Version mongoVersion; - - @Before - public void setUp() { - - queryMongoVersionIfNecessary(); - cleanDb(); - initSampleDataIfNecessary(); - } - - private void queryMongoVersionIfNecessary() { - - if (mongoVersion == null) { - CommandResult result = mongoTemplate.executeCommand("{ buildInfo: 1 }"); - mongoVersion = Version.parse(result.get("version").toString()); - } - } - - @After - public void cleanUp() { - cleanDb(); - } - - private void cleanDb() { - mongoTemplate.dropCollection(INPUT_COLLECTION); - mongoTemplate.dropCollection(Product.class); - mongoTemplate.dropCollection(UserWithLikes.class); - mongoTemplate.dropCollection(DATAMONGO753.class); - mongoTemplate.dropCollection(Data.class); - mongoTemplate.dropCollection(DATAMONGO788.class); - mongoTemplate.dropCollection(User.class); - mongoTemplate.dropCollection(Person.class); - mongoTemplate.dropCollection(Reservation.class); - mongoTemplate.dropCollection(Venue.class); - mongoTemplate.dropCollection(MeterData.class); - mongoTemplate.dropCollection(LineItem.class); - mongoTemplate.dropCollection(InventoryItem.class); - mongoTemplate.dropCollection(Sales.class); - mongoTemplate.dropCollection(Sales2.class); - mongoTemplate.dropCollection(Employee.class); - mongoTemplate.dropCollection(Art.class); - } - - /** - * Imports the sample dataset (zips.json) if necessary (e.g. if it doesn't exist yet). The dataset can originally be - * found on the mongodb aggregation framework example website: - * - * @see MongoDB Aggregation Examples - */ - private void initSampleDataIfNecessary() { - - if (!initialized) { - - LOGGER.debug("Server uses MongoDB Version: {}", mongoVersion); - - mongoTemplate.dropCollection(ZipInfo.class); - mongoTemplate.execute(ZipInfo.class, new CollectionCallback() { - - @Override - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - - Scanner scanner = null; - try { - scanner = new Scanner(new BufferedInputStream(new ClassPathResource("zips.json").getInputStream())); - while (scanner.hasNextLine()) { - String zipInfoRecord = scanner.nextLine(); - collection.save((DBObject) JSON.parse(zipInfoRecord)); - } - } catch (Exception e) { - if (scanner != null) { - scanner.close(); - } - throw new RuntimeException("Could not load mongodb sample dataset!", e); - } - - return null; - } - }); + private static final String INPUT_COLLECTION = "aggregation_test_collection"; + private static final Logger LOGGER = LoggerFactory.getLogger(AggregationTests.class); + private static final Version TWO_DOT_FOUR = new Version(2, 4); + private static final Version TWO_DOT_SIX = new Version(2, 6); + private static final Version THREE_DOT_TWO = new Version(3, 2); + private static final Version THREE_DOT_FOUR = new Version(3, 4); + + private static boolean initialized = false; + + @Autowired + MongoTemplate mongoTemplate; + + @Rule + public ExpectedException exception = ExpectedException.none(); + private static Version mongoVersion; + + @Before + public void setUp() { + + queryMongoVersionIfNecessary(); + cleanDb(); + initSampleDataIfNecessary(); + } + + private void queryMongoVersionIfNecessary() { + + if (mongoVersion == null) { + CommandResult result = mongoTemplate.executeCommand("{ buildInfo: 1 }"); + mongoVersion = Version.parse(result.get("version").toString()); + } + } + + @After + public void cleanUp() { + cleanDb(); + } + + private void cleanDb() { + mongoTemplate.dropCollection(INPUT_COLLECTION); + mongoTemplate.dropCollection(Product.class); + mongoTemplate.dropCollection(UserWithLikes.class); + mongoTemplate.dropCollection(DATAMONGO753.class); + mongoTemplate.dropCollection(Data.class); + mongoTemplate.dropCollection(DATAMONGO788.class); + mongoTemplate.dropCollection(User.class); + mongoTemplate.dropCollection(Person.class); + mongoTemplate.dropCollection(Reservation.class); + mongoTemplate.dropCollection(Venue.class); + mongoTemplate.dropCollection(MeterData.class); + mongoTemplate.dropCollection(LineItem.class); + mongoTemplate.dropCollection(InventoryItem.class); + mongoTemplate.dropCollection(Sales.class); + mongoTemplate.dropCollection(Sales2.class); + mongoTemplate.dropCollection(Employee.class); + mongoTemplate.dropCollection(Art.class); + } + + /** + * Imports the sample dataset (zips.json) if necessary (e.g. if it doesn't exist yet). The dataset can originally be + * found on the mongodb aggregation framework example website: + * + * @see MongoDB Aggregation Examples + */ + private void initSampleDataIfNecessary() { + + if (!initialized) { + + LOGGER.debug("Server uses MongoDB Version: {}", mongoVersion); + + mongoTemplate.dropCollection(ZipInfo.class); + mongoTemplate.execute(ZipInfo.class, new CollectionCallback() { + + @Override + public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { + + Scanner scanner = null; + try { + scanner = new Scanner(new BufferedInputStream(new ClassPathResource("zips.json").getInputStream())); + while (scanner.hasNextLine()) { + String zipInfoRecord = scanner.nextLine(); + collection.save((DBObject) JSON.parse(zipInfoRecord)); + } + } catch (Exception e) { + if (scanner != null) { + scanner.close(); + } + throw new RuntimeException("Could not load mongodb sample dataset!", e); + } + + return null; + } + }); + + long count = mongoTemplate.count(new Query(), ZipInfo.class); + assertThat(count, is(29467L)); + + initialized = true; + } + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 + public void shouldHandleMissingInputCollection() { + mongoTemplate.aggregate(newAggregation(), (String) null, TagCount.class); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 + public void shouldHandleMissingAggregationPipeline() { + mongoTemplate.aggregate(null, INPUT_COLLECTION, TagCount.class); + } + + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 + public void shouldHandleMissingEntityClass() { + mongoTemplate.aggregate(newAggregation(), INPUT_COLLECTION, null); + } + + @Test // DATAMONGO-586 + public void shouldAggregate() { + + createTagDocuments(); + + Aggregation agg = newAggregation( // + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("n"), // + project("n") // + .and("tag").previousOperation(), // + sort(DESC, "n") // + ); + + AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, TagCount.class); + + assertThat(results, is(notNullValue())); + + List tagCount = results.getMappedResults(); + + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(3)); + + assertTagCount("spring", 3, tagCount.get(0)); + assertTagCount("mongodb", 2, tagCount.get(1)); + assertTagCount("nosql", 1, tagCount.get(2)); + } + + @Test // DATAMONGO-586 + public void shouldAggregateAndStream() { + + createTagDocuments(); + + Aggregation agg = newAggregation( // + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("n"), // + project("n") // + .and("tag").previousOperation(), // + sort(DESC, "n") // + ); - long count = mongoTemplate.count(new Query(), ZipInfo.class); - assertThat(count, is(29467L)); + CloseableIterator iterator = mongoTemplate.aggregateStream(agg, INPUT_COLLECTION, TagCount.class); - initialized = true; - } - } + assertThat(iterator, is(notNullValue())); + List tagCount = new ArrayList(); + while (iterator.hasNext()) { + tagCount.add(iterator.next()); + } - @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 - public void shouldHandleMissingInputCollection() { - mongoTemplate.aggregate(newAggregation(), (String) null, TagCount.class); - } + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(3)); - @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 - public void shouldHandleMissingAggregationPipeline() { - mongoTemplate.aggregate(null, INPUT_COLLECTION, TagCount.class); - } + assertTagCount("spring", 3, tagCount.get(0)); + assertTagCount("mongodb", 2, tagCount.get(1)); + assertTagCount("nosql", 1, tagCount.get(2)); + } - @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 - public void shouldHandleMissingEntityClass() { - mongoTemplate.aggregate(newAggregation(), INPUT_COLLECTION, null); - } + @Test // DATAMONGO-586 + public void shouldAggregateEmptyCollection() { - @Test // DATAMONGO-586 - public void shouldAggregate() { + Aggregation aggregation = newAggregation(// + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("n"), // + project("n") // + .and("tag").previousOperation(), // + sort(DESC, "n") // + ); - createTagDocuments(); + AggregationResults results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class); - Aggregation agg = newAggregation( // - project("tags"), // - unwind("tags"), // - group("tags") // - .count().as("n"), // - project("n") // - .and("tag").previousOperation(), // - sort(DESC, "n") // - ); + assertThat(results, is(notNullValue())); - AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, TagCount.class); + List tagCount = results.getMappedResults(); - assertThat(results, is(notNullValue())); + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(0)); + } - List tagCount = results.getMappedResults(); + @Test // DATAMONGO-586 + public void shouldAggregateEmptyCollectionAndStream() { - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(3)); + Aggregation aggregation = newAggregation(// + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("n"), // + project("n") // + .and("tag").previousOperation(), // + sort(DESC, "n") // + ); - assertTagCount("spring", 3, tagCount.get(0)); - assertTagCount("mongodb", 2, tagCount.get(1)); - assertTagCount("nosql", 1, tagCount.get(2)); - } + CloseableIterator results = mongoTemplate.aggregateStream(aggregation, INPUT_COLLECTION, TagCount.class); - @Test // DATAMONGO-586 - public void shouldAggregateEmptyCollection() { + assertThat(results, is(notNullValue())); - Aggregation aggregation = newAggregation(// - project("tags"), // - unwind("tags"), // - group("tags") // - .count().as("n"), // - project("n") // - .and("tag").previousOperation(), // - sort(DESC, "n") // - ); + List tagCount = new ArrayList(); + while (results.hasNext()){ + tagCount.add(results.next()); + } - AggregationResults results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class); + // assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(0)); + } - assertThat(results, is(notNullValue())); + @Test // DATAMONGO-1391 + public void shouldUnwindWithIndex() { - List tagCount = results.getMappedResults(); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(0)); - } + DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); - @Test // DATAMONGO-1391 - public void shouldUnwindWithIndex() { + coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); + coll.insert(createDocument("Doc2")); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + Aggregation agg = newAggregation( // + project("tags"), // + unwind("tags", "n"), // + project("n") // + .and("tag").previousOperation(), // + sort(DESC, "n") // + ); - DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); + AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, TagCount.class); - coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); - coll.insert(createDocument("Doc2")); + assertThat(results, is(notNullValue())); - Aggregation agg = newAggregation( // - project("tags"), // - unwind("tags", "n"), // - project("n") // - .and("tag").previousOperation(), // - sort(DESC, "n") // - ); + List tagCount = results.getMappedResults(); - AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, TagCount.class); + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(3)); + } - assertThat(results, is(notNullValue())); + @Test // DATAMONGO-1391 + public void shouldUnwindPreserveEmpty() { - List tagCount = results.getMappedResults(); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(3)); - } + DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); - @Test // DATAMONGO-1391 - public void shouldUnwindPreserveEmpty() { + coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); + coll.insert(createDocument("Doc2")); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + Aggregation agg = newAggregation( // + project("tags"), // + unwind("tags", "n", true), // + sort(DESC, "n") // + ); - DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); + AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, DBObject.class); - coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); - coll.insert(createDocument("Doc2")); + assertThat(results, is(notNullValue())); - Aggregation agg = newAggregation( // - project("tags"), // - unwind("tags", "n", true), // - sort(DESC, "n") // - ); + List tagCount = results.getMappedResults(); - AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, DBObject.class); + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(4)); + assertThat(tagCount.get(0), isBsonObject().containing("n", 2L)); + assertThat(tagCount.get(3), isBsonObject().notContaining("n")); + } - assertThat(results, is(notNullValue())); + @Test // DATAMONGO-586 + public void shouldDetectResultMismatch() { - List tagCount = results.getMappedResults(); + createTagDocuments(); - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(4)); - assertThat(tagCount.get(0), isBsonObject().containing("n", 2L)); - assertThat(tagCount.get(3), isBsonObject().notContaining("n")); - } + Aggregation aggregation = newAggregation( // + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("count"), // count field not present + limit(2) // + ); - @Test // DATAMONGO-586 - public void shouldDetectResultMismatch() { + AggregationResults results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class); - createTagDocuments(); + assertThat(results, is(notNullValue())); - Aggregation aggregation = newAggregation( // - project("tags"), // - unwind("tags"), // - group("tags") // - .count().as("count"), // count field not present - limit(2) // - ); + List tagCount = results.getMappedResults(); - AggregationResults results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class); + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(2)); + assertTagCount(null, 0, tagCount.get(0)); + assertTagCount(null, 0, tagCount.get(1)); + } - assertThat(results, is(notNullValue())); - List tagCount = results.getMappedResults(); + @Test // DATAMONGO-586 + public void shouldDetectResultMismatchWhileStreaming() { - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(2)); - assertTagCount(null, 0, tagCount.get(0)); - assertTagCount(null, 0, tagCount.get(1)); - } + createTagDocuments(); - @Test // DATAMONGO-586 - public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { - /* + Aggregation aggregation = newAggregation( // + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("count"), // count field not present + limit(2) // + ); + + CloseableIterator results = mongoTemplate.aggregateStream(aggregation, INPUT_COLLECTION, TagCount.class); + + assertThat(results, is(notNullValue())); + + List tagCount = new ArrayList(); + while (results.hasNext()){ + tagCount.add(results.next()); + } +// assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(2)); + assertTagCount(null, 0, tagCount.get(0)); + assertTagCount(null, 0, tagCount.get(1)); + } + + + @Test // DATAMONGO-586 + public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { + /* //complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state db.zipInfo.aggregate( { @@ -401,54 +490,54 @@ public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { ) */ - TypedAggregation aggregation = newAggregation(ZipInfo.class, // - group("state", "city").sum("population").as("pop"), // - sort(ASC, "pop", "state", "city"), // - group("state") // - .last("city").as("biggestCity") // - .last("pop").as("biggestPop") // - .first("city").as("smallestCity") // - .first("pop").as("smallestPop"), // - project() // - .and("state").previousOperation() // - .and("biggestCity").nested(bind("name", "biggestCity").and("population", "biggestPop")) // - .and("smallestCity").nested(bind("name", "smallestCity").and("population", "smallestPop")), // - sort(ASC, "state") // - ); - - assertThat(aggregation, is(notNullValue())); - assertThat(aggregation.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(aggregation, ZipInfoStats.class); - assertThat(result, is(notNullValue())); - assertThat(result.getMappedResults(), is(notNullValue())); - assertThat(result.getMappedResults().size(), is(51)); - - ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0); - assertThat(firstZipInfoStats, is(notNullValue())); - assertThat(firstZipInfoStats.id, is(nullValue())); - assertThat(firstZipInfoStats.state, is("AK")); - assertThat(firstZipInfoStats.smallestCity, is(notNullValue())); - assertThat(firstZipInfoStats.smallestCity.name, is("CHEVAK")); - assertThat(firstZipInfoStats.smallestCity.population, is(0)); - assertThat(firstZipInfoStats.biggestCity, is(notNullValue())); - assertThat(firstZipInfoStats.biggestCity.name, is("ANCHORAGE")); - assertThat(firstZipInfoStats.biggestCity.population, is(183987)); - - ZipInfoStats lastZipInfoStats = result.getMappedResults().get(50); - assertThat(lastZipInfoStats, is(notNullValue())); - assertThat(lastZipInfoStats.id, is(nullValue())); - assertThat(lastZipInfoStats.state, is("WY")); - assertThat(lastZipInfoStats.smallestCity, is(notNullValue())); - assertThat(lastZipInfoStats.smallestCity.name, is("LOST SPRINGS")); - assertThat(lastZipInfoStats.smallestCity.population, is(6)); - assertThat(lastZipInfoStats.biggestCity, is(notNullValue())); - assertThat(lastZipInfoStats.biggestCity.name, is("CHEYENNE")); - assertThat(lastZipInfoStats.biggestCity.population, is(70185)); - } - - @Test // DATAMONGO-586 - public void findStatesWithPopulationOver10MillionAggregationExample() { + TypedAggregation aggregation = newAggregation(ZipInfo.class, // + group("state", "city").sum("population").as("pop"), // + sort(ASC, "pop", "state", "city"), // + group("state") // + .last("city").as("biggestCity") // + .last("pop").as("biggestPop") // + .first("city").as("smallestCity") // + .first("pop").as("smallestPop"), // + project() // + .and("state").previousOperation() // + .and("biggestCity").nested(bind("name", "biggestCity").and("population", "biggestPop")) // + .and("smallestCity").nested(bind("name", "smallestCity").and("population", "smallestPop")), // + sort(ASC, "state") // + ); + + assertThat(aggregation, is(notNullValue())); + assertThat(aggregation.toString(), is(notNullValue())); + + AggregationResults result = mongoTemplate.aggregate(aggregation, ZipInfoStats.class); + assertThat(result, is(notNullValue())); + assertThat(result.getMappedResults(), is(notNullValue())); + assertThat(result.getMappedResults().size(), is(51)); + + ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0); + assertThat(firstZipInfoStats, is(notNullValue())); + assertThat(firstZipInfoStats.id, is(nullValue())); + assertThat(firstZipInfoStats.state, is("AK")); + assertThat(firstZipInfoStats.smallestCity, is(notNullValue())); + assertThat(firstZipInfoStats.smallestCity.name, is("CHEVAK")); + assertThat(firstZipInfoStats.smallestCity.population, is(0)); + assertThat(firstZipInfoStats.biggestCity, is(notNullValue())); + assertThat(firstZipInfoStats.biggestCity.name, is("ANCHORAGE")); + assertThat(firstZipInfoStats.biggestCity.population, is(183987)); + + ZipInfoStats lastZipInfoStats = result.getMappedResults().get(50); + assertThat(lastZipInfoStats, is(notNullValue())); + assertThat(lastZipInfoStats.id, is(nullValue())); + assertThat(lastZipInfoStats.state, is("WY")); + assertThat(lastZipInfoStats.smallestCity, is(notNullValue())); + assertThat(lastZipInfoStats.smallestCity.name, is("LOST SPRINGS")); + assertThat(lastZipInfoStats.smallestCity.population, is(6)); + assertThat(lastZipInfoStats.biggestCity, is(notNullValue())); + assertThat(lastZipInfoStats.biggestCity.name, is("CHEYENNE")); + assertThat(lastZipInfoStats.biggestCity.population, is(70185)); + } + + @Test // DATAMONGO-586 + public void findStatesWithPopulationOver10MillionAggregationExample() { /* //complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state @@ -471,34 +560,34 @@ public void findStatesWithPopulationOver10MillionAggregationExample() { ) */ - TypedAggregation agg = newAggregation(ZipInfo.class, // - group("state") // - .sum("population").as("totalPop"), // - sort(ASC, previousOperation(), "totalPop"), // - match(where("totalPop").gte(10 * 1000 * 1000)) // - ); - - assertThat(agg, is(notNullValue())); - assertThat(agg.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(agg, StateStats.class); - assertThat(result, is(notNullValue())); - assertThat(result.getMappedResults(), is(notNullValue())); - assertThat(result.getMappedResults().size(), is(7)); - - StateStats stateStats = result.getMappedResults().get(0); - assertThat(stateStats, is(notNullValue())); - assertThat(stateStats.id, is("CA")); - assertThat(stateStats.state, is(nullValue())); - assertThat(stateStats.totalPopulation, is(29760021)); - } - - /** - * @see MongoDB Aggregation - * Framework: $cond - */ - @Test // DATAMONGO-861 - public void aggregationUsingConditionalProjectionToCalculateDiscount() { + TypedAggregation agg = newAggregation(ZipInfo.class, // + group("state") // + .sum("population").as("totalPop"), // + sort(ASC, previousOperation(), "totalPop"), // + match(where("totalPop").gte(10 * 1000 * 1000)) // + ); + + assertThat(agg, is(notNullValue())); + assertThat(agg.toString(), is(notNullValue())); + + AggregationResults result = mongoTemplate.aggregate(agg, StateStats.class); + assertThat(result, is(notNullValue())); + assertThat(result.getMappedResults(), is(notNullValue())); + assertThat(result.getMappedResults().size(), is(7)); + + StateStats stateStats = result.getMappedResults().get(0); + assertThat(stateStats, is(notNullValue())); + assertThat(stateStats.id, is("CA")); + assertThat(stateStats.state, is(nullValue())); + assertThat(stateStats.totalPopulation, is(29760021)); + } + + /** + * @see MongoDB Aggregation + * Framework: $cond + */ + @Test // DATAMONGO-861 + public void aggregationUsingConditionalProjectionToCalculateDiscount() { /* db.inventory.aggregate( @@ -517,41 +606,41 @@ public void aggregationUsingConditionalProjectionToCalculateDiscount() { ) */ - mongoTemplate.insert(new InventoryItem(1, "abc1", 300)); - mongoTemplate.insert(new InventoryItem(2, "abc2", 200)); - mongoTemplate.insert(new InventoryItem(3, "xyz1", 250)); + mongoTemplate.insert(new InventoryItem(1, "abc1", 300)); + mongoTemplate.insert(new InventoryItem(2, "abc2", 200)); + mongoTemplate.insert(new InventoryItem(3, "xyz1", 250)); - TypedAggregation aggregation = newAggregation(InventoryItem.class, // - project("item") // - .and("discount")// - .applyCondition(ConditionalOperators.Cond.newBuilder().when(Criteria.where("qty").gte(250)) // - .then(30) // - .otherwise(20))); + TypedAggregation aggregation = newAggregation(InventoryItem.class, // + project("item") // + .and("discount")// + .applyCondition(ConditionalOperators.Cond.newBuilder().when(Criteria.where("qty").gte(250)) // + .then(30) // + .otherwise(20))); - assertThat(aggregation.toString(), is(notNullValue())); + assertThat(aggregation.toString(), is(notNullValue())); - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(3)); + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); - DBObject first = result.getMappedResults().get(0); - assertThat(first.get("_id"), is((Object) 1)); - assertThat(first.get("discount"), is((Object) 30)); + DBObject first = result.getMappedResults().get(0); + assertThat(first.get("_id"), is((Object) 1)); + assertThat(first.get("discount"), is((Object) 30)); - DBObject second = result.getMappedResults().get(1); - assertThat(second.get("_id"), is((Object) 2)); - assertThat(second.get("discount"), is((Object) 20)); + DBObject second = result.getMappedResults().get(1); + assertThat(second.get("_id"), is((Object) 2)); + assertThat(second.get("discount"), is((Object) 20)); - DBObject third = result.getMappedResults().get(2); - assertThat(third.get("_id"), is((Object) 3)); - assertThat(third.get("discount"), is((Object) 30)); - } + DBObject third = result.getMappedResults().get(2); + assertThat(third.get("_id"), is((Object) 3)); + assertThat(third.get("discount"), is((Object) 30)); + } - /** - * @see MongoDB Aggregation - * Framework: $ifNull - */ - @Test // DATAMONGO-861 - public void aggregationUsingIfNullToProjectSaneDefaults() { + /** + * @see MongoDB Aggregation + * Framework: $ifNull + */ + @Test // DATAMONGO-861 + public void aggregationUsingIfNullToProjectSaneDefaults() { /* db.inventory.aggregate( @@ -566,1408 +655,1478 @@ public void aggregationUsingIfNullToProjectSaneDefaults() { ) */ - mongoTemplate.insert(new InventoryItem(1, "abc1", "product 1", 300)); - mongoTemplate.insert(new InventoryItem(2, "abc2", 200)); - mongoTemplate.insert(new InventoryItem(3, "xyz1", 250)); - - TypedAggregation aggregation = newAggregation(InventoryItem.class, // - project("item") // - .and(ConditionalOperators.ifNull("description").then("Unspecified")) // - .as("description")// - ); - - assertThat(aggregation.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(3)); + mongoTemplate.insert(new InventoryItem(1, "abc1", "product 1", 300)); + mongoTemplate.insert(new InventoryItem(2, "abc2", 200)); + mongoTemplate.insert(new InventoryItem(3, "xyz1", 250)); - DBObject first = result.getMappedResults().get(0); - assertThat(first.get("_id"), is((Object) 1)); - assertThat(first.get("description"), is((Object) "product 1")); + TypedAggregation aggregation = newAggregation(InventoryItem.class, // + project("item") // + .and(ConditionalOperators.ifNull("description").then("Unspecified")) // + .as("description")// + ); - DBObject second = result.getMappedResults().get(1); - assertThat(second.get("_id"), is((Object) 2)); - assertThat(second.get("description"), is((Object) "Unspecified")); - } + assertThat(aggregation.toString(), is(notNullValue())); - @Test // DATAMONGO-861 - public void aggregationUsingConditionalProjection() { + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); - TypedAggregation aggregation = newAggregation(ZipInfo.class, // - project() // - .and("largePopulation")// - .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // - .then(true) // - .otherwise(false)) // - .and("population").as("population")); + DBObject first = result.getMappedResults().get(0); + assertThat(first.get("_id"), is((Object) 1)); + assertThat(first.get("description"), is((Object) "product 1")); - assertThat(aggregation, is(notNullValue())); - assertThat(aggregation.toString(), is(notNullValue())); + DBObject second = result.getMappedResults().get(1); + assertThat(second.get("_id"), is((Object) 2)); + assertThat(second.get("description"), is((Object) "Unspecified")); + } - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(29467)); + @Test // DATAMONGO-861 + public void aggregationUsingConditionalProjection() { - DBObject firstZipInfoStats = result.getMappedResults().get(0); - assertThat(firstZipInfoStats.get("largePopulation"), is((Object) false)); - assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); - } + TypedAggregation aggregation = newAggregation(ZipInfo.class, // + project() // + .and("largePopulation")// + .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // + .then(true) // + .otherwise(false)) // + .and("population").as("population")); - @Test // DATAMONGO-861 - public void aggregationUsingNestedConditionalProjection() { + assertThat(aggregation, is(notNullValue())); + assertThat(aggregation.toString(), is(notNullValue())); - TypedAggregation aggregation = newAggregation(ZipInfo.class, // - project() // - .and("size")// - .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // - .then( - ConditionalOperators.when(Criteria.where("population").gte(200000)).then("huge").otherwise("small")) // - .otherwise("small")) // - .and("population").as("population")); + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(29467)); - assertThat(aggregation, is(notNullValue())); - assertThat(aggregation.toString(), is(notNullValue())); + DBObject firstZipInfoStats = result.getMappedResults().get(0); + assertThat(firstZipInfoStats.get("largePopulation"), is((Object) false)); + assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); + } - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(29467)); + @Test // DATAMONGO-861 + public void aggregationUsingNestedConditionalProjection() { - DBObject firstZipInfoStats = result.getMappedResults().get(0); - assertThat(firstZipInfoStats.get("size"), is((Object) "small")); - assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); - } + TypedAggregation aggregation = newAggregation(ZipInfo.class, // + project() // + .and("size")// + .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // + .then( + ConditionalOperators.when(Criteria.where("population").gte(200000)).then("huge").otherwise("small")) // + .otherwise("small")) // + .and("population").as("population")); - @Test // DATAMONGO-861 - public void aggregationUsingIfNullProjection() { + assertThat(aggregation, is(notNullValue())); + assertThat(aggregation.toString(), is(notNullValue())); - mongoTemplate.insert(new LineItem("id", "caption", 0)); - mongoTemplate.insert(new LineItem("idonly", null, 0)); + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(29467)); - TypedAggregation aggregation = newAggregation(LineItem.class, // - project("id") // - .and("caption")// - .applyCondition(ConditionalOperators.ifNull("caption").then("unknown")), - sort(ASC, "id")); + DBObject firstZipInfoStats = result.getMappedResults().get(0); + assertThat(firstZipInfoStats.get("size"), is((Object) "small")); + assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); + } - assertThat(aggregation.toString(), is(notNullValue())); + @Test // DATAMONGO-861 + public void aggregationUsingIfNullProjection() { - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(2)); + mongoTemplate.insert(new LineItem("id", "caption", 0)); + mongoTemplate.insert(new LineItem("idonly", null, 0)); - DBObject id = result.getMappedResults().get(0); - assertThat((String) id.get("caption"), is(equalTo("caption"))); + TypedAggregation aggregation = newAggregation(LineItem.class, // + project("id") // + .and("caption")// + .applyCondition(ConditionalOperators.ifNull("caption").then("unknown")), + sort(ASC, "id")); - DBObject idonly = result.getMappedResults().get(1); - assertThat((String) idonly.get("caption"), is(equalTo("unknown"))); - } - - @Test // DATAMONGO-861 - public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() { - - mongoTemplate.insert(new LineItem("id", "caption", 0)); - mongoTemplate.insert(new LineItem("idonly", null, 0)); - - TypedAggregation aggregation = newAggregation(LineItem.class, // - project("id") // - .and("caption")// - .applyCondition(ConditionalOperators.ifNull("caption").thenValueOf("id")), - sort(ASC, "id")); - - assertThat(aggregation.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(2)); - - DBObject id = result.getMappedResults().get(0); - assertThat((String) id.get("caption"), is(equalTo("caption"))); - - DBObject idonly = result.getMappedResults().get(1); - assertThat((String) idonly.get("caption"), is(equalTo("idonly"))); - } - - @Test // DATAMONGO-861 - public void shouldAllowGroupingUsingConditionalExpressions() { - - mongoTemplate.dropCollection(CarPerson.class); - - CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000), - new CarDescriptor.Entry("MAKE1", "MODEL2", 2001)); - - CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); - CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2015)); - - mongoTemplate.save(person1); - mongoTemplate.save(person2); - mongoTemplate.save(person3); - - TypedAggregation agg = Aggregation.newAggregation(CarPerson.class, - unwind("descriptors.carDescriptor.entries"), // - project() // - .and(ConditionalOperators // - .when(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1")).then("good") - .otherwise("meh")) - .as("make") // - .and("descriptors.carDescriptor.entries.model").as("model") // - .and("descriptors.carDescriptor.entries.year").as("year"), // - group("make").avg(ConditionalOperators // - .when(Criteria.where("year").gte(2012)) // - .then(1) // - .otherwise(9000)).as("score"), - sort(ASC, "make")); - - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - - assertThat(result.getMappedResults(), hasSize(2)); - - DBObject meh = result.getMappedResults().get(0); - assertThat((String) meh.get("_id"), is(equalTo("meh"))); - assertThat(((Number) meh.get("score")).longValue(), is(equalTo(1L))); - - DBObject good = result.getMappedResults().get(1); - assertThat((String) good.get("_id"), is(equalTo("good"))); - assertThat(((Number) good.get("score")).longValue(), is(equalTo(9000L))); - } - - /** - * @see Return - * the Five Most Common “Likes” - */ - @Test // DATAMONGO-586 - public void returnFiveMostCommonLikesAggregationFrameworkExample() { - - createUserWithLikesDocuments(); - - TypedAggregation agg = createUsersWithCommonLikesAggregation(); - - assertThat(agg, is(notNullValue())); - assertThat(agg.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); - assertThat(result, is(notNullValue())); - assertThat(result.getMappedResults(), is(notNullValue())); - assertThat(result.getMappedResults().size(), is(5)); - - assertLikeStats(result.getMappedResults().get(0), "a", 4); - assertLikeStats(result.getMappedResults().get(1), "b", 2); - assertLikeStats(result.getMappedResults().get(2), "c", 4); - assertLikeStats(result.getMappedResults().get(3), "d", 2); - assertLikeStats(result.getMappedResults().get(4), "e", 3); - } - - protected TypedAggregation createUsersWithCommonLikesAggregation() { - return newAggregation(UserWithLikes.class, // - unwind("likes"), // - group("likes").count().as("number"), // - sort(DESC, "number"), // - limit(5), // - sort(ASC, previousOperation()) // - ); - } - - @Test // DATAMONGO-586 - public void arithmenticOperatorsInProjectionExample() { - - Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); - mongoTemplate.insert(product); - - TypedAggregation agg = newAggregation(Product.class, // - project("name", "netPrice") // - .and("netPrice").plus(1).as("netPricePlus1") // - .and("netPrice").minus(1).as("netPriceMinus1") // - .and("netPrice").multiply(2).as("netPriceMul2") // - .and("netPrice").divide(1.19).as("netPriceDiv119") // - .and("spaceUnits").mod(2).as("spaceUnitsMod2") // - .and("spaceUnits").plus("spaceUnits").as("spaceUnitsPlusSpaceUnits") // - .and("spaceUnits").minus("spaceUnits").as("spaceUnitsMinusSpaceUnits") // - .and("spaceUnits").multiply("spaceUnits").as("spaceUnitsMultiplySpaceUnits") // - .and("spaceUnits").divide("spaceUnits").as("spaceUnitsDivideSpaceUnits") // - .and("spaceUnits").mod("spaceUnits").as("spaceUnitsModSpaceUnits") // - ); - - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - List resultList = result.getMappedResults(); - - assertThat(resultList, is(notNullValue())); - assertThat((String) resultList.get(0).get("_id"), is(product.id)); - assertThat((String) resultList.get(0).get("name"), is(product.name)); - assertThat((Double) resultList.get(0).get("netPricePlus1"), is(product.netPrice + 1)); - assertThat((Double) resultList.get(0).get("netPriceMinus1"), is(product.netPrice - 1)); - assertThat((Double) resultList.get(0).get("netPriceMul2"), is(product.netPrice * 2)); - assertThat((Double) resultList.get(0).get("netPriceDiv119"), is(product.netPrice / 1.19)); - assertThat((Integer) resultList.get(0).get("spaceUnitsMod2"), is(product.spaceUnits % 2)); - assertThat((Integer) resultList.get(0).get("spaceUnitsPlusSpaceUnits"), - is(product.spaceUnits + product.spaceUnits)); - assertThat((Integer) resultList.get(0).get("spaceUnitsMinusSpaceUnits"), - is(product.spaceUnits - product.spaceUnits)); - assertThat((Integer) resultList.get(0).get("spaceUnitsMultiplySpaceUnits"), - is(product.spaceUnits * product.spaceUnits)); - assertThat((Double) resultList.get(0).get("spaceUnitsDivideSpaceUnits"), - is((double) (product.spaceUnits / product.spaceUnits))); - assertThat((Integer) resultList.get(0).get("spaceUnitsModSpaceUnits"), is(product.spaceUnits % product.spaceUnits)); - } - - @Test // DATAMONGO-774 - public void expressionsInProjectionExample() { - - Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); - mongoTemplate.insert(product); - - TypedAggregation agg = newAggregation(Product.class, // - project("name", "netPrice") // - .andExpression("netPrice + 1").as("netPricePlus1") // - .andExpression("netPrice - 1").as("netPriceMinus1") // - .andExpression("netPrice / 2").as("netPriceDiv2") // - .andExpression("netPrice * 1.19").as("grossPrice") // - .andExpression("spaceUnits % 2").as("spaceUnitsMod2") // - .andExpression("(netPrice * 0.8 + 1.2) * 1.19").as("grossPriceIncludingDiscountAndCharge") // - - ); - - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - List resultList = result.getMappedResults(); - - assertThat(resultList, is(notNullValue())); - assertThat((String) resultList.get(0).get("_id"), is(product.id)); - assertThat((String) resultList.get(0).get("name"), is(product.name)); - assertThat((Double) resultList.get(0).get("netPricePlus1"), is(product.netPrice + 1)); - assertThat((Double) resultList.get(0).get("netPriceMinus1"), is(product.netPrice - 1)); - assertThat((Double) resultList.get(0).get("netPriceDiv2"), is(product.netPrice / 2)); - assertThat((Double) resultList.get(0).get("grossPrice"), is(product.netPrice * 1.19)); - assertThat((Integer) resultList.get(0).get("spaceUnitsMod2"), is(product.spaceUnits % 2)); - assertThat((Double) resultList.get(0).get("grossPriceIncludingDiscountAndCharge"), - is((product.netPrice * 0.8 + 1.2) * 1.19)); - } - - @Test // DATAMONGO-774 - public void stringExpressionsInProjectionExample() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); - - Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); - mongoTemplate.insert(product); - - TypedAggregation agg = newAggregation(Product.class, // - project("name", "netPrice") // - .andExpression("concat(name, '_bubu')").as("name_bubu") // - ); - - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - List resultList = result.getMappedResults(); - - assertThat(resultList, is(notNullValue())); - assertThat((String) resultList.get(0).get("_id"), is(product.id)); - assertThat((String) resultList.get(0).get("name"), is(product.name)); - assertThat((String) resultList.get(0).get("name_bubu"), is(product.name + "_bubu")); - } - - @Test // DATAMONGO-774 - public void expressionsInProjectionExampleShowcase() { - - Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); - mongoTemplate.insert(product); - - double shippingCosts = 1.2; - - TypedAggregation agg = newAggregation(Product.class, // - project("name", "netPrice") // - .andExpression("(netPrice * (1-discountRate) + [0]) * (1+taxRate)", shippingCosts).as("salesPrice") // - ); - - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - List resultList = result.getMappedResults(); - - assertThat(resultList, is(notNullValue())); - DBObject firstItem = resultList.get(0); - assertThat((String) firstItem.get("_id"), is(product.id)); - assertThat((String) firstItem.get("name"), is(product.name)); - assertThat((Double) firstItem.get("salesPrice"), - is((product.netPrice * (1 - product.discountRate) + shippingCosts) * (1 + product.taxRate))); - } - - @Test - public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressionsInProjection() { - - exception.expect(MappingException.class); - exception.expectMessage("unknown"); - - Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); - mongoTemplate.insert(product); - - TypedAggregation agg = newAggregation(Product.class, // - project("name", "netPrice") // - .andExpression("unknown + 1").as("netPricePlus1") // - ); - - mongoTemplate.aggregate(agg, DBObject.class); - } - - /** - * @see Spring - * Data MongoDB - Aggregation Framework - invalid reference in group Operation - */ - @Test // DATAMONGO-753 - public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { - - mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); - mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("B", 1), new PD("B", 1), new PD("C", 1))); - - TypedAggregation agg = newAggregation(DATAMONGO753.class, // - unwind("pd"), // - group("pd.pDch") // the nested field expression - .sum("pd.up").as("uplift"), // - project("_id", "uplift")); - - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - List stats = result.getMappedResults(); - - assertThat(stats.size(), is(3)); - assertThat(stats.get(0).get("_id").toString(), is("C")); - assertThat((Integer) stats.get(0).get("uplift"), is(2)); - assertThat(stats.get(1).get("_id").toString(), is("B")); - assertThat((Integer) stats.get(1).get("uplift"), is(3)); - assertThat(stats.get(2).get("_id").toString(), is("A")); - assertThat((Integer) stats.get(2).get("uplift"), is(1)); - } - - /** - * @see Spring - * Data MongoDB - Aggregation Framework - invalid reference in group Operation - */ - @Test // DATAMONGO-753 - public void aliasesNestedFieldInProjectionImmediately() { - - mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); - mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("B", 1), new PD("B", 1), new PD("C", 1))); - - TypedAggregation agg = newAggregation(DATAMONGO753.class, // - unwind("pd"), // - project().and("pd.up").as("up")); - - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - List mappedResults = results.getMappedResults(); - - assertThat(mappedResults, hasSize(6)); - for (DBObject element : mappedResults) { - assertThat(element.get("up"), is((Object) 1)); - } - } - - @Test // DATAMONGO-774 - public void shouldPerformDateProjectionOperatorsCorrectly() throws ParseException { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); - - Data data = new Data(); - data.stringValue = "ABC"; - mongoTemplate.insert(data); - - TypedAggregation agg = newAggregation(Data.class, - project() // - .andExpression("concat(stringValue, 'DE')").as("concat") // - .andExpression("strcasecmp(stringValue,'XYZ')").as("strcasecmp") // - .andExpression("substr(stringValue,1,1)").as("substr") // - .andExpression("toLower(stringValue)").as("toLower") // - .andExpression("toUpper(toLower(stringValue))").as("toUpper") // - ); - - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - DBObject dbo = results.getUniqueMappedResult(); - - assertThat(dbo, is(notNullValue())); - assertThat((String) dbo.get("concat"), is("ABCDE")); - assertThat((Integer) dbo.get("strcasecmp"), is(-1)); - assertThat((String) dbo.get("substr"), is("B")); - assertThat((String) dbo.get("toLower"), is("abc")); - assertThat((String) dbo.get("toUpper"), is("ABC")); - } - - @Test // DATAMONGO-774 - public void shouldPerformStringProjectionOperatorsCorrectly() throws ParseException { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); - - Data data = new Data(); - data.dateValue = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSSZ").parse("29.08.1983 12:34:56.789+0000"); - mongoTemplate.insert(data); - - TypedAggregation agg = newAggregation(Data.class, - project() // - .andExpression("dayOfYear(dateValue)").as("dayOfYear") // - .andExpression("dayOfMonth(dateValue)").as("dayOfMonth") // - .andExpression("dayOfWeek(dateValue)").as("dayOfWeek") // - .andExpression("year(dateValue)").as("year") // - .andExpression("month(dateValue)").as("month") // - .andExpression("week(dateValue)").as("week") // - .andExpression("hour(dateValue)").as("hour") // - .andExpression("minute(dateValue)").as("minute") // - .andExpression("second(dateValue)").as("second") // - .andExpression("millisecond(dateValue)").as("millisecond") // - ); - - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - DBObject dbo = results.getUniqueMappedResult(); - - assertThat(dbo, is(notNullValue())); - assertThat((Integer) dbo.get("dayOfYear"), is(241)); - assertThat((Integer) dbo.get("dayOfMonth"), is(29)); - assertThat((Integer) dbo.get("dayOfWeek"), is(2)); - assertThat((Integer) dbo.get("year"), is(1983)); - assertThat((Integer) dbo.get("month"), is(8)); - assertThat((Integer) dbo.get("week"), is(35)); - assertThat((Integer) dbo.get("hour"), is(12)); - assertThat((Integer) dbo.get("minute"), is(34)); - assertThat((Integer) dbo.get("second"), is(56)); - assertThat((Integer) dbo.get("millisecond"), is(789)); - } - - @Test // DATAMONGO-1550 - public void shouldPerformReplaceRootOperatorCorrectly() throws ParseException { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - - Data data = new Data(); - DataItem dataItem = new DataItem(); - dataItem.primitiveIntValue = 42; - data.item = dataItem; - mongoTemplate.insert(data); - - TypedAggregation agg = newAggregation(Data.class, project("item"), // - replaceRoot("item"), // - project().and("primitiveIntValue").as("my_primitiveIntValue")); - - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - DBObject dbo = results.getUniqueMappedResult(); - - assertThat(dbo, is(notNullValue())); - assertThat((Integer) dbo.get("my_primitiveIntValue"), is(42)); - assertThat((Integer) dbo.keySet().size(), is(1)); - } - - @Test // DATAMONGO-788 - public void referencesToGroupIdsShouldBeRenderedProperly() { - - mongoTemplate.insert(new DATAMONGO788(1, 1)); - mongoTemplate.insert(new DATAMONGO788(1, 1)); - mongoTemplate.insert(new DATAMONGO788(1, 1)); - mongoTemplate.insert(new DATAMONGO788(2, 1)); - mongoTemplate.insert(new DATAMONGO788(2, 1)); - - AggregationOperation projectFirst = Aggregation.project("x", "y").and("xField").as("x").and("yField").as("y"); - AggregationOperation group = Aggregation.group("x", "y").count().as("xPerY"); - AggregationOperation project = Aggregation.project("xPerY", "x", "y").andExclude("_id"); - - TypedAggregation aggregation = Aggregation.newAggregation(DATAMONGO788.class, projectFirst, group, - project); - AggregationResults aggResults = mongoTemplate.aggregate(aggregation, DBObject.class); - List items = aggResults.getMappedResults(); - - assertThat(items.size(), is(2)); - assertThat((Integer) items.get(0).get("xPerY"), is(2)); - assertThat((Integer) items.get(0).get("x"), is(2)); - assertThat((Integer) items.get(0).get("y"), is(1)); - assertThat((Integer) items.get(1).get("xPerY"), is(3)); - assertThat((Integer) items.get(1).get("x"), is(1)); - assertThat((Integer) items.get(1).get("y"), is(1)); - } - - @Test // DATAMONGO-806 - public void shouldAllowGroupByIdFields() { - - mongoTemplate.dropCollection(User.class); - - LocalDateTime now = new LocalDateTime(); - - User user1 = new User("u1", new PushMessage("1", "aaa", now.toDate())); - User user2 = new User("u2", new PushMessage("2", "bbb", now.minusDays(2).toDate())); - User user3 = new User("u3", new PushMessage("3", "ccc", now.minusDays(1).toDate())); - - mongoTemplate.save(user1); - mongoTemplate.save(user2); - mongoTemplate.save(user3); - - Aggregation agg = newAggregation( // - project("id", "msgs"), // - unwind("msgs"), // - match(where("msgs.createDate").gt(now.minusDays(1).toDate())), // - group("id").push("msgs").as("msgs") // - ); - - AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); - - List mappedResults = results.getMappedResults(); - - DBObject firstItem = mappedResults.get(0); - assertThat(firstItem.get("_id"), is(notNullValue())); - assertThat(String.valueOf(firstItem.get("_id")), is("u1")); - } + assertThat(aggregation.toString(), is(notNullValue())); - @Test // DATAMONGO-840 - public void shouldAggregateOrderDataToAnInvoice() { + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(2)); - mongoTemplate.dropCollection(Order.class); + DBObject id = result.getMappedResults().get(0); + assertThat((String) id.get("caption"), is(equalTo("caption"))); - double taxRate = 0.19; + DBObject idonly = result.getMappedResults().get(1); + assertThat((String) idonly.get("caption"), is(equalTo("unknown"))); + } + + @Test // DATAMONGO-861 + public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() { + + mongoTemplate.insert(new LineItem("id", "caption", 0)); + mongoTemplate.insert(new LineItem("idonly", null, 0)); + + TypedAggregation aggregation = newAggregation(LineItem.class, // + project("id") // + .and("caption")// + .applyCondition(ConditionalOperators.ifNull("caption").thenValueOf("id")), + sort(ASC, "id")); + + assertThat(aggregation.toString(), is(notNullValue())); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(2)); + + DBObject id = result.getMappedResults().get(0); + assertThat((String) id.get("caption"), is(equalTo("caption"))); + + DBObject idonly = result.getMappedResults().get(1); + assertThat((String) idonly.get("caption"), is(equalTo("idonly"))); + } + + @Test // DATAMONGO-861 + public void shouldAllowGroupingUsingConditionalExpressions() { + + mongoTemplate.dropCollection(CarPerson.class); + + CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000), + new CarDescriptor.Entry("MAKE1", "MODEL2", 2001)); + + CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); + CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2015)); + + mongoTemplate.save(person1); + mongoTemplate.save(person2); + mongoTemplate.save(person3); + + TypedAggregation agg = Aggregation.newAggregation(CarPerson.class, + unwind("descriptors.carDescriptor.entries"), // + project() // + .and(ConditionalOperators // + .when(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1")).then("good") + .otherwise("meh")) + .as("make") // + .and("descriptors.carDescriptor.entries.model").as("model") // + .and("descriptors.carDescriptor.entries.year").as("year"), // + group("make").avg(ConditionalOperators // + .when(Criteria.where("year").gte(2012)) // + .then(1) // + .otherwise(9000)).as("score"), + sort(ASC, "make")); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + + assertThat(result.getMappedResults(), hasSize(2)); + + DBObject meh = result.getMappedResults().get(0); + assertThat((String) meh.get("_id"), is(equalTo("meh"))); + assertThat(((Number) meh.get("score")).longValue(), is(equalTo(1L))); + + DBObject good = result.getMappedResults().get(1); + assertThat((String) good.get("_id"), is(equalTo("good"))); + assertThat(((Number) good.get("score")).longValue(), is(equalTo(9000L))); + } + + /** + * @see Return + * the Five Most Common “Likes” + */ + @Test // DATAMONGO-586 + public void returnFiveMostCommonLikesAggregationFrameworkExample() { + + createUserWithLikesDocuments(); + + TypedAggregation agg = createUsersWithCommonLikesAggregation(); + + assertThat(agg, is(notNullValue())); + assertThat(agg.toString(), is(notNullValue())); + + AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); + assertThat(result, is(notNullValue())); + assertThat(result.getMappedResults(), is(notNullValue())); + assertThat(result.getMappedResults().size(), is(5)); + + assertLikeStats(result.getMappedResults().get(0), "a", 4); + assertLikeStats(result.getMappedResults().get(1), "b", 2); + assertLikeStats(result.getMappedResults().get(2), "c", 4); + assertLikeStats(result.getMappedResults().get(3), "d", 2); + assertLikeStats(result.getMappedResults().get(4), "e", 3); + } + + protected TypedAggregation createUsersWithCommonLikesAggregation() { + return newAggregation(UserWithLikes.class, // + unwind("likes"), // + group("likes").count().as("number"), // + sort(DESC, "number"), // + limit(5), // + sort(ASC, previousOperation()) // + ); + } + + @Test // DATAMONGO-586 + public void arithmenticOperatorsInProjectionExample() { + + Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); + mongoTemplate.insert(product); + + TypedAggregation agg = newAggregation(Product.class, // + project("name", "netPrice") // + .and("netPrice").plus(1).as("netPricePlus1") // + .and("netPrice").minus(1).as("netPriceMinus1") // + .and("netPrice").multiply(2).as("netPriceMul2") // + .and("netPrice").divide(1.19).as("netPriceDiv119") // + .and("spaceUnits").mod(2).as("spaceUnitsMod2") // + .and("spaceUnits").plus("spaceUnits").as("spaceUnitsPlusSpaceUnits") // + .and("spaceUnits").minus("spaceUnits").as("spaceUnitsMinusSpaceUnits") // + .and("spaceUnits").multiply("spaceUnits").as("spaceUnitsMultiplySpaceUnits") // + .and("spaceUnits").divide("spaceUnits").as("spaceUnitsDivideSpaceUnits") // + .and("spaceUnits").mod("spaceUnits").as("spaceUnitsModSpaceUnits") // + ); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + List resultList = result.getMappedResults(); + + assertThat(resultList, is(notNullValue())); + assertThat((String) resultList.get(0).get("_id"), is(product.id)); + assertThat((String) resultList.get(0).get("name"), is(product.name)); + assertThat((Double) resultList.get(0).get("netPricePlus1"), is(product.netPrice + 1)); + assertThat((Double) resultList.get(0).get("netPriceMinus1"), is(product.netPrice - 1)); + assertThat((Double) resultList.get(0).get("netPriceMul2"), is(product.netPrice * 2)); + assertThat((Double) resultList.get(0).get("netPriceDiv119"), is(product.netPrice / 1.19)); + assertThat((Integer) resultList.get(0).get("spaceUnitsMod2"), is(product.spaceUnits % 2)); + assertThat((Integer) resultList.get(0).get("spaceUnitsPlusSpaceUnits"), + is(product.spaceUnits + product.spaceUnits)); + assertThat((Integer) resultList.get(0).get("spaceUnitsMinusSpaceUnits"), + is(product.spaceUnits - product.spaceUnits)); + assertThat((Integer) resultList.get(0).get("spaceUnitsMultiplySpaceUnits"), + is(product.spaceUnits * product.spaceUnits)); + assertThat((Double) resultList.get(0).get("spaceUnitsDivideSpaceUnits"), + is((double) (product.spaceUnits / product.spaceUnits))); + assertThat((Integer) resultList.get(0).get("spaceUnitsModSpaceUnits"), is(product.spaceUnits % product.spaceUnits)); + } + + @Test // DATAMONGO-774 + public void expressionsInProjectionExample() { + + Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); + mongoTemplate.insert(product); + + TypedAggregation agg = newAggregation(Product.class, // + project("name", "netPrice") // + .andExpression("netPrice + 1").as("netPricePlus1") // + .andExpression("netPrice - 1").as("netPriceMinus1") // + .andExpression("netPrice / 2").as("netPriceDiv2") // + .andExpression("netPrice * 1.19").as("grossPrice") // + .andExpression("spaceUnits % 2").as("spaceUnitsMod2") // + .andExpression("(netPrice * 0.8 + 1.2) * 1.19").as("grossPriceIncludingDiscountAndCharge") // + + ); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + List resultList = result.getMappedResults(); + + assertThat(resultList, is(notNullValue())); + assertThat((String) resultList.get(0).get("_id"), is(product.id)); + assertThat((String) resultList.get(0).get("name"), is(product.name)); + assertThat((Double) resultList.get(0).get("netPricePlus1"), is(product.netPrice + 1)); + assertThat((Double) resultList.get(0).get("netPriceMinus1"), is(product.netPrice - 1)); + assertThat((Double) resultList.get(0).get("netPriceDiv2"), is(product.netPrice / 2)); + assertThat((Double) resultList.get(0).get("grossPrice"), is(product.netPrice * 1.19)); + assertThat((Integer) resultList.get(0).get("spaceUnitsMod2"), is(product.spaceUnits % 2)); + assertThat((Double) resultList.get(0).get("grossPriceIncludingDiscountAndCharge"), + is((product.netPrice * 0.8 + 1.2) * 1.19)); + } + + @Test // DATAMONGO-774 + public void stringExpressionsInProjectionExample() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); + + Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); + mongoTemplate.insert(product); + + TypedAggregation agg = newAggregation(Product.class, // + project("name", "netPrice") // + .andExpression("concat(name, '_bubu')").as("name_bubu") // + ); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + List resultList = result.getMappedResults(); + + assertThat(resultList, is(notNullValue())); + assertThat((String) resultList.get(0).get("_id"), is(product.id)); + assertThat((String) resultList.get(0).get("name"), is(product.name)); + assertThat((String) resultList.get(0).get("name_bubu"), is(product.name + "_bubu")); + } + + @Test // DATAMONGO-774 + public void expressionsInProjectionExampleShowcase() { + + Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); + mongoTemplate.insert(product); + + double shippingCosts = 1.2; + + TypedAggregation agg = newAggregation(Product.class, // + project("name", "netPrice") // + .andExpression("(netPrice * (1-discountRate) + [0]) * (1+taxRate)", shippingCosts).as("salesPrice") // + ); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + List resultList = result.getMappedResults(); + + assertThat(resultList, is(notNullValue())); + DBObject firstItem = resultList.get(0); + assertThat((String) firstItem.get("_id"), is(product.id)); + assertThat((String) firstItem.get("name"), is(product.name)); + assertThat((Double) firstItem.get("salesPrice"), + is((product.netPrice * (1 - product.discountRate) + shippingCosts) * (1 + product.taxRate))); + } + + @Test + public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressionsInProjection() { + + exception.expect(MappingException.class); + exception.expectMessage("unknown"); + + Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); + mongoTemplate.insert(product); + + TypedAggregation agg = newAggregation(Product.class, // + project("name", "netPrice") // + .andExpression("unknown + 1").as("netPricePlus1") // + ); + + mongoTemplate.aggregate(agg, DBObject.class); + } + + /** + * @see Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation + */ + @Test // DATAMONGO-753 + public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { + + mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); + mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("B", 1), new PD("B", 1), new PD("C", 1))); + + TypedAggregation agg = newAggregation(DATAMONGO753.class, // + unwind("pd"), // + group("pd.pDch") // the nested field expression + .sum("pd.up").as("uplift"), // + project("_id", "uplift")); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + List stats = result.getMappedResults(); + + assertThat(stats.size(), is(3)); + assertThat(stats.get(0).get("_id").toString(), is("C")); + assertThat((Integer) stats.get(0).get("uplift"), is(2)); + assertThat(stats.get(1).get("_id").toString(), is("B")); + assertThat((Integer) stats.get(1).get("uplift"), is(3)); + assertThat(stats.get(2).get("_id").toString(), is("A")); + assertThat((Integer) stats.get(2).get("uplift"), is(1)); + } + + /** + * @see Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation + */ + @Test // DATAMONGO-753 + public void aliasesNestedFieldInProjectionImmediately() { + + mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); + mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("B", 1), new PD("B", 1), new PD("C", 1))); + + TypedAggregation agg = newAggregation(DATAMONGO753.class, // + unwind("pd"), // + project().and("pd.up").as("up")); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + List mappedResults = results.getMappedResults(); + + assertThat(mappedResults, hasSize(6)); + for (DBObject element : mappedResults) { + assertThat(element.get("up"), is((Object) 1)); + } + } + + @Test // DATAMONGO-774 + public void shouldPerformDateProjectionOperatorsCorrectly() throws ParseException { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); + + Data data = new Data(); + data.stringValue = "ABC"; + mongoTemplate.insert(data); + + TypedAggregation agg = newAggregation(Data.class, + project() // + .andExpression("concat(stringValue, 'DE')").as("concat") // + .andExpression("strcasecmp(stringValue,'XYZ')").as("strcasecmp") // + .andExpression("substr(stringValue,1,1)").as("substr") // + .andExpression("toLower(stringValue)").as("toLower") // + .andExpression("toUpper(toLower(stringValue))").as("toUpper") // + ); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + DBObject dbo = results.getUniqueMappedResult(); + + assertThat(dbo, is(notNullValue())); + assertThat((String) dbo.get("concat"), is("ABCDE")); + assertThat((Integer) dbo.get("strcasecmp"), is(-1)); + assertThat((String) dbo.get("substr"), is("B")); + assertThat((String) dbo.get("toLower"), is("abc")); + assertThat((String) dbo.get("toUpper"), is("ABC")); + } + + @Test // DATAMONGO-774 + public void shouldPerformStringProjectionOperatorsCorrectly() throws ParseException { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); + + Data data = new Data(); + data.dateValue = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSSZ").parse("29.08.1983 12:34:56.789+0000"); + mongoTemplate.insert(data); + + TypedAggregation agg = newAggregation(Data.class, + project() // + .andExpression("dayOfYear(dateValue)").as("dayOfYear") // + .andExpression("dayOfMonth(dateValue)").as("dayOfMonth") // + .andExpression("dayOfWeek(dateValue)").as("dayOfWeek") // + .andExpression("year(dateValue)").as("year") // + .andExpression("month(dateValue)").as("month") // + .andExpression("week(dateValue)").as("week") // + .andExpression("hour(dateValue)").as("hour") // + .andExpression("minute(dateValue)").as("minute") // + .andExpression("second(dateValue)").as("second") // + .andExpression("millisecond(dateValue)").as("millisecond") // + ); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + DBObject dbo = results.getUniqueMappedResult(); + + assertThat(dbo, is(notNullValue())); + assertThat((Integer) dbo.get("dayOfYear"), is(241)); + assertThat((Integer) dbo.get("dayOfMonth"), is(29)); + assertThat((Integer) dbo.get("dayOfWeek"), is(2)); + assertThat((Integer) dbo.get("year"), is(1983)); + assertThat((Integer) dbo.get("month"), is(8)); + assertThat((Integer) dbo.get("week"), is(35)); + assertThat((Integer) dbo.get("hour"), is(12)); + assertThat((Integer) dbo.get("minute"), is(34)); + assertThat((Integer) dbo.get("second"), is(56)); + assertThat((Integer) dbo.get("millisecond"), is(789)); + } + + @Test // DATAMONGO-1550 + public void shouldPerformReplaceRootOperatorCorrectly() throws ParseException { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Data data = new Data(); + DataItem dataItem = new DataItem(); + dataItem.primitiveIntValue = 42; + data.item = dataItem; + mongoTemplate.insert(data); + + TypedAggregation agg = newAggregation(Data.class, project("item"), // + replaceRoot("item"), // + project().and("primitiveIntValue").as("my_primitiveIntValue")); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + DBObject dbo = results.getUniqueMappedResult(); + + assertThat(dbo, is(notNullValue())); + assertThat((Integer) dbo.get("my_primitiveIntValue"), is(42)); + assertThat((Integer) dbo.keySet().size(), is(1)); + } + + @Test // DATAMONGO-788 + public void referencesToGroupIdsShouldBeRenderedProperly() { + + mongoTemplate.insert(new DATAMONGO788(1, 1)); + mongoTemplate.insert(new DATAMONGO788(1, 1)); + mongoTemplate.insert(new DATAMONGO788(1, 1)); + mongoTemplate.insert(new DATAMONGO788(2, 1)); + mongoTemplate.insert(new DATAMONGO788(2, 1)); + + AggregationOperation projectFirst = Aggregation.project("x", "y").and("xField").as("x").and("yField").as("y"); + AggregationOperation group = Aggregation.group("x", "y").count().as("xPerY"); + AggregationOperation project = Aggregation.project("xPerY", "x", "y").andExclude("_id"); + + TypedAggregation aggregation = Aggregation.newAggregation(DATAMONGO788.class, projectFirst, group, + project); + AggregationResults aggResults = mongoTemplate.aggregate(aggregation, DBObject.class); + List items = aggResults.getMappedResults(); + + assertThat(items.size(), is(2)); + assertThat((Integer) items.get(0).get("xPerY"), is(2)); + assertThat((Integer) items.get(0).get("x"), is(2)); + assertThat((Integer) items.get(0).get("y"), is(1)); + assertThat((Integer) items.get(1).get("xPerY"), is(3)); + assertThat((Integer) items.get(1).get("x"), is(1)); + assertThat((Integer) items.get(1).get("y"), is(1)); + } + + @Test // DATAMONGO-806 + public void shouldAllowGroupByIdFields() { + + mongoTemplate.dropCollection(User.class); + + LocalDateTime now = new LocalDateTime(); + + User user1 = new User("u1", new PushMessage("1", "aaa", now.toDate())); + User user2 = new User("u2", new PushMessage("2", "bbb", now.minusDays(2).toDate())); + User user3 = new User("u3", new PushMessage("3", "ccc", now.minusDays(1).toDate())); + + mongoTemplate.save(user1); + mongoTemplate.save(user2); + mongoTemplate.save(user3); + + Aggregation agg = newAggregation( // + project("id", "msgs"), // + unwind("msgs"), // + match(where("msgs.createDate").gt(now.minusDays(1).toDate())), // + group("id").push("msgs").as("msgs") // + ); + + AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); + + List mappedResults = results.getMappedResults(); + + DBObject firstItem = mappedResults.get(0); + assertThat(firstItem.get("_id"), is(notNullValue())); + assertThat(String.valueOf(firstItem.get("_id")), is("u1")); + } - LineItem product1 = new LineItem("1", "p1", 1.23); - LineItem product2 = new LineItem("2", "p2", 0.87, 2); - LineItem product3 = new LineItem("3", "p3", 5.33); + @Test // DATAMONGO-840 + public void shouldAggregateOrderDataToAnInvoice() { - Order order = new Order("o4711", "c42", new Date()).addItem(product1).addItem(product2).addItem(product3); + mongoTemplate.dropCollection(Order.class); - mongoTemplate.save(order); + double taxRate = 0.19; - AggregationResults results = mongoTemplate.aggregate(newAggregation(Order.class, // - match(where("id").is(order.getId())), unwind("items"), // - project("id", "customerId", "items") // - .andExpression("items.price * items.quantity").as("lineTotal"), // - group("id") // - .sum("lineTotal").as("netAmount") // - .addToSet("items").as("items"), // - project("id", "items", "netAmount") // - .and("orderId").previousOperation() // - .andExpression("netAmount * [0]", taxRate).as("taxAmount") // - .andExpression("netAmount * (1 + [0])", taxRate).as("totalAmount") // - ), Invoice.class); + LineItem product1 = new LineItem("1", "p1", 1.23); + LineItem product2 = new LineItem("2", "p2", 0.87, 2); + LineItem product3 = new LineItem("3", "p3", 5.33); - Invoice invoice = results.getUniqueMappedResult(); + Order order = new Order("o4711", "c42", new Date()).addItem(product1).addItem(product2).addItem(product3); - assertThat(invoice, is(notNullValue())); - assertThat(invoice.getOrderId(), is(order.getId())); - assertThat(invoice.getNetAmount(), is(closeTo(8.3, 000001))); - assertThat(invoice.getTaxAmount(), is(closeTo(1.577, 000001))); - assertThat(invoice.getTotalAmount(), is(closeTo(9.877, 000001))); - } + mongoTemplate.save(order); - @Test // DATAMONGO-924 - public void shouldAllowGroupingByAliasedFieldDefinedInFormerAggregationStage() { + AggregationResults results = mongoTemplate.aggregate(newAggregation(Order.class, // + match(where("id").is(order.getId())), unwind("items"), // + project("id", "customerId", "items") // + .andExpression("items.price * items.quantity").as("lineTotal"), // + group("id") // + .sum("lineTotal").as("netAmount") // + .addToSet("items").as("items"), // + project("id", "items", "netAmount") // + .and("orderId").previousOperation() // + .andExpression("netAmount * [0]", taxRate).as("taxAmount") // + .andExpression("netAmount * (1 + [0])", taxRate).as("totalAmount") // + ), Invoice.class); - mongoTemplate.dropCollection(CarPerson.class); + Invoice invoice = results.getUniqueMappedResult(); - CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000), - new CarDescriptor.Entry("MAKE1", "MODEL2", 2001), new CarDescriptor.Entry("MAKE2", "MODEL3", 2010), - new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); + assertThat(invoice, is(notNullValue())); + assertThat(invoice.getOrderId(), is(order.getId())); + assertThat(invoice.getNetAmount(), is(closeTo(8.3, 000001))); + assertThat(invoice.getTaxAmount(), is(closeTo(1.577, 000001))); + assertThat(invoice.getTotalAmount(), is(closeTo(9.877, 000001))); + } - CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); + @Test // DATAMONGO-924 + public void shouldAllowGroupingByAliasedFieldDefinedInFormerAggregationStage() { - CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2011)); + mongoTemplate.dropCollection(CarPerson.class); - mongoTemplate.save(person1); - mongoTemplate.save(person2); - mongoTemplate.save(person3); + CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000), + new CarDescriptor.Entry("MAKE1", "MODEL2", 2001), new CarDescriptor.Entry("MAKE2", "MODEL3", 2010), + new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); - TypedAggregation agg = Aggregation.newAggregation(CarPerson.class, - unwind("descriptors.carDescriptor.entries"), // - project() // - .and("descriptors.carDescriptor.entries.make").as("make") // - .and("descriptors.carDescriptor.entries.model").as("model") // - .and("firstName").as("firstName") // - .and("lastName").as("lastName"), // - group("make")); + CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2011)); - assertThat(result.getMappedResults(), hasSize(3)); - } + mongoTemplate.save(person1); + mongoTemplate.save(person2); + mongoTemplate.save(person3); - @Test // DATAMONGO-960 - public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabled() { + TypedAggregation agg = Aggregation.newAggregation(CarPerson.class, + unwind("descriptors.carDescriptor.entries"), // + project() // + .and("descriptors.carDescriptor.entries.make").as("make") // + .and("descriptors.carDescriptor.entries.model").as("model") // + .and("firstName").as("firstName") // + .and("lastName").as("lastName"), // + group("make")); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - createUserWithLikesDocuments(); + assertThat(result.getMappedResults(), hasSize(3)); + } - TypedAggregation agg = createUsersWithCommonLikesAggregation() // - .withOptions(newAggregationOptions().allowDiskUse(true).build()); + @Test // DATAMONGO-960 + public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabled() { - assertThat(agg, is(notNullValue())); - assertThat(agg.toString(), is(notNullValue())); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); - assertThat(result, is(notNullValue())); - assertThat(result.getMappedResults(), is(notNullValue())); - assertThat(result.getMappedResults().size(), is(5)); + createUserWithLikesDocuments(); - assertLikeStats(result.getMappedResults().get(0), "a", 4); - assertLikeStats(result.getMappedResults().get(1), "b", 2); - assertLikeStats(result.getMappedResults().get(2), "c", 4); - assertLikeStats(result.getMappedResults().get(3), "d", 2); - assertLikeStats(result.getMappedResults().get(4), "e", 3); - } + TypedAggregation agg = createUsersWithCommonLikesAggregation() // + .withOptions(newAggregationOptions().allowDiskUse(true).build()); - @Test // DATAMONGO-960 - public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithExplainOptionEnabled() { + assertThat(agg, is(notNullValue())); + assertThat(agg.toString(), is(notNullValue())); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); + assertThat(result, is(notNullValue())); + assertThat(result.getMappedResults(), is(notNullValue())); + assertThat(result.getMappedResults().size(), is(5)); - createUserWithLikesDocuments(); + assertLikeStats(result.getMappedResults().get(0), "a", 4); + assertLikeStats(result.getMappedResults().get(1), "b", 2); + assertLikeStats(result.getMappedResults().get(2), "c", 4); + assertLikeStats(result.getMappedResults().get(3), "d", 2); + assertLikeStats(result.getMappedResults().get(4), "e", 3); + } - TypedAggregation agg = createUsersWithCommonLikesAggregation() // - .withOptions(newAggregationOptions().explain(true).build()); + @Test // DATAMONGO-960 + public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabledWhileStreaming() { - AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - assertThat(result.getMappedResults(), is(empty())); + createUserWithLikesDocuments(); - DBObject rawResult = result.getRawResults(); + TypedAggregation agg = createUsersWithCommonLikesAggregation() // + .withOptions(newAggregationOptions().allowDiskUse(true).build()); - assertThat(rawResult, is(notNullValue())); - assertThat(rawResult.containsField("stages"), is(true)); - } + assertThat(agg, is(notNullValue())); + assertThat(agg.toString(), is(notNullValue())); - @Test // DATAMONGO-954 - public void shouldSupportReturningCurrentAggregationRoot() { + CloseableIterator iterator = mongoTemplate.aggregateStream(agg, LikeStats.class); + List result=new ArrayList(); + while (iterator.hasNext()){ + result.add(iterator.next()); + } + assertThat(result, is(notNullValue())); + assertThat(result, is(notNullValue())); + assertThat(result.size(), is(5)); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + assertLikeStats(result.get(0), "a", 4); + assertLikeStats(result.get(1), "b", 2); + assertLikeStats(result.get(2), "c", 4); + assertLikeStats(result.get(3), "d", 2); + assertLikeStats(result.get(4), "e", 3); + } - mongoTemplate.save(new Person("p1_first", "p1_last", 25)); - mongoTemplate.save(new Person("p2_first", "p2_last", 32)); - mongoTemplate.save(new Person("p3_first", "p3_last", 25)); - mongoTemplate.save(new Person("p4_first", "p4_last", 15)); - List personsWithAge25 = mongoTemplate.find(Query.query(where("age").is(25)), DBObject.class, - mongoTemplate.getCollectionName(Person.class)); + @Test // DATAMONGO-960 + public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithExplainOptionEnabled() { - Aggregation agg = newAggregation(group("age").push(Aggregation.ROOT).as("users")); - AggregationResults result = mongoTemplate.aggregate(agg, Person.class, DBObject.class); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - assertThat(result.getMappedResults(), hasSize(3)); - DBObject o = (DBObject) result.getMappedResults().get(2); + createUserWithLikesDocuments(); - assertThat(o.get("_id"), is((Object) 25)); - assertThat((List) o.get("users"), hasSize(2)); - assertThat((List) o.get("users"), is(contains(personsWithAge25.toArray()))); - } + TypedAggregation agg = createUsersWithCommonLikesAggregation() // + .withOptions(newAggregationOptions().explain(true).build()); - /** - * {@link http://stackoverflow.com/questions/24185987/using-root-inside-spring-data-mongodb-for-retrieving-whole-document} - */ - @Test // DATAMONGO-954 - public void shouldSupportReturningCurrentAggregationRootInReference() { + AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + assertThat(result.getMappedResults(), is(empty())); - mongoTemplate.save(new Reservation("0123", "42", 100)); - mongoTemplate.save(new Reservation("0360", "43", 200)); - mongoTemplate.save(new Reservation("0360", "44", 300)); + DBObject rawResult = result.getRawResults(); - Aggregation agg = newAggregation( // - match(where("hotelCode").is("0360")), // - sort(Direction.DESC, "confirmationNumber", "timestamp"), // - group("confirmationNumber") // - .first("timestamp").as("timestamp") // - .first(Aggregation.ROOT).as("reservationImage") // - ); - AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); + assertThat(rawResult, is(notNullValue())); + assertThat(rawResult.containsField("stages"), is(true)); + } - assertThat(result.getMappedResults(), hasSize(2)); - } + @Test // DATAMONGO-954 + public void shouldSupportReturningCurrentAggregationRoot() { - @Test // DATAMONGO-1549 - public void shouldApplyCountCorrectly() { + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + mongoTemplate.save(new Person("p1_first", "p1_last", 25)); + mongoTemplate.save(new Person("p2_first", "p2_last", 32)); + mongoTemplate.save(new Person("p3_first", "p3_last", 25)); + mongoTemplate.save(new Person("p4_first", "p4_last", 15)); - mongoTemplate.save(new Reservation("0123", "42", 100)); - mongoTemplate.save(new Reservation("0360", "43", 200)); - mongoTemplate.save(new Reservation("0360", "44", 300)); + List personsWithAge25 = mongoTemplate.find(Query.query(where("age").is(25)), DBObject.class, + mongoTemplate.getCollectionName(Person.class)); - Aggregation agg = newAggregation( // - count().as("documents"), // - project("documents") // - .andExpression("documents * 2").as("twice")); - AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); + Aggregation agg = newAggregation(group("age").push(Aggregation.ROOT).as("users")); + AggregationResults result = mongoTemplate.aggregate(agg, Person.class, DBObject.class); - assertThat(result.getMappedResults(), hasSize(1)); + assertThat(result.getMappedResults(), hasSize(3)); + DBObject o = (DBObject) result.getMappedResults().get(2); - DBObject dbObject = result.getMappedResults().get(0); - assertThat(dbObject, isBsonObject().containing("documents", 3).containing("twice", 6)); - } + assertThat(o.get("_id"), is((Object) 25)); + assertThat((List) o.get("users"), hasSize(2)); + assertThat((List) o.get("users"), is(contains(personsWithAge25.toArray()))); + } - @Test // DATAMONGO-975 - public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception { + /** + * {@link http://stackoverflow.com/questions/24185987/using-root-inside-spring-data-mongodb-for-retrieving-whole-document} + */ + @Test // DATAMONGO-954 + public void shouldSupportReturningCurrentAggregationRootInReference() { - mongoTemplate.dropCollection(ObjectWithDate.class); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - DateTime dateTime = new DateTime() // - .withYear(2014) // - .withMonthOfYear(2) // - .withDayOfMonth(7) // - .withTime(3, 4, 5, 6).toDateTime(DateTimeZone.UTC).toDateTimeISO(); + mongoTemplate.save(new Reservation("0123", "42", 100)); + mongoTemplate.save(new Reservation("0360", "43", 200)); + mongoTemplate.save(new Reservation("0360", "44", 300)); - ObjectWithDate owd = new ObjectWithDate(dateTime.toDate()); - mongoTemplate.insert(owd); + Aggregation agg = newAggregation( // + match(where("hotelCode").is("0360")), // + sort(Direction.DESC, "confirmationNumber", "timestamp"), // + group("confirmationNumber") // + .first("timestamp").as("timestamp") // + .first(Aggregation.ROOT).as("reservationImage") // + ); + AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); - ProjectionOperation dateProjection = Aggregation.project() // - .and("dateValue").extractHour().as("hour") // - .and("dateValue").extractMinute().as("min") // - .and("dateValue").extractSecond().as("second") // - .and("dateValue").extractMillisecond().as("millis") // - .and("dateValue").extractYear().as("year") // - .and("dateValue").extractMonth().as("month") // - .and("dateValue").extractWeek().as("week") // - .and("dateValue").extractDayOfYear().as("dayOfYear") // - .and("dateValue").extractDayOfMonth().as("dayOfMonth") // - .and("dateValue").extractDayOfWeek().as("dayOfWeek") // - .andExpression("dateValue + 86400000").extractDayOfYear().as("dayOfYearPlus1Day") // - .andExpression("dateValue + 86400000").project("dayOfYear").as("dayOfYearPlus1DayManually") // - ; + assertThat(result.getMappedResults(), hasSize(2)); + } - Aggregation agg = newAggregation(dateProjection); - AggregationResults result = mongoTemplate.aggregate(agg, ObjectWithDate.class, DBObject.class); + @Test // DATAMONGO-1549 + public void shouldApplyCountCorrectly() { - assertThat(result.getMappedResults(), hasSize(1)); - DBObject dbo = result.getMappedResults().get(0); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - assertThat(dbo.get("hour"), is((Object) dateTime.getHourOfDay())); - assertThat(dbo.get("min"), is((Object) dateTime.getMinuteOfHour())); - assertThat(dbo.get("second"), is((Object) dateTime.getSecondOfMinute())); - assertThat(dbo.get("millis"), is((Object) dateTime.getMillisOfSecond())); - assertThat(dbo.get("year"), is((Object) dateTime.getYear())); - assertThat(dbo.get("month"), is((Object) dateTime.getMonthOfYear())); - // dateTime.getWeekOfWeekyear()) returns 6 since for MongoDB the week starts on sunday and not on monday. - assertThat(dbo.get("week"), is((Object) 5)); - assertThat(dbo.get("dayOfYear"), is((Object) dateTime.getDayOfYear())); - assertThat(dbo.get("dayOfMonth"), is((Object) dateTime.getDayOfMonth())); + mongoTemplate.save(new Reservation("0123", "42", 100)); + mongoTemplate.save(new Reservation("0360", "43", 200)); + mongoTemplate.save(new Reservation("0360", "44", 300)); - // dateTime.getDayOfWeek() - assertThat(dbo.get("dayOfWeek"), is((Object) 6)); - assertThat(dbo.get("dayOfYearPlus1Day"), is((Object) dateTime.plusDays(1).getDayOfYear())); - assertThat(dbo.get("dayOfYearPlus1DayManually"), is((Object) dateTime.plusDays(1).getDayOfYear())); - } + Aggregation agg = newAggregation( // + count().as("documents"), // + project("documents") // + .andExpression("documents * 2").as("twice")); + AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); - @Test // DATAMONGO-1127 - public void shouldSupportGeoNearQueriesForAggregationWithDistanceField() { + assertThat(result.getMappedResults(), hasSize(1)); - mongoTemplate.insert(new Venue("Penn Station", -73.99408, 40.75057)); - mongoTemplate.insert(new Venue("10gen Office", -73.99171, 40.738868)); - mongoTemplate.insert(new Venue("Flatiron Building", -73.988135, 40.741404)); + DBObject dbObject = result.getMappedResults().get(0); + assertThat(dbObject, isBsonObject().containing("documents", 3).containing("twice", 6)); + } - mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location")); + @Test // DATAMONGO-975 + public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception { - NearQuery geoNear = NearQuery.near(-73, 40, Metrics.KILOMETERS).num(10).maxDistance(150); + mongoTemplate.dropCollection(ObjectWithDate.class); - Aggregation agg = newAggregation(Aggregation.geoNear(geoNear, "distance")); - AggregationResults result = mongoTemplate.aggregate(agg, Venue.class, DBObject.class); + DateTime dateTime = new DateTime() // + .withYear(2014) // + .withMonthOfYear(2) // + .withDayOfMonth(7) // + .withTime(3, 4, 5, 6).toDateTime(DateTimeZone.UTC).toDateTimeISO(); - assertThat(result.getMappedResults(), hasSize(3)); + ObjectWithDate owd = new ObjectWithDate(dateTime.toDate()); + mongoTemplate.insert(owd); - DBObject firstResult = result.getMappedResults().get(0); - assertThat(firstResult.containsField("distance"), is(true)); - assertThat((Double) firstResult.get("distance"), closeTo(117.620092203928, 0.00001)); - } + ProjectionOperation dateProjection = Aggregation.project() // + .and("dateValue").extractHour().as("hour") // + .and("dateValue").extractMinute().as("min") // + .and("dateValue").extractSecond().as("second") // + .and("dateValue").extractMillisecond().as("millis") // + .and("dateValue").extractYear().as("year") // + .and("dateValue").extractMonth().as("month") // + .and("dateValue").extractWeek().as("week") // + .and("dateValue").extractDayOfYear().as("dayOfYear") // + .and("dateValue").extractDayOfMonth().as("dayOfMonth") // + .and("dateValue").extractDayOfWeek().as("dayOfWeek") // + .andExpression("dateValue + 86400000").extractDayOfYear().as("dayOfYearPlus1Day") // + .andExpression("dateValue + 86400000").project("dayOfYear").as("dayOfYearPlus1DayManually") // + ; - @Test // DATAMONGO-1133 - public void shouldHonorFieldAliasesForFieldReferences() { + Aggregation agg = newAggregation(dateProjection); + AggregationResults result = mongoTemplate.aggregate(agg, ObjectWithDate.class, DBObject.class); - mongoTemplate.insert(new MeterData("m1", "counter1", 42)); - mongoTemplate.insert(new MeterData("m1", "counter1", 13)); - mongoTemplate.insert(new MeterData("m1", "counter1", 45)); + assertThat(result.getMappedResults(), hasSize(1)); + DBObject dbo = result.getMappedResults().get(0); - TypedAggregation agg = newAggregation(MeterData.class, // - match(where("resourceId").is("m1")), // - group("counterName").sum("counterVolume").as("totalValue")); + assertThat(dbo.get("hour"), is((Object) dateTime.getHourOfDay())); + assertThat(dbo.get("min"), is((Object) dateTime.getMinuteOfHour())); + assertThat(dbo.get("second"), is((Object) dateTime.getSecondOfMinute())); + assertThat(dbo.get("millis"), is((Object) dateTime.getMillisOfSecond())); + assertThat(dbo.get("year"), is((Object) dateTime.getYear())); + assertThat(dbo.get("month"), is((Object) dateTime.getMonthOfYear())); + // dateTime.getWeekOfWeekyear()) returns 6 since for MongoDB the week starts on sunday and not on monday. + assertThat(dbo.get("week"), is((Object) 5)); + assertThat(dbo.get("dayOfYear"), is((Object) dateTime.getDayOfYear())); + assertThat(dbo.get("dayOfMonth"), is((Object) dateTime.getDayOfMonth())); - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + // dateTime.getDayOfWeek() + assertThat(dbo.get("dayOfWeek"), is((Object) 6)); + assertThat(dbo.get("dayOfYearPlus1Day"), is((Object) dateTime.plusDays(1).getDayOfYear())); + assertThat(dbo.get("dayOfYearPlus1DayManually"), is((Object) dateTime.plusDays(1).getDayOfYear())); + } - assertThat(results.getMappedResults(), hasSize(1)); - DBObject result = results.getMappedResults().get(0); + @Test // DATAMONGO-1127 + public void shouldSupportGeoNearQueriesForAggregationWithDistanceField() { - assertThat(result.get("_id"), is(equalTo((Object) "counter1"))); - assertThat(result.get("totalValue"), is(equalTo((Object) 100.0))); - } + mongoTemplate.insert(new Venue("Penn Station", -73.99408, 40.75057)); + mongoTemplate.insert(new Venue("10gen Office", -73.99171, 40.738868)); + mongoTemplate.insert(new Venue("Flatiron Building", -73.988135, 40.741404)); - @Test // DATAMONGO-1326 - public void shouldLookupPeopleCorectly() { + mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location")); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + NearQuery geoNear = NearQuery.near(-73, 40, Metrics.KILOMETERS).num(10).maxDistance(150); - createUsersWithReferencedPersons(); + Aggregation agg = newAggregation(Aggregation.geoNear(geoNear, "distance")); + AggregationResults result = mongoTemplate.aggregate(agg, Venue.class, DBObject.class); - TypedAggregation agg = newAggregation(User.class, // - lookup("person", "_id", "firstname", "linkedPerson"), // - sort(ASC, "id")); + assertThat(result.getMappedResults(), hasSize(3)); - AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); + DBObject firstResult = result.getMappedResults().get(0); + assertThat(firstResult.containsField("distance"), is(true)); + assertThat((Double) firstResult.get("distance"), closeTo(117.620092203928, 0.00001)); + } - List mappedResults = results.getMappedResults(); + @Test // DATAMONGO-1133 + public void shouldHonorFieldAliasesForFieldReferences() { - DBObject firstItem = mappedResults.get(0); + mongoTemplate.insert(new MeterData("m1", "counter1", 42)); + mongoTemplate.insert(new MeterData("m1", "counter1", 13)); + mongoTemplate.insert(new MeterData("m1", "counter1", 45)); - assertThat(firstItem, isBsonObject().containing("_id", "u1")); - assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); - } + TypedAggregation agg = newAggregation(MeterData.class, // + match(where("resourceId").is("m1")), // + group("counterName").sum("counterVolume").as("totalValue")); - @Test // DATAMONGO-1326 - public void shouldGroupByAndLookupPeopleCorectly() { + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + assertThat(results.getMappedResults(), hasSize(1)); + DBObject result = results.getMappedResults().get(0); - createUsersWithReferencedPersons(); + assertThat(result.get("_id"), is(equalTo((Object) "counter1"))); + assertThat(result.get("totalValue"), is(equalTo((Object) 100.0))); + } - TypedAggregation agg = newAggregation(User.class, // - group().min("id").as("foreignKey"), // - lookup("person", "foreignKey", "firstname", "linkedPerson"), // - sort(ASC, "foreignKey", "linkedPerson.firstname")); + @Test // DATAMONGO-1326 + public void shouldLookupPeopleCorectly() { - AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - List mappedResults = results.getMappedResults(); + createUsersWithReferencedPersons(); - DBObject firstItem = mappedResults.get(0); + TypedAggregation agg = newAggregation(User.class, // + lookup("person", "_id", "firstname", "linkedPerson"), // + sort(ASC, "id")); - assertThat(firstItem, isBsonObject().containing("foreignKey", "u1")); - assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); - } + AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); - @Test // DATAMONGO-1418 - public void shouldCreateOutputCollection() { + List mappedResults = results.getMappedResults(); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + DBObject firstItem = mappedResults.get(0); - mongoTemplate.save(new Person("Anna", "Ivanova", 21, Person.Sex.FEMALE)); - mongoTemplate.save(new Person("Pavel", "Sidorov", 36, Person.Sex.MALE)); - mongoTemplate.save(new Person("Anastasia", "Volochkova", 29, Person.Sex.FEMALE)); - mongoTemplate.save(new Person("Igor", "Stepanov", 31, Person.Sex.MALE)); - mongoTemplate.save(new Person("Leoniv", "Yakubov", 55, Person.Sex.MALE)); + assertThat(firstItem, isBsonObject().containing("_id", "u1")); + assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); + } - String tempOutCollection = "personQueryTemp"; - TypedAggregation agg = newAggregation(Person.class, // - group("sex").count().as("count"), // - sort(DESC, "count"), // - out(tempOutCollection)); + @Test // DATAMONGO-1326 + public void shouldGroupByAndLookupPeopleCorectly() { - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - assertThat(results.getMappedResults(), is(empty())); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); + createUsersWithReferencedPersons(); - assertThat(list, hasSize(2)); - assertThat(list.get(0), isBsonObject().containing("_id", "MALE").containing("count", 3)); - assertThat(list.get(1), isBsonObject().containing("_id", "FEMALE").containing("count", 2)); + TypedAggregation agg = newAggregation(User.class, // + group().min("id").as("foreignKey"), // + lookup("person", "foreignKey", "firstname", "linkedPerson"), // + sort(ASC, "foreignKey", "linkedPerson.firstname")); - mongoTemplate.dropCollection(tempOutCollection); - } + AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); - @Test(expected = IllegalArgumentException.class) // DATAMONGO-1418 - public void outShouldOutBeTheLastOperation() { + List mappedResults = results.getMappedResults(); - newAggregation(match(new Criteria()), // - group("field1").count().as("totalCount"), // - out("collection1"), // - skip(100L)); - } + DBObject firstItem = mappedResults.get(0); - @Test // DATAMONGO-1457 - public void sliceShouldBeAppliedCorrectly() { + assertThat(firstItem, isBsonObject().containing("foreignKey", "u1")); + assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); + } - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + @Test // DATAMONGO-1418 + public void shouldCreateOutputCollection() { - createUserWithLikesDocuments(); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - TypedAggregation agg = newAggregation(UserWithLikes.class, match(new Criteria()), - project().and("likes").slice(2)); + mongoTemplate.save(new Person("Anna", "Ivanova", 21, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Pavel", "Sidorov", 36, Person.Sex.MALE)); + mongoTemplate.save(new Person("Anastasia", "Volochkova", 29, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Igor", "Stepanov", 31, Person.Sex.MALE)); + mongoTemplate.save(new Person("Leoniv", "Yakubov", 55, Person.Sex.MALE)); - AggregationResults result = mongoTemplate.aggregate(agg, UserWithLikes.class); + String tempOutCollection = "personQueryTemp"; + TypedAggregation agg = newAggregation(Person.class, // + group("sex").count().as("count"), // + sort(DESC, "count"), // + out(tempOutCollection)); - assertThat(result.getMappedResults(), hasSize(9)); - for (UserWithLikes user : result) { - assertThat(user.likes.size() <= 2, is(true)); - } - } + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + assertThat(results.getMappedResults(), is(empty())); - @Test // DATAMONGO-1491 - public void filterShouldBeAppliedCorrectly() { + List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + assertThat(list, hasSize(2)); + assertThat(list.get(0), isBsonObject().containing("_id", "MALE").containing("count", 3)); + assertThat(list.get(1), isBsonObject().containing("_id", "FEMALE").containing("count", 2)); - Item item43 = Item.builder().itemId("43").quantity(2).price(2L).build(); - Item item2 = Item.builder().itemId("2").quantity(1).price(240L).build(); - Sales sales1 = Sales.builder().id("0") - .items(Arrays.asList( // - item43, item2)) // - .build(); + mongoTemplate.dropCollection(tempOutCollection); + } - Item item23 = Item.builder().itemId("23").quantity(3).price(110L).build(); - Item item103 = Item.builder().itemId("103").quantity(4).price(5L).build(); - Item item38 = Item.builder().itemId("38").quantity(1).price(300L).build(); - Sales sales2 = Sales.builder().id("1").items(Arrays.asList( // - item23, item103, item38)).build(); + @Test // DATAMONGO-1418 + public void shouldCreateOutputCollectionWhileStreaming() { - Item item4 = Item.builder().itemId("4").quantity(1).price(23L).build(); - Sales sales3 = Sales.builder().id("2").items(Arrays.asList( // - item4)).build(); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - mongoTemplate.insert(Arrays.asList(sales1, sales2, sales3), Sales.class); + mongoTemplate.save(new Person("Anna", "Ivanova", 21, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Pavel", "Sidorov", 36, Person.Sex.MALE)); + mongoTemplate.save(new Person("Anastasia", "Volochkova", 29, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Igor", "Stepanov", 31, Person.Sex.MALE)); + mongoTemplate.save(new Person("Leoniv", "Yakubov", 55, Person.Sex.MALE)); - TypedAggregation agg = newAggregation(Sales.class, project().and("items") - .filter("item", AggregationFunctionExpressions.GTE.of(field("item.price"), 100)).as("items")); + String tempOutCollection = "personQueryTemp"; + TypedAggregation agg = newAggregation(Person.class, // + group("sex").count().as("count"), // + sort(DESC, "count"), // + out(tempOutCollection)); - assertThat(mongoTemplate.aggregate(agg, Sales.class).getMappedResults(), - contains(Sales.builder().id("0").items(Collections.singletonList(item2)).build(), - Sales.builder().id("1").items(Arrays.asList(item23, item38)).build(), - Sales.builder().id("2").items(Collections. emptyList()).build())); - } + CloseableIterator iterator = mongoTemplate.aggregateStream(agg, DBObject.class); - @Test // DATAMONGO-1538 - public void letShouldBeAppliedCorrectly() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); - Sales2 sales1 = Sales2.builder().id("1").price(10).tax(0.5F).applyDiscount(true).build(); - Sales2 sales2 = Sales2.builder().id("2").price(10).tax(0.25F).applyDiscount(false).build(); + assertThat(list, hasSize(2)); + assertThat(list.get(0), isBsonObject().containing("_id", "MALE").containing("count", 3)); + assertThat(list.get(1), isBsonObject().containing("_id", "FEMALE").containing("count", 2)); - mongoTemplate.insert(Arrays.asList(sales1, sales2), Sales2.class); + mongoTemplate.dropCollection(tempOutCollection); + } - ExpressionVariable total = ExpressionVariable.newVariable("total") - .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))); - ExpressionVariable discounted = ExpressionVariable.newVariable("discounted") - .forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1418 + public void outShouldOutBeTheLastOperation() { - TypedAggregation agg = Aggregation.newAggregation(Sales2.class, - Aggregation.project() - .and(VariableOperators.Let.define(total, discounted).andApply( - AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) - .as("finalTotal")); + newAggregation(match(new Criteria()), // + group("field1").count().as("totalCount"), // + out("collection1"), // + skip(100L)); + } - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - assertThat(result.getMappedResults(), - contains(new BasicDBObjectBuilder().add("_id", "1").add("finalTotal", 9.450000000000001D).get(), - new BasicDBObjectBuilder().add("_id", "2").add("finalTotal", 10.25D).get())); - } + @Test // DATAMONGO-1457 + public void sliceShouldBeAppliedCorrectly() { - @Test // DATAMONGO-1551 - public void graphLookupShouldBeAppliedCorrectly() { + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + createUserWithLikesDocuments(); - Employee em1 = Employee.builder().id(1).name("Dev").build(); - Employee em2 = Employee.builder().id(2).name("Eliot").reportsTo("Dev").build(); - Employee em4 = Employee.builder().id(4).name("Andrew").reportsTo("Eliot").build(); + TypedAggregation agg = newAggregation(UserWithLikes.class, match(new Criteria()), + project().and("likes").slice(2)); - mongoTemplate.insert(Arrays.asList(em1, em2, em4), Employee.class); + AggregationResults result = mongoTemplate.aggregate(agg, UserWithLikes.class); - TypedAggregation agg = Aggregation.newAggregation(Employee.class, - match(Criteria.where("name").is("Andrew")), // - Aggregation.graphLookup("employee") // - .startWith("reportsTo") // - .connectFrom("reportsTo") // - .connectTo("name") // - .depthField("depth") // - .maxDepth(5) // - .as("reportingHierarchy")); + assertThat(result.getMappedResults(), hasSize(9)); + for (UserWithLikes user : result) { + assertThat(user.likes.size() <= 2, is(true)); + } + } - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + @Test // DATAMONGO-1491 + public void filterShouldBeAppliedCorrectly() { - DBObject object = result.getUniqueMappedResult(); - BasicDBList list = (BasicDBList) object.get("reportingHierarchy"); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - assertThat(object, isBsonObject().containing("reportingHierarchy", List.class)); - assertThat((DBObject) list.get(0), isBsonObject().containing("name", "Dev").containing("depth", 1L)); - assertThat((DBObject) list.get(1), isBsonObject().containing("name", "Eliot").containing("depth", 0L)); - } + Item item43 = Item.builder().itemId("43").quantity(2).price(2L).build(); + Item item2 = Item.builder().itemId("2").quantity(1).price(240L).build(); + Sales sales1 = Sales.builder().id("0") + .items(Arrays.asList( // + item43, item2)) // + .build(); - @Test // DATAMONGO-1552 - public void bucketShouldCollectDocumentsIntoABucket() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - - Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); - Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); - Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); - Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); - - mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); - - TypedAggregation aggregation = newAggregation(Art.class, // - bucket("price") // - .withBoundaries(0, 100, 200) // - .withDefaultBucket("other") // - .andOutputCount().as("count") // - .andOutput("title").push().as("titles") // - .andOutputExpression("price * 10").sum().as("sum")); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(3)); - - // { "_id" : 0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} - DBObject bound0 = result.getMappedResults().get(0); - assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer")); - assertThat((Double) bound0.get("sum"), is(closeTo(760.40, 0.1))); - - // { "_id" : 100 , "count" : 2 , "titles" : [ "The Pillars of Society" , "The Great Wave off Kanagawa"] , "sum" : - // 3672.9} - DBObject bound100 = result.getMappedResults().get(1); - assertThat(bound100, isBsonObject().containing("count", 2).containing("_id", 100)); - assertThat((List) bound100.get("titles"), - hasItems("The Pillars of Society", "The Great Wave off Kanagawa")); - assertThat((Double) bound100.get("sum"), is(closeTo(3672.9, 0.1))); - } - - @Test // DATAMONGO-1552 - public void bucketAutoShouldCollectDocumentsIntoABucket() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - - Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); - Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); - Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); - Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); - - mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); - - TypedAggregation aggregation = newAggregation(Art.class, // - bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // - .withGranularity(Granularities.E12) // - .andOutputCount().as("count") // - .andOutput("title").push().as("titles") // - .andOutputExpression("price * 10").sum().as("sum")); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(3)); - - // { "min" : 680.0 , "max" : 820.0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} - DBObject bound0 = result.getMappedResults().get(0); - assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer").containing("min", 680.0) - .containing("max")); - - // { "min" : 820.0 , "max" : 1800.0 , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : 1673.0} - DBObject bound1 = result.getMappedResults().get(1); - assertThat(bound1, isBsonObject().containing("count", 1).containing("min", 820.0)); - assertThat((List) bound1.get("titles"), hasItems("The Great Wave off Kanagawa")); - assertThat((Double) bound1.get("sum"), is(closeTo(1673.0, 0.1))); - } - - @Test // DATAMONGO-1552 - public void facetShouldCreateFacets() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - - Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); - Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); - Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); - Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); - - mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); - - BucketAutoOperation bucketPrice = bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // - .withGranularity(Granularities.E12) // - .andOutputCount().as("count") // - .andOutput("title").push().as("titles") // - .andOutputExpression("price * 10") // - .sum().as("sum"); - - TypedAggregation aggregation = newAggregation(Art.class, // - project("title", "artist", "year", "price"), // - facet(bucketPrice).as("categorizeByPrice") // - .and(bucketAuto("year", 3)).as("categorizeByYear")); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(1)); - - DBObject mappedResult = result.getUniqueMappedResult(); - - // [ { "_id" : { "min" : 680.0 , "max" : 820.0} , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} - // , - // { "_id" : { "min" : 820.0 , "max" : 1800.0} , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : - // 1673.0} , - // { "_id" : { "min" : 1800.0 , "max" : 3300.0} , "count" : 2 , "titles" : [ "The Pillars of Society" , "Melancholy - // III"] , "sum" : 4799.9}] - BasicDBList categorizeByPrice = (BasicDBList) mappedResult.get("categorizeByPrice"); - assertThat(categorizeByPrice, hasSize(3)); - - // [ { "_id" : { "min" : null , "max" : 1902} , "count" : 1} , - // { "_id" : { "min" : 1902 , "max" : 1925} , "count" : 1} , - // { "_id" : { "min" : 1925 , "max" : 1926} , "count" : 2}] - BasicDBList categorizeByYear = (BasicDBList) mappedResult.get("categorizeByYear"); - assertThat(categorizeByYear, hasSize(3)); - } - - private void createUsersWithReferencedPersons() { - - mongoTemplate.dropCollection(User.class); - mongoTemplate.dropCollection(Person.class); - - User user1 = new User("u1"); - User user2 = new User("u2"); - User user3 = new User("u3"); - - mongoTemplate.save(user1); - mongoTemplate.save(user2); - mongoTemplate.save(user3); - - Person person1 = new Person("u1", "User 1"); - Person person2 = new Person("u2", "User 2"); - - mongoTemplate.save(person1); - mongoTemplate.save(person2); - mongoTemplate.save(user3); - } + Item item23 = Item.builder().itemId("23").quantity(3).price(110L).build(); + Item item103 = Item.builder().itemId("103").quantity(4).price(5L).build(); + Item item38 = Item.builder().itemId("38").quantity(1).price(300L).build(); + Sales sales2 = Sales.builder().id("1").items(Arrays.asList( // + item23, item103, item38)).build(); - private void assertLikeStats(LikeStats like, String id, long count) { - - assertThat(like, is(notNullValue())); - assertThat(like.id, is(id)); - assertThat(like.count, is(count)); - } - - private void createUserWithLikesDocuments() { - mongoTemplate.insert(new UserWithLikes("u1", new Date(), "a", "b", "c")); - mongoTemplate.insert(new UserWithLikes("u2", new Date(), "a")); - mongoTemplate.insert(new UserWithLikes("u3", new Date(), "b", "c")); - mongoTemplate.insert(new UserWithLikes("u4", new Date(), "c", "d", "e")); - mongoTemplate.insert(new UserWithLikes("u5", new Date(), "a", "e", "c")); - mongoTemplate.insert(new UserWithLikes("u6", new Date())); - mongoTemplate.insert(new UserWithLikes("u7", new Date(), "a")); - mongoTemplate.insert(new UserWithLikes("u8", new Date(), "x", "e")); - mongoTemplate.insert(new UserWithLikes("u9", new Date(), "y", "d")); - } - - private void createTagDocuments() { - - DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); - - coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); - coll.insert(createDocument("Doc2", "spring", "mongodb")); - coll.insert(createDocument("Doc3", "spring")); - } - - private static DBObject createDocument(String title, String... tags) { - - DBObject doc = new BasicDBObject("title", title); - List tagList = new ArrayList(); + Item item4 = Item.builder().itemId("4").quantity(1).price(23L).build(); + Sales sales3 = Sales.builder().id("2").items(Arrays.asList( // + item4)).build(); - for (String tag : tags) { - tagList.add(tag); - } + mongoTemplate.insert(Arrays.asList(sales1, sales2, sales3), Sales.class); - doc.put("tags", tagList); - return doc; - } + TypedAggregation agg = newAggregation(Sales.class, project().and("items") + .filter("item", AggregationFunctionExpressions.GTE.of(field("item.price"), 100)).as("items")); - private static void assertTagCount(String tag, int n, TagCount tagCount) { + assertThat(mongoTemplate.aggregate(agg, Sales.class).getMappedResults(), + contains(Sales.builder().id("0").items(Collections.singletonList(item2)).build(), + Sales.builder().id("1").items(Arrays.asList(item23, item38)).build(), + Sales.builder().id("2").items(Collections.emptyList()).build())); + } - assertThat(tagCount.getTag(), is(tag)); - assertThat(tagCount.getN(), is(n)); - } + @Test // DATAMONGO-1538 + public void letShouldBeAppliedCorrectly() { - static class DATAMONGO753 { - PD[] pd; + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - DATAMONGO753 withPDs(PD... pds) { - this.pd = pds; - return this; - } - } + Sales2 sales1 = Sales2.builder().id("1").price(10).tax(0.5F).applyDiscount(true).build(); + Sales2 sales2 = Sales2.builder().id("2").price(10).tax(0.25F).applyDiscount(false).build(); - static class PD { - String pDch; - @org.springframework.data.mongodb.core.mapping.Field("alias") int up; + mongoTemplate.insert(Arrays.asList(sales1, sales2), Sales2.class); - public PD(String pDch, int up) { - this.pDch = pDch; - this.up = up; - } - } + ExpressionVariable total = ExpressionVariable.newVariable("total") + .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))); + ExpressionVariable discounted = ExpressionVariable.newVariable("discounted") + .forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); - static class DATAMONGO788 { + TypedAggregation agg = Aggregation.newAggregation(Sales2.class, + Aggregation.project() + .and(VariableOperators.Let.define(total, discounted).andApply( + AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) + .as("finalTotal")); - int x; - int y; - int xField; - int yField; + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + assertThat(result.getMappedResults(), + contains(new BasicDBObjectBuilder().add("_id", "1").add("finalTotal", 9.450000000000001D).get(), + new BasicDBObjectBuilder().add("_id", "2").add("finalTotal", 10.25D).get())); + } - public DATAMONGO788() {} + @Test // DATAMONGO-1551 + public void graphLookupShouldBeAppliedCorrectly() { - public DATAMONGO788(int x, int y) { - this.x = x; - this.xField = x; - this.y = y; - this.yField = y; - } - } + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - // DATAMONGO-806 - static class User { + Employee em1 = Employee.builder().id(1).name("Dev").build(); + Employee em2 = Employee.builder().id(2).name("Eliot").reportsTo("Dev").build(); + Employee em4 = Employee.builder().id(4).name("Andrew").reportsTo("Eliot").build(); - @Id String id; - List msgs; + mongoTemplate.insert(Arrays.asList(em1, em2, em4), Employee.class); - public User() {} + TypedAggregation agg = Aggregation.newAggregation(Employee.class, + match(Criteria.where("name").is("Andrew")), // + Aggregation.graphLookup("employee") // + .startWith("reportsTo") // + .connectFrom("reportsTo") // + .connectTo("name") // + .depthField("depth") // + .maxDepth(5) // + .as("reportingHierarchy")); - public User(String id, PushMessage... msgs) { - this.id = id; - this.msgs = Arrays.asList(msgs); - } - } + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - // DATAMONGO-806 - static class PushMessage { + DBObject object = result.getUniqueMappedResult(); + BasicDBList list = (BasicDBList) object.get("reportingHierarchy"); - @Id String id; - String content; - Date createDate; + assertThat(object, isBsonObject().containing("reportingHierarchy", List.class)); + assertThat((DBObject) list.get(0), isBsonObject().containing("name", "Dev").containing("depth", 1L)); + assertThat((DBObject) list.get(1), isBsonObject().containing("name", "Eliot").containing("depth", 0L)); + } - public PushMessage() {} + @Test // DATAMONGO-1552 + public void bucketShouldCollectDocumentsIntoABucket() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + TypedAggregation aggregation = newAggregation(Art.class, // + bucket("price") // + .withBoundaries(0, 100, 200) // + .withDefaultBucket("other") // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10").sum().as("sum")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); + + // { "_id" : 0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + DBObject bound0 = result.getMappedResults().get(0); + assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer")); + assertThat((Double) bound0.get("sum"), is(closeTo(760.40, 0.1))); + + // { "_id" : 100 , "count" : 2 , "titles" : [ "The Pillars of Society" , "The Great Wave off Kanagawa"] , "sum" : + // 3672.9} + DBObject bound100 = result.getMappedResults().get(1); + assertThat(bound100, isBsonObject().containing("count", 2).containing("_id", 100)); + assertThat((List) bound100.get("titles"), + hasItems("The Pillars of Society", "The Great Wave off Kanagawa")); + assertThat((Double) bound100.get("sum"), is(closeTo(3672.9, 0.1))); + } + + @Test // DATAMONGO-1552 + public void bucketAutoShouldCollectDocumentsIntoABucket() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + TypedAggregation aggregation = newAggregation(Art.class, // + bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // + .withGranularity(Granularities.E12) // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10").sum().as("sum")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); + + // { "min" : 680.0 , "max" : 820.0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + DBObject bound0 = result.getMappedResults().get(0); + assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer").containing("min", 680.0) + .containing("max")); + + // { "min" : 820.0 , "max" : 1800.0 , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : 1673.0} + DBObject bound1 = result.getMappedResults().get(1); + assertThat(bound1, isBsonObject().containing("count", 1).containing("min", 820.0)); + assertThat((List) bound1.get("titles"), hasItems("The Great Wave off Kanagawa")); + assertThat((Double) bound1.get("sum"), is(closeTo(1673.0, 0.1))); + } + + @Test // DATAMONGO-1552 + public void facetShouldCreateFacets() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + BucketAutoOperation bucketPrice = bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // + .withGranularity(Granularities.E12) // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10") // + .sum().as("sum"); + + TypedAggregation aggregation = newAggregation(Art.class, // + project("title", "artist", "year", "price"), // + facet(bucketPrice).as("categorizeByPrice") // + .and(bucketAuto("year", 3)).as("categorizeByYear")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(1)); + + DBObject mappedResult = result.getUniqueMappedResult(); + + // [ { "_id" : { "min" : 680.0 , "max" : 820.0} , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + // , + // { "_id" : { "min" : 820.0 , "max" : 1800.0} , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : + // 1673.0} , + // { "_id" : { "min" : 1800.0 , "max" : 3300.0} , "count" : 2 , "titles" : [ "The Pillars of Society" , "Melancholy + // III"] , "sum" : 4799.9}] + BasicDBList categorizeByPrice = (BasicDBList) mappedResult.get("categorizeByPrice"); + assertThat(categorizeByPrice, hasSize(3)); + + // [ { "_id" : { "min" : null , "max" : 1902} , "count" : 1} , + // { "_id" : { "min" : 1902 , "max" : 1925} , "count" : 1} , + // { "_id" : { "min" : 1925 , "max" : 1926} , "count" : 2}] + BasicDBList categorizeByYear = (BasicDBList) mappedResult.get("categorizeByYear"); + assertThat(categorizeByYear, hasSize(3)); + } + + private void createUsersWithReferencedPersons() { + + mongoTemplate.dropCollection(User.class); + mongoTemplate.dropCollection(Person.class); + + User user1 = new User("u1"); + User user2 = new User("u2"); + User user3 = new User("u3"); + + mongoTemplate.save(user1); + mongoTemplate.save(user2); + mongoTemplate.save(user3); + + Person person1 = new Person("u1", "User 1"); + Person person2 = new Person("u2", "User 2"); + + mongoTemplate.save(person1); + mongoTemplate.save(person2); + mongoTemplate.save(user3); + } + + private void assertLikeStats(LikeStats like, String id, long count) { + + assertThat(like, is(notNullValue())); + assertThat(like.id, is(id)); + assertThat(like.count, is(count)); + } + + private void createUserWithLikesDocuments() { + mongoTemplate.insert(new UserWithLikes("u1", new Date(), "a", "b", "c")); + mongoTemplate.insert(new UserWithLikes("u2", new Date(), "a")); + mongoTemplate.insert(new UserWithLikes("u3", new Date(), "b", "c")); + mongoTemplate.insert(new UserWithLikes("u4", new Date(), "c", "d", "e")); + mongoTemplate.insert(new UserWithLikes("u5", new Date(), "a", "e", "c")); + mongoTemplate.insert(new UserWithLikes("u6", new Date())); + mongoTemplate.insert(new UserWithLikes("u7", new Date(), "a")); + mongoTemplate.insert(new UserWithLikes("u8", new Date(), "x", "e")); + mongoTemplate.insert(new UserWithLikes("u9", new Date(), "y", "d")); + } + + private void createTagDocuments() { + + DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); + + coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); + coll.insert(createDocument("Doc2", "spring", "mongodb")); + coll.insert(createDocument("Doc3", "spring")); + } + + private static DBObject createDocument(String title, String... tags) { + + DBObject doc = new BasicDBObject("title", title); + List tagList = new ArrayList(); + + for (String tag : tags) { + tagList.add(tag); + } + + doc.put("tags", tagList); + return doc; + } + + private static void assertTagCount(String tag, int n, TagCount tagCount) { + + assertThat(tagCount.getTag(), is(tag)); + assertThat(tagCount.getN(), is(n)); + } + + static class DATAMONGO753 { + PD[] pd; + + DATAMONGO753 withPDs(PD... pds) { + this.pd = pds; + return this; + } + } + + static class PD { + String pDch; + @org.springframework.data.mongodb.core.mapping.Field("alias") + int up; + + public PD(String pDch, int up) { + this.pDch = pDch; + this.up = up; + } + } + + static class DATAMONGO788 { + + int x; + int y; + int xField; + int yField; + + public DATAMONGO788() { + } - public PushMessage(String id, String content, Date createDate) { - this.id = id; - this.content = content; - this.createDate = createDate; - } - } + public DATAMONGO788(int x, int y) { + this.x = x; + this.xField = x; + this.y = y; + this.yField = y; + } + } + + // DATAMONGO-806 + static class User { + + @Id + String id; + List msgs; - @org.springframework.data.mongodb.core.mapping.Document - static class CarPerson { + public User() { + } + + public User(String id, PushMessage... msgs) { + this.id = id; + this.msgs = Arrays.asList(msgs); + } + } + + // DATAMONGO-806 + static class PushMessage { + + @Id + String id; + String content; + Date createDate; + + public PushMessage() { + } - @Id private String id; - private String firstName; - private String lastName; - private Descriptors descriptors; + public PushMessage(String id, String content, Date createDate) { + this.id = id; + this.content = content; + this.createDate = createDate; + } + } - public CarPerson(String firstname, String lastname, Entry... entries) { - this.firstName = firstname; - this.lastName = lastname; + @org.springframework.data.mongodb.core.mapping.Document + static class CarPerson { - this.descriptors = new Descriptors(); + @Id + private String id; + private String firstName; + private String lastName; + private Descriptors descriptors; - this.descriptors.carDescriptor = new CarDescriptor(entries); - } - } - - @SuppressWarnings("unused") - static class Descriptors { - private CarDescriptor carDescriptor; - } - - static class CarDescriptor { - - private List entries = new ArrayList(); - - public CarDescriptor(Entry... entries) { - - for (Entry entry : entries) { - this.entries.add(entry); - } - } - - @SuppressWarnings("unused") - static class Entry { - private String make; - private String model; - private int year; - - public Entry() {} - - public Entry(String make, String model, int year) { - this.make = make; - this.model = model; - this.year = year; - } - } - } - - static class Reservation { - - String hotelCode; - String confirmationNumber; - int timestamp; - - public Reservation() {} - - public Reservation(String hotelCode, String confirmationNumber, int timestamp) { - this.hotelCode = hotelCode; - this.confirmationNumber = confirmationNumber; - this.timestamp = timestamp; - } - } - - static class ObjectWithDate { - - Date dateValue; - - public ObjectWithDate(Date dateValue) { - this.dateValue = dateValue; - } - } - - // DATAMONGO-861 - @Document(collection = "inventory") - static class InventoryItem { - - int id; - String item; - String description; - int qty; - - public InventoryItem() {} - - public InventoryItem(int id, String item, int qty) { - - this.id = id; - this.item = item; - this.qty = qty; - } - - public InventoryItem(int id, String item, String description, int qty) { - - this.id = id; - this.item = item; - this.description = description; - this.qty = qty; - } - } - - // DATAMONGO-1491 - @lombok.Data - @Builder - static class Sales { - - @Id String id; - List items; - } - - // DATAMONGO-1491 - @lombok.Data - @Builder - static class Item { - - @org.springframework.data.mongodb.core.mapping.Field("item_id") // - String itemId; - Integer quantity; - Long price; - } - - // DATAMONGO-1538 - @lombok.Data - @Builder - static class Sales2 { - - String id; - Integer price; - Float tax; - boolean applyDiscount; - } - - // DATAMONGO-1551 - @lombok.Data - @Builder - static class Employee { - - int id; - String name; - String reportsTo; - } - - // DATAMONGO-1552 - @lombok.Data - @Builder - static class Art { - - int id; - String title; - String artist; - Integer year; - double price; - } + public CarPerson(String firstname, String lastname, Entry... entries) { + this.firstName = firstname; + this.lastName = lastname; + + this.descriptors = new Descriptors(); + + this.descriptors.carDescriptor = new CarDescriptor(entries); + } + } + + @SuppressWarnings("unused") + static class Descriptors { + private CarDescriptor carDescriptor; + } + + static class CarDescriptor { + + private List entries = new ArrayList(); + + public CarDescriptor(Entry... entries) { + + for (Entry entry : entries) { + this.entries.add(entry); + } + } + + @SuppressWarnings("unused") + static class Entry { + private String make; + private String model; + private int year; + + public Entry() { + } + + public Entry(String make, String model, int year) { + this.make = make; + this.model = model; + this.year = year; + } + } + } + + static class Reservation { + + String hotelCode; + String confirmationNumber; + int timestamp; + + public Reservation() { + } + + public Reservation(String hotelCode, String confirmationNumber, int timestamp) { + this.hotelCode = hotelCode; + this.confirmationNumber = confirmationNumber; + this.timestamp = timestamp; + } + } + + static class ObjectWithDate { + + Date dateValue; + + public ObjectWithDate(Date dateValue) { + this.dateValue = dateValue; + } + } + + // DATAMONGO-861 + @Document(collection = "inventory") + static class InventoryItem { + + int id; + String item; + String description; + int qty; + + public InventoryItem() { + } + + public InventoryItem(int id, String item, int qty) { + + this.id = id; + this.item = item; + this.qty = qty; + } + + public InventoryItem(int id, String item, String description, int qty) { + + this.id = id; + this.item = item; + this.description = description; + this.qty = qty; + } + } + + // DATAMONGO-1491 + @lombok.Data + @Builder + static class Sales { + + @Id + String id; + List items; + } + + // DATAMONGO-1491 + @lombok.Data + @Builder + static class Item { + + @org.springframework.data.mongodb.core.mapping.Field("item_id") // + String itemId; + Integer quantity; + Long price; + } + + // DATAMONGO-1538 + @lombok.Data + @Builder + static class Sales2 { + + String id; + Integer price; + Float tax; + boolean applyDiscount; + } + + // DATAMONGO-1551 + @lombok.Data + @Builder + static class Employee { + + int id; + String name; + String reportsTo; + } + + // DATAMONGO-1552 + @lombok.Data + @Builder + static class Art { + + int id; + String title; + String artist; + Integer year; + double price; + } } From 6ec6f039aa1c2cfb5633037e7f51482109317392 Mon Sep 17 00:00:00 2001 From: manindr Date: Fri, 3 Mar 2017 19:58:29 +0530 Subject: [PATCH 109/118] formatting --- .../data/mongodb/core/MongoTemplate.java | 95 +++++++++------ .../core/aggregation/AggregationTests.java | 115 +++++++----------- 2 files changed, 102 insertions(+), 108 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 6b569de3c6..6467945820 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -15,9 +15,6 @@ */ package org.springframework.data.mongodb.core; -import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.ALLOW_DISK_USE; -import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.CURSOR; -import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.EXPLAIN; import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.SerializationUtils.*; @@ -34,8 +31,6 @@ import java.util.Scanner; import java.util.Set; -import com.mongodb.*; -import com.mongodb.AggregationOptions; import org.bson.types.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,7 +61,12 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.BulkOperations.BulkMode; -import org.springframework.data.mongodb.core.aggregation.*; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.aggregation.Fields; +import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext; +import org.springframework.data.mongodb.core.aggregation.TypedAggregation; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; @@ -92,7 +92,11 @@ import org.springframework.data.mongodb.core.mapreduce.GroupByResults; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; import org.springframework.data.mongodb.core.mapreduce.MapReduceResults; -import org.springframework.data.mongodb.core.query.*; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Meta; +import org.springframework.data.mongodb.core.query.NearQuery; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.util.MongoClientVersion; import org.springframework.data.util.CloseableIterator; import org.springframework.jca.cci.core.ConnectionCallback; @@ -102,6 +106,21 @@ import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; +import com.mongodb.BasicDBObject; +import com.mongodb.Bytes; +import com.mongodb.CommandResult; +import com.mongodb.Cursor; +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.DBCursor; +import com.mongodb.DBObject; +import com.mongodb.MapReduceCommand; +import com.mongodb.MapReduceOutput; +import com.mongodb.Mongo; +import com.mongodb.MongoException; +import com.mongodb.ReadPreference; +import com.mongodb.WriteConcern; +import com.mongodb.WriteResult; import com.mongodb.util.JSON; import com.mongodb.util.JSONParseException; @@ -160,7 +179,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { /** * Constructor used for a basic template configuration * - * @param mongo must not be {@literal null}. + * @param mongo must not be {@literal null}. * @param databaseName must not be {@literal null} or empty. */ public MongoTemplate(Mongo mongo, String databaseName) { @@ -171,8 +190,8 @@ public MongoTemplate(Mongo mongo, String databaseName) { * Constructor used for a template configuration with user credentials in the form of * {@link org.springframework.data.authentication.UserCredentials} * - * @param mongo must not be {@literal null}. - * @param databaseName must not be {@literal null} or empty. + * @param mongo must not be {@literal null}. + * @param databaseName must not be {@literal null} or empty. * @param userCredentials */ public MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCredentials) { @@ -411,12 +430,12 @@ public void executeQuery(Query query, String collectionName, DocumentCallbackHan * Execute a MongoDB query and iterate over the query results on a per-document basis with a * {@link DocumentCallbackHandler} using the provided CursorPreparer. * - * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification, must not be {@literal null}. + * @param query the query class that specifies the criteria used to find a record and also an optional fields + * specification, must not be {@literal null}. * @param collectionName name of the collection to retrieve the objects from - * @param dch the handler that will extract results, one document at a time - * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply - * limits, skips and so on). + * @param dch the handler that will extract results, one document at a time + * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply + * limits, skips and so on). */ protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, CursorPreparer preparer) { @@ -679,7 +698,7 @@ public GeoResults geoNear(NearQuery near, Class entityClass, String co for (Object element : results) { /* - * As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries + * As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries * we skip the elements ourselves to avoid at least the document 2 object mapping overhead. * * @see MongoDB Jira: SERVER-3925 @@ -1324,7 +1343,7 @@ public WriteResult doInCollection(DBCollection collection) throws MongoException if (LOGGER.isDebugEnabled()) { LOGGER.debug("Remove using query: {} in collection: {}.", - new Object[]{serializeToJsonSafely(dboq), collectionName}); + new Object[] { serializeToJsonSafely(dboq), collectionName }); } WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) @@ -1522,8 +1541,8 @@ public CloseableIterator aggregateStream(Aggregation aggregation, String /* * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String) - */ + * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String) + */ @Override public List findAllAndRemove(Query query, String collectionName) { return findAndRemove(query, null, collectionName); @@ -1770,9 +1789,9 @@ public DBCollection doInDB(DB db) throws MongoException, DataAccessException { * The query document is specified as a standard {@link DBObject} and so is the fields specification. * * @param collectionName name of the collection to retrieve the objects from. - * @param query the query document that specifies the criteria used to find a record. - * @param fields the document that specifies the fields to be returned. - * @param entityClass the parameterized type of the returned list. + * @param query the query document that specifies the criteria used to find a record. + * @param fields the document that specifies the fields to be returned. + * @param entityClass the parameterized type of the returned list. * @return the {@link List} of converted objects. */ protected T doFindOne(String collectionName, DBObject query, DBObject fields, Class entityClass) { @@ -1795,9 +1814,9 @@ protected T doFindOne(String collectionName, DBObject query, DBObject fields * query document is specified as a standard DBObject and so is the fields specification. * * @param collectionName name of the collection to retrieve the objects from - * @param query the query document that specifies the criteria used to find a record - * @param fields the document that specifies the fields to be returned - * @param entityClass the parameterized type of the returned list. + * @param query the query document that specifies the criteria used to find a record + * @param fields the document that specifies the fields to be returned + * @param entityClass the parameterized type of the returned list. * @return the List of converted objects. */ protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass) { @@ -1811,11 +1830,11 @@ protected List doFind(String collectionName, DBObject query, DBObject fie * specified as a standard DBObject and so is the fields specification. * * @param collectionName name of the collection to retrieve the objects from. - * @param query the query document that specifies the criteria used to find a record. - * @param fields the document that specifies the fields to be returned. - * @param entityClass the parameterized type of the returned list. - * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply - * limits, skips and so on). + * @param query the query document that specifies the criteria used to find a record. + * @param fields the document that specifies the fields to be returned. + * @param entityClass the parameterized type of the returned list. + * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply + * limits, skips and so on). * @return the {@link List} of converted objects. */ protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, @@ -1864,8 +1883,8 @@ protected DBObject convertToDbObject(CollectionOptions collectionOptions) { * The query document is specified as a standard DBObject and so is the fields specification. * * @param collectionName name of the collection to retrieve the objects from - * @param query the query document that specifies the criteria used to find a record - * @param entityClass the parameterized type of the returned list. + * @param query the query document that specifies the criteria used to find a record + * @param entityClass the parameterized type of the returned list. * @return the List of converted objects. */ protected T doFindAndRemove(String collectionName, DBObject query, DBObject fields, DBObject sort, @@ -1966,8 +1985,8 @@ private DBCollection getAndPrepareCollection(DB db, String collectionName) { * * @param * @param collectionCallback the callback to retrieve the {@link DBObject} with - * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type - * @param collectionName the collection to be queried + * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type + * @param collectionName the collection to be queried * @return */ private T executeFindOneInternal(CollectionCallback collectionCallback, @@ -1995,9 +2014,9 @@ private T executeFindOneInternal(CollectionCallback collectionCall * * @param * @param collectionCallback the callback to retrieve the {@link DBCursor} with - * @param preparer the {@link CursorPreparer} to potentially modify the {@link DBCursor} before ireating over it - * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type - * @param collectionName the collection to be queried + * @param preparer the {@link CursorPreparer} to potentially modify the {@link DBCursor} before ireating over it + * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type + * @param collectionName the collection to be queried * @return */ private List executeFindMultiInternal(CollectionCallback collectionCallback, CursorPreparer preparer, @@ -2182,7 +2201,7 @@ private DBObject getMappedSortObject(Query query, Class type) { * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original * exception if the conversation failed. Thus allows safe re-throwing of the return value. * - * @param ex the exception to translate + * @param ex the exception to translate * @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation * @return */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 73077994dc..f12b111227 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -15,27 +15,9 @@ */ package org.springframework.data.mongodb.core.aggregation; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.junit.Assume.*; -import static org.springframework.data.domain.Sort.Direction.*; -import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; -import static org.springframework.data.mongodb.core.aggregation.Fields.*; -import static org.springframework.data.mongodb.core.query.Criteria.*; -import static org.springframework.data.mongodb.test.util.IsBsonObject.*; - +import com.mongodb.*; +import com.mongodb.util.JSON; import lombok.Builder; - -import java.io.BufferedInputStream; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Scanner; - import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDateTime; @@ -71,14 +53,20 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import com.mongodb.BasicDBList; -import com.mongodb.BasicDBObject; -import com.mongodb.BasicDBObjectBuilder; -import com.mongodb.CommandResult; -import com.mongodb.DBCollection; -import com.mongodb.DBObject; -import com.mongodb.MongoException; -import com.mongodb.util.JSON; +import java.io.BufferedInputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeTrue; +import static org.springframework.data.domain.Sort.Direction.ASC; +import static org.springframework.data.domain.Sort.Direction.DESC; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; +import static org.springframework.data.mongodb.core.aggregation.Fields.field; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.test.util.IsBsonObject.isBsonObject; /** * Tests for {@link MongoTemplate#aggregate(String, AggregationPipeline, Class)}. @@ -103,11 +91,9 @@ public class AggregationTests { private static boolean initialized = false; - @Autowired - MongoTemplate mongoTemplate; + @Autowired MongoTemplate mongoTemplate; - @Rule - public ExpectedException exception = ExpectedException.none(); + @Rule public ExpectedException exception = ExpectedException.none(); private static Version mongoVersion; @Before @@ -430,9 +416,9 @@ public void shouldDetectResultMismatchWhileStreaming() { @Test // DATAMONGO-586 public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { - /* + /* //complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state - db.zipInfo.aggregate( + db.zipInfo.aggregate( { $group: { _id: { @@ -539,18 +525,18 @@ public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { @Test // DATAMONGO-586 public void findStatesWithPopulationOver10MillionAggregationExample() { /* - //complex mongodb aggregation framework example from + //complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state - - db.zipcodes.aggregate( + + db.zipcodes.aggregate( { $group: { _id:"$state", totalPop:{ $sum:"$pop"} } }, - { - $sort: { _id: 1, "totalPop": 1 } + { + $sort: { _id: 1, "totalPop": 1 } }, { $match: { @@ -584,7 +570,7 @@ public void findStatesWithPopulationOver10MillionAggregationExample() { /** * @see MongoDB Aggregation - * Framework: $cond + * Framework: $cond */ @Test // DATAMONGO-861 public void aggregationUsingConditionalProjectionToCalculateDiscount() { @@ -637,7 +623,7 @@ public void aggregationUsingConditionalProjectionToCalculateDiscount() { /** * @see MongoDB Aggregation - * Framework: $ifNull + * Framework: $ifNull */ @Test // DATAMONGO-861 public void aggregationUsingIfNullToProjectSaneDefaults() { @@ -817,8 +803,8 @@ public void shouldAllowGroupingUsingConditionalExpressions() { /** * @see Return - * the Five Most Common “Likes” + * "https://docs.mongodb.com/manual/tutorial/aggregation-with-user-preference-data/#return-the-five-most-common-likes">Return + * the Five Most Common “Likes” */ @Test // DATAMONGO-586 public void returnFiveMostCommonLikesAggregationFrameworkExample() { @@ -991,8 +977,8 @@ public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressio /** * @see Spring - * Data MongoDB - Aggregation Framework - invalid reference in group Operation + * "http://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group-operati">Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation */ @Test // DATAMONGO-753 public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { @@ -1020,8 +1006,8 @@ public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { /** * @see Spring - * Data MongoDB - Aggregation Framework - invalid reference in group Operation + * "http://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group-operati">Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation */ @Test // DATAMONGO-753 public void aliasesNestedFieldInProjectionImmediately() { @@ -1654,7 +1640,7 @@ public void filterShouldBeAppliedCorrectly() { assertThat(mongoTemplate.aggregate(agg, Sales.class).getMappedResults(), contains(Sales.builder().id("0").items(Collections.singletonList(item2)).build(), Sales.builder().id("1").items(Arrays.asList(item23, item38)).build(), - Sales.builder().id("2").items(Collections.emptyList()).build())); + Sales.builder().id("2").items(Collections. emptyList()).build())); } @Test // DATAMONGO-1538 @@ -1910,8 +1896,7 @@ DATAMONGO753 withPDs(PD... pds) { static class PD { String pDch; - @org.springframework.data.mongodb.core.mapping.Field("alias") - int up; + @org.springframework.data.mongodb.core.mapping.Field("alias") int up; public PD(String pDch, int up) { this.pDch = pDch; @@ -1926,8 +1911,7 @@ static class DATAMONGO788 { int xField; int yField; - public DATAMONGO788() { - } + public DATAMONGO788() {} public DATAMONGO788(int x, int y) { this.x = x; @@ -1940,12 +1924,10 @@ public DATAMONGO788(int x, int y) { // DATAMONGO-806 static class User { - @Id - String id; + @Id String id; List msgs; - public User() { - } + public User() {} public User(String id, PushMessage... msgs) { this.id = id; @@ -1956,13 +1938,11 @@ public User(String id, PushMessage... msgs) { // DATAMONGO-806 static class PushMessage { - @Id - String id; + @Id String id; String content; Date createDate; - public PushMessage() { - } + public PushMessage() {} public PushMessage(String id, String content, Date createDate) { this.id = id; @@ -1974,8 +1954,7 @@ public PushMessage(String id, String content, Date createDate) { @org.springframework.data.mongodb.core.mapping.Document static class CarPerson { - @Id - private String id; + @Id private String id; private String firstName; private String lastName; private Descriptors descriptors; @@ -2012,8 +1991,7 @@ static class Entry { private String model; private int year; - public Entry() { - } + public Entry() {} public Entry(String make, String model, int year) { this.make = make; @@ -2029,8 +2007,7 @@ static class Reservation { String confirmationNumber; int timestamp; - public Reservation() { - } + public Reservation() {} public Reservation(String hotelCode, String confirmationNumber, int timestamp) { this.hotelCode = hotelCode; @@ -2057,8 +2034,7 @@ static class InventoryItem { String description; int qty; - public InventoryItem() { - } + public InventoryItem() {} public InventoryItem(int id, String item, int qty) { @@ -2081,8 +2057,7 @@ public InventoryItem(int id, String item, String description, int qty) { @Builder static class Sales { - @Id - String id; + @Id String id; List items; } From 6cd3263f5829d74b758420262981280c176067e5 Mon Sep 17 00:00:00 2001 From: manindr Date: Fri, 3 Mar 2017 20:01:04 +0530 Subject: [PATCH 110/118] formatting --- .../data/mongodb/core/MongoTemplate.java | 48 +------------------ 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 6467945820..661f3c3331 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -1608,50 +1608,6 @@ protected AggregationResults aggregate(Aggregation aggregation, String co commandResult); } - protected CloseableIterator aggregateStream(final Aggregation aggregation, final String collectionName, final Class outputType, - AggregationOperationContext context) { - - Assert.hasText(collectionName, "Collection name must not be null or empty!"); - Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - Assert.notNull(outputType, "Output type must not be null!"); - - AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; - - final DBObject command = aggregation.toDbObject(collectionName, rootContext); - - Assert.isNull(command.get(CURSOR), "Custom options not allowed while streaming"); - Assert.isNull(command.get(EXPLAIN), "Explain option can't be used while streaming"); - - return execute(collectionName, new CollectionCallback>() { - - @Override - public CloseableIterator doInCollection(DBCollection collection) throws MongoException, DataAccessException { - - List pipeline = (List) command.get("pipeline"); - Cursor cursor = collection.aggregate(pipeline, getNativeAggregationOptionsFromCommand(command)); - - ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, outputType, collectionName); - - return new CloseableIterableCursorAdapter(cursor, exceptionTranslator, readCallback); - } - - private AggregationOptions getNativeAggregationOptionsFromCommand(DBObject command) { - AggregationOptions.Builder builder = AggregationOptions.builder(); - Object allowDiskUse = command.get(ALLOW_DISK_USE); - if(allowDiskUse !=null && String.valueOf(allowDiskUse).equals("true")){ - builder.allowDiskUse(true); - } - return builder.build(); - } - }); - -/* - Query query = new BasicQuery(command); - return stream(query, outputType); -*/ - } - - /** * Returns the potentially mapped results of the given {@commandResult} contained some. * @@ -2544,8 +2500,8 @@ public GeoResult doWith(DBObject object) { /** * A {@link CloseableIterator} that is backed by a MongoDB {@link Cursor}. * - * @author Thomas Darimont * @since 1.7 + * @author Thomas Darimont */ static class CloseableIterableCursorAdapter implements CloseableIterator { @@ -2613,4 +2569,4 @@ public void close() { } } } -} +} \ No newline at end of file From 016b17c22db678c4b558e207923a5bf5c57bfe2f Mon Sep 17 00:00:00 2001 From: manindr Date: Fri, 3 Mar 2017 20:04:09 +0530 Subject: [PATCH 111/118] formatting --- .../data/mongodb/core/MongoTemplate.java | 4591 ++++++++--------- 1 file changed, 2280 insertions(+), 2311 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 661f3c3331..732b8d3f9f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -146,556 +146,556 @@ @SuppressWarnings("deprecation") public class MongoTemplate implements MongoOperations, ApplicationContextAware { - private static final Logger LOGGER = LoggerFactory.getLogger(MongoTemplate.class); - private static final String ID_FIELD = "_id"; - private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE; - private static final Collection ITERABLE_CLASSES; - - static { - - Set iterableClasses = new HashSet(); - iterableClasses.add(List.class.getName()); - iterableClasses.add(Collection.class.getName()); - iterableClasses.add(Iterator.class.getName()); - - ITERABLE_CLASSES = Collections.unmodifiableCollection(iterableClasses); - } - - private final MongoConverter mongoConverter; - private final MappingContext, MongoPersistentProperty> mappingContext; - private final MongoDbFactory mongoDbFactory; - private final PersistenceExceptionTranslator exceptionTranslator; - private final QueryMapper queryMapper; - private final UpdateMapper updateMapper; - - private WriteConcern writeConcern; - private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE; - private WriteResultChecking writeResultChecking = WriteResultChecking.NONE; - private ReadPreference readPreference; - private ApplicationEventPublisher eventPublisher; - private ResourceLoader resourceLoader; - private MongoPersistentEntityIndexCreator indexCreator; - - /** - * Constructor used for a basic template configuration - * - * @param mongo must not be {@literal null}. - * @param databaseName must not be {@literal null} or empty. - */ - public MongoTemplate(Mongo mongo, String databaseName) { - this(new SimpleMongoDbFactory(mongo, databaseName), null); - } - - /** - * Constructor used for a template configuration with user credentials in the form of - * {@link org.springframework.data.authentication.UserCredentials} - * - * @param mongo must not be {@literal null}. - * @param databaseName must not be {@literal null} or empty. - * @param userCredentials - */ - public MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCredentials) { - this(new SimpleMongoDbFactory(mongo, databaseName, userCredentials)); - } - - /** - * Constructor used for a basic template configuration. - * - * @param mongoDbFactory must not be {@literal null}. - */ - public MongoTemplate(MongoDbFactory mongoDbFactory) { - this(mongoDbFactory, null); - } - - /** - * Constructor used for a basic template configuration. - * - * @param mongoDbFactory must not be {@literal null}. - * @param mongoConverter - */ - public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { - - Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!"); - - this.mongoDbFactory = mongoDbFactory; - this.exceptionTranslator = mongoDbFactory.getExceptionTranslator(); - this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter; - this.queryMapper = new QueryMapper(this.mongoConverter); - this.updateMapper = new UpdateMapper(this.mongoConverter); - - // We always have a mapping context in the converter, whether it's a simple one or not - mappingContext = this.mongoConverter.getMappingContext(); - // We create indexes based on mapping events - if (null != mappingContext && mappingContext instanceof MongoMappingContext) { - indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext, mongoDbFactory); - eventPublisher = new MongoMappingEventPublisher(indexCreator); - if (mappingContext instanceof ApplicationEventPublisherAware) { - ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); - } - } - } - - /** - * Configures the {@link WriteResultChecking} to be used with the template. Setting {@literal null} will reset the - * default of {@value #DEFAULT_WRITE_RESULT_CHECKING}. - * - * @param resultChecking - */ - public void setWriteResultChecking(WriteResultChecking resultChecking) { - this.writeResultChecking = resultChecking == null ? DEFAULT_WRITE_RESULT_CHECKING : resultChecking; - } - - /** - * Configures the {@link WriteConcern} to be used with the template. If none is configured the {@link WriteConcern} - * configured on the {@link MongoDbFactory} will apply. If you configured a {@link Mongo} instance no - * {@link WriteConcern} will be used. - * - * @param writeConcern - */ - public void setWriteConcern(WriteConcern writeConcern) { - this.writeConcern = writeConcern; - } - - /** - * Configures the {@link WriteConcernResolver} to be used with the template. - * - * @param writeConcernResolver - */ - public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) { - this.writeConcernResolver = writeConcernResolver; - } - - /** - * Used by @{link {@link #prepareCollection(DBCollection)} to set the {@link ReadPreference} before any operations are - * performed. - * - * @param readPreference - */ - public void setReadPreference(ReadPreference readPreference) { - this.readPreference = readPreference; - } - - /* - * (non-Javadoc) - * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) - */ - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - - prepareIndexCreator(applicationContext); - - eventPublisher = applicationContext; - if (mappingContext instanceof ApplicationEventPublisherAware) { - ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); - } - resourceLoader = applicationContext; - } - - /** - * Inspects the given {@link ApplicationContext} for {@link MongoPersistentEntityIndexCreator} and those in turn if - * they were registered for the current {@link MappingContext}. If no creator for the current {@link MappingContext} - * can be found we manually add the internally created one as {@link ApplicationListener} to make sure indexes get - * created appropriately for entity types persisted through this {@link MongoTemplate} instance. - * - * @param context must not be {@literal null}. - */ - private void prepareIndexCreator(ApplicationContext context) { - - String[] indexCreators = context.getBeanNamesForType(MongoPersistentEntityIndexCreator.class); - - for (String creator : indexCreators) { - MongoPersistentEntityIndexCreator creatorBean = context.getBean(creator, MongoPersistentEntityIndexCreator.class); - if (creatorBean.isIndexCreatorFor(mappingContext)) { - return; - } - } - - if (context instanceof ConfigurableApplicationContext) { - ((ConfigurableApplicationContext) context).addApplicationListener(indexCreator); - } - } - - /** - * Returns the default {@link org.springframework.data.mongodb.core.convert.MongoConverter}. - * - * @return - */ - public MongoConverter getConverter() { - return this.mongoConverter; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#executeAsStream(org.springframework.data.mongodb.core.query.Query, java.lang.Class) - */ - @Override - public CloseableIterator stream(final Query query, final Class entityType) { - - return stream(query, entityType, determineCollectionName(entityType)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#stream(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) - */ - @Override - public CloseableIterator stream(final Query query, final Class entityType, final String collectionName) { - - Assert.notNull(query, "Query must not be null!"); - Assert.notNull(entityType, "Entity type must not be null!"); - Assert.hasText(collectionName, "Collection name must not be null or empty!"); - - return execute(collectionName, new CollectionCallback>() { - - @Override - public CloseableIterator doInCollection(DBCollection collection) throws MongoException, DataAccessException { - - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityType); - - DBObject mappedFields = queryMapper.getMappedFields(query.getFieldsObject(), persistentEntity); - DBObject mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity); - - DBCursor cursor = collection.find(mappedQuery, mappedFields); - QueryCursorPreparer cursorPreparer = new QueryCursorPreparer(query, entityType); - - ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, entityType, collectionName); - - return new CloseableIterableCursorAdapter(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback); - } - }); - } - - public String getCollectionName(Class entityClass) { - return this.determineCollectionName(entityClass); - } - - public CommandResult executeCommand(String jsonCommand) { - return executeCommand((DBObject) JSON.parse(jsonCommand)); - } - - public CommandResult executeCommand(final DBObject command) { - - CommandResult result = execute(new DbCallback() { - public CommandResult doInDB(DB db) throws MongoException, DataAccessException { - return db.command(command); - } - }); - - logCommandExecutionError(command, result); - return result; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(com.mongodb.DBObject, int) - */ - @Deprecated - public CommandResult executeCommand(final DBObject command, final int options) { - return executeCommand(command, - (options & Bytes.QUERYOPTION_SLAVEOK) != 0 ? ReadPreference.secondaryPreferred() : ReadPreference.primary()); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(com.mongodb.DBObject, com.mongodb.ReadPreference) - */ - public CommandResult executeCommand(final DBObject command, final ReadPreference readPreference) { - - Assert.notNull(command, "Command must not be null!"); - - CommandResult result = execute(new DbCallback() { - public CommandResult doInDB(DB db) throws MongoException, DataAccessException { - return readPreference != null ? db.command(command, readPreference) : db.command(command); - } - }); - - logCommandExecutionError(command, result); - - return result; - } - - protected void logCommandExecutionError(final DBObject command, CommandResult result) { - - String error = result.getErrorMessage(); - - if (error != null) { - LOGGER.warn("Command execution of {} failed: {}", command.toString(), error); - } - } - - public void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch) { - executeQuery(query, collectionName, dch, new QueryCursorPreparer(query, null)); - } - - /** - * Execute a MongoDB query and iterate over the query results on a per-document basis with a - * {@link DocumentCallbackHandler} using the provided CursorPreparer. - * - * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification, must not be {@literal null}. - * @param collectionName name of the collection to retrieve the objects from - * @param dch the handler that will extract results, one document at a time - * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply - * limits, skips and so on). - */ - protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, - CursorPreparer preparer) { - - Assert.notNull(query, "Query must not be null!"); - - DBObject queryObject = queryMapper.getMappedObject(query.getQueryObject(), null); - DBObject sortObject = query.getSortObject(); - DBObject fieldsObject = query.getFieldsObject(); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing query: {} sort: {} fields: {} in collection: {}", serializeToJsonSafely(queryObject), - sortObject, fieldsObject, collectionName); - } - - this.executeQueryInternal(new FindCallback(queryObject, fieldsObject), preparer, dch, collectionName); - } - - public T execute(DbCallback action) { - - Assert.notNull(action, "DbCallbackmust not be null!"); - - try { - DB db = this.getDb(); - return action.doInDB(db); - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } - - public T execute(Class entityClass, CollectionCallback callback) { - return execute(determineCollectionName(entityClass), callback); - } - - public T execute(String collectionName, CollectionCallback callback) { - - Assert.notNull(callback, "CollectionCallback must not be null!"); - - try { - DBCollection collection = getAndPrepareCollection(getDb(), collectionName); - return callback.doInCollection(collection); - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#executeInSession(org.springframework.data.mongodb.core.DbCallback) - */ - @Deprecated - public T executeInSession(final DbCallback action) { - - return execute(new DbCallback() { - public T doInDB(DB db) throws MongoException, DataAccessException { - try { - ReflectiveDbInvoker.requestStart(db); - return action.doInDB(db); - } finally { - ReflectiveDbInvoker.requestDone(db); - } - } - }); - } - - public DBCollection createCollection(Class entityClass) { - return createCollection(determineCollectionName(entityClass)); - } - - public DBCollection createCollection(Class entityClass, CollectionOptions collectionOptions) { - return createCollection(determineCollectionName(entityClass), collectionOptions); - } - - public DBCollection createCollection(final String collectionName) { - return doCreateCollection(collectionName, new BasicDBObject()); - } - - public DBCollection createCollection(final String collectionName, final CollectionOptions collectionOptions) { - return doCreateCollection(collectionName, convertToDbObject(collectionOptions)); - } - - public DBCollection getCollection(final String collectionName) { - return execute(new DbCallback() { - public DBCollection doInDB(DB db) throws MongoException, DataAccessException { - return db.getCollection(collectionName); - } - }); - } - - public boolean collectionExists(Class entityClass) { - return collectionExists(determineCollectionName(entityClass)); - } - - public boolean collectionExists(final String collectionName) { - return execute(new DbCallback() { - public Boolean doInDB(DB db) throws MongoException, DataAccessException { - return db.collectionExists(collectionName); - } - }); - } - - public void dropCollection(Class entityClass) { - dropCollection(determineCollectionName(entityClass)); - } - - public void dropCollection(String collectionName) { - execute(collectionName, new CollectionCallback() { - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - collection.drop(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Dropped collection [{}]", collection.getFullName()); - } - return null; - } - }); - } - - public IndexOperations indexOps(String collectionName) { - return new DefaultIndexOperations(this, collectionName); - } - - public IndexOperations indexOps(Class entityClass) { - return new DefaultIndexOperations(this, determineCollectionName(entityClass), entityClass); - } - - public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) { - return bulkOps(bulkMode, null, collectionName); - } - - public BulkOperations bulkOps(BulkMode bulkMode, Class entityClass) { - return bulkOps(bulkMode, entityClass, determineCollectionName(entityClass)); - } - - public BulkOperations bulkOps(BulkMode mode, Class entityType, String collectionName) { - - Assert.notNull(mode, "BulkMode must not be null!"); - Assert.hasText(collectionName, "Collection name must not be null or empty!"); - - DefaultBulkOperations operations = new DefaultBulkOperations(this, mode, collectionName, entityType); - - operations.setExceptionTranslator(exceptionTranslator); - operations.setWriteConcernResolver(writeConcernResolver); - operations.setDefaultWriteConcern(writeConcern); - - return operations; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#scriptOps() - */ - @Override - public ScriptOperations scriptOps() { - return new DefaultScriptOperations(this); - } - - // Find methods that take a Query to express the query and that return a single object. - - public T findOne(Query query, Class entityClass) { - return findOne(query, entityClass, determineCollectionName(entityClass)); - } - - public T findOne(Query query, Class entityClass, String collectionName) { - if (query.getSortObject() == null) { - return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass); - } else { - query.limit(1); - List results = find(query, entityClass, collectionName); - return results.isEmpty() ? null : results.get(0); - } - } - - public boolean exists(Query query, Class entityClass) { - return exists(query, entityClass, determineCollectionName(entityClass)); - } - - public boolean exists(Query query, String collectionName) { - return exists(query, null, collectionName); - } - - public boolean exists(Query query, Class entityClass, String collectionName) { - - if (query == null) { - throw new InvalidDataAccessApiUsageException("Query passed in to exist can't be null"); - } - - DBObject mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass)); - return execute(collectionName, new FindCallback(mappedQuery)).hasNext(); - } - - // Find methods that take a Query to express the query and that return a List of objects. - - public List find(Query query, Class entityClass) { - return find(query, entityClass, determineCollectionName(entityClass)); - } - - public List find(final Query query, Class entityClass, String collectionName) { - - if (query == null) { - return findAll(entityClass, collectionName); - } - - return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass, - new QueryCursorPreparer(query, entityClass)); - } - - public T findById(Object id, Class entityClass) { - return findById(id, entityClass, determineCollectionName(entityClass)); - } + private static final Logger LOGGER = LoggerFactory.getLogger(MongoTemplate.class); + private static final String ID_FIELD = "_id"; + private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE; + private static final Collection ITERABLE_CLASSES; + + static { + + Set iterableClasses = new HashSet(); + iterableClasses.add(List.class.getName()); + iterableClasses.add(Collection.class.getName()); + iterableClasses.add(Iterator.class.getName()); + + ITERABLE_CLASSES = Collections.unmodifiableCollection(iterableClasses); + } + + private final MongoConverter mongoConverter; + private final MappingContext, MongoPersistentProperty> mappingContext; + private final MongoDbFactory mongoDbFactory; + private final PersistenceExceptionTranslator exceptionTranslator; + private final QueryMapper queryMapper; + private final UpdateMapper updateMapper; + + private WriteConcern writeConcern; + private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE; + private WriteResultChecking writeResultChecking = WriteResultChecking.NONE; + private ReadPreference readPreference; + private ApplicationEventPublisher eventPublisher; + private ResourceLoader resourceLoader; + private MongoPersistentEntityIndexCreator indexCreator; + + /** + * Constructor used for a basic template configuration + * + * @param mongo must not be {@literal null}. + * @param databaseName must not be {@literal null} or empty. + */ + public MongoTemplate(Mongo mongo, String databaseName) { + this(new SimpleMongoDbFactory(mongo, databaseName), null); + } + + /** + * Constructor used for a template configuration with user credentials in the form of + * {@link UserCredentials} + * + * @param mongo must not be {@literal null}. + * @param databaseName must not be {@literal null} or empty. + * @param userCredentials + */ + public MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCredentials) { + this(new SimpleMongoDbFactory(mongo, databaseName, userCredentials)); + } + + /** + * Constructor used for a basic template configuration. + * + * @param mongoDbFactory must not be {@literal null}. + */ + public MongoTemplate(MongoDbFactory mongoDbFactory) { + this(mongoDbFactory, null); + } + + /** + * Constructor used for a basic template configuration. + * + * @param mongoDbFactory must not be {@literal null}. + * @param mongoConverter + */ + public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { + + Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!"); + + this.mongoDbFactory = mongoDbFactory; + this.exceptionTranslator = mongoDbFactory.getExceptionTranslator(); + this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter; + this.queryMapper = new QueryMapper(this.mongoConverter); + this.updateMapper = new UpdateMapper(this.mongoConverter); + + // We always have a mapping context in the converter, whether it's a simple one or not + mappingContext = this.mongoConverter.getMappingContext(); + // We create indexes based on mapping events + if (null != mappingContext && mappingContext instanceof MongoMappingContext) { + indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext, mongoDbFactory); + eventPublisher = new MongoMappingEventPublisher(indexCreator); + if (mappingContext instanceof ApplicationEventPublisherAware) { + ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); + } + } + } + + /** + * Configures the {@link WriteResultChecking} to be used with the template. Setting {@literal null} will reset the + * default of {@value #DEFAULT_WRITE_RESULT_CHECKING}. + * + * @param resultChecking + */ + public void setWriteResultChecking(WriteResultChecking resultChecking) { + this.writeResultChecking = resultChecking == null ? DEFAULT_WRITE_RESULT_CHECKING : resultChecking; + } + + /** + * Configures the {@link WriteConcern} to be used with the template. If none is configured the {@link WriteConcern} + * configured on the {@link MongoDbFactory} will apply. If you configured a {@link Mongo} instance no + * {@link WriteConcern} will be used. + * + * @param writeConcern + */ + public void setWriteConcern(WriteConcern writeConcern) { + this.writeConcern = writeConcern; + } + + /** + * Configures the {@link WriteConcernResolver} to be used with the template. + * + * @param writeConcernResolver + */ + public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) { + this.writeConcernResolver = writeConcernResolver; + } + + /** + * Used by @{link {@link #prepareCollection(DBCollection)} to set the {@link ReadPreference} before any operations are + * performed. + * + * @param readPreference + */ + public void setReadPreference(ReadPreference readPreference) { + this.readPreference = readPreference; + } + + /* + * (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + + prepareIndexCreator(applicationContext); + + eventPublisher = applicationContext; + if (mappingContext instanceof ApplicationEventPublisherAware) { + ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); + } + resourceLoader = applicationContext; + } + + /** + * Inspects the given {@link ApplicationContext} for {@link MongoPersistentEntityIndexCreator} and those in turn if + * they were registered for the current {@link MappingContext}. If no creator for the current {@link MappingContext} + * can be found we manually add the internally created one as {@link ApplicationListener} to make sure indexes get + * created appropriately for entity types persisted through this {@link MongoTemplate} instance. + * + * @param context must not be {@literal null}. + */ + private void prepareIndexCreator(ApplicationContext context) { + + String[] indexCreators = context.getBeanNamesForType(MongoPersistentEntityIndexCreator.class); + + for (String creator : indexCreators) { + MongoPersistentEntityIndexCreator creatorBean = context.getBean(creator, MongoPersistentEntityIndexCreator.class); + if (creatorBean.isIndexCreatorFor(mappingContext)) { + return; + } + } + + if (context instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) context).addApplicationListener(indexCreator); + } + } + + /** + * Returns the default {@link MongoConverter}. + * + * @return + */ + public MongoConverter getConverter() { + return this.mongoConverter; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#executeAsStream(org.springframework.data.mongodb.core.query.Query, java.lang.Class) + */ + @Override + public CloseableIterator stream(final Query query, final Class entityType) { + + return stream(query, entityType, determineCollectionName(entityType)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#stream(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + */ + @Override + public CloseableIterator stream(final Query query, final Class entityType, final String collectionName) { + + Assert.notNull(query, "Query must not be null!"); + Assert.notNull(entityType, "Entity type must not be null!"); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + + return execute(collectionName, new CollectionCallback>() { + + @Override + public CloseableIterator doInCollection(DBCollection collection) throws MongoException, DataAccessException { + + MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityType); + + DBObject mappedFields = queryMapper.getMappedFields(query.getFieldsObject(), persistentEntity); + DBObject mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity); + + DBCursor cursor = collection.find(mappedQuery, mappedFields); + QueryCursorPreparer cursorPreparer = new QueryCursorPreparer(query, entityType); + + ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, entityType, collectionName); + + return new CloseableIterableCursorAdapter(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback); + } + }); + } + + public String getCollectionName(Class entityClass) { + return this.determineCollectionName(entityClass); + } + + public CommandResult executeCommand(String jsonCommand) { + return executeCommand((DBObject) JSON.parse(jsonCommand)); + } + + public CommandResult executeCommand(final DBObject command) { + + CommandResult result = execute(new DbCallback() { + public CommandResult doInDB(DB db) throws MongoException, DataAccessException { + return db.command(command); + } + }); + + logCommandExecutionError(command, result); + return result; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(com.mongodb.DBObject, int) + */ + @Deprecated + public CommandResult executeCommand(final DBObject command, final int options) { + return executeCommand(command, + (options & Bytes.QUERYOPTION_SLAVEOK) != 0 ? ReadPreference.secondaryPreferred() : ReadPreference.primary()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(com.mongodb.DBObject, com.mongodb.ReadPreference) + */ + public CommandResult executeCommand(final DBObject command, final ReadPreference readPreference) { + + Assert.notNull(command, "Command must not be null!"); + + CommandResult result = execute(new DbCallback() { + public CommandResult doInDB(DB db) throws MongoException, DataAccessException { + return readPreference != null ? db.command(command, readPreference) : db.command(command); + } + }); + + logCommandExecutionError(command, result); + + return result; + } + + protected void logCommandExecutionError(final DBObject command, CommandResult result) { + + String error = result.getErrorMessage(); + + if (error != null) { + LOGGER.warn("Command execution of {} failed: {}", command.toString(), error); + } + } + + public void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch) { + executeQuery(query, collectionName, dch, new QueryCursorPreparer(query, null)); + } + + /** + * Execute a MongoDB query and iterate over the query results on a per-document basis with a + * {@link DocumentCallbackHandler} using the provided CursorPreparer. + * + * @param query the query class that specifies the criteria used to find a record and also an optional fields + * specification, must not be {@literal null}. + * @param collectionName name of the collection to retrieve the objects from + * @param dch the handler that will extract results, one document at a time + * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply + * limits, skips and so on). + */ + protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, + CursorPreparer preparer) { + + Assert.notNull(query, "Query must not be null!"); + + DBObject queryObject = queryMapper.getMappedObject(query.getQueryObject(), null); + DBObject sortObject = query.getSortObject(); + DBObject fieldsObject = query.getFieldsObject(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing query: {} sort: {} fields: {} in collection: {}", serializeToJsonSafely(queryObject), + sortObject, fieldsObject, collectionName); + } + + this.executeQueryInternal(new FindCallback(queryObject, fieldsObject), preparer, dch, collectionName); + } + + public T execute(DbCallback action) { + + Assert.notNull(action, "DbCallbackmust not be null!"); + + try { + DB db = this.getDb(); + return action.doInDB(db); + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + public T execute(Class entityClass, CollectionCallback callback) { + return execute(determineCollectionName(entityClass), callback); + } + + public T execute(String collectionName, CollectionCallback callback) { + + Assert.notNull(callback, "CollectionCallback must not be null!"); + + try { + DBCollection collection = getAndPrepareCollection(getDb(), collectionName); + return callback.doInCollection(collection); + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#executeInSession(org.springframework.data.mongodb.core.DbCallback) + */ + @Deprecated + public T executeInSession(final DbCallback action) { + + return execute(new DbCallback() { + public T doInDB(DB db) throws MongoException, DataAccessException { + try { + ReflectiveDbInvoker.requestStart(db); + return action.doInDB(db); + } finally { + ReflectiveDbInvoker.requestDone(db); + } + } + }); + } + + public DBCollection createCollection(Class entityClass) { + return createCollection(determineCollectionName(entityClass)); + } + + public DBCollection createCollection(Class entityClass, CollectionOptions collectionOptions) { + return createCollection(determineCollectionName(entityClass), collectionOptions); + } + + public DBCollection createCollection(final String collectionName) { + return doCreateCollection(collectionName, new BasicDBObject()); + } + + public DBCollection createCollection(final String collectionName, final CollectionOptions collectionOptions) { + return doCreateCollection(collectionName, convertToDbObject(collectionOptions)); + } + + public DBCollection getCollection(final String collectionName) { + return execute(new DbCallback() { + public DBCollection doInDB(DB db) throws MongoException, DataAccessException { + return db.getCollection(collectionName); + } + }); + } + + public boolean collectionExists(Class entityClass) { + return collectionExists(determineCollectionName(entityClass)); + } + + public boolean collectionExists(final String collectionName) { + return execute(new DbCallback() { + public Boolean doInDB(DB db) throws MongoException, DataAccessException { + return db.collectionExists(collectionName); + } + }); + } + + public void dropCollection(Class entityClass) { + dropCollection(determineCollectionName(entityClass)); + } + + public void dropCollection(String collectionName) { + execute(collectionName, new CollectionCallback() { + public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { + collection.drop(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Dropped collection [{}]", collection.getFullName()); + } + return null; + } + }); + } + + public IndexOperations indexOps(String collectionName) { + return new DefaultIndexOperations(this, collectionName); + } + + public IndexOperations indexOps(Class entityClass) { + return new DefaultIndexOperations(this, determineCollectionName(entityClass), entityClass); + } + + public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) { + return bulkOps(bulkMode, null, collectionName); + } + + public BulkOperations bulkOps(BulkMode bulkMode, Class entityClass) { + return bulkOps(bulkMode, entityClass, determineCollectionName(entityClass)); + } + + public BulkOperations bulkOps(BulkMode mode, Class entityType, String collectionName) { + + Assert.notNull(mode, "BulkMode must not be null!"); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + + DefaultBulkOperations operations = new DefaultBulkOperations(this, mode, collectionName, entityType); + + operations.setExceptionTranslator(exceptionTranslator); + operations.setWriteConcernResolver(writeConcernResolver); + operations.setDefaultWriteConcern(writeConcern); + + return operations; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#scriptOps() + */ + @Override + public ScriptOperations scriptOps() { + return new DefaultScriptOperations(this); + } + + // Find methods that take a Query to express the query and that return a single object. + + public T findOne(Query query, Class entityClass) { + return findOne(query, entityClass, determineCollectionName(entityClass)); + } + + public T findOne(Query query, Class entityClass, String collectionName) { + if (query.getSortObject() == null) { + return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass); + } else { + query.limit(1); + List results = find(query, entityClass, collectionName); + return results.isEmpty() ? null : results.get(0); + } + } + + public boolean exists(Query query, Class entityClass) { + return exists(query, entityClass, determineCollectionName(entityClass)); + } + + public boolean exists(Query query, String collectionName) { + return exists(query, null, collectionName); + } + + public boolean exists(Query query, Class entityClass, String collectionName) { + + if (query == null) { + throw new InvalidDataAccessApiUsageException("Query passed in to exist can't be null"); + } + + DBObject mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass)); + return execute(collectionName, new FindCallback(mappedQuery)).hasNext(); + } + + // Find methods that take a Query to express the query and that return a List of objects. + + public List find(Query query, Class entityClass) { + return find(query, entityClass, determineCollectionName(entityClass)); + } + + public List find(final Query query, Class entityClass, String collectionName) { + + if (query == null) { + return findAll(entityClass, collectionName); + } + + return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass, + new QueryCursorPreparer(query, entityClass)); + } + + public T findById(Object id, Class entityClass) { + return findById(id, entityClass, determineCollectionName(entityClass)); + } - public T findById(Object id, Class entityClass, String collectionName) { - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityClass); - MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty(); - String idKey = idProperty == null ? ID_FIELD : idProperty.getName(); - return doFindOne(collectionName, new BasicDBObject(idKey, id), null, entityClass); - } + public T findById(Object id, Class entityClass, String collectionName) { + MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityClass); + MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty(); + String idKey = idProperty == null ? ID_FIELD : idProperty.getName(); + return doFindOne(collectionName, new BasicDBObject(idKey, id), null, entityClass); + } - public GeoResults geoNear(NearQuery near, Class entityClass) { - return geoNear(near, entityClass, determineCollectionName(entityClass)); - } + public GeoResults geoNear(NearQuery near, Class entityClass) { + return geoNear(near, entityClass, determineCollectionName(entityClass)); + } - @SuppressWarnings("unchecked") - public GeoResults geoNear(NearQuery near, Class entityClass, String collectionName) { + @SuppressWarnings("unchecked") + public GeoResults geoNear(NearQuery near, Class entityClass, String collectionName) { - if (near == null) { - throw new InvalidDataAccessApiUsageException("NearQuery must not be null!"); - } + if (near == null) { + throw new InvalidDataAccessApiUsageException("NearQuery must not be null!"); + } - if (entityClass == null) { - throw new InvalidDataAccessApiUsageException("Entity class must not be null!"); - } + if (entityClass == null) { + throw new InvalidDataAccessApiUsageException("Entity class must not be null!"); + } - String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(entityClass); - DBObject nearDbObject = near.toDBObject(); + String collection = StringUtils.hasText(collectionName) ? collectionName : determineCollectionName(entityClass); + DBObject nearDbObject = near.toDBObject(); - BasicDBObject command = new BasicDBObject("geoNear", collection); - command.putAll(nearDbObject); + BasicDBObject command = new BasicDBObject("geoNear", collection); + command.putAll(nearDbObject); - if (nearDbObject.containsField("query")) { - DBObject query = (DBObject) nearDbObject.get("query"); - command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(entityClass))); - } + if (nearDbObject.containsField("query")) { + DBObject query = (DBObject) nearDbObject.get("query"); + command.put("query", queryMapper.getMappedObject(query, getPersistentEntity(entityClass))); + } - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing geoNear using: {} for class: {} in collection: {}", serializeToJsonSafely(command), - entityClass, collectionName); - } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing geoNear using: {} for class: {} in collection: {}", serializeToJsonSafely(command), + entityClass, collectionName); + } - CommandResult commandResult = executeCommand(command, this.readPreference); - List results = (List) commandResult.get("results"); - results = results == null ? Collections.emptyList() : results; + CommandResult commandResult = executeCommand(command, this.readPreference); + List results = (List) commandResult.get("results"); + results = results == null ? Collections.emptyList() : results; - DbObjectCallback> callback = new GeoNearResultDbObjectCallback( - new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), near.getMetric()); - List> result = new ArrayList>(results.size()); + DbObjectCallback> callback = new GeoNearResultDbObjectCallback( + new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), near.getMetric()); + List> result = new ArrayList>(results.size()); - int index = 0; - int elementsToSkip = near.getSkip() != null ? near.getSkip() : 0; + int index = 0; + int elementsToSkip = near.getSkip() != null ? near.getSkip() : 0; - for (Object element : results) { + for (Object element : results) { /* * As MongoDB currently (2.4.4) doesn't support the skipping of elements in near queries @@ -703,1870 +703,1839 @@ public GeoResults geoNear(NearQuery near, Class entityClass, String co * * @see MongoDB Jira: SERVER-3925 */ - if (index >= elementsToSkip) { - result.add(callback.doWith((DBObject) element)); - } - index++; - } - - if (elementsToSkip > 0) { - // as we skipped some elements we have to calculate the averageDistance ourselves: - return new GeoResults(result, near.getMetric()); - } - - GeoCommandStatistics stats = GeoCommandStatistics.from(commandResult); - return new GeoResults(result, new Distance(stats.getAverageDistance(), near.getMetric())); - } - - public T findAndModify(Query query, Update update, Class entityClass) { - return findAndModify(query, update, new FindAndModifyOptions(), entityClass, determineCollectionName(entityClass)); - } - - public T findAndModify(Query query, Update update, Class entityClass, String collectionName) { - return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName); - } - - public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass) { - return findAndModify(query, update, options, entityClass, determineCollectionName(entityClass)); - } - - public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass, - String collectionName) { - return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(), - getMappedSortObject(query, entityClass), entityClass, update, options); - } - - // Find methods that take a Query to express the query and that return a single object that is also removed from the - // collection in the database. - - public T findAndRemove(Query query, Class entityClass) { - return findAndRemove(query, entityClass, determineCollectionName(entityClass)); - } - - public T findAndRemove(Query query, Class entityClass, String collectionName) { - - return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(), - getMappedSortObject(query, entityClass), entityClass); - } - - public long count(Query query, Class entityClass) { - - Assert.notNull(entityClass, "Entity class must not be null!"); - return count(query, entityClass, determineCollectionName(entityClass)); - } - - public long count(final Query query, String collectionName) { - return count(query, null, collectionName); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) - */ - public long count(Query query, Class entityClass, String collectionName) { - - Assert.hasText(collectionName, "Collection name must not be null or empty!"); - - final DBObject dbObject = query == null ? null - : queryMapper.getMappedObject(query.getQueryObject(), - entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); - - return execute(collectionName, new CollectionCallback() { - public Long doInCollection(DBCollection collection) throws MongoException, DataAccessException { - return collection.count(dbObject); - } - }); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object) - */ - public void insert(Object objectToSave) { - ensureNotIterable(objectToSave); - insert(objectToSave, determineEntityCollectionName(objectToSave)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object, java.lang.String) - */ - public void insert(Object objectToSave, String collectionName) { - ensureNotIterable(objectToSave); - doInsert(collectionName, objectToSave, this.mongoConverter); - } - - protected void ensureNotIterable(Object o) { - if (null != o) { - if (o.getClass().isArray() || ITERABLE_CLASSES.contains(o.getClass().getName())) { - throw new IllegalArgumentException("Cannot use a collection here."); - } - } - } - - /** - * Prepare the collection before any processing is done using it. This allows a convenient way to apply settings like - * slaveOk() etc. Can be overridden in sub-classes. - * - * @param collection - */ - protected void prepareCollection(DBCollection collection) { - if (this.readPreference != null) { - collection.setReadPreference(readPreference); - } - } - - /** - * Prepare the WriteConcern before any processing is done using it. This allows a convenient way to apply custom - * settings in sub-classes.
                        - * In case of using MongoDB Java driver version 3 the returned {@link WriteConcern} will be defaulted to - * {@link WriteConcern#ACKNOWLEDGED} when {@link WriteResultChecking} is set to {@link WriteResultChecking#EXCEPTION}. - * - * @param writeConcern any WriteConcern already configured or null - * @return The prepared WriteConcern or null - */ - protected WriteConcern prepareWriteConcern(MongoAction mongoAction) { - - WriteConcern wc = writeConcernResolver.resolve(mongoAction); - return potentiallyForceAcknowledgedWrite(wc); - } - - private WriteConcern potentiallyForceAcknowledgedWrite(WriteConcern wc) { - - if (ObjectUtils.nullSafeEquals(WriteResultChecking.EXCEPTION, writeResultChecking) - && MongoClientVersion.isMongo3Driver()) { - if (wc == null || wc.getWObject() == null - || (wc.getWObject() instanceof Number && ((Number) wc.getWObject()).intValue() < 1)) { - return WriteConcern.ACKNOWLEDGED; - } - } - return wc; - } - - protected void doInsert(String collectionName, T objectToSave, MongoWriter writer) { - - initializeVersionProperty(objectToSave); - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); - assertUpdateableIdIfNotSet(objectToSave); - - DBObject dbDoc = toDbObject(objectToSave, writer); - - maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName)); - Object id = insertDBObject(collectionName, dbDoc, objectToSave.getClass()); - - populateIdIfNecessary(objectToSave, id); - maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc, collectionName)); - } - - /** - * @param objectToSave - * @param writer - * @return - */ - private DBObject toDbObject(T objectToSave, MongoWriter writer) { - - if (!(objectToSave instanceof String)) { - DBObject dbDoc = new BasicDBObject(); - writer.write(objectToSave, dbDoc); - return dbDoc; - } else { - try { - return (DBObject) JSON.parse((String) objectToSave); - } catch (JSONParseException e) { - throw new MappingException("Could not parse given String to save into a JSON document!", e); - } - } - } - - private void initializeVersionProperty(Object entity) { - - MongoPersistentEntity mongoPersistentEntity = getPersistentEntity(entity.getClass()); - - if (mongoPersistentEntity != null && mongoPersistentEntity.hasVersionProperty()) { - ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor( - mongoPersistentEntity.getPropertyAccessor(entity), mongoConverter.getConversionService()); - accessor.setProperty(mongoPersistentEntity.getVersionProperty(), 0); - } - } - - public void insert(Collection batchToSave, Class entityClass) { - doInsertBatch(determineCollectionName(entityClass), batchToSave, this.mongoConverter); - } - - public void insert(Collection batchToSave, String collectionName) { - doInsertBatch(collectionName, batchToSave, this.mongoConverter); - } - - public void insertAll(Collection objectsToSave) { - doInsertAll(objectsToSave, this.mongoConverter); - } + if (index >= elementsToSkip) { + result.add(callback.doWith((DBObject) element)); + } + index++; + } + + if (elementsToSkip > 0) { + // as we skipped some elements we have to calculate the averageDistance ourselves: + return new GeoResults(result, near.getMetric()); + } + + GeoCommandStatistics stats = GeoCommandStatistics.from(commandResult); + return new GeoResults(result, new Distance(stats.getAverageDistance(), near.getMetric())); + } + + public T findAndModify(Query query, Update update, Class entityClass) { + return findAndModify(query, update, new FindAndModifyOptions(), entityClass, determineCollectionName(entityClass)); + } + + public T findAndModify(Query query, Update update, Class entityClass, String collectionName) { + return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName); + } + + public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass) { + return findAndModify(query, update, options, entityClass, determineCollectionName(entityClass)); + } + + public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass, + String collectionName) { + return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(), + getMappedSortObject(query, entityClass), entityClass, update, options); + } + + // Find methods that take a Query to express the query and that return a single object that is also removed from the + // collection in the database. + + public T findAndRemove(Query query, Class entityClass) { + return findAndRemove(query, entityClass, determineCollectionName(entityClass)); + } + + public T findAndRemove(Query query, Class entityClass, String collectionName) { + + return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(), + getMappedSortObject(query, entityClass), entityClass); + } + + public long count(Query query, Class entityClass) { + + Assert.notNull(entityClass, "Entity class must not be null!"); + return count(query, entityClass, determineCollectionName(entityClass)); + } + + public long count(final Query query, String collectionName) { + return count(query, null, collectionName); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + */ + public long count(Query query, Class entityClass, String collectionName) { + + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + + final DBObject dbObject = query == null ? null + : queryMapper.getMappedObject(query.getQueryObject(), + entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); + + return execute(collectionName, new CollectionCallback() { + public Long doInCollection(DBCollection collection) throws MongoException, DataAccessException { + return collection.count(dbObject); + } + }); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object) + */ + public void insert(Object objectToSave) { + ensureNotIterable(objectToSave); + insert(objectToSave, determineEntityCollectionName(objectToSave)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object, java.lang.String) + */ + public void insert(Object objectToSave, String collectionName) { + ensureNotIterable(objectToSave); + doInsert(collectionName, objectToSave, this.mongoConverter); + } + + protected void ensureNotIterable(Object o) { + if (null != o) { + if (o.getClass().isArray() || ITERABLE_CLASSES.contains(o.getClass().getName())) { + throw new IllegalArgumentException("Cannot use a collection here."); + } + } + } + + /** + * Prepare the collection before any processing is done using it. This allows a convenient way to apply settings like + * slaveOk() etc. Can be overridden in sub-classes. + * + * @param collection + */ + protected void prepareCollection(DBCollection collection) { + if (this.readPreference != null) { + collection.setReadPreference(readPreference); + } + } + + /** + * Prepare the WriteConcern before any processing is done using it. This allows a convenient way to apply custom + * settings in sub-classes.
                        + * In case of using MongoDB Java driver version 3 the returned {@link WriteConcern} will be defaulted to + * {@link WriteConcern#ACKNOWLEDGED} when {@link WriteResultChecking} is set to {@link WriteResultChecking#EXCEPTION}. + * + * @param writeConcern any WriteConcern already configured or null + * @return The prepared WriteConcern or null + */ + protected WriteConcern prepareWriteConcern(MongoAction mongoAction) { + + WriteConcern wc = writeConcernResolver.resolve(mongoAction); + return potentiallyForceAcknowledgedWrite(wc); + } + + private WriteConcern potentiallyForceAcknowledgedWrite(WriteConcern wc) { + + if (ObjectUtils.nullSafeEquals(WriteResultChecking.EXCEPTION, writeResultChecking) + && MongoClientVersion.isMongo3Driver()) { + if (wc == null || wc.getWObject() == null + || (wc.getWObject() instanceof Number && ((Number) wc.getWObject()).intValue() < 1)) { + return WriteConcern.ACKNOWLEDGED; + } + } + return wc; + } + + protected void doInsert(String collectionName, T objectToSave, MongoWriter writer) { + + initializeVersionProperty(objectToSave); + maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); + assertUpdateableIdIfNotSet(objectToSave); + + DBObject dbDoc = toDbObject(objectToSave, writer); + + maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName)); + Object id = insertDBObject(collectionName, dbDoc, objectToSave.getClass()); + + populateIdIfNecessary(objectToSave, id); + maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc, collectionName)); + } + + /** + * @param objectToSave + * @param writer + * @return + */ + private DBObject toDbObject(T objectToSave, MongoWriter writer) { + + if (!(objectToSave instanceof String)) { + DBObject dbDoc = new BasicDBObject(); + writer.write(objectToSave, dbDoc); + return dbDoc; + } else { + try { + return (DBObject) JSON.parse((String) objectToSave); + } catch (JSONParseException e) { + throw new MappingException("Could not parse given String to save into a JSON document!", e); + } + } + } + + private void initializeVersionProperty(Object entity) { + + MongoPersistentEntity mongoPersistentEntity = getPersistentEntity(entity.getClass()); + + if (mongoPersistentEntity != null && mongoPersistentEntity.hasVersionProperty()) { + ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor( + mongoPersistentEntity.getPropertyAccessor(entity), mongoConverter.getConversionService()); + accessor.setProperty(mongoPersistentEntity.getVersionProperty(), 0); + } + } + + public void insert(Collection batchToSave, Class entityClass) { + doInsertBatch(determineCollectionName(entityClass), batchToSave, this.mongoConverter); + } + + public void insert(Collection batchToSave, String collectionName) { + doInsertBatch(collectionName, batchToSave, this.mongoConverter); + } + + public void insertAll(Collection objectsToSave) { + doInsertAll(objectsToSave, this.mongoConverter); + } - protected void doInsertAll(Collection listToSave, MongoWriter writer) { + protected void doInsertAll(Collection listToSave, MongoWriter writer) { - Map> elementsByCollection = new HashMap>(); + Map> elementsByCollection = new HashMap>(); - for (T element : listToSave) { + for (T element : listToSave) { - if (element == null) { - continue; - } + if (element == null) { + continue; + } - MongoPersistentEntity entity = mappingContext.getPersistentEntity(element.getClass()); + MongoPersistentEntity entity = mappingContext.getPersistentEntity(element.getClass()); - if (entity == null) { - throw new InvalidDataAccessApiUsageException("No PersistentEntity information found for " + element.getClass()); - } + if (entity == null) { + throw new InvalidDataAccessApiUsageException("No PersistentEntity information found for " + element.getClass()); + } - String collection = entity.getCollection(); - List collectionElements = elementsByCollection.get(collection); + String collection = entity.getCollection(); + List collectionElements = elementsByCollection.get(collection); - if (null == collectionElements) { - collectionElements = new ArrayList(); - elementsByCollection.put(collection, collectionElements); - } + if (null == collectionElements) { + collectionElements = new ArrayList(); + elementsByCollection.put(collection, collectionElements); + } - collectionElements.add(element); - } + collectionElements.add(element); + } - for (Map.Entry> entry : elementsByCollection.entrySet()) { - doInsertBatch(entry.getKey(), entry.getValue(), this.mongoConverter); - } - } + for (Entry> entry : elementsByCollection.entrySet()) { + doInsertBatch(entry.getKey(), entry.getValue(), this.mongoConverter); + } + } - protected void doInsertBatch(String collectionName, Collection batchToSave, MongoWriter writer) { + protected void doInsertBatch(String collectionName, Collection batchToSave, MongoWriter writer) { - Assert.notNull(writer, "MongoWriter must not be null!"); + Assert.notNull(writer, "MongoWriter must not be null!"); - List dbObjectList = new ArrayList(); + List dbObjectList = new ArrayList(); - for (T o : batchToSave) { + for (T o : batchToSave) { - initializeVersionProperty(o); - maybeEmitEvent(new BeforeConvertEvent(o, collectionName)); + initializeVersionProperty(o); + maybeEmitEvent(new BeforeConvertEvent(o, collectionName)); - BasicDBObject dbDoc = new BasicDBObject(); - writer.write(o, dbDoc); + BasicDBObject dbDoc = new BasicDBObject(); + writer.write(o, dbDoc); - maybeEmitEvent(new BeforeSaveEvent(o, dbDoc, collectionName)); - dbObjectList.add(dbDoc); - } + maybeEmitEvent(new BeforeSaveEvent(o, dbDoc, collectionName)); + dbObjectList.add(dbDoc); + } - List ids = consolidateIdentifiers(insertDBObjectList(collectionName, dbObjectList), dbObjectList); + List ids = consolidateIdentifiers(insertDBObjectList(collectionName, dbObjectList), dbObjectList); - int i = 0; - for (T obj : batchToSave) { - if (i < ids.size()) { - populateIdIfNecessary(obj, ids.get(i)); - maybeEmitEvent(new AfterSaveEvent(obj, dbObjectList.get(i), collectionName)); - } - i++; - } - } + int i = 0; + for (T obj : batchToSave) { + if (i < ids.size()) { + populateIdIfNecessary(obj, ids.get(i)); + maybeEmitEvent(new AfterSaveEvent(obj, dbObjectList.get(i), collectionName)); + } + i++; + } + } - public void save(Object objectToSave) { + public void save(Object objectToSave) { - Assert.notNull(objectToSave, "Object to save must not be null!"); - save(objectToSave, determineEntityCollectionName(objectToSave)); - } + Assert.notNull(objectToSave, "Object to save must not be null!"); + save(objectToSave, determineEntityCollectionName(objectToSave)); + } - public void save(Object objectToSave, String collectionName) { + public void save(Object objectToSave, String collectionName) { - Assert.notNull(objectToSave, "Object to save must not be null!"); - Assert.hasText(collectionName, "Collection name must not be null or empty!"); + Assert.notNull(objectToSave, "Object to save must not be null!"); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); - MongoPersistentEntity mongoPersistentEntity = getPersistentEntity(objectToSave.getClass()); + MongoPersistentEntity mongoPersistentEntity = getPersistentEntity(objectToSave.getClass()); - // No optimistic locking -> simple save - if (mongoPersistentEntity == null || !mongoPersistentEntity.hasVersionProperty()) { - doSave(collectionName, objectToSave, this.mongoConverter); - return; - } + // No optimistic locking -> simple save + if (mongoPersistentEntity == null || !mongoPersistentEntity.hasVersionProperty()) { + doSave(collectionName, objectToSave, this.mongoConverter); + return; + } - doSaveVersioned(objectToSave, mongoPersistentEntity, collectionName); - } + doSaveVersioned(objectToSave, mongoPersistentEntity, collectionName); + } - private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity, String collectionName) { + private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity, String collectionName) { - ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor( - entity.getPropertyAccessor(objectToSave), mongoConverter.getConversionService()); + ConvertingPropertyAccessor convertingAccessor = new ConvertingPropertyAccessor( + entity.getPropertyAccessor(objectToSave), mongoConverter.getConversionService()); - MongoPersistentProperty idProperty = entity.getIdProperty(); - MongoPersistentProperty versionProperty = entity.getVersionProperty(); + MongoPersistentProperty idProperty = entity.getIdProperty(); + MongoPersistentProperty versionProperty = entity.getVersionProperty(); - Object version = convertingAccessor.getProperty(versionProperty); - Number versionNumber = convertingAccessor.getProperty(versionProperty, Number.class); + Object version = convertingAccessor.getProperty(versionProperty); + Number versionNumber = convertingAccessor.getProperty(versionProperty, Number.class); - // Fresh instance -> initialize version property - if (version == null) { - doInsert(collectionName, objectToSave, this.mongoConverter); - } else { + // Fresh instance -> initialize version property + if (version == null) { + doInsert(collectionName, objectToSave, this.mongoConverter); + } else { - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); - assertUpdateableIdIfNotSet(objectToSave); + maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); + assertUpdateableIdIfNotSet(objectToSave); - // Create query for entity with the id and old version - Object id = convertingAccessor.getProperty(idProperty); - Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version)); - - // Bump version number - convertingAccessor.setProperty(versionProperty, versionNumber.longValue() + 1); + // Create query for entity with the id and old version + Object id = convertingAccessor.getProperty(idProperty); + Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version)); + + // Bump version number + convertingAccessor.setProperty(versionProperty, versionNumber.longValue() + 1); - BasicDBObject dbObject = new BasicDBObject(); - - this.mongoConverter.write(objectToSave, dbObject); - - maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbObject, collectionName)); - Update update = Update.fromDBObject(dbObject, ID_FIELD); - - doUpdate(collectionName, query, update, objectToSave.getClass(), false, false); - maybeEmitEvent(new AfterSaveEvent(objectToSave, dbObject, collectionName)); - } - } - - protected void doSave(String collectionName, T objectToSave, MongoWriter writer) { - - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); - assertUpdateableIdIfNotSet(objectToSave); - - DBObject dbDoc = toDbObject(objectToSave, writer); - - maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName)); - Object id = saveDBObject(collectionName, dbDoc, objectToSave.getClass()); - - populateIdIfNecessary(objectToSave, id); - maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc, collectionName)); - } - - protected Object insertDBObject(final String collectionName, final DBObject dbDoc, final Class entityClass) { - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Inserting DBObject containing fields: {} in collection: {}", dbDoc.keySet(), collectionName); - } - - return execute(collectionName, new CollectionCallback() { - public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, - entityClass, dbDoc, null); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDoc) - : collection.insert(dbDoc, writeConcernToUse); - handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.INSERT); - return dbDoc.get(ID_FIELD); - } - }); - } - - // TODO: 2.0 - Change method signature to return List and return all identifiers (DATAMONGO-1513, - // DATAMONGO-1519) - protected List insertDBObjectList(final String collectionName, final List dbDocList) { - if (dbDocList.isEmpty()) { - return Collections.emptyList(); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Inserting list of DBObjects containing {} items", dbDocList.size()); - } - - execute(collectionName, new CollectionCallback() { - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null, - null, null); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDocList) - : collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse); - handleAnyWriteResultErrors(writeResult, null, MongoActionOperation.INSERT_LIST); - return null; - } - }); - - List ids = new ArrayList(); - for (DBObject dbo : dbDocList) { - Object id = dbo.get(ID_FIELD); - if (id instanceof ObjectId) { - ids.add((ObjectId) id); - } else { - // no id was generated - ids.add(null); - } - } - return ids; - } - - protected Object saveDBObject(final String collectionName, final DBObject dbDoc, final Class entityClass) { - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Saving DBObject containing fields: {}", dbDoc.keySet()); - } - - return execute(collectionName, new CollectionCallback() { - public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass, - dbDoc, null); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.save(dbDoc) - : collection.save(dbDoc, writeConcernToUse); - handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.SAVE); - return dbDoc.get(ID_FIELD); - } - }); - } - - public WriteResult upsert(Query query, Update update, Class entityClass) { - return doUpdate(determineCollectionName(entityClass), query, update, entityClass, true, false); - } - - public WriteResult upsert(Query query, Update update, String collectionName) { - return doUpdate(collectionName, query, update, null, true, false); - } - - public WriteResult upsert(Query query, Update update, Class entityClass, String collectionName) { - return doUpdate(collectionName, query, update, entityClass, true, false); - } - - public WriteResult updateFirst(Query query, Update update, Class entityClass) { - return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, false); - } - - public WriteResult updateFirst(final Query query, final Update update, final String collectionName) { - return doUpdate(collectionName, query, update, null, false, false); - } - - public WriteResult updateFirst(Query query, Update update, Class entityClass, String collectionName) { - return doUpdate(collectionName, query, update, entityClass, false, false); - } - - public WriteResult updateMulti(Query query, Update update, Class entityClass) { - return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, true); - } - - public WriteResult updateMulti(final Query query, final Update update, String collectionName) { - return doUpdate(collectionName, query, update, null, false, true); - } - - public WriteResult updateMulti(final Query query, final Update update, Class entityClass, String collectionName) { - return doUpdate(collectionName, query, update, entityClass, false, true); - } - - protected WriteResult doUpdate(final String collectionName, final Query query, final Update update, - final Class entityClass, final boolean upsert, final boolean multi) { - - return execute(collectionName, new CollectionCallback() { - public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { - - MongoPersistentEntity entity = entityClass == null ? null : getPersistentEntity(entityClass); - - increaseVersionForUpdateIfNecessary(entity, update); - - DBObject queryObj = query == null ? new BasicDBObject() - : queryMapper.getMappedObject(query.getQueryObject(), entity); - DBObject updateObj = update == null ? new BasicDBObject() - : updateMapper.getMappedObject(update.getUpdateObject(), entity); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", - serializeToJsonSafely(queryObj), serializeToJsonSafely(updateObj), collectionName); - } - - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, - entityClass, updateObj, queryObj); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - WriteResult writeResult = writeConcernToUse == null ? collection.update(queryObj, updateObj, upsert, multi) - : collection.update(queryObj, updateObj, upsert, multi, writeConcernToUse); - - if (entity != null && entity.hasVersionProperty() && !multi) { - if (ReflectiveWriteResultInvoker.wasAcknowledged(writeResult) && writeResult.getN() == 0 - && dbObjectContainsVersionProperty(queryObj, entity)) { - throw new OptimisticLockingFailureException("Optimistic lock exception on saving entity: " - + updateObj.toMap().toString() + " to collection " + collectionName); - } - } - - handleAnyWriteResultErrors(writeResult, queryObj, MongoActionOperation.UPDATE); - return writeResult; - } - }); - } - - private void increaseVersionForUpdateIfNecessary(MongoPersistentEntity persistentEntity, Update update) { - - if (persistentEntity != null && persistentEntity.hasVersionProperty()) { - String versionFieldName = persistentEntity.getVersionProperty().getFieldName(); - if (!update.modifies(versionFieldName)) { - update.inc(versionFieldName, 1L); - } - } - } - - private boolean dbObjectContainsVersionProperty(DBObject dbObject, MongoPersistentEntity persistentEntity) { - - if (persistentEntity == null || !persistentEntity.hasVersionProperty()) { - return false; - } + BasicDBObject dbObject = new BasicDBObject(); + + this.mongoConverter.write(objectToSave, dbObject); + + maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbObject, collectionName)); + Update update = Update.fromDBObject(dbObject, ID_FIELD); + + doUpdate(collectionName, query, update, objectToSave.getClass(), false, false); + maybeEmitEvent(new AfterSaveEvent(objectToSave, dbObject, collectionName)); + } + } + + protected void doSave(String collectionName, T objectToSave, MongoWriter writer) { + + maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); + assertUpdateableIdIfNotSet(objectToSave); + + DBObject dbDoc = toDbObject(objectToSave, writer); + + maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName)); + Object id = saveDBObject(collectionName, dbDoc, objectToSave.getClass()); + + populateIdIfNecessary(objectToSave, id); + maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc, collectionName)); + } + + protected Object insertDBObject(final String collectionName, final DBObject dbDoc, final Class entityClass) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Inserting DBObject containing fields: {} in collection: {}", dbDoc.keySet(), collectionName); + } + + return execute(collectionName, new CollectionCallback() { + public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, + entityClass, dbDoc, null); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDoc) + : collection.insert(dbDoc, writeConcernToUse); + handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.INSERT); + return dbDoc.get(ID_FIELD); + } + }); + } + + // TODO: 2.0 - Change method signature to return List and return all identifiers (DATAMONGO-1513, + // DATAMONGO-1519) + protected List insertDBObjectList(final String collectionName, final List dbDocList) { + if (dbDocList.isEmpty()) { + return Collections.emptyList(); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Inserting list of DBObjects containing {} items", dbDocList.size()); + } + + execute(collectionName, new CollectionCallback() { + public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null, + null, null); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult writeResult = writeConcernToUse == null ? collection.insert(dbDocList) + : collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse); + handleAnyWriteResultErrors(writeResult, null, MongoActionOperation.INSERT_LIST); + return null; + } + }); + + List ids = new ArrayList(); + for (DBObject dbo : dbDocList) { + Object id = dbo.get(ID_FIELD); + if (id instanceof ObjectId) { + ids.add((ObjectId) id); + } else { + // no id was generated + ids.add(null); + } + } + return ids; + } + + protected Object saveDBObject(final String collectionName, final DBObject dbDoc, final Class entityClass) { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Saving DBObject containing fields: {}", dbDoc.keySet()); + } + + return execute(collectionName, new CollectionCallback() { + public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass, + dbDoc, null); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult writeResult = writeConcernToUse == null ? collection.save(dbDoc) + : collection.save(dbDoc, writeConcernToUse); + handleAnyWriteResultErrors(writeResult, dbDoc, MongoActionOperation.SAVE); + return dbDoc.get(ID_FIELD); + } + }); + } + + public WriteResult upsert(Query query, Update update, Class entityClass) { + return doUpdate(determineCollectionName(entityClass), query, update, entityClass, true, false); + } + + public WriteResult upsert(Query query, Update update, String collectionName) { + return doUpdate(collectionName, query, update, null, true, false); + } + + public WriteResult upsert(Query query, Update update, Class entityClass, String collectionName) { + return doUpdate(collectionName, query, update, entityClass, true, false); + } + + public WriteResult updateFirst(Query query, Update update, Class entityClass) { + return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, false); + } + + public WriteResult updateFirst(final Query query, final Update update, final String collectionName) { + return doUpdate(collectionName, query, update, null, false, false); + } + + public WriteResult updateFirst(Query query, Update update, Class entityClass, String collectionName) { + return doUpdate(collectionName, query, update, entityClass, false, false); + } + + public WriteResult updateMulti(Query query, Update update, Class entityClass) { + return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, true); + } + + public WriteResult updateMulti(final Query query, final Update update, String collectionName) { + return doUpdate(collectionName, query, update, null, false, true); + } + + public WriteResult updateMulti(final Query query, final Update update, Class entityClass, String collectionName) { + return doUpdate(collectionName, query, update, entityClass, false, true); + } + + protected WriteResult doUpdate(final String collectionName, final Query query, final Update update, + final Class entityClass, final boolean upsert, final boolean multi) { + + return execute(collectionName, new CollectionCallback() { + public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { + + MongoPersistentEntity entity = entityClass == null ? null : getPersistentEntity(entityClass); + + increaseVersionForUpdateIfNecessary(entity, update); + + DBObject queryObj = query == null ? new BasicDBObject() + : queryMapper.getMappedObject(query.getQueryObject(), entity); + DBObject updateObj = update == null ? new BasicDBObject() + : updateMapper.getMappedObject(update.getUpdateObject(), entity); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", + serializeToJsonSafely(queryObj), serializeToJsonSafely(updateObj), collectionName); + } + + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, + entityClass, updateObj, queryObj); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteResult writeResult = writeConcernToUse == null ? collection.update(queryObj, updateObj, upsert, multi) + : collection.update(queryObj, updateObj, upsert, multi, writeConcernToUse); + + if (entity != null && entity.hasVersionProperty() && !multi) { + if (ReflectiveWriteResultInvoker.wasAcknowledged(writeResult) && writeResult.getN() == 0 + && dbObjectContainsVersionProperty(queryObj, entity)) { + throw new OptimisticLockingFailureException("Optimistic lock exception on saving entity: " + + updateObj.toMap().toString() + " to collection " + collectionName); + } + } + + handleAnyWriteResultErrors(writeResult, queryObj, MongoActionOperation.UPDATE); + return writeResult; + } + }); + } + + private void increaseVersionForUpdateIfNecessary(MongoPersistentEntity persistentEntity, Update update) { + + if (persistentEntity != null && persistentEntity.hasVersionProperty()) { + String versionFieldName = persistentEntity.getVersionProperty().getFieldName(); + if (!update.modifies(versionFieldName)) { + update.inc(versionFieldName, 1L); + } + } + } + + private boolean dbObjectContainsVersionProperty(DBObject dbObject, MongoPersistentEntity persistentEntity) { + + if (persistentEntity == null || !persistentEntity.hasVersionProperty()) { + return false; + } - return dbObject.containsField(persistentEntity.getVersionProperty().getFieldName()); - } + return dbObject.containsField(persistentEntity.getVersionProperty().getFieldName()); + } - public WriteResult remove(Object object) { + public WriteResult remove(Object object) { - if (object == null) { - return null; - } + if (object == null) { + return null; + } - return remove(getIdQueryFor(object), object.getClass()); - } + return remove(getIdQueryFor(object), object.getClass()); + } - public WriteResult remove(Object object, String collection) { + public WriteResult remove(Object object, String collection) { - Assert.hasText(collection, "Collection name must not be null or empty!"); + Assert.hasText(collection, "Collection name must not be null or empty!"); - if (object == null) { - return null; - } + if (object == null) { + return null; + } - return doRemove(collection, getIdQueryFor(object), object.getClass()); - } + return doRemove(collection, getIdQueryFor(object), object.getClass()); + } - /** - * Returns {@link Entry} containing the field name of the id property as {@link Entry#getKey()} and the {@link Id}s - * property value as its {@link Entry#getValue()}. - * - * @param object - * @return - */ - private Entry extractIdPropertyAndValue(Object object) { + /** + * Returns {@link Entry} containing the field name of the id property as {@link Entry#getKey()} and the {@link Id}s + * property value as its {@link Entry#getValue()}. + * + * @param object + * @return + */ + private Entry extractIdPropertyAndValue(Object object) { - Assert.notNull(object, "Id cannot be extracted from 'null'."); + Assert.notNull(object, "Id cannot be extracted from 'null'."); - Class objectType = object.getClass(); + Class objectType = object.getClass(); - if (object instanceof DBObject) { - return Collections.singletonMap(ID_FIELD, ((DBObject) object).get(ID_FIELD)).entrySet().iterator().next(); - } + if (object instanceof DBObject) { + return Collections.singletonMap(ID_FIELD, ((DBObject) object).get(ID_FIELD)).entrySet().iterator().next(); + } - MongoPersistentEntity entity = mappingContext.getPersistentEntity(objectType); - MongoPersistentProperty idProp = entity == null ? null : entity.getIdProperty(); + MongoPersistentEntity entity = mappingContext.getPersistentEntity(objectType); + MongoPersistentProperty idProp = entity == null ? null : entity.getIdProperty(); - if (idProp == null || entity == null) { - throw new MappingException("No id property found for object of type " + objectType); - } + if (idProp == null || entity == null) { + throw new MappingException("No id property found for object of type " + objectType); + } - Object idValue = entity.getPropertyAccessor(object).getProperty(idProp); - return Collections.singletonMap(idProp.getFieldName(), idValue).entrySet().iterator().next(); - } + Object idValue = entity.getPropertyAccessor(object).getProperty(idProp); + return Collections.singletonMap(idProp.getFieldName(), idValue).entrySet().iterator().next(); + } - /** - * Returns a {@link Query} for the given entity by its id. - * - * @param object must not be {@literal null}. - * @return - */ - private Query getIdQueryFor(Object object) { + /** + * Returns a {@link Query} for the given entity by its id. + * + * @param object must not be {@literal null}. + * @return + */ + private Query getIdQueryFor(Object object) { - Entry id = extractIdPropertyAndValue(object); - return new Query(where(id.getKey()).is(id.getValue())); - } + Entry id = extractIdPropertyAndValue(object); + return new Query(where(id.getKey()).is(id.getValue())); + } - /** - * Returns a {@link Query} for the given entities by their ids. - * - * @param objects must not be {@literal null} or {@literal empty}. - * @return - */ - private Query getIdInQueryFor(Collection objects) { + /** + * Returns a {@link Query} for the given entities by their ids. + * + * @param objects must not be {@literal null} or {@literal empty}. + * @return + */ + private Query getIdInQueryFor(Collection objects) { - Assert.notEmpty(objects, "Cannot create Query for empty collection."); + Assert.notEmpty(objects, "Cannot create Query for empty collection."); - Iterator it = objects.iterator(); - Entry firstEntry = extractIdPropertyAndValue(it.next()); + Iterator it = objects.iterator(); + Entry firstEntry = extractIdPropertyAndValue(it.next()); - ArrayList ids = new ArrayList(objects.size()); - ids.add(firstEntry.getValue()); + ArrayList ids = new ArrayList(objects.size()); + ids.add(firstEntry.getValue()); - while (it.hasNext()) { - ids.add(extractIdPropertyAndValue(it.next()).getValue()); - } + while (it.hasNext()) { + ids.add(extractIdPropertyAndValue(it.next()).getValue()); + } - return new Query(where(firstEntry.getKey()).in(ids)); - } + return new Query(where(firstEntry.getKey()).in(ids)); + } - private void assertUpdateableIdIfNotSet(Object entity) { + private void assertUpdateableIdIfNotSet(Object entity) { - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entity.getClass()); - MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty(); + MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entity.getClass()); + MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty(); - if (idProperty == null || persistentEntity == null) { - return; - } + if (idProperty == null || persistentEntity == null) { + return; + } - Object idValue = persistentEntity.getPropertyAccessor(entity).getProperty(idProperty); + Object idValue = persistentEntity.getPropertyAccessor(entity).getProperty(idProperty); - if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) { - throw new InvalidDataAccessApiUsageException( - String.format("Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(), - entity.getClass().getName())); - } - } + if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) { + throw new InvalidDataAccessApiUsageException( + String.format("Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(), + entity.getClass().getName())); + } + } - public WriteResult remove(Query query, String collectionName) { - return remove(query, null, collectionName); - } + public WriteResult remove(Query query, String collectionName) { + return remove(query, null, collectionName); + } - public WriteResult remove(Query query, Class entityClass) { - return remove(query, entityClass, determineCollectionName(entityClass)); - } + public WriteResult remove(Query query, Class entityClass) { + return remove(query, entityClass, determineCollectionName(entityClass)); + } - public WriteResult remove(Query query, Class entityClass, String collectionName) { - return doRemove(collectionName, query, entityClass); - } + public WriteResult remove(Query query, Class entityClass, String collectionName) { + return doRemove(collectionName, query, entityClass); + } - protected WriteResult doRemove(final String collectionName, final Query query, final Class entityClass) { + protected WriteResult doRemove(final String collectionName, final Query query, final Class entityClass) { - if (query == null) { - throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null!"); - } + if (query == null) { + throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null!"); + } - Assert.hasText(collectionName, "Collection name must not be null or empty!"); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); - final DBObject queryObject = query.getQueryObject(); - final MongoPersistentEntity entity = getPersistentEntity(entityClass); + final DBObject queryObject = query.getQueryObject(); + final MongoPersistentEntity entity = getPersistentEntity(entityClass); - return execute(collectionName, new CollectionCallback() { - public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { + return execute(collectionName, new CollectionCallback() { + public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { - maybeEmitEvent(new BeforeDeleteEvent(queryObject, entityClass, collectionName)); + maybeEmitEvent(new BeforeDeleteEvent(queryObject, entityClass, collectionName)); - DBObject dboq = queryMapper.getMappedObject(queryObject, entity); + DBObject dboq = queryMapper.getMappedObject(queryObject, entity); - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, - entityClass, null, queryObject); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, + entityClass, null, queryObject); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Remove using query: {} in collection: {}.", - new Object[] { serializeToJsonSafely(dboq), collectionName }); - } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Remove using query: {} in collection: {}.", + new Object[] { serializeToJsonSafely(dboq), collectionName }); + } - WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) - : collection.remove(dboq, writeConcernToUse); + WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) + : collection.remove(dboq, writeConcernToUse); - handleAnyWriteResultErrors(wr, dboq, MongoActionOperation.REMOVE); + handleAnyWriteResultErrors(wr, dboq, MongoActionOperation.REMOVE); - maybeEmitEvent(new AfterDeleteEvent(queryObject, entityClass, collectionName)); + maybeEmitEvent(new AfterDeleteEvent(queryObject, entityClass, collectionName)); - return wr; - } - }); - } + return wr; + } + }); + } - public List findAll(Class entityClass) { - return findAll(entityClass, determineCollectionName(entityClass)); - } + public List findAll(Class entityClass) { + return findAll(entityClass, determineCollectionName(entityClass)); + } - public List findAll(Class entityClass, String collectionName) { - return executeFindMultiInternal(new FindCallback(null), null, - new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), collectionName); - } + public List findAll(Class entityClass, String collectionName) { + return executeFindMultiInternal(new FindCallback(null), null, + new ReadDbObjectCallback(mongoConverter, entityClass, collectionName), collectionName); + } - public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - Class entityClass) { - return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), - entityClass); - } + public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, + Class entityClass) { + return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), + entityClass); + } - public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - MapReduceOptions mapReduceOptions, Class entityClass) { - return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, mapReduceOptions, entityClass); - } + public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, + MapReduceOptions mapReduceOptions, Class entityClass) { + return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, mapReduceOptions, entityClass); + } - public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, - String reduceFunction, Class entityClass) { - return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), - entityClass); - } + public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, + String reduceFunction, Class entityClass) { + return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), + entityClass); + } - public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, - String reduceFunction, MapReduceOptions mapReduceOptions, Class entityClass) { + public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, + String reduceFunction, MapReduceOptions mapReduceOptions, Class entityClass) { - String mapFunc = replaceWithResourceIfNecessary(mapFunction); - String reduceFunc = replaceWithResourceIfNecessary(reduceFunction); - DBCollection inputCollection = getCollection(inputCollectionName); + String mapFunc = replaceWithResourceIfNecessary(mapFunction); + String reduceFunc = replaceWithResourceIfNecessary(reduceFunction); + DBCollection inputCollection = getCollection(inputCollectionName); - MapReduceCommand command = new MapReduceCommand(inputCollection, mapFunc, reduceFunc, - mapReduceOptions.getOutputCollection(), mapReduceOptions.getOutputType(), - query == null || query.getQueryObject() == null ? null - : queryMapper.getMappedObject(query.getQueryObject(), null)); + MapReduceCommand command = new MapReduceCommand(inputCollection, mapFunc, reduceFunc, + mapReduceOptions.getOutputCollection(), mapReduceOptions.getOutputType(), + query == null || query.getQueryObject() == null ? null + : queryMapper.getMappedObject(query.getQueryObject(), null)); - copyMapReduceOptionsToCommand(query, mapReduceOptions, command); + copyMapReduceOptionsToCommand(query, mapReduceOptions, command); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing MapReduce on collection [{}], mapFunction [{}], reduceFunction [{}]", command.getInput(), - mapFunc, reduceFunc); - } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing MapReduce on collection [{}], mapFunction [{}], reduceFunction [{}]", command.getInput(), + mapFunc, reduceFunc); + } - MapReduceOutput mapReduceOutput = inputCollection.mapReduce(command); + MapReduceOutput mapReduceOutput = inputCollection.mapReduce(command); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("MapReduce command result = [{}]", serializeToJsonSafely(mapReduceOutput.results())); - } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("MapReduce command result = [{}]", serializeToJsonSafely(mapReduceOutput.results())); + } - List mappedResults = new ArrayList(); - DbObjectCallback callback = new ReadDbObjectCallback(mongoConverter, entityClass, inputCollectionName); + List mappedResults = new ArrayList(); + DbObjectCallback callback = new ReadDbObjectCallback(mongoConverter, entityClass, inputCollectionName); - for (DBObject dbObject : mapReduceOutput.results()) { - mappedResults.add(callback.doWith(dbObject)); - } + for (DBObject dbObject : mapReduceOutput.results()) { + mappedResults.add(callback.doWith(dbObject)); + } + + return new MapReduceResults(mappedResults, mapReduceOutput); + } - return new MapReduceResults(mappedResults, mapReduceOutput); - } - - public GroupByResults group(String inputCollectionName, GroupBy groupBy, Class entityClass) { - return group(null, inputCollectionName, groupBy, entityClass); - } - - public GroupByResults group(Criteria criteria, String inputCollectionName, GroupBy groupBy, - Class entityClass) { - - DBObject dbo = groupBy.getGroupByObject(); - dbo.put("ns", inputCollectionName); - - if (criteria == null) { - dbo.put("cond", null); - } else { - dbo.put("cond", queryMapper.getMappedObject(criteria.getCriteriaObject(), null)); - } - // If initial document was a JavaScript string, potentially loaded by Spring's Resource abstraction, load it and - // convert to DBObject - - if (dbo.containsField("initial")) { - Object initialObj = dbo.get("initial"); - if (initialObj instanceof String) { - String initialAsString = replaceWithResourceIfNecessary((String) initialObj); - dbo.put("initial", JSON.parse(initialAsString)); - } - } - - if (dbo.containsField("$reduce")) { - dbo.put("$reduce", replaceWithResourceIfNecessary(dbo.get("$reduce").toString())); - } - if (dbo.containsField("$keyf")) { - dbo.put("$keyf", replaceWithResourceIfNecessary(dbo.get("$keyf").toString())); - } - if (dbo.containsField("finalize")) { - dbo.put("finalize", replaceWithResourceIfNecessary(dbo.get("finalize").toString())); - } - - DBObject commandObject = new BasicDBObject("group", dbo); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing Group with DBObject [{}]", serializeToJsonSafely(commandObject)); - } - - CommandResult commandResult = executeCommand(commandObject, getDb().getOptions()); - handleCommandError(commandResult, commandObject); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Group command result = [{}]", commandResult); - } - - @SuppressWarnings("unchecked") - Iterable resultSet = (Iterable) commandResult.get("retval"); - List mappedResults = new ArrayList(); - DbObjectCallback callback = new ReadDbObjectCallback(mongoConverter, entityClass, inputCollectionName); - - for (DBObject dbObject : resultSet) { - mappedResults.add(callback.doWith(dbObject)); - } - - return new GroupByResults(mappedResults, commandResult); - } - - @Override - public AggregationResults aggregate(TypedAggregation aggregation, Class outputType) { - return aggregate(aggregation, determineCollectionName(aggregation.getInputType()), outputType); - } - - @Override - public CloseableIterator aggregateStream(TypedAggregation aggregation, Class outputType) { - return aggregateStream(aggregation, determineCollectionName(aggregation.getInputType()), outputType); - } - - @Override - public AggregationResults aggregate(TypedAggregation aggregation, String inputCollectionName, - Class outputType) { - - Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - - AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), - mappingContext, queryMapper); - return aggregate(aggregation, inputCollectionName, outputType, context); - } - - @Override - public CloseableIterator aggregateStream(TypedAggregation aggregation, String inputCollectionName, - Class outputType) { - - Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - - AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), - mappingContext, queryMapper); - return aggregateStream(aggregation, inputCollectionName, outputType, context); - } - - @Override - public AggregationResults aggregate(Aggregation aggregation, Class inputType, Class outputType) { - - return aggregate(aggregation, determineCollectionName(inputType), outputType, - new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); - } - - @Override - public CloseableIterator aggregateStream(Aggregation aggregation, Class inputType, Class outputType) { - - return aggregateStream(aggregation, determineCollectionName(inputType), outputType, - new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); - } - - - @Override - public AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType) { - return aggregate(aggregation, collectionName, outputType, null); - } - - - @Override - public CloseableIterator aggregateStream(Aggregation aggregation, String collectionName, Class outputType) { - return aggregateStream(aggregation, collectionName, outputType, null); - } - - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String) - */ - @Override - public List findAllAndRemove(Query query, String collectionName) { - return findAndRemove(query, null, collectionName); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class) - */ - @Override - public List findAllAndRemove(Query query, Class entityClass) { - return findAllAndRemove(query, entityClass, determineCollectionName(entityClass)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) - */ - @Override - public List findAllAndRemove(Query query, Class entityClass, String collectionName) { - return doFindAndDelete(collectionName, query, entityClass); - } - - /** - * Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)} - * and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is - * constructed out of the find result. - * - * @param collectionName - * @param query - * @param entityClass - * @return - */ - protected List doFindAndDelete(String collectionName, Query query, Class entityClass) { - - List result = find(query, entityClass, collectionName); - - if (!CollectionUtils.isEmpty(result)) { - remove(getIdInQueryFor(result), entityClass, collectionName); - } - - return result; - } - - protected AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType, - AggregationOperationContext context) { - - Assert.hasText(collectionName, "Collection name must not be null or empty!"); - Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - Assert.notNull(outputType, "Output type must not be null!"); - - AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; - DBObject command = aggregation.toDbObject(collectionName, rootContext); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing aggregation: {}", serializeToJsonSafely(command)); - } - - CommandResult commandResult = executeCommand(command, this.readPreference); - handleCommandError(commandResult, command); - - return new AggregationResults(returnPotentiallyMappedResults(outputType, commandResult, collectionName), - commandResult); - } - - /** - * Returns the potentially mapped results of the given {@commandResult} contained some. - * - * @param outputType - * @param commandResult - * @return - */ - private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, - String collectionName) { - - @SuppressWarnings("unchecked") - Iterable resultSet = (Iterable) commandResult.get("result"); - if (resultSet == null) { - return Collections.emptyList(); - } - - DbObjectCallback callback = new UnwrapAndReadDbObjectCallback(mongoConverter, outputType, collectionName); - - List mappedResults = new ArrayList(); - for (DBObject dbObject : resultSet) { - mappedResults.add(callback.doWith(dbObject)); - } - - return mappedResults; - } - - protected String replaceWithResourceIfNecessary(String function) { - - String func = function; - - if (this.resourceLoader != null && ResourceUtils.isUrl(function)) { - - Resource functionResource = resourceLoader.getResource(func); - - if (!functionResource.exists()) { - throw new InvalidDataAccessApiUsageException(String.format("Resource %s not found!", function)); - } - - Scanner scanner = null; - - try { - scanner = new Scanner(functionResource.getInputStream()); - return scanner.useDelimiter("\\A").next(); - } catch (IOException e) { - throw new InvalidDataAccessApiUsageException(String.format("Cannot read map-reduce file %s!", function), e); - } finally { - if (scanner != null) { - scanner.close(); - } - } - } - - return func; - } - - private void copyMapReduceOptionsToCommand(Query query, MapReduceOptions mapReduceOptions, - MapReduceCommand mapReduceCommand) { - - if (query != null) { - if (query.getSkip() != 0 || query.getFieldsObject() != null) { - throw new InvalidDataAccessApiUsageException( - "Can not use skip or field specification with map reduce operations"); - } - if (query.getLimit() > 0 && mapReduceOptions.getLimit() == null) { - mapReduceCommand.setLimit(query.getLimit()); - } - if (query.getSortObject() != null) { - mapReduceCommand.setSort(queryMapper.getMappedObject(query.getSortObject(), null)); - } - } - - if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit().intValue() > 0) { - mapReduceCommand.setLimit(mapReduceOptions.getLimit()); - } - - if (mapReduceOptions.getJavaScriptMode() != null) { - mapReduceCommand.setJsMode(true); - } - if (!mapReduceOptions.getExtraOptions().isEmpty()) { - for (Map.Entry entry : mapReduceOptions.getExtraOptions().entrySet()) { - ReflectiveMapReduceInvoker.addExtraOption(mapReduceCommand, entry.getKey(), entry.getValue()); - } - } - if (mapReduceOptions.getFinalizeFunction() != null) { - mapReduceCommand.setFinalize(this.replaceWithResourceIfNecessary(mapReduceOptions.getFinalizeFunction())); - } - if (mapReduceOptions.getOutputDatabase() != null) { - mapReduceCommand.setOutputDB(mapReduceOptions.getOutputDatabase()); - } - if (!mapReduceOptions.getScopeVariables().isEmpty()) { - mapReduceCommand.setScope(mapReduceOptions.getScopeVariables()); - } - } - - public Set getCollectionNames() { - return execute(new DbCallback>() { - public Set doInDB(DB db) throws MongoException, DataAccessException { - return db.getCollectionNames(); - } - }); - } - - public DB getDb() { - return mongoDbFactory.getDb(); - } - - protected void maybeEmitEvent(MongoMappingEvent event) { - if (null != eventPublisher) { - eventPublisher.publishEvent(event); - } - } - - /** - * Create the specified collection using the provided options - * - * @param collectionName - * @param collectionOptions - * @return the collection that was created - */ - protected DBCollection doCreateCollection(final String collectionName, final DBObject collectionOptions) { - return execute(new DbCallback() { - public DBCollection doInDB(DB db) throws MongoException, DataAccessException { - DBCollection coll = db.createCollection(collectionName, collectionOptions); - // TODO: Emit a collection created event - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Created collection [{}]", coll.getFullName()); - } - return coll; - } - }); - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. - * The query document is specified as a standard {@link DBObject} and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from. - * @param query the query document that specifies the criteria used to find a record. - * @param fields the document that specifies the fields to be returned. - * @param entityClass the parameterized type of the returned list. - * @return the {@link List} of converted objects. - */ - protected T doFindOne(String collectionName, DBObject query, DBObject fields, Class entityClass) { - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - DBObject mappedQuery = queryMapper.getMappedObject(query, entity); - DBObject mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findOne using query: {} fields: {} for class: {} in collection: {}", serializeToJsonSafely(query), - mappedFields, entityClass, collectionName); - } - - return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields), - new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName), collectionName); - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to a List using the template's converter. The - * query document is specified as a standard DBObject and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from - * @param query the query document that specifies the criteria used to find a record - * @param fields the document that specifies the fields to be returned - * @param entityClass the parameterized type of the returned list. - * @return the List of converted objects. - */ - protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass) { - return doFind(collectionName, query, fields, entityClass, null, - new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName)); - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified type. The object is - * converted from the MongoDB native representation using an instance of {@see MongoConverter}. The query document is - * specified as a standard DBObject and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from. - * @param query the query document that specifies the criteria used to find a record. - * @param fields the document that specifies the fields to be returned. - * @param entityClass the parameterized type of the returned list. - * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply - * limits, skips and so on). - * @return the {@link List} of converted objects. - */ - protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, - CursorPreparer preparer) { - return doFind(collectionName, query, fields, entityClass, preparer, - new ReadDbObjectCallback(mongoConverter, entityClass, collectionName)); - } - - protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, - CursorPreparer preparer, DbObjectCallback objectCallback) { - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - - DBObject mappedFields = queryMapper.getMappedFields(fields, entity); - DBObject mappedQuery = queryMapper.getMappedObject(query, entity); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}", - serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName); - } - - return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer, objectCallback, - collectionName); - } - - protected DBObject convertToDbObject(CollectionOptions collectionOptions) { - DBObject dbo = new BasicDBObject(); - if (collectionOptions != null) { - if (collectionOptions.getCapped() != null) { - dbo.put("capped", collectionOptions.getCapped().booleanValue()); - } - if (collectionOptions.getSize() != null) { - dbo.put("size", collectionOptions.getSize().intValue()); - } - if (collectionOptions.getMaxDocuments() != null) { - dbo.put("max", collectionOptions.getMaxDocuments().intValue()); - } - } - return dbo; - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. - * The first document that matches the query is returned and also removed from the collection in the database. - *

                        - * The query document is specified as a standard DBObject and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from - * @param query the query document that specifies the criteria used to find a record - * @param entityClass the parameterized type of the returned list. - * @return the List of converted objects. - */ - protected T doFindAndRemove(String collectionName, DBObject query, DBObject fields, DBObject sort, - Class entityClass) { - - EntityReader readerToUse = this.mongoConverter; - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findAndRemove using query: {} fields: {} sort: {} for class: {} in collection: {}", - serializeToJsonSafely(query), fields, sort, entityClass, collectionName); - } - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - - return executeFindOneInternal(new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort), - new ReadDbObjectCallback(readerToUse, entityClass, collectionName), collectionName); - } - - protected T doFindAndModify(String collectionName, DBObject query, DBObject fields, DBObject sort, - Class entityClass, Update update, FindAndModifyOptions options) { - - EntityReader readerToUse = this.mongoConverter; - - if (options == null) { - options = new FindAndModifyOptions(); - } - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - - increaseVersionForUpdateIfNecessary(entity, update); - - DBObject mappedQuery = queryMapper.getMappedObject(query, entity); - DBObject mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - "findAndModify using query: {} fields: {} sort: {} for class: {} and update: {} " + "in collection: {}", - serializeToJsonSafely(mappedQuery), fields, sort, entityClass, serializeToJsonSafely(mappedUpdate), - collectionName); - } - - return executeFindOneInternal(new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate, options), - new ReadDbObjectCallback(readerToUse, entityClass, collectionName), collectionName); - } - - /** - * Populates the id property of the saved object, if it's not set already. - * - * @param savedObject - * @param id - */ - protected void populateIdIfNecessary(Object savedObject, Object id) { - - if (id == null) { - return; - } - - if (savedObject instanceof BasicDBObject) { - DBObject dbObject = (DBObject) savedObject; - dbObject.put(ID_FIELD, id); - return; - } - - MongoPersistentProperty idProp = getIdPropertyFor(savedObject.getClass()); - - if (idProp == null) { - return; - } - - ConversionService conversionService = mongoConverter.getConversionService(); - MongoPersistentEntity entity = mappingContext.getPersistentEntity(savedObject.getClass()); - PersistentPropertyAccessor accessor = entity.getPropertyAccessor(savedObject); - - if (accessor.getProperty(idProp) != null) { - return; - } - - new ConvertingPropertyAccessor(accessor, conversionService).setProperty(idProp, id); - } - - private DBCollection getAndPrepareCollection(DB db, String collectionName) { - try { - DBCollection collection = db.getCollection(collectionName); - prepareCollection(collection); - return collection; - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } - - /** - * Internal method using callbacks to do queries against the datastore that requires reading a single object from a - * collection of objects. It will take the following steps - *

                          - *
                        1. Execute the given {@link ConnectionCallback} for a {@link DBObject}.
                        2. - *
                        3. Apply the given {@link DbObjectCallback} to each of the {@link DBObject}s to obtain the result.
                        4. - *
                            - * - * @param - * @param collectionCallback the callback to retrieve the {@link DBObject} with - * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type - * @param collectionName the collection to be queried - * @return - */ - private T executeFindOneInternal(CollectionCallback collectionCallback, - DbObjectCallback objectCallback, String collectionName) { - - try { - T result = objectCallback - .doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName))); - return result; - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } - - /** - * Internal method using callback to do queries against the datastore that requires reading a collection of objects. - * It will take the following steps - *
                              - *
                            1. Execute the given {@link ConnectionCallback} for a {@link DBCursor}.
                            2. - *
                            3. Prepare that {@link DBCursor} with the given {@link CursorPreparer} (will be skipped if {@link CursorPreparer} - * is {@literal null}
                            4. - *
                            5. Iterate over the {@link DBCursor} and applies the given {@link DbObjectCallback} to each of the - * {@link DBObject}s collecting the actual result {@link List}.
                            6. - *
                                - * - * @param - * @param collectionCallback the callback to retrieve the {@link DBCursor} with - * @param preparer the {@link CursorPreparer} to potentially modify the {@link DBCursor} before ireating over it - * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type - * @param collectionName the collection to be queried - * @return - */ - private List executeFindMultiInternal(CollectionCallback collectionCallback, CursorPreparer preparer, - DbObjectCallback objectCallback, String collectionName) { - - try { - - DBCursor cursor = null; - - try { - - cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)); - - if (preparer != null) { - cursor = preparer.prepare(cursor); - } - - List result = new ArrayList(); - - while (cursor.hasNext()) { - DBObject object = cursor.next(); - result.add(objectCallback.doWith(object)); - } - - return result; - - } finally { - - if (cursor != null) { - cursor.close(); - } - } - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } - - private void executeQueryInternal(CollectionCallback collectionCallback, CursorPreparer preparer, - DocumentCallbackHandler callbackHandler, String collectionName) { - - try { - - DBCursor cursor = null; - - try { - cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)); - - if (preparer != null) { - cursor = preparer.prepare(cursor); - } - - while (cursor.hasNext()) { - DBObject dbobject = cursor.next(); - callbackHandler.processDocument(dbobject); - } - - } finally { - if (cursor != null) { - cursor.close(); - } - } - - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - } - - private MongoPersistentEntity getPersistentEntity(Class type) { - return type == null ? null : mappingContext.getPersistentEntity(type); - } - - private MongoPersistentProperty getIdPropertyFor(Class type) { - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(type); - return persistentEntity == null ? null : persistentEntity.getIdProperty(); - } - - private String determineEntityCollectionName(T obj) { - if (null != obj) { - return determineCollectionName(obj.getClass()); - } - - return null; - } - - String determineCollectionName(Class entityClass) { - - if (entityClass == null) { - throw new InvalidDataAccessApiUsageException( - "No class parameter provided, entity collection can't be determined!"); - } - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - if (entity == null) { - throw new InvalidDataAccessApiUsageException( - "No Persistent Entity information found for the class " + entityClass.getName()); - } - return entity.getCollection(); - } - - /** - * Handles {@link WriteResult} errors based on the configured {@link WriteResultChecking}. - * - * @param writeResult - * @param query - * @param operation - */ - protected void handleAnyWriteResultErrors(WriteResult writeResult, DBObject query, MongoActionOperation operation) { - - if (writeResultChecking == WriteResultChecking.NONE) { - return; - } - - String error = ReflectiveWriteResultInvoker.getError(writeResult); - - if (error == null) { - return; - } - - String message; - - switch (operation) { - - case INSERT: - case SAVE: - message = String.format("Insert/Save for %s failed: %s", query, error); - break; - case INSERT_LIST: - message = String.format("Insert list failed: %s", error); - break; - default: - message = String.format("Execution of %s%s failed: %s", operation, - query == null ? "" : " using query " + query.toString(), error); - } - - if (writeResultChecking == WriteResultChecking.EXCEPTION) { - throw new MongoDataIntegrityViolationException(message, writeResult, operation); - } else { - LOGGER.error(message); - return; - } - } - - /** - * Inspects the given {@link CommandResult} for erros and potentially throws an - * {@link InvalidDataAccessApiUsageException} for that error. - * - * @param result must not be {@literal null}. - * @param source must not be {@literal null}. - */ - private void handleCommandError(CommandResult result, DBObject source) { - - try { - result.throwOnError(); - } catch (MongoException ex) { - - String error = result.getErrorMessage(); - error = error == null ? "NO MESSAGE" : error; - - throw new InvalidDataAccessApiUsageException( - "Command execution failed: Error [" + error + "], Command = " + source, ex); - } - } - - private static final MongoConverter getDefaultMongoConverter(MongoDbFactory factory) { - - DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory); - MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext()); - converter.afterPropertiesSet(); - return converter; - } - - private DBObject getMappedSortObject(Query query, Class type) { - - if (query == null || query.getSortObject() == null) { - return null; - } - - return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type)); - } - - /** - * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original - * exception if the conversation failed. Thus allows safe re-throwing of the return value. - * - * @param ex the exception to translate - * @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation - * @return - */ - private static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex, - PersistenceExceptionTranslator exceptionTranslator) { - RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex); - return resolved == null ? ex : resolved; - } - - /** - * Returns all identifiers for the given documents. Will augment the given identifiers and fill in only the ones that - * are {@literal null} currently. This would've been better solved in {@link #insertDBObjectList(String, List)} - * directly but would require a signature change of that method. - * - * @param ids - * @param documents - * @return TODO: Remove for 2.0 and change method signature of {@link #insertDBObjectList(String, List)}. - */ - private static List consolidateIdentifiers(List ids, List documents) { - - List result = new ArrayList(ids.size()); - - for (int i = 0; i < ids.size(); i++) { - - ObjectId objectId = ids.get(i); - result.add(objectId == null ? documents.get(i).get(ID_FIELD) : objectId); - } - - return result; - } - - // Callback implementations - - /** - * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification - * {@link DBObject} and executes that against the {@link DBCollection}. - * - * @author Oliver Gierke - * @author Thomas Risberg - */ - private static class FindOneCallback implements CollectionCallback { - - private final DBObject query; - private final DBObject fields; - - public FindOneCallback(DBObject query, DBObject fields) { - this.query = query; - this.fields = fields; - } - - public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { - if (fields == null) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findOne using query: {} in db.collection: {}", serializeToJsonSafely(query), - collection.getFullName()); - } - return collection.findOne(query); - } else { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query), fields, - collection.getFullName()); - } - return collection.findOne(query, fields); - } - } - } - - /** - * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification - * {@link DBObject} and executes that against the {@link DBCollection}. - * - * @author Oliver Gierke - * @author Thomas Risberg - */ - private static class FindCallback implements CollectionCallback { - - private final DBObject query; - private final DBObject fields; - - public FindCallback(DBObject query) { - this(query, null); - } - - public FindCallback(DBObject query, DBObject fields) { - this.query = query == null ? new BasicDBObject() : query; - this.fields = fields; - } - - public DBCursor doInCollection(DBCollection collection) throws MongoException, DataAccessException { - - if (fields == null || fields.toMap().isEmpty()) { - return collection.find(query); - } else { - return collection.find(query, fields); - } - } - } - - /** - * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification - * {@link DBObject} and executes that against the {@link DBCollection}. - * - * @author Thomas Risberg - */ - private static class FindAndRemoveCallback implements CollectionCallback { - - private final DBObject query; - private final DBObject fields; - private final DBObject sort; - - public FindAndRemoveCallback(DBObject query, DBObject fields, DBObject sort) { - this.query = query; - this.fields = fields; - this.sort = sort; - } - - public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { - return collection.findAndModify(query, fields, sort, true, null, false, false); - } - } - - private static class FindAndModifyCallback implements CollectionCallback { - - private final DBObject query; - private final DBObject fields; - private final DBObject sort; - private final DBObject update; - private final FindAndModifyOptions options; - - public FindAndModifyCallback(DBObject query, DBObject fields, DBObject sort, DBObject update, - FindAndModifyOptions options) { - this.query = query; - this.fields = fields; - this.sort = sort; - this.update = update; - this.options = options; - } - - public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { - return collection.findAndModify(query, fields, sort, options.isRemove(), update, options.isReturnNew(), - options.isUpsert()); - } - } - - /** - * Simple internal callback to allow operations on a {@link DBObject}. - * - * @author Oliver Gierke - * @author Thomas Darimont - */ - - interface DbObjectCallback { - - T doWith(DBObject object); - } - - /** - * Simple {@link DbObjectCallback} that will transform {@link DBObject} into the given target type using the given - * {@link MongoReader}. - * - * @author Oliver Gierke - * @author Christoph Strobl - */ - private class ReadDbObjectCallback implements DbObjectCallback { - - private final EntityReader reader; - private final Class type; - private final String collectionName; - - public ReadDbObjectCallback(EntityReader reader, Class type, String collectionName) { - - Assert.notNull(reader, "EntityReader must not be null!"); - Assert.notNull(type, "Entity type must not be null!"); - - this.reader = reader; - this.type = type; - this.collectionName = collectionName; - } - - public T doWith(DBObject object) { - if (null != object) { - maybeEmitEvent(new AfterLoadEvent(object, type, collectionName)); - } - T source = reader.read(type, object); - if (null != source) { - maybeEmitEvent(new AfterConvertEvent(object, source, collectionName)); - } - return source; - } - } - - class UnwrapAndReadDbObjectCallback extends ReadDbObjectCallback { - - public UnwrapAndReadDbObjectCallback(EntityReader reader, Class type, - String collectionName) { - super(reader, type, collectionName); - } - - @Override - public T doWith(DBObject object) { - - Object idField = object.get(Fields.UNDERSCORE_ID); - - if (!(idField instanceof DBObject)) { - return super.doWith(object); - } - - DBObject toMap = new BasicDBObject(); - DBObject nested = (DBObject) idField; - toMap.putAll(nested); - - for (String key : object.keySet()) { - if (!Fields.UNDERSCORE_ID.equals(key)) { - toMap.put(key, object.get(key)); - } - } - - return super.doWith(toMap); - } - } - - class QueryCursorPreparer implements CursorPreparer { - - private final Query query; - private final Class type; - - public QueryCursorPreparer(Query query, Class type) { - - this.query = query; - this.type = type; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.CursorPreparer#prepare(com.mongodb.DBCursor) - */ - public DBCursor prepare(DBCursor cursor) { - - if (query == null) { - return cursor; - } - - if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null - && !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) { - return cursor; - } - - DBCursor cursorToUse = cursor.copy(); - - try { - - if (query.getSkip() > 0) { - cursorToUse = cursorToUse.skip(query.getSkip()); - } - - if (query.getLimit() > 0) { - cursorToUse = cursorToUse.limit(query.getLimit()); - } - - if (query.getSortObject() != null) { - DBObject sortDbo = type != null ? getMappedSortObject(query, type) : query.getSortObject(); - cursorToUse = cursorToUse.sort(sortDbo); - } - - if (StringUtils.hasText(query.getHint())) { - cursorToUse = cursorToUse.hint(query.getHint()); - } - - if (query.getMeta().hasValues()) { - - for (Entry entry : query.getMeta().values()) { - cursorToUse = cursorToUse.addSpecial(entry.getKey(), entry.getValue()); - } - - for (Meta.CursorOption option : query.getMeta().getFlags()) { - - switch (option) { - case EXHAUST: - cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_EXHAUST); - break; - case NO_TIMEOUT: - cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_NOTIMEOUT); - break; - case PARTIAL: - cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_PARTIAL); - break; - case SLAVE_OK: - cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_SLAVEOK); - break; - default: - throw new IllegalArgumentException(String.format("%s is no supported flag.", option)); - } - } - } - - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e, exceptionTranslator); - } - - return cursorToUse; - } - } - - /** - * {@link DbObjectCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to - * a delegate and creates a {@link GeoResult} from the result. - * - * @author Oliver Gierke - */ - static class GeoNearResultDbObjectCallback implements DbObjectCallback> { - - private final DbObjectCallback delegate; - private final Metric metric; - - /** - * Creates a new {@link GeoNearResultDbObjectCallback} using the given {@link DbObjectCallback} delegate for - * {@link GeoResult} content unmarshalling. - * - * @param delegate must not be {@literal null}. - */ - public GeoNearResultDbObjectCallback(DbObjectCallback delegate, Metric metric) { - - Assert.notNull(delegate, "DocumentCallback must not be null!"); - - this.delegate = delegate; - this.metric = metric; - } - - public GeoResult doWith(DBObject object) { - - double distance = ((Double) object.get("dis")).doubleValue(); - DBObject content = (DBObject) object.get("obj"); - - T doWith = delegate.doWith(content); - - return new GeoResult(doWith, new Distance(distance, metric)); - } - } - - /** - * A {@link CloseableIterator} that is backed by a MongoDB {@link Cursor}. - * - * @since 1.7 - * @author Thomas Darimont - */ - static class CloseableIterableCursorAdapter implements CloseableIterator { - - private volatile Cursor cursor; - private PersistenceExceptionTranslator exceptionTranslator; - private DbObjectCallback objectReadCallback; - - /** - * Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link Cursor}. - * - * @param cursor - * @param exceptionTranslator - * @param objectReadCallback - */ - public CloseableIterableCursorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator, - DbObjectCallback objectReadCallback) { - - this.cursor = cursor; - this.exceptionTranslator = exceptionTranslator; - this.objectReadCallback = objectReadCallback; - } - - @Override - public boolean hasNext() { - - if (cursor == null) { - return false; - } - - try { - return cursor.hasNext(); - } catch (RuntimeException ex) { - throw potentiallyConvertRuntimeException(ex, exceptionTranslator); - } - } - - @Override - public T next() { - - if (cursor == null) { - return null; - } - - try { - DBObject item = cursor.next(); - T converted = objectReadCallback.doWith(item); - return converted; - } catch (RuntimeException ex) { - throw potentiallyConvertRuntimeException(ex, exceptionTranslator); - } - } - - @Override - public void close() { - - Cursor c = cursor; - try { - c.close(); - } catch (RuntimeException ex) { - throw potentiallyConvertRuntimeException(ex, exceptionTranslator); - } finally { - cursor = null; - exceptionTranslator = null; - objectReadCallback = null; - } - } - } -} \ No newline at end of file + public GroupByResults group(String inputCollectionName, GroupBy groupBy, Class entityClass) { + return group(null, inputCollectionName, groupBy, entityClass); + } + + public GroupByResults group(Criteria criteria, String inputCollectionName, GroupBy groupBy, + Class entityClass) { + + DBObject dbo = groupBy.getGroupByObject(); + dbo.put("ns", inputCollectionName); + + if (criteria == null) { + dbo.put("cond", null); + } else { + dbo.put("cond", queryMapper.getMappedObject(criteria.getCriteriaObject(), null)); + } + // If initial document was a JavaScript string, potentially loaded by Spring's Resource abstraction, load it and + // convert to DBObject + + if (dbo.containsField("initial")) { + Object initialObj = dbo.get("initial"); + if (initialObj instanceof String) { + String initialAsString = replaceWithResourceIfNecessary((String) initialObj); + dbo.put("initial", JSON.parse(initialAsString)); + } + } + + if (dbo.containsField("$reduce")) { + dbo.put("$reduce", replaceWithResourceIfNecessary(dbo.get("$reduce").toString())); + } + if (dbo.containsField("$keyf")) { + dbo.put("$keyf", replaceWithResourceIfNecessary(dbo.get("$keyf").toString())); + } + if (dbo.containsField("finalize")) { + dbo.put("finalize", replaceWithResourceIfNecessary(dbo.get("finalize").toString())); + } + + DBObject commandObject = new BasicDBObject("group", dbo); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing Group with DBObject [{}]", serializeToJsonSafely(commandObject)); + } + + CommandResult commandResult = executeCommand(commandObject, getDb().getOptions()); + handleCommandError(commandResult, commandObject); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Group command result = [{}]", commandResult); + } + + @SuppressWarnings("unchecked") + Iterable resultSet = (Iterable) commandResult.get("retval"); + List mappedResults = new ArrayList(); + DbObjectCallback callback = new ReadDbObjectCallback(mongoConverter, entityClass, inputCollectionName); + + for (DBObject dbObject : resultSet) { + mappedResults.add(callback.doWith(dbObject)); + } + + return new GroupByResults(mappedResults, commandResult); + } + + @Override + public AggregationResults aggregate(TypedAggregation aggregation, Class outputType) { + return aggregate(aggregation, determineCollectionName(aggregation.getInputType()), outputType); + } + + @Override + public AggregationResults aggregate(TypedAggregation aggregation, String inputCollectionName, + Class outputType) { + + Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); + + AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), + mappingContext, queryMapper); + return aggregate(aggregation, inputCollectionName, outputType, context); + } + + @Override + public AggregationResults aggregate(Aggregation aggregation, Class inputType, Class outputType) { + + return aggregate(aggregation, determineCollectionName(inputType), outputType, + new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); + } + + @Override + public AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType) { + return aggregate(aggregation, collectionName, outputType, null); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String) + */ + @Override + public List findAllAndRemove(Query query, String collectionName) { + return findAndRemove(query, null, collectionName); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class) + */ + @Override + public List findAllAndRemove(Query query, Class entityClass) { + return findAllAndRemove(query, entityClass, determineCollectionName(entityClass)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + */ + @Override + public List findAllAndRemove(Query query, Class entityClass, String collectionName) { + return doFindAndDelete(collectionName, query, entityClass); + } + + /** + * Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)} + * and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is + * constructed out of the find result. + * + * @param collectionName + * @param query + * @param entityClass + * @return + */ + protected List doFindAndDelete(String collectionName, Query query, Class entityClass) { + + List result = find(query, entityClass, collectionName); + + if (!CollectionUtils.isEmpty(result)) { + remove(getIdInQueryFor(result), entityClass, collectionName); + } + + return result; + } + + protected AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType, + AggregationOperationContext context) { + + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); + Assert.notNull(outputType, "Output type must not be null!"); + + AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; + DBObject command = aggregation.toDbObject(collectionName, rootContext); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing aggregation: {}", serializeToJsonSafely(command)); + } + + CommandResult commandResult = executeCommand(command, this.readPreference); + handleCommandError(commandResult, command); + + return new AggregationResults(returnPotentiallyMappedResults(outputType, commandResult, collectionName), + commandResult); + } + + /** + * Returns the potentially mapped results of the given {@commandResult} contained some. + * + * @param outputType + * @param commandResult + * @return + */ + private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, + String collectionName) { + + @SuppressWarnings("unchecked") + Iterable resultSet = (Iterable) commandResult.get("result"); + if (resultSet == null) { + return Collections.emptyList(); + } + + DbObjectCallback callback = new UnwrapAndReadDbObjectCallback(mongoConverter, outputType, collectionName); + + List mappedResults = new ArrayList(); + for (DBObject dbObject : resultSet) { + mappedResults.add(callback.doWith(dbObject)); + } + + return mappedResults; + } + + protected String replaceWithResourceIfNecessary(String function) { + + String func = function; + + if (this.resourceLoader != null && ResourceUtils.isUrl(function)) { + + Resource functionResource = resourceLoader.getResource(func); + + if (!functionResource.exists()) { + throw new InvalidDataAccessApiUsageException(String.format("Resource %s not found!", function)); + } + + Scanner scanner = null; + + try { + scanner = new Scanner(functionResource.getInputStream()); + return scanner.useDelimiter("\\A").next(); + } catch (IOException e) { + throw new InvalidDataAccessApiUsageException(String.format("Cannot read map-reduce file %s!", function), e); + } finally { + if (scanner != null) { + scanner.close(); + } + } + } + + return func; + } + + private void copyMapReduceOptionsToCommand(Query query, MapReduceOptions mapReduceOptions, + MapReduceCommand mapReduceCommand) { + + if (query != null) { + if (query.getSkip() != 0 || query.getFieldsObject() != null) { + throw new InvalidDataAccessApiUsageException( + "Can not use skip or field specification with map reduce operations"); + } + if (query.getLimit() > 0 && mapReduceOptions.getLimit() == null) { + mapReduceCommand.setLimit(query.getLimit()); + } + if (query.getSortObject() != null) { + mapReduceCommand.setSort(queryMapper.getMappedObject(query.getSortObject(), null)); + } + } + + if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit().intValue() > 0) { + mapReduceCommand.setLimit(mapReduceOptions.getLimit()); + } + + if (mapReduceOptions.getJavaScriptMode() != null) { + mapReduceCommand.setJsMode(true); + } + if (!mapReduceOptions.getExtraOptions().isEmpty()) { + for (Entry entry : mapReduceOptions.getExtraOptions().entrySet()) { + ReflectiveMapReduceInvoker.addExtraOption(mapReduceCommand, entry.getKey(), entry.getValue()); + } + } + if (mapReduceOptions.getFinalizeFunction() != null) { + mapReduceCommand.setFinalize(this.replaceWithResourceIfNecessary(mapReduceOptions.getFinalizeFunction())); + } + if (mapReduceOptions.getOutputDatabase() != null) { + mapReduceCommand.setOutputDB(mapReduceOptions.getOutputDatabase()); + } + if (!mapReduceOptions.getScopeVariables().isEmpty()) { + mapReduceCommand.setScope(mapReduceOptions.getScopeVariables()); + } + } + + public Set getCollectionNames() { + return execute(new DbCallback>() { + public Set doInDB(DB db) throws MongoException, DataAccessException { + return db.getCollectionNames(); + } + }); + } + + public DB getDb() { + return mongoDbFactory.getDb(); + } + + protected void maybeEmitEvent(MongoMappingEvent event) { + if (null != eventPublisher) { + eventPublisher.publishEvent(event); + } + } + + /** + * Create the specified collection using the provided options + * + * @param collectionName + * @param collectionOptions + * @return the collection that was created + */ + protected DBCollection doCreateCollection(final String collectionName, final DBObject collectionOptions) { + return execute(new DbCallback() { + public DBCollection doInDB(DB db) throws MongoException, DataAccessException { + DBCollection coll = db.createCollection(collectionName, collectionOptions); + // TODO: Emit a collection created event + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Created collection [{}]", coll.getFullName()); + } + return coll; + } + }); + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. + * The query document is specified as a standard {@link DBObject} and so is the fields specification. + * + * @param collectionName name of the collection to retrieve the objects from. + * @param query the query document that specifies the criteria used to find a record. + * @param fields the document that specifies the fields to be returned. + * @param entityClass the parameterized type of the returned list. + * @return the {@link List} of converted objects. + */ + protected T doFindOne(String collectionName, DBObject query, DBObject fields, Class entityClass) { + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + DBObject mappedQuery = queryMapper.getMappedObject(query, entity); + DBObject mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findOne using query: {} fields: {} for class: {} in collection: {}", serializeToJsonSafely(query), + mappedFields, entityClass, collectionName); + } + + return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields), + new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName), collectionName); + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to a List using the template's converter. The + * query document is specified as a standard DBObject and so is the fields specification. + * + * @param collectionName name of the collection to retrieve the objects from + * @param query the query document that specifies the criteria used to find a record + * @param fields the document that specifies the fields to be returned + * @param entityClass the parameterized type of the returned list. + * @return the List of converted objects. + */ + protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass) { + return doFind(collectionName, query, fields, entityClass, null, + new ReadDbObjectCallback(this.mongoConverter, entityClass, collectionName)); + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified type. The object is + * converted from the MongoDB native representation using an instance of {@see MongoConverter}. The query document is + * specified as a standard DBObject and so is the fields specification. + * + * @param collectionName name of the collection to retrieve the objects from. + * @param query the query document that specifies the criteria used to find a record. + * @param fields the document that specifies the fields to be returned. + * @param entityClass the parameterized type of the returned list. + * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply + * limits, skips and so on). + * @return the {@link List} of converted objects. + */ + protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, + CursorPreparer preparer) { + return doFind(collectionName, query, fields, entityClass, preparer, + new ReadDbObjectCallback(mongoConverter, entityClass, collectionName)); + } + + protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, + CursorPreparer preparer, DbObjectCallback objectCallback) { + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + + DBObject mappedFields = queryMapper.getMappedFields(fields, entity); + DBObject mappedQuery = queryMapper.getMappedObject(query, entity); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}", + serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName); + } + + return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer, objectCallback, + collectionName); + } + + protected DBObject convertToDbObject(CollectionOptions collectionOptions) { + DBObject dbo = new BasicDBObject(); + if (collectionOptions != null) { + if (collectionOptions.getCapped() != null) { + dbo.put("capped", collectionOptions.getCapped().booleanValue()); + } + if (collectionOptions.getSize() != null) { + dbo.put("size", collectionOptions.getSize().intValue()); + } + if (collectionOptions.getMaxDocuments() != null) { + dbo.put("max", collectionOptions.getMaxDocuments().intValue()); + } + } + return dbo; + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. + * The first document that matches the query is returned and also removed from the collection in the database. + *

                                + * The query document is specified as a standard DBObject and so is the fields specification. + * + * @param collectionName name of the collection to retrieve the objects from + * @param query the query document that specifies the criteria used to find a record + * @param entityClass the parameterized type of the returned list. + * @return the List of converted objects. + */ + protected T doFindAndRemove(String collectionName, DBObject query, DBObject fields, DBObject sort, + Class entityClass) { + + EntityReader readerToUse = this.mongoConverter; + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findAndRemove using query: {} fields: {} sort: {} for class: {} in collection: {}", + serializeToJsonSafely(query), fields, sort, entityClass, collectionName); + } + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + + return executeFindOneInternal(new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort), + new ReadDbObjectCallback(readerToUse, entityClass, collectionName), collectionName); + } + + protected T doFindAndModify(String collectionName, DBObject query, DBObject fields, DBObject sort, + Class entityClass, Update update, FindAndModifyOptions options) { + + EntityReader readerToUse = this.mongoConverter; + + if (options == null) { + options = new FindAndModifyOptions(); + } + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + + increaseVersionForUpdateIfNecessary(entity, update); + + DBObject mappedQuery = queryMapper.getMappedObject(query, entity); + DBObject mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "findAndModify using query: {} fields: {} sort: {} for class: {} and update: {} " + "in collection: {}", + serializeToJsonSafely(mappedQuery), fields, sort, entityClass, serializeToJsonSafely(mappedUpdate), + collectionName); + } + + return executeFindOneInternal(new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate, options), + new ReadDbObjectCallback(readerToUse, entityClass, collectionName), collectionName); + } + + /** + * Populates the id property of the saved object, if it's not set already. + * + * @param savedObject + * @param id + */ + protected void populateIdIfNecessary(Object savedObject, Object id) { + + if (id == null) { + return; + } + + if (savedObject instanceof BasicDBObject) { + DBObject dbObject = (DBObject) savedObject; + dbObject.put(ID_FIELD, id); + return; + } + + MongoPersistentProperty idProp = getIdPropertyFor(savedObject.getClass()); + + if (idProp == null) { + return; + } + + ConversionService conversionService = mongoConverter.getConversionService(); + MongoPersistentEntity entity = mappingContext.getPersistentEntity(savedObject.getClass()); + PersistentPropertyAccessor accessor = entity.getPropertyAccessor(savedObject); + + if (accessor.getProperty(idProp) != null) { + return; + } + + new ConvertingPropertyAccessor(accessor, conversionService).setProperty(idProp, id); + } + + private DBCollection getAndPrepareCollection(DB db, String collectionName) { + try { + DBCollection collection = db.getCollection(collectionName); + prepareCollection(collection); + return collection; + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + /** + * Internal method using callbacks to do queries against the datastore that requires reading a single object from a + * collection of objects. It will take the following steps + *

                                  + *
                                1. Execute the given {@link ConnectionCallback} for a {@link DBObject}.
                                2. + *
                                3. Apply the given {@link DbObjectCallback} to each of the {@link DBObject}s to obtain the result.
                                4. + *
                                    + * + * @param + * @param collectionCallback the callback to retrieve the {@link DBObject} with + * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type + * @param collectionName the collection to be queried + * @return + */ + private T executeFindOneInternal(CollectionCallback collectionCallback, + DbObjectCallback objectCallback, String collectionName) { + + try { + T result = objectCallback + .doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName))); + return result; + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + /** + * Internal method using callback to do queries against the datastore that requires reading a collection of objects. + * It will take the following steps + *
                                      + *
                                    1. Execute the given {@link ConnectionCallback} for a {@link DBCursor}.
                                    2. + *
                                    3. Prepare that {@link DBCursor} with the given {@link CursorPreparer} (will be skipped if {@link CursorPreparer} + * is {@literal null}
                                    4. + *
                                    5. Iterate over the {@link DBCursor} and applies the given {@link DbObjectCallback} to each of the + * {@link DBObject}s collecting the actual result {@link List}.
                                    6. + *
                                        + * + * @param + * @param collectionCallback the callback to retrieve the {@link DBCursor} with + * @param preparer the {@link CursorPreparer} to potentially modify the {@link DBCursor} before ireating over it + * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type + * @param collectionName the collection to be queried + * @return + */ + private List executeFindMultiInternal(CollectionCallback collectionCallback, CursorPreparer preparer, + DbObjectCallback objectCallback, String collectionName) { + + try { + + DBCursor cursor = null; + + try { + + cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)); + + if (preparer != null) { + cursor = preparer.prepare(cursor); + } + + List result = new ArrayList(); + + while (cursor.hasNext()) { + DBObject object = cursor.next(); + result.add(objectCallback.doWith(object)); + } + + return result; + + } finally { + + if (cursor != null) { + cursor.close(); + } + } + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + private void executeQueryInternal(CollectionCallback collectionCallback, CursorPreparer preparer, + DocumentCallbackHandler callbackHandler, String collectionName) { + + try { + + DBCursor cursor = null; + + try { + cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)); + + if (preparer != null) { + cursor = preparer.prepare(cursor); + } + + while (cursor.hasNext()) { + DBObject dbobject = cursor.next(); + callbackHandler.processDocument(dbobject); + } + + } finally { + if (cursor != null) { + cursor.close(); + } + } + + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + } + + private MongoPersistentEntity getPersistentEntity(Class type) { + return type == null ? null : mappingContext.getPersistentEntity(type); + } + + private MongoPersistentProperty getIdPropertyFor(Class type) { + MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(type); + return persistentEntity == null ? null : persistentEntity.getIdProperty(); + } + + private String determineEntityCollectionName(T obj) { + if (null != obj) { + return determineCollectionName(obj.getClass()); + } + + return null; + } + + String determineCollectionName(Class entityClass) { + + if (entityClass == null) { + throw new InvalidDataAccessApiUsageException( + "No class parameter provided, entity collection can't be determined!"); + } + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); + if (entity == null) { + throw new InvalidDataAccessApiUsageException( + "No Persistent Entity information found for the class " + entityClass.getName()); + } + return entity.getCollection(); + } + + /** + * Handles {@link WriteResult} errors based on the configured {@link WriteResultChecking}. + * + * @param writeResult + * @param query + * @param operation + */ + protected void handleAnyWriteResultErrors(WriteResult writeResult, DBObject query, MongoActionOperation operation) { + + if (writeResultChecking == WriteResultChecking.NONE) { + return; + } + + String error = ReflectiveWriteResultInvoker.getError(writeResult); + + if (error == null) { + return; + } + + String message; + + switch (operation) { + + case INSERT: + case SAVE: + message = String.format("Insert/Save for %s failed: %s", query, error); + break; + case INSERT_LIST: + message = String.format("Insert list failed: %s", error); + break; + default: + message = String.format("Execution of %s%s failed: %s", operation, + query == null ? "" : " using query " + query.toString(), error); + } + + if (writeResultChecking == WriteResultChecking.EXCEPTION) { + throw new MongoDataIntegrityViolationException(message, writeResult, operation); + } else { + LOGGER.error(message); + return; + } + } + + /** + * Inspects the given {@link CommandResult} for erros and potentially throws an + * {@link InvalidDataAccessApiUsageException} for that error. + * + * @param result must not be {@literal null}. + * @param source must not be {@literal null}. + */ + private void handleCommandError(CommandResult result, DBObject source) { + + try { + result.throwOnError(); + } catch (MongoException ex) { + + String error = result.getErrorMessage(); + error = error == null ? "NO MESSAGE" : error; + + throw new InvalidDataAccessApiUsageException( + "Command execution failed: Error [" + error + "], Command = " + source, ex); + } + } + + private static final MongoConverter getDefaultMongoConverter(MongoDbFactory factory) { + + DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory); + MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext()); + converter.afterPropertiesSet(); + return converter; + } + + private DBObject getMappedSortObject(Query query, Class type) { + + if (query == null || query.getSortObject() == null) { + return null; + } + + return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type)); + } + + /** + * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original + * exception if the conversation failed. Thus allows safe re-throwing of the return value. + * + * @param ex the exception to translate + * @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation + * @return + */ + private static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex, + PersistenceExceptionTranslator exceptionTranslator) { + RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex); + return resolved == null ? ex : resolved; + } + + /** + * Returns all identifiers for the given documents. Will augment the given identifiers and fill in only the ones that + * are {@literal null} currently. This would've been better solved in {@link #insertDBObjectList(String, List)} + * directly but would require a signature change of that method. + * + * @param ids + * @param documents + * @return TODO: Remove for 2.0 and change method signature of {@link #insertDBObjectList(String, List)}. + */ + private static List consolidateIdentifiers(List ids, List documents) { + + List result = new ArrayList(ids.size()); + + for (int i = 0; i < ids.size(); i++) { + + ObjectId objectId = ids.get(i); + result.add(objectId == null ? documents.get(i).get(ID_FIELD) : objectId); + } + + return result; + } + + // Callback implementations + + /** + * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification + * {@link DBObject} and executes that against the {@link DBCollection}. + * + * @author Oliver Gierke + * @author Thomas Risberg + */ + private static class FindOneCallback implements CollectionCallback { + + private final DBObject query; + private final DBObject fields; + + public FindOneCallback(DBObject query, DBObject fields) { + this.query = query; + this.fields = fields; + } + + public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { + if (fields == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findOne using query: {} in db.collection: {}", serializeToJsonSafely(query), + collection.getFullName()); + } + return collection.findOne(query); + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query), fields, + collection.getFullName()); + } + return collection.findOne(query, fields); + } + } + } + + /** + * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification + * {@link DBObject} and executes that against the {@link DBCollection}. + * + * @author Oliver Gierke + * @author Thomas Risberg + */ + private static class FindCallback implements CollectionCallback { + + private final DBObject query; + private final DBObject fields; + + public FindCallback(DBObject query) { + this(query, null); + } + + public FindCallback(DBObject query, DBObject fields) { + this.query = query == null ? new BasicDBObject() : query; + this.fields = fields; + } + + public DBCursor doInCollection(DBCollection collection) throws MongoException, DataAccessException { + + if (fields == null || fields.toMap().isEmpty()) { + return collection.find(query); + } else { + return collection.find(query, fields); + } + } + } + + /** + * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification + * {@link DBObject} and executes that against the {@link DBCollection}. + * + * @author Thomas Risberg + */ + private static class FindAndRemoveCallback implements CollectionCallback { + + private final DBObject query; + private final DBObject fields; + private final DBObject sort; + + public FindAndRemoveCallback(DBObject query, DBObject fields, DBObject sort) { + this.query = query; + this.fields = fields; + this.sort = sort; + } + + public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { + return collection.findAndModify(query, fields, sort, true, null, false, false); + } + } + + private static class FindAndModifyCallback implements CollectionCallback { + + private final DBObject query; + private final DBObject fields; + private final DBObject sort; + private final DBObject update; + private final FindAndModifyOptions options; + + public FindAndModifyCallback(DBObject query, DBObject fields, DBObject sort, DBObject update, + FindAndModifyOptions options) { + this.query = query; + this.fields = fields; + this.sort = sort; + this.update = update; + this.options = options; + } + + public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { + return collection.findAndModify(query, fields, sort, options.isRemove(), update, options.isReturnNew(), + options.isUpsert()); + } + } + + /** + * Simple internal callback to allow operations on a {@link DBObject}. + * + * @author Oliver Gierke + * @author Thomas Darimont + */ + + interface DbObjectCallback { + + T doWith(DBObject object); + } + + /** + * Simple {@link DbObjectCallback} that will transform {@link DBObject} into the given target type using the given + * {@link MongoReader}. + * + * @author Oliver Gierke + * @author Christoph Strobl + */ + private class ReadDbObjectCallback implements DbObjectCallback { + + private final EntityReader reader; + private final Class type; + private final String collectionName; + + public ReadDbObjectCallback(EntityReader reader, Class type, String collectionName) { + + Assert.notNull(reader, "EntityReader must not be null!"); + Assert.notNull(type, "Entity type must not be null!"); + + this.reader = reader; + this.type = type; + this.collectionName = collectionName; + } + + public T doWith(DBObject object) { + if (null != object) { + maybeEmitEvent(new AfterLoadEvent(object, type, collectionName)); + } + T source = reader.read(type, object); + if (null != source) { + maybeEmitEvent(new AfterConvertEvent(object, source, collectionName)); + } + return source; + } + } + + class UnwrapAndReadDbObjectCallback extends ReadDbObjectCallback { + + public UnwrapAndReadDbObjectCallback(EntityReader reader, Class type, + String collectionName) { + super(reader, type, collectionName); + } + + @Override + public T doWith(DBObject object) { + + Object idField = object.get(Fields.UNDERSCORE_ID); + + if (!(idField instanceof DBObject)) { + return super.doWith(object); + } + + DBObject toMap = new BasicDBObject(); + DBObject nested = (DBObject) idField; + toMap.putAll(nested); + + for (String key : object.keySet()) { + if (!Fields.UNDERSCORE_ID.equals(key)) { + toMap.put(key, object.get(key)); + } + } + + return super.doWith(toMap); + } + } + + class QueryCursorPreparer implements CursorPreparer { + + private final Query query; + private final Class type; + + public QueryCursorPreparer(Query query, Class type) { + + this.query = query; + this.type = type; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.CursorPreparer#prepare(com.mongodb.DBCursor) + */ + public DBCursor prepare(DBCursor cursor) { + + if (query == null) { + return cursor; + } + + if (query.getSkip() <= 0 && query.getLimit() <= 0 && query.getSortObject() == null + && !StringUtils.hasText(query.getHint()) && !query.getMeta().hasValues()) { + return cursor; + } + + DBCursor cursorToUse = cursor.copy(); + + try { + + if (query.getSkip() > 0) { + cursorToUse = cursorToUse.skip(query.getSkip()); + } + + if (query.getLimit() > 0) { + cursorToUse = cursorToUse.limit(query.getLimit()); + } + + if (query.getSortObject() != null) { + DBObject sortDbo = type != null ? getMappedSortObject(query, type) : query.getSortObject(); + cursorToUse = cursorToUse.sort(sortDbo); + } + + if (StringUtils.hasText(query.getHint())) { + cursorToUse = cursorToUse.hint(query.getHint()); + } + + if (query.getMeta().hasValues()) { + + for (Entry entry : query.getMeta().values()) { + cursorToUse = cursorToUse.addSpecial(entry.getKey(), entry.getValue()); + } + + for (Meta.CursorOption option : query.getMeta().getFlags()) { + + switch (option) { + case EXHAUST: + cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_EXHAUST); + break; + case NO_TIMEOUT: + cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_NOTIMEOUT); + break; + case PARTIAL: + cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_PARTIAL); + break; + case SLAVE_OK: + cursorToUse = cursorToUse.addOption(Bytes.QUERYOPTION_SLAVEOK); + break; + default: + throw new IllegalArgumentException(String.format("%s is no supported flag.", option)); + } + } + } + + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e, exceptionTranslator); + } + + return cursorToUse; + } + } + + /** + * {@link DbObjectCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to + * a delegate and creates a {@link GeoResult} from the result. + * + * @author Oliver Gierke + */ + static class GeoNearResultDbObjectCallback implements DbObjectCallback> { + + private final DbObjectCallback delegate; + private final Metric metric; + + /** + * Creates a new {@link GeoNearResultDbObjectCallback} using the given {@link DbObjectCallback} delegate for + * {@link GeoResult} content unmarshalling. + * + * @param delegate must not be {@literal null}. + */ + public GeoNearResultDbObjectCallback(DbObjectCallback delegate, Metric metric) { + + Assert.notNull(delegate, "DocumentCallback must not be null!"); + + this.delegate = delegate; + this.metric = metric; + } + + public GeoResult doWith(DBObject object) { + + double distance = ((Double) object.get("dis")).doubleValue(); + DBObject content = (DBObject) object.get("obj"); + + T doWith = delegate.doWith(content); + + return new GeoResult(doWith, new Distance(distance, metric)); + } + } + + /** + * A {@link CloseableIterator} that is backed by a MongoDB {@link Cursor}. + * + * @since 1.7 + * @author Thomas Darimont + */ + static class CloseableIterableCursorAdapter implements CloseableIterator { + + private volatile Cursor cursor; + private PersistenceExceptionTranslator exceptionTranslator; + private DbObjectCallback objectReadCallback; + + /** + * Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link Cursor}. + * + * @param cursor + * @param exceptionTranslator + * @param objectReadCallback + */ + public CloseableIterableCursorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator, + DbObjectCallback objectReadCallback) { + + this.cursor = cursor; + this.exceptionTranslator = exceptionTranslator; + this.objectReadCallback = objectReadCallback; + } + + @Override + public boolean hasNext() { + + if (cursor == null) { + return false; + } + + try { + return cursor.hasNext(); + } catch (RuntimeException ex) { + throw potentiallyConvertRuntimeException(ex, exceptionTranslator); + } + } + + @Override + public T next() { + + if (cursor == null) { + return null; + } + + try { + DBObject item = cursor.next(); + T converted = objectReadCallback.doWith(item); + return converted; + } catch (RuntimeException ex) { + throw potentiallyConvertRuntimeException(ex, exceptionTranslator); + } + } + + @Override + public void close() { + + Cursor c = cursor; + try { + c.close(); + } catch (RuntimeException ex) { + throw potentiallyConvertRuntimeException(ex, exceptionTranslator); + } finally { + cursor = null; + exceptionTranslator = null; + objectReadCallback = null; + } + } + } +} From 62ecde204abda267da8fe31ae4a64aa6fadc85b6 Mon Sep 17 00:00:00 2001 From: manindr Date: Fri, 3 Mar 2017 20:20:47 +0530 Subject: [PATCH 112/118] formatting changed to help better diff in pull request --- .../data/mongodb/core/MongoTemplate.java | 270 +- .../core/aggregation/AggregationTests.java | 3064 ++++++++--------- 2 files changed, 1680 insertions(+), 1654 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 732b8d3f9f..a8ca776b23 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -15,32 +15,15 @@ */ package org.springframework.data.mongodb.core; -import static org.springframework.data.mongodb.core.query.Criteria.*; -import static org.springframework.data.mongodb.core.query.SerializationUtils.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Scanner; -import java.util.Set; - +import com.mongodb.AggregationOptions; +import com.mongodb.*; +import com.mongodb.util.JSON; +import com.mongodb.util.JSONParseException; import org.bson.types.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.*; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -61,68 +44,36 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.BulkOperations.BulkMode; -import org.springframework.data.mongodb.core.aggregation.Aggregation; -import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; -import org.springframework.data.mongodb.core.aggregation.AggregationResults; -import org.springframework.data.mongodb.core.aggregation.Fields; -import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext; -import org.springframework.data.mongodb.core.aggregation.TypedAggregation; -import org.springframework.data.mongodb.core.convert.DbRefResolver; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.data.mongodb.core.convert.MongoConverter; -import org.springframework.data.mongodb.core.convert.MongoWriter; -import org.springframework.data.mongodb.core.convert.QueryMapper; -import org.springframework.data.mongodb.core.convert.UpdateMapper; +import org.springframework.data.mongodb.core.aggregation.*; +import org.springframework.data.mongodb.core.convert.*; import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; -import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent; -import org.springframework.data.mongodb.core.mapping.event.AfterDeleteEvent; -import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent; -import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent; -import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; -import org.springframework.data.mongodb.core.mapping.event.BeforeDeleteEvent; -import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent; -import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent; +import org.springframework.data.mongodb.core.mapping.event.*; import org.springframework.data.mongodb.core.mapreduce.GroupBy; import org.springframework.data.mongodb.core.mapreduce.GroupByResults; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; import org.springframework.data.mongodb.core.mapreduce.MapReduceResults; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Meta; -import org.springframework.data.mongodb.core.query.NearQuery; -import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.*; import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.util.MongoClientVersion; import org.springframework.data.util.CloseableIterator; import org.springframework.jca.cci.core.ConnectionCallback; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; -import org.springframework.util.ResourceUtils; +import org.springframework.util.*; import org.springframework.util.StringUtils; -import com.mongodb.BasicDBObject; -import com.mongodb.Bytes; -import com.mongodb.CommandResult; -import com.mongodb.Cursor; -import com.mongodb.DB; -import com.mongodb.DBCollection; -import com.mongodb.DBCursor; -import com.mongodb.DBObject; -import com.mongodb.MapReduceCommand; -import com.mongodb.MapReduceOutput; -import com.mongodb.Mongo; -import com.mongodb.MongoException; -import com.mongodb.ReadPreference; -import com.mongodb.WriteConcern; -import com.mongodb.WriteResult; -import com.mongodb.util.JSON; -import com.mongodb.util.JSONParseException; +import java.io.IOException; +import java.util.*; +import java.util.Map.Entry; + +import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.ALLOW_DISK_USE; +import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.CURSOR; +import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.EXPLAIN; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.SerializationUtils.serializeToJsonSafely; /** * Primary implementation of {@link MongoOperations}. @@ -188,7 +139,7 @@ public MongoTemplate(Mongo mongo, String databaseName) { /** * Constructor used for a template configuration with user credentials in the form of - * {@link UserCredentials} + * {@link org.springframework.data.authentication.UserCredentials} * * @param mongo must not be {@literal null}. * @param databaseName must not be {@literal null} or empty. @@ -315,7 +266,7 @@ private void prepareIndexCreator(ApplicationContext context) { } /** - * Returns the default {@link MongoConverter}. + * Returns the default {@link org.springframework.data.mongodb.core.convert.MongoConverter}. * * @return */ @@ -438,7 +389,7 @@ public void executeQuery(Query query, String collectionName, DocumentCallbackHan * limits, skips and so on). */ protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, - CursorPreparer preparer) { + CursorPreparer preparer) { Assert.notNull(query, "Query must not be null!"); @@ -731,7 +682,7 @@ public T findAndModify(Query query, Update update, FindAndModifyOptions opti } public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass, - String collectionName) { + String collectionName) { return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(), getMappedSortObject(query, entityClass), entityClass, update, options); } @@ -769,7 +720,7 @@ public long count(Query query, Class entityClass, String collectionName) { final DBObject dbObject = query == null ? null : queryMapper.getMappedObject(query.getQueryObject(), - entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); + entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); return execute(collectionName, new CollectionCallback() { public Long doInCollection(DBCollection collection) throws MongoException, DataAccessException { @@ -928,7 +879,7 @@ protected void doInsertAll(Collection listToSave, MongoWriter> entry : elementsByCollection.entrySet()) { + for (Map.Entry> entry : elementsByCollection.entrySet()) { doInsertBatch(entry.getKey(), entry.getValue(), this.mongoConverter); } } @@ -1148,7 +1099,7 @@ public WriteResult updateMulti(final Query query, final Update update, Class } protected WriteResult doUpdate(final String collectionName, final Query query, final Update update, - final Class entityClass, final boolean upsert, final boolean multi) { + final Class entityClass, final boolean upsert, final boolean multi) { return execute(collectionName, new CollectionCallback() { public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { @@ -1368,24 +1319,24 @@ public List findAll(Class entityClass, String collectionName) { } public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - Class entityClass) { + Class entityClass) { return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), entityClass); } public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - MapReduceOptions mapReduceOptions, Class entityClass) { + MapReduceOptions mapReduceOptions, Class entityClass) { return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, mapReduceOptions, entityClass); } public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, - String reduceFunction, Class entityClass) { + String reduceFunction, Class entityClass) { return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), entityClass); } public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, - String reduceFunction, MapReduceOptions mapReduceOptions, Class entityClass) { + String reduceFunction, MapReduceOptions mapReduceOptions, Class entityClass) { String mapFunc = replaceWithResourceIfNecessary(mapFunction); String reduceFunc = replaceWithResourceIfNecessary(reduceFunction); @@ -1424,7 +1375,7 @@ public GroupByResults group(String inputCollectionName, GroupBy groupBy, } public GroupByResults group(Criteria criteria, String inputCollectionName, GroupBy groupBy, - Class entityClass) { + Class entityClass) { DBObject dbo = groupBy.getGroupByObject(); dbo.put("ns", inputCollectionName); @@ -1485,9 +1436,14 @@ public AggregationResults aggregate(TypedAggregation aggregation, Clas return aggregate(aggregation, determineCollectionName(aggregation.getInputType()), outputType); } - @Override - public AggregationResults aggregate(TypedAggregation aggregation, String inputCollectionName, - Class outputType) { + @Override + public CloseableIterator aggregateStream(TypedAggregation aggregation, Class outputType) { + return aggregateStream(aggregation, determineCollectionName(aggregation.getInputType()), outputType); + } + + @Override + public AggregationResults aggregate(TypedAggregation aggregation, String inputCollectionName, + Class outputType) { Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); @@ -1496,26 +1452,52 @@ public AggregationResults aggregate(TypedAggregation aggregation, Stri return aggregate(aggregation, inputCollectionName, outputType, context); } - @Override - public AggregationResults aggregate(Aggregation aggregation, Class inputType, Class outputType) { + @Override + public CloseableIterator aggregateStream(TypedAggregation aggregation, String inputCollectionName, + Class outputType) { + + Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); + + AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), + mappingContext, queryMapper); + return aggregateStream(aggregation, inputCollectionName, outputType, context); + } + + @Override + public AggregationResults aggregate(Aggregation aggregation, Class inputType, Class outputType) { return aggregate(aggregation, determineCollectionName(inputType), outputType, new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); } - @Override - public AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType) { - return aggregate(aggregation, collectionName, outputType, null); - } + @Override + public CloseableIterator aggregateStream(Aggregation aggregation, Class inputType, Class outputType) { - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String) - */ - @Override - public List findAllAndRemove(Query query, String collectionName) { - return findAndRemove(query, null, collectionName); - } + return aggregateStream(aggregation, determineCollectionName(inputType), outputType, + new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); + } + + + @Override + public AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType) { + return aggregate(aggregation, collectionName, outputType, null); + } + + + @Override + public CloseableIterator aggregateStream(Aggregation aggregation, String collectionName, Class outputType) { + return aggregateStream(aggregation, collectionName, outputType, null); + } + + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String) + */ + @Override + public List findAllAndRemove(Query query, String collectionName) { + return findAndRemove(query, null, collectionName); + } /* * (non-Javadoc) @@ -1557,7 +1539,7 @@ protected List doFindAndDelete(String collectionName, Query query, Class< } protected AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType, - AggregationOperationContext context) { + AggregationOperationContext context) { Assert.hasText(collectionName, "Collection name must not be null or empty!"); Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); @@ -1577,15 +1559,59 @@ protected AggregationResults aggregate(Aggregation aggregation, String co commandResult); } - /** - * Returns the potentially mapped results of the given {@commandResult} contained some. - * - * @param outputType - * @param commandResult - * @return - */ - private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, - String collectionName) { + protected CloseableIterator aggregateStream(final Aggregation aggregation, final String collectionName, final Class outputType, + AggregationOperationContext context) { + + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); + Assert.notNull(outputType, "Output type must not be null!"); + + AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; + + final DBObject command = aggregation.toDbObject(collectionName, rootContext); + + Assert.isNull(command.get(CURSOR), "Custom options not allowed while streaming"); + Assert.isNull(command.get(EXPLAIN), "Explain option can't be used while streaming"); + + return execute(collectionName, new CollectionCallback>() { + + @Override + public CloseableIterator doInCollection(DBCollection collection) throws MongoException, DataAccessException { + + List pipeline = (List) command.get("pipeline"); + Cursor cursor = collection.aggregate(pipeline, getNativeAggregationOptionsFromCommand(command)); + + ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, outputType, collectionName); + + return new CloseableIterableCursorAdapter(cursor, exceptionTranslator, readCallback); + } + + private AggregationOptions getNativeAggregationOptionsFromCommand(DBObject command) { + AggregationOptions.Builder builder = AggregationOptions.builder(); + Object allowDiskUse = command.get(ALLOW_DISK_USE); + if(allowDiskUse !=null && String.valueOf(allowDiskUse).equals("true")){ + builder.allowDiskUse(true); + } + return builder.build(); + } + }); + +/* + Query query = new BasicQuery(command); + return stream(query, outputType); +*/ + } + + + /** + * Returns the potentially mapped results of the given {@commandResult} contained some. + * + * @param outputType + * @param commandResult + * @return + */ + private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, + String collectionName) { @SuppressWarnings("unchecked") Iterable resultSet = (Iterable) commandResult.get("result"); @@ -1633,7 +1659,7 @@ protected String replaceWithResourceIfNecessary(String function) { } private void copyMapReduceOptionsToCommand(Query query, MapReduceOptions mapReduceOptions, - MapReduceCommand mapReduceCommand) { + MapReduceCommand mapReduceCommand) { if (query != null) { if (query.getSkip() != 0 || query.getFieldsObject() != null) { @@ -1656,7 +1682,7 @@ private void copyMapReduceOptionsToCommand(Query query, MapReduceOptions mapRedu mapReduceCommand.setJsMode(true); } if (!mapReduceOptions.getExtraOptions().isEmpty()) { - for (Entry entry : mapReduceOptions.getExtraOptions().entrySet()) { + for (Map.Entry entry : mapReduceOptions.getExtraOptions().entrySet()) { ReflectiveMapReduceInvoker.addExtraOption(mapReduceCommand, entry.getKey(), entry.getValue()); } } @@ -1763,13 +1789,13 @@ protected List doFind(String collectionName, DBObject query, DBObject fie * @return the {@link List} of converted objects. */ protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, - CursorPreparer preparer) { + CursorPreparer preparer) { return doFind(collectionName, query, fields, entityClass, preparer, new ReadDbObjectCallback(mongoConverter, entityClass, collectionName)); } protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, - CursorPreparer preparer, DbObjectCallback objectCallback) { + CursorPreparer preparer, DbObjectCallback objectCallback) { MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); @@ -1813,7 +1839,7 @@ protected DBObject convertToDbObject(CollectionOptions collectionOptions) { * @return the List of converted objects. */ protected T doFindAndRemove(String collectionName, DBObject query, DBObject fields, DBObject sort, - Class entityClass) { + Class entityClass) { EntityReader readerToUse = this.mongoConverter; @@ -1829,7 +1855,7 @@ protected T doFindAndRemove(String collectionName, DBObject query, DBObject } protected T doFindAndModify(String collectionName, DBObject query, DBObject fields, DBObject sort, - Class entityClass, Update update, FindAndModifyOptions options) { + Class entityClass, Update update, FindAndModifyOptions options) { EntityReader readerToUse = this.mongoConverter; @@ -1915,7 +1941,7 @@ private DBCollection getAndPrepareCollection(DB db, String collectionName) { * @return */ private T executeFindOneInternal(CollectionCallback collectionCallback, - DbObjectCallback objectCallback, String collectionName) { + DbObjectCallback objectCallback, String collectionName) { try { T result = objectCallback @@ -1945,7 +1971,7 @@ private T executeFindOneInternal(CollectionCallback collectionCall * @return */ private List executeFindMultiInternal(CollectionCallback collectionCallback, CursorPreparer preparer, - DbObjectCallback objectCallback, String collectionName) { + DbObjectCallback objectCallback, String collectionName) { try { @@ -1980,7 +2006,7 @@ private List executeFindMultiInternal(CollectionCallback collec } private void executeQueryInternal(CollectionCallback collectionCallback, CursorPreparer preparer, - DocumentCallbackHandler callbackHandler, String collectionName) { + DocumentCallbackHandler callbackHandler, String collectionName) { try { @@ -2131,7 +2157,7 @@ private DBObject getMappedSortObject(Query query, Class type) { * @return */ private static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex, - PersistenceExceptionTranslator exceptionTranslator) { + PersistenceExceptionTranslator exceptionTranslator) { RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex); return resolved == null ? ex : resolved; } @@ -2140,7 +2166,7 @@ private static RuntimeException potentiallyConvertRuntimeException(RuntimeExcept * Returns all identifiers for the given documents. Will augment the given identifiers and fill in only the ones that * are {@literal null} currently. This would've been better solved in {@link #insertDBObjectList(String, List)} * directly but would require a signature change of that method. - * + * * @param ids * @param documents * @return TODO: Remove for 2.0 and change method signature of {@link #insertDBObjectList(String, List)}. @@ -2257,7 +2283,7 @@ private static class FindAndModifyCallback implements CollectionCallback extends ReadDbObjectCallback { public UnwrapAndReadDbObjectCallback(EntityReader reader, Class type, - String collectionName) { + String collectionName) { super(reader, type, collectionName); } @@ -2486,7 +2512,7 @@ static class CloseableIterableCursorAdapter implements CloseableIterator { * @param objectReadCallback */ public CloseableIterableCursorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator, - DbObjectCallback objectReadCallback) { + DbObjectCallback objectReadCallback) { this.cursor = cursor; this.exceptionTranslator = exceptionTranslator; @@ -2538,4 +2564,4 @@ public void close() { } } } -} +} \ No newline at end of file diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index f12b111227..b630cb4c98 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -82,147 +82,147 @@ @ContextConfiguration("classpath:infrastructure.xml") public class AggregationTests { - private static final String INPUT_COLLECTION = "aggregation_test_collection"; - private static final Logger LOGGER = LoggerFactory.getLogger(AggregationTests.class); - private static final Version TWO_DOT_FOUR = new Version(2, 4); - private static final Version TWO_DOT_SIX = new Version(2, 6); - private static final Version THREE_DOT_TWO = new Version(3, 2); - private static final Version THREE_DOT_FOUR = new Version(3, 4); - - private static boolean initialized = false; - - @Autowired MongoTemplate mongoTemplate; - - @Rule public ExpectedException exception = ExpectedException.none(); - private static Version mongoVersion; - - @Before - public void setUp() { - - queryMongoVersionIfNecessary(); - cleanDb(); - initSampleDataIfNecessary(); - } - - private void queryMongoVersionIfNecessary() { - - if (mongoVersion == null) { - CommandResult result = mongoTemplate.executeCommand("{ buildInfo: 1 }"); - mongoVersion = Version.parse(result.get("version").toString()); - } - } - - @After - public void cleanUp() { - cleanDb(); - } + private static final String INPUT_COLLECTION = "aggregation_test_collection"; + private static final Logger LOGGER = LoggerFactory.getLogger(AggregationTests.class); + private static final Version TWO_DOT_FOUR = new Version(2, 4); + private static final Version TWO_DOT_SIX = new Version(2, 6); + private static final Version THREE_DOT_TWO = new Version(3, 2); + private static final Version THREE_DOT_FOUR = new Version(3, 4); + + private static boolean initialized = false; + + @Autowired MongoTemplate mongoTemplate; + + @Rule public ExpectedException exception = ExpectedException.none(); + private static Version mongoVersion; + + @Before + public void setUp() { + + queryMongoVersionIfNecessary(); + cleanDb(); + initSampleDataIfNecessary(); + } + + private void queryMongoVersionIfNecessary() { + + if (mongoVersion == null) { + CommandResult result = mongoTemplate.executeCommand("{ buildInfo: 1 }"); + mongoVersion = Version.parse(result.get("version").toString()); + } + } + + @After + public void cleanUp() { + cleanDb(); + } + + private void cleanDb() { + mongoTemplate.dropCollection(INPUT_COLLECTION); + mongoTemplate.dropCollection(Product.class); + mongoTemplate.dropCollection(UserWithLikes.class); + mongoTemplate.dropCollection(DATAMONGO753.class); + mongoTemplate.dropCollection(Data.class); + mongoTemplate.dropCollection(DATAMONGO788.class); + mongoTemplate.dropCollection(User.class); + mongoTemplate.dropCollection(Person.class); + mongoTemplate.dropCollection(Reservation.class); + mongoTemplate.dropCollection(Venue.class); + mongoTemplate.dropCollection(MeterData.class); + mongoTemplate.dropCollection(LineItem.class); + mongoTemplate.dropCollection(InventoryItem.class); + mongoTemplate.dropCollection(Sales.class); + mongoTemplate.dropCollection(Sales2.class); + mongoTemplate.dropCollection(Employee.class); + mongoTemplate.dropCollection(Art.class); + } + + /** + * Imports the sample dataset (zips.json) if necessary (e.g. if it doesn't exist yet). The dataset can originally be + * found on the mongodb aggregation framework example website: + * + * @see MongoDB Aggregation Examples + */ + private void initSampleDataIfNecessary() { + + if (!initialized) { + + LOGGER.debug("Server uses MongoDB Version: {}", mongoVersion); + + mongoTemplate.dropCollection(ZipInfo.class); + mongoTemplate.execute(ZipInfo.class, new CollectionCallback() { + + @Override + public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { + + Scanner scanner = null; + try { + scanner = new Scanner(new BufferedInputStream(new ClassPathResource("zips.json").getInputStream())); + while (scanner.hasNextLine()) { + String zipInfoRecord = scanner.nextLine(); + collection.save((DBObject) JSON.parse(zipInfoRecord)); + } + } catch (Exception e) { + if (scanner != null) { + scanner.close(); + } + throw new RuntimeException("Could not load mongodb sample dataset!", e); + } + + return null; + } + }); - private void cleanDb() { - mongoTemplate.dropCollection(INPUT_COLLECTION); - mongoTemplate.dropCollection(Product.class); - mongoTemplate.dropCollection(UserWithLikes.class); - mongoTemplate.dropCollection(DATAMONGO753.class); - mongoTemplate.dropCollection(Data.class); - mongoTemplate.dropCollection(DATAMONGO788.class); - mongoTemplate.dropCollection(User.class); - mongoTemplate.dropCollection(Person.class); - mongoTemplate.dropCollection(Reservation.class); - mongoTemplate.dropCollection(Venue.class); - mongoTemplate.dropCollection(MeterData.class); - mongoTemplate.dropCollection(LineItem.class); - mongoTemplate.dropCollection(InventoryItem.class); - mongoTemplate.dropCollection(Sales.class); - mongoTemplate.dropCollection(Sales2.class); - mongoTemplate.dropCollection(Employee.class); - mongoTemplate.dropCollection(Art.class); - } + long count = mongoTemplate.count(new Query(), ZipInfo.class); + assertThat(count, is(29467L)); - /** - * Imports the sample dataset (zips.json) if necessary (e.g. if it doesn't exist yet). The dataset can originally be - * found on the mongodb aggregation framework example website: - * - * @see MongoDB Aggregation Examples - */ - private void initSampleDataIfNecessary() { - - if (!initialized) { - - LOGGER.debug("Server uses MongoDB Version: {}", mongoVersion); - - mongoTemplate.dropCollection(ZipInfo.class); - mongoTemplate.execute(ZipInfo.class, new CollectionCallback() { - - @Override - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - - Scanner scanner = null; - try { - scanner = new Scanner(new BufferedInputStream(new ClassPathResource("zips.json").getInputStream())); - while (scanner.hasNextLine()) { - String zipInfoRecord = scanner.nextLine(); - collection.save((DBObject) JSON.parse(zipInfoRecord)); - } - } catch (Exception e) { - if (scanner != null) { - scanner.close(); - } - throw new RuntimeException("Could not load mongodb sample dataset!", e); - } - - return null; - } - }); - - long count = mongoTemplate.count(new Query(), ZipInfo.class); - assertThat(count, is(29467L)); - - initialized = true; - } - } + initialized = true; + } + } - @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 - public void shouldHandleMissingInputCollection() { - mongoTemplate.aggregate(newAggregation(), (String) null, TagCount.class); - } + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 + public void shouldHandleMissingInputCollection() { + mongoTemplate.aggregate(newAggregation(), (String) null, TagCount.class); + } - @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 - public void shouldHandleMissingAggregationPipeline() { - mongoTemplate.aggregate(null, INPUT_COLLECTION, TagCount.class); - } + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 + public void shouldHandleMissingAggregationPipeline() { + mongoTemplate.aggregate(null, INPUT_COLLECTION, TagCount.class); + } - @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 - public void shouldHandleMissingEntityClass() { - mongoTemplate.aggregate(newAggregation(), INPUT_COLLECTION, null); - } + @Test(expected = IllegalArgumentException.class) // DATAMONGO-586 + public void shouldHandleMissingEntityClass() { + mongoTemplate.aggregate(newAggregation(), INPUT_COLLECTION, null); + } - @Test // DATAMONGO-586 - public void shouldAggregate() { + @Test // DATAMONGO-586 + public void shouldAggregate() { - createTagDocuments(); + createTagDocuments(); - Aggregation agg = newAggregation( // - project("tags"), // - unwind("tags"), // - group("tags") // - .count().as("n"), // - project("n") // - .and("tag").previousOperation(), // - sort(DESC, "n") // - ); + Aggregation agg = newAggregation( // + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("n"), // + project("n") // + .and("tag").previousOperation(), // + sort(DESC, "n") // + ); - AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, TagCount.class); + AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, TagCount.class); - assertThat(results, is(notNullValue())); + assertThat(results, is(notNullValue())); - List tagCount = results.getMappedResults(); + List tagCount = results.getMappedResults(); - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(3)); + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(3)); - assertTagCount("spring", 3, tagCount.get(0)); - assertTagCount("mongodb", 2, tagCount.get(1)); - assertTagCount("nosql", 1, tagCount.get(2)); - } + assertTagCount("spring", 3, tagCount.get(0)); + assertTagCount("mongodb", 2, tagCount.get(1)); + assertTagCount("nosql", 1, tagCount.get(2)); + } @Test // DATAMONGO-586 public void shouldAggregateAndStream() { @@ -258,25 +258,25 @@ public void shouldAggregateAndStream() { @Test // DATAMONGO-586 public void shouldAggregateEmptyCollection() { - Aggregation aggregation = newAggregation(// - project("tags"), // - unwind("tags"), // - group("tags") // - .count().as("n"), // - project("n") // - .and("tag").previousOperation(), // - sort(DESC, "n") // - ); + Aggregation aggregation = newAggregation(// + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("n"), // + project("n") // + .and("tag").previousOperation(), // + sort(DESC, "n") // + ); - AggregationResults results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class); + AggregationResults results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class); - assertThat(results, is(notNullValue())); + assertThat(results, is(notNullValue())); - List tagCount = results.getMappedResults(); + List tagCount = results.getMappedResults(); - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(0)); - } + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(0)); + } @Test // DATAMONGO-586 public void shouldAggregateEmptyCollectionAndStream() { @@ -307,83 +307,83 @@ public void shouldAggregateEmptyCollectionAndStream() { @Test // DATAMONGO-1391 public void shouldUnwindWithIndex() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); + DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); - coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); - coll.insert(createDocument("Doc2")); + coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); + coll.insert(createDocument("Doc2")); - Aggregation agg = newAggregation( // - project("tags"), // - unwind("tags", "n"), // - project("n") // - .and("tag").previousOperation(), // - sort(DESC, "n") // - ); + Aggregation agg = newAggregation( // + project("tags"), // + unwind("tags", "n"), // + project("n") // + .and("tag").previousOperation(), // + sort(DESC, "n") // + ); - AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, TagCount.class); + AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, TagCount.class); - assertThat(results, is(notNullValue())); + assertThat(results, is(notNullValue())); - List tagCount = results.getMappedResults(); + List tagCount = results.getMappedResults(); - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(3)); - } + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(3)); + } - @Test // DATAMONGO-1391 - public void shouldUnwindPreserveEmpty() { + @Test // DATAMONGO-1391 + public void shouldUnwindPreserveEmpty() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); + DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); - coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); - coll.insert(createDocument("Doc2")); + coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); + coll.insert(createDocument("Doc2")); - Aggregation agg = newAggregation( // - project("tags"), // - unwind("tags", "n", true), // - sort(DESC, "n") // - ); + Aggregation agg = newAggregation( // + project("tags"), // + unwind("tags", "n", true), // + sort(DESC, "n") // + ); - AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, DBObject.class); + AggregationResults results = mongoTemplate.aggregate(agg, INPUT_COLLECTION, DBObject.class); - assertThat(results, is(notNullValue())); + assertThat(results, is(notNullValue())); - List tagCount = results.getMappedResults(); + List tagCount = results.getMappedResults(); - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(4)); - assertThat(tagCount.get(0), isBsonObject().containing("n", 2L)); - assertThat(tagCount.get(3), isBsonObject().notContaining("n")); - } + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(4)); + assertThat(tagCount.get(0), isBsonObject().containing("n", 2L)); + assertThat(tagCount.get(3), isBsonObject().notContaining("n")); + } - @Test // DATAMONGO-586 - public void shouldDetectResultMismatch() { + @Test // DATAMONGO-586 + public void shouldDetectResultMismatch() { - createTagDocuments(); + createTagDocuments(); - Aggregation aggregation = newAggregation( // - project("tags"), // - unwind("tags"), // - group("tags") // - .count().as("count"), // count field not present - limit(2) // - ); + Aggregation aggregation = newAggregation( // + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("count"), // count field not present + limit(2) // + ); - AggregationResults results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class); + AggregationResults results = mongoTemplate.aggregate(aggregation, INPUT_COLLECTION, TagCount.class); - assertThat(results, is(notNullValue())); + assertThat(results, is(notNullValue())); - List tagCount = results.getMappedResults(); + List tagCount = results.getMappedResults(); - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(2)); - assertTagCount(null, 0, tagCount.get(0)); - assertTagCount(null, 0, tagCount.get(1)); - } + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(2)); + assertTagCount(null, 0, tagCount.get(0)); + assertTagCount(null, 0, tagCount.get(1)); + } @Test // DATAMONGO-586 @@ -418,7 +418,7 @@ public void shouldDetectResultMismatchWhileStreaming() { public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { /* //complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state - db.zipInfo.aggregate( + db.zipInfo.aggregate( { $group: { _id: { @@ -476,67 +476,67 @@ public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { ) */ - TypedAggregation aggregation = newAggregation(ZipInfo.class, // - group("state", "city").sum("population").as("pop"), // - sort(ASC, "pop", "state", "city"), // - group("state") // - .last("city").as("biggestCity") // - .last("pop").as("biggestPop") // - .first("city").as("smallestCity") // - .first("pop").as("smallestPop"), // - project() // - .and("state").previousOperation() // - .and("biggestCity").nested(bind("name", "biggestCity").and("population", "biggestPop")) // - .and("smallestCity").nested(bind("name", "smallestCity").and("population", "smallestPop")), // - sort(ASC, "state") // - ); - - assertThat(aggregation, is(notNullValue())); - assertThat(aggregation.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(aggregation, ZipInfoStats.class); - assertThat(result, is(notNullValue())); - assertThat(result.getMappedResults(), is(notNullValue())); - assertThat(result.getMappedResults().size(), is(51)); - - ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0); - assertThat(firstZipInfoStats, is(notNullValue())); - assertThat(firstZipInfoStats.id, is(nullValue())); - assertThat(firstZipInfoStats.state, is("AK")); - assertThat(firstZipInfoStats.smallestCity, is(notNullValue())); - assertThat(firstZipInfoStats.smallestCity.name, is("CHEVAK")); - assertThat(firstZipInfoStats.smallestCity.population, is(0)); - assertThat(firstZipInfoStats.biggestCity, is(notNullValue())); - assertThat(firstZipInfoStats.biggestCity.name, is("ANCHORAGE")); - assertThat(firstZipInfoStats.biggestCity.population, is(183987)); - - ZipInfoStats lastZipInfoStats = result.getMappedResults().get(50); - assertThat(lastZipInfoStats, is(notNullValue())); - assertThat(lastZipInfoStats.id, is(nullValue())); - assertThat(lastZipInfoStats.state, is("WY")); - assertThat(lastZipInfoStats.smallestCity, is(notNullValue())); - assertThat(lastZipInfoStats.smallestCity.name, is("LOST SPRINGS")); - assertThat(lastZipInfoStats.smallestCity.population, is(6)); - assertThat(lastZipInfoStats.biggestCity, is(notNullValue())); - assertThat(lastZipInfoStats.biggestCity.name, is("CHEYENNE")); - assertThat(lastZipInfoStats.biggestCity.population, is(70185)); - } - - @Test // DATAMONGO-586 - public void findStatesWithPopulationOver10MillionAggregationExample() { + TypedAggregation aggregation = newAggregation(ZipInfo.class, // + group("state", "city").sum("population").as("pop"), // + sort(ASC, "pop", "state", "city"), // + group("state") // + .last("city").as("biggestCity") // + .last("pop").as("biggestPop") // + .first("city").as("smallestCity") // + .first("pop").as("smallestPop"), // + project() // + .and("state").previousOperation() // + .and("biggestCity").nested(bind("name", "biggestCity").and("population", "biggestPop")) // + .and("smallestCity").nested(bind("name", "smallestCity").and("population", "smallestPop")), // + sort(ASC, "state") // + ); + + assertThat(aggregation, is(notNullValue())); + assertThat(aggregation.toString(), is(notNullValue())); + + AggregationResults result = mongoTemplate.aggregate(aggregation, ZipInfoStats.class); + assertThat(result, is(notNullValue())); + assertThat(result.getMappedResults(), is(notNullValue())); + assertThat(result.getMappedResults().size(), is(51)); + + ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0); + assertThat(firstZipInfoStats, is(notNullValue())); + assertThat(firstZipInfoStats.id, is(nullValue())); + assertThat(firstZipInfoStats.state, is("AK")); + assertThat(firstZipInfoStats.smallestCity, is(notNullValue())); + assertThat(firstZipInfoStats.smallestCity.name, is("CHEVAK")); + assertThat(firstZipInfoStats.smallestCity.population, is(0)); + assertThat(firstZipInfoStats.biggestCity, is(notNullValue())); + assertThat(firstZipInfoStats.biggestCity.name, is("ANCHORAGE")); + assertThat(firstZipInfoStats.biggestCity.population, is(183987)); + + ZipInfoStats lastZipInfoStats = result.getMappedResults().get(50); + assertThat(lastZipInfoStats, is(notNullValue())); + assertThat(lastZipInfoStats.id, is(nullValue())); + assertThat(lastZipInfoStats.state, is("WY")); + assertThat(lastZipInfoStats.smallestCity, is(notNullValue())); + assertThat(lastZipInfoStats.smallestCity.name, is("LOST SPRINGS")); + assertThat(lastZipInfoStats.smallestCity.population, is(6)); + assertThat(lastZipInfoStats.biggestCity, is(notNullValue())); + assertThat(lastZipInfoStats.biggestCity.name, is("CHEYENNE")); + assertThat(lastZipInfoStats.biggestCity.population, is(70185)); + } + + @Test // DATAMONGO-586 + public void findStatesWithPopulationOver10MillionAggregationExample() { /* - //complex mongodb aggregation framework example from + //complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state - - db.zipcodes.aggregate( + + db.zipcodes.aggregate( { $group: { _id:"$state", totalPop:{ $sum:"$pop"} } }, - { - $sort: { _id: 1, "totalPop": 1 } + { + $sort: { _id: 1, "totalPop": 1 } }, { $match: { @@ -546,34 +546,34 @@ public void findStatesWithPopulationOver10MillionAggregationExample() { ) */ - TypedAggregation agg = newAggregation(ZipInfo.class, // - group("state") // - .sum("population").as("totalPop"), // - sort(ASC, previousOperation(), "totalPop"), // - match(where("totalPop").gte(10 * 1000 * 1000)) // - ); - - assertThat(agg, is(notNullValue())); - assertThat(agg.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(agg, StateStats.class); - assertThat(result, is(notNullValue())); - assertThat(result.getMappedResults(), is(notNullValue())); - assertThat(result.getMappedResults().size(), is(7)); - - StateStats stateStats = result.getMappedResults().get(0); - assertThat(stateStats, is(notNullValue())); - assertThat(stateStats.id, is("CA")); - assertThat(stateStats.state, is(nullValue())); - assertThat(stateStats.totalPopulation, is(29760021)); - } - - /** - * @see MongoDB Aggregation - * Framework: $cond - */ - @Test // DATAMONGO-861 - public void aggregationUsingConditionalProjectionToCalculateDiscount() { + TypedAggregation agg = newAggregation(ZipInfo.class, // + group("state") // + .sum("population").as("totalPop"), // + sort(ASC, previousOperation(), "totalPop"), // + match(where("totalPop").gte(10 * 1000 * 1000)) // + ); + + assertThat(agg, is(notNullValue())); + assertThat(agg.toString(), is(notNullValue())); + + AggregationResults result = mongoTemplate.aggregate(agg, StateStats.class); + assertThat(result, is(notNullValue())); + assertThat(result.getMappedResults(), is(notNullValue())); + assertThat(result.getMappedResults().size(), is(7)); + + StateStats stateStats = result.getMappedResults().get(0); + assertThat(stateStats, is(notNullValue())); + assertThat(stateStats.id, is("CA")); + assertThat(stateStats.state, is(nullValue())); + assertThat(stateStats.totalPopulation, is(29760021)); + } + + /** + * @see MongoDB Aggregation + * Framework: $cond + */ + @Test // DATAMONGO-861 + public void aggregationUsingConditionalProjectionToCalculateDiscount() { /* db.inventory.aggregate( @@ -592,41 +592,41 @@ public void aggregationUsingConditionalProjectionToCalculateDiscount() { ) */ - mongoTemplate.insert(new InventoryItem(1, "abc1", 300)); - mongoTemplate.insert(new InventoryItem(2, "abc2", 200)); - mongoTemplate.insert(new InventoryItem(3, "xyz1", 250)); + mongoTemplate.insert(new InventoryItem(1, "abc1", 300)); + mongoTemplate.insert(new InventoryItem(2, "abc2", 200)); + mongoTemplate.insert(new InventoryItem(3, "xyz1", 250)); - TypedAggregation aggregation = newAggregation(InventoryItem.class, // - project("item") // - .and("discount")// - .applyCondition(ConditionalOperators.Cond.newBuilder().when(Criteria.where("qty").gte(250)) // - .then(30) // - .otherwise(20))); + TypedAggregation aggregation = newAggregation(InventoryItem.class, // + project("item") // + .and("discount")// + .applyCondition(ConditionalOperators.Cond.newBuilder().when(Criteria.where("qty").gte(250)) // + .then(30) // + .otherwise(20))); - assertThat(aggregation.toString(), is(notNullValue())); + assertThat(aggregation.toString(), is(notNullValue())); - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(3)); + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); - DBObject first = result.getMappedResults().get(0); - assertThat(first.get("_id"), is((Object) 1)); - assertThat(first.get("discount"), is((Object) 30)); + DBObject first = result.getMappedResults().get(0); + assertThat(first.get("_id"), is((Object) 1)); + assertThat(first.get("discount"), is((Object) 30)); - DBObject second = result.getMappedResults().get(1); - assertThat(second.get("_id"), is((Object) 2)); - assertThat(second.get("discount"), is((Object) 20)); + DBObject second = result.getMappedResults().get(1); + assertThat(second.get("_id"), is((Object) 2)); + assertThat(second.get("discount"), is((Object) 20)); - DBObject third = result.getMappedResults().get(2); - assertThat(third.get("_id"), is((Object) 3)); - assertThat(third.get("discount"), is((Object) 30)); - } + DBObject third = result.getMappedResults().get(2); + assertThat(third.get("_id"), is((Object) 3)); + assertThat(third.get("discount"), is((Object) 30)); + } - /** - * @see MongoDB Aggregation - * Framework: $ifNull - */ - @Test // DATAMONGO-861 - public void aggregationUsingIfNullToProjectSaneDefaults() { + /** + * @see MongoDB Aggregation + * Framework: $ifNull + */ + @Test // DATAMONGO-861 + public void aggregationUsingIfNullToProjectSaneDefaults() { /* db.inventory.aggregate( @@ -641,634 +641,634 @@ public void aggregationUsingIfNullToProjectSaneDefaults() { ) */ - mongoTemplate.insert(new InventoryItem(1, "abc1", "product 1", 300)); - mongoTemplate.insert(new InventoryItem(2, "abc2", 200)); - mongoTemplate.insert(new InventoryItem(3, "xyz1", 250)); - - TypedAggregation aggregation = newAggregation(InventoryItem.class, // - project("item") // - .and(ConditionalOperators.ifNull("description").then("Unspecified")) // - .as("description")// - ); - - assertThat(aggregation.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(3)); - - DBObject first = result.getMappedResults().get(0); - assertThat(first.get("_id"), is((Object) 1)); - assertThat(first.get("description"), is((Object) "product 1")); - - DBObject second = result.getMappedResults().get(1); - assertThat(second.get("_id"), is((Object) 2)); - assertThat(second.get("description"), is((Object) "Unspecified")); - } - - @Test // DATAMONGO-861 - public void aggregationUsingConditionalProjection() { - - TypedAggregation aggregation = newAggregation(ZipInfo.class, // - project() // - .and("largePopulation")// - .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // - .then(true) // - .otherwise(false)) // - .and("population").as("population")); - - assertThat(aggregation, is(notNullValue())); - assertThat(aggregation.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(29467)); - - DBObject firstZipInfoStats = result.getMappedResults().get(0); - assertThat(firstZipInfoStats.get("largePopulation"), is((Object) false)); - assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); - } - - @Test // DATAMONGO-861 - public void aggregationUsingNestedConditionalProjection() { - - TypedAggregation aggregation = newAggregation(ZipInfo.class, // - project() // - .and("size")// - .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // - .then( - ConditionalOperators.when(Criteria.where("population").gte(200000)).then("huge").otherwise("small")) // - .otherwise("small")) // - .and("population").as("population")); - - assertThat(aggregation, is(notNullValue())); - assertThat(aggregation.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(29467)); - - DBObject firstZipInfoStats = result.getMappedResults().get(0); - assertThat(firstZipInfoStats.get("size"), is((Object) "small")); - assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); - } - - @Test // DATAMONGO-861 - public void aggregationUsingIfNullProjection() { - - mongoTemplate.insert(new LineItem("id", "caption", 0)); - mongoTemplate.insert(new LineItem("idonly", null, 0)); - - TypedAggregation aggregation = newAggregation(LineItem.class, // - project("id") // - .and("caption")// - .applyCondition(ConditionalOperators.ifNull("caption").then("unknown")), - sort(ASC, "id")); - - assertThat(aggregation.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(2)); - - DBObject id = result.getMappedResults().get(0); - assertThat((String) id.get("caption"), is(equalTo("caption"))); - - DBObject idonly = result.getMappedResults().get(1); - assertThat((String) idonly.get("caption"), is(equalTo("unknown"))); - } - - @Test // DATAMONGO-861 - public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() { - - mongoTemplate.insert(new LineItem("id", "caption", 0)); - mongoTemplate.insert(new LineItem("idonly", null, 0)); - - TypedAggregation aggregation = newAggregation(LineItem.class, // - project("id") // - .and("caption")// - .applyCondition(ConditionalOperators.ifNull("caption").thenValueOf("id")), - sort(ASC, "id")); - - assertThat(aggregation.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(2)); - - DBObject id = result.getMappedResults().get(0); - assertThat((String) id.get("caption"), is(equalTo("caption"))); - - DBObject idonly = result.getMappedResults().get(1); - assertThat((String) idonly.get("caption"), is(equalTo("idonly"))); - } - - @Test // DATAMONGO-861 - public void shouldAllowGroupingUsingConditionalExpressions() { + mongoTemplate.insert(new InventoryItem(1, "abc1", "product 1", 300)); + mongoTemplate.insert(new InventoryItem(2, "abc2", 200)); + mongoTemplate.insert(new InventoryItem(3, "xyz1", 250)); - mongoTemplate.dropCollection(CarPerson.class); + TypedAggregation aggregation = newAggregation(InventoryItem.class, // + project("item") // + .and(ConditionalOperators.ifNull("description").then("Unspecified")) // + .as("description")// + ); - CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000), - new CarDescriptor.Entry("MAKE1", "MODEL2", 2001)); + assertThat(aggregation.toString(), is(notNullValue())); - CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); - CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2015)); + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); - mongoTemplate.save(person1); - mongoTemplate.save(person2); - mongoTemplate.save(person3); + DBObject first = result.getMappedResults().get(0); + assertThat(first.get("_id"), is((Object) 1)); + assertThat(first.get("description"), is((Object) "product 1")); - TypedAggregation agg = Aggregation.newAggregation(CarPerson.class, - unwind("descriptors.carDescriptor.entries"), // - project() // - .and(ConditionalOperators // - .when(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1")).then("good") - .otherwise("meh")) - .as("make") // - .and("descriptors.carDescriptor.entries.model").as("model") // - .and("descriptors.carDescriptor.entries.year").as("year"), // - group("make").avg(ConditionalOperators // - .when(Criteria.where("year").gte(2012)) // - .then(1) // - .otherwise(9000)).as("score"), - sort(ASC, "make")); + DBObject second = result.getMappedResults().get(1); + assertThat(second.get("_id"), is((Object) 2)); + assertThat(second.get("description"), is((Object) "Unspecified")); + } - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + @Test // DATAMONGO-861 + public void aggregationUsingConditionalProjection() { - assertThat(result.getMappedResults(), hasSize(2)); + TypedAggregation aggregation = newAggregation(ZipInfo.class, // + project() // + .and("largePopulation")// + .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // + .then(true) // + .otherwise(false)) // + .and("population").as("population")); - DBObject meh = result.getMappedResults().get(0); - assertThat((String) meh.get("_id"), is(equalTo("meh"))); - assertThat(((Number) meh.get("score")).longValue(), is(equalTo(1L))); + assertThat(aggregation, is(notNullValue())); + assertThat(aggregation.toString(), is(notNullValue())); - DBObject good = result.getMappedResults().get(1); - assertThat((String) good.get("_id"), is(equalTo("good"))); - assertThat(((Number) good.get("score")).longValue(), is(equalTo(9000L))); - } + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(29467)); - /** - * @see Return - * the Five Most Common “Likes” - */ - @Test // DATAMONGO-586 - public void returnFiveMostCommonLikesAggregationFrameworkExample() { + DBObject firstZipInfoStats = result.getMappedResults().get(0); + assertThat(firstZipInfoStats.get("largePopulation"), is((Object) false)); + assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); + } - createUserWithLikesDocuments(); + @Test // DATAMONGO-861 + public void aggregationUsingNestedConditionalProjection() { - TypedAggregation agg = createUsersWithCommonLikesAggregation(); + TypedAggregation aggregation = newAggregation(ZipInfo.class, // + project() // + .and("size")// + .applyCondition(ConditionalOperators.when(Criteria.where("population").gte(20000)) // + .then( + ConditionalOperators.when(Criteria.where("population").gte(200000)).then("huge").otherwise("small")) // + .otherwise("small")) // + .and("population").as("population")); - assertThat(agg, is(notNullValue())); - assertThat(agg.toString(), is(notNullValue())); + assertThat(aggregation, is(notNullValue())); + assertThat(aggregation.toString(), is(notNullValue())); - AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); - assertThat(result, is(notNullValue())); - assertThat(result.getMappedResults(), is(notNullValue())); - assertThat(result.getMappedResults().size(), is(5)); - - assertLikeStats(result.getMappedResults().get(0), "a", 4); - assertLikeStats(result.getMappedResults().get(1), "b", 2); - assertLikeStats(result.getMappedResults().get(2), "c", 4); - assertLikeStats(result.getMappedResults().get(3), "d", 2); - assertLikeStats(result.getMappedResults().get(4), "e", 3); - } + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(29467)); - protected TypedAggregation createUsersWithCommonLikesAggregation() { - return newAggregation(UserWithLikes.class, // - unwind("likes"), // - group("likes").count().as("number"), // - sort(DESC, "number"), // - limit(5), // - sort(ASC, previousOperation()) // - ); - } + DBObject firstZipInfoStats = result.getMappedResults().get(0); + assertThat(firstZipInfoStats.get("size"), is((Object) "small")); + assertThat(firstZipInfoStats.get("population"), is((Object) 6055)); + } - @Test // DATAMONGO-586 - public void arithmenticOperatorsInProjectionExample() { - - Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); - mongoTemplate.insert(product); - - TypedAggregation agg = newAggregation(Product.class, // - project("name", "netPrice") // - .and("netPrice").plus(1).as("netPricePlus1") // - .and("netPrice").minus(1).as("netPriceMinus1") // - .and("netPrice").multiply(2).as("netPriceMul2") // - .and("netPrice").divide(1.19).as("netPriceDiv119") // - .and("spaceUnits").mod(2).as("spaceUnitsMod2") // - .and("spaceUnits").plus("spaceUnits").as("spaceUnitsPlusSpaceUnits") // - .and("spaceUnits").minus("spaceUnits").as("spaceUnitsMinusSpaceUnits") // - .and("spaceUnits").multiply("spaceUnits").as("spaceUnitsMultiplySpaceUnits") // - .and("spaceUnits").divide("spaceUnits").as("spaceUnitsDivideSpaceUnits") // - .and("spaceUnits").mod("spaceUnits").as("spaceUnitsModSpaceUnits") // - ); + @Test // DATAMONGO-861 + public void aggregationUsingIfNullProjection() { - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - List resultList = result.getMappedResults(); - - assertThat(resultList, is(notNullValue())); - assertThat((String) resultList.get(0).get("_id"), is(product.id)); - assertThat((String) resultList.get(0).get("name"), is(product.name)); - assertThat((Double) resultList.get(0).get("netPricePlus1"), is(product.netPrice + 1)); - assertThat((Double) resultList.get(0).get("netPriceMinus1"), is(product.netPrice - 1)); - assertThat((Double) resultList.get(0).get("netPriceMul2"), is(product.netPrice * 2)); - assertThat((Double) resultList.get(0).get("netPriceDiv119"), is(product.netPrice / 1.19)); - assertThat((Integer) resultList.get(0).get("spaceUnitsMod2"), is(product.spaceUnits % 2)); - assertThat((Integer) resultList.get(0).get("spaceUnitsPlusSpaceUnits"), - is(product.spaceUnits + product.spaceUnits)); - assertThat((Integer) resultList.get(0).get("spaceUnitsMinusSpaceUnits"), - is(product.spaceUnits - product.spaceUnits)); - assertThat((Integer) resultList.get(0).get("spaceUnitsMultiplySpaceUnits"), - is(product.spaceUnits * product.spaceUnits)); - assertThat((Double) resultList.get(0).get("spaceUnitsDivideSpaceUnits"), - is((double) (product.spaceUnits / product.spaceUnits))); - assertThat((Integer) resultList.get(0).get("spaceUnitsModSpaceUnits"), is(product.spaceUnits % product.spaceUnits)); - } + mongoTemplate.insert(new LineItem("id", "caption", 0)); + mongoTemplate.insert(new LineItem("idonly", null, 0)); - @Test // DATAMONGO-774 - public void expressionsInProjectionExample() { + TypedAggregation aggregation = newAggregation(LineItem.class, // + project("id") // + .and("caption")// + .applyCondition(ConditionalOperators.ifNull("caption").then("unknown")), + sort(ASC, "id")); - Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); - mongoTemplate.insert(product); + assertThat(aggregation.toString(), is(notNullValue())); - TypedAggregation agg = newAggregation(Product.class, // - project("name", "netPrice") // - .andExpression("netPrice + 1").as("netPricePlus1") // - .andExpression("netPrice - 1").as("netPriceMinus1") // - .andExpression("netPrice / 2").as("netPriceDiv2") // - .andExpression("netPrice * 1.19").as("grossPrice") // - .andExpression("spaceUnits % 2").as("spaceUnitsMod2") // - .andExpression("(netPrice * 0.8 + 1.2) * 1.19").as("grossPriceIncludingDiscountAndCharge") // + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(2)); - ); + DBObject id = result.getMappedResults().get(0); + assertThat((String) id.get("caption"), is(equalTo("caption"))); - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - List resultList = result.getMappedResults(); - - assertThat(resultList, is(notNullValue())); - assertThat((String) resultList.get(0).get("_id"), is(product.id)); - assertThat((String) resultList.get(0).get("name"), is(product.name)); - assertThat((Double) resultList.get(0).get("netPricePlus1"), is(product.netPrice + 1)); - assertThat((Double) resultList.get(0).get("netPriceMinus1"), is(product.netPrice - 1)); - assertThat((Double) resultList.get(0).get("netPriceDiv2"), is(product.netPrice / 2)); - assertThat((Double) resultList.get(0).get("grossPrice"), is(product.netPrice * 1.19)); - assertThat((Integer) resultList.get(0).get("spaceUnitsMod2"), is(product.spaceUnits % 2)); - assertThat((Double) resultList.get(0).get("grossPriceIncludingDiscountAndCharge"), - is((product.netPrice * 0.8 + 1.2) * 1.19)); - } + DBObject idonly = result.getMappedResults().get(1); + assertThat((String) idonly.get("caption"), is(equalTo("unknown"))); + } + + @Test // DATAMONGO-861 + public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() { + + mongoTemplate.insert(new LineItem("id", "caption", 0)); + mongoTemplate.insert(new LineItem("idonly", null, 0)); + + TypedAggregation aggregation = newAggregation(LineItem.class, // + project("id") // + .and("caption")// + .applyCondition(ConditionalOperators.ifNull("caption").thenValueOf("id")), + sort(ASC, "id")); + + assertThat(aggregation.toString(), is(notNullValue())); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(2)); + + DBObject id = result.getMappedResults().get(0); + assertThat((String) id.get("caption"), is(equalTo("caption"))); + + DBObject idonly = result.getMappedResults().get(1); + assertThat((String) idonly.get("caption"), is(equalTo("idonly"))); + } + + @Test // DATAMONGO-861 + public void shouldAllowGroupingUsingConditionalExpressions() { + + mongoTemplate.dropCollection(CarPerson.class); + + CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000), + new CarDescriptor.Entry("MAKE1", "MODEL2", 2001)); + + CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); + CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2015)); + + mongoTemplate.save(person1); + mongoTemplate.save(person2); + mongoTemplate.save(person3); + + TypedAggregation agg = Aggregation.newAggregation(CarPerson.class, + unwind("descriptors.carDescriptor.entries"), // + project() // + .and(ConditionalOperators // + .when(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1")).then("good") + .otherwise("meh")) + .as("make") // + .and("descriptors.carDescriptor.entries.model").as("model") // + .and("descriptors.carDescriptor.entries.year").as("year"), // + group("make").avg(ConditionalOperators // + .when(Criteria.where("year").gte(2012)) // + .then(1) // + .otherwise(9000)).as("score"), + sort(ASC, "make")); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + + assertThat(result.getMappedResults(), hasSize(2)); + + DBObject meh = result.getMappedResults().get(0); + assertThat((String) meh.get("_id"), is(equalTo("meh"))); + assertThat(((Number) meh.get("score")).longValue(), is(equalTo(1L))); + + DBObject good = result.getMappedResults().get(1); + assertThat((String) good.get("_id"), is(equalTo("good"))); + assertThat(((Number) good.get("score")).longValue(), is(equalTo(9000L))); + } + + /** + * @see Return + * the Five Most Common “Likes” + */ + @Test // DATAMONGO-586 + public void returnFiveMostCommonLikesAggregationFrameworkExample() { + + createUserWithLikesDocuments(); + + TypedAggregation agg = createUsersWithCommonLikesAggregation(); + + assertThat(agg, is(notNullValue())); + assertThat(agg.toString(), is(notNullValue())); + + AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); + assertThat(result, is(notNullValue())); + assertThat(result.getMappedResults(), is(notNullValue())); + assertThat(result.getMappedResults().size(), is(5)); + + assertLikeStats(result.getMappedResults().get(0), "a", 4); + assertLikeStats(result.getMappedResults().get(1), "b", 2); + assertLikeStats(result.getMappedResults().get(2), "c", 4); + assertLikeStats(result.getMappedResults().get(3), "d", 2); + assertLikeStats(result.getMappedResults().get(4), "e", 3); + } + + protected TypedAggregation createUsersWithCommonLikesAggregation() { + return newAggregation(UserWithLikes.class, // + unwind("likes"), // + group("likes").count().as("number"), // + sort(DESC, "number"), // + limit(5), // + sort(ASC, previousOperation()) // + ); + } + + @Test // DATAMONGO-586 + public void arithmenticOperatorsInProjectionExample() { + + Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); + mongoTemplate.insert(product); + + TypedAggregation agg = newAggregation(Product.class, // + project("name", "netPrice") // + .and("netPrice").plus(1).as("netPricePlus1") // + .and("netPrice").minus(1).as("netPriceMinus1") // + .and("netPrice").multiply(2).as("netPriceMul2") // + .and("netPrice").divide(1.19).as("netPriceDiv119") // + .and("spaceUnits").mod(2).as("spaceUnitsMod2") // + .and("spaceUnits").plus("spaceUnits").as("spaceUnitsPlusSpaceUnits") // + .and("spaceUnits").minus("spaceUnits").as("spaceUnitsMinusSpaceUnits") // + .and("spaceUnits").multiply("spaceUnits").as("spaceUnitsMultiplySpaceUnits") // + .and("spaceUnits").divide("spaceUnits").as("spaceUnitsDivideSpaceUnits") // + .and("spaceUnits").mod("spaceUnits").as("spaceUnitsModSpaceUnits") // + ); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + List resultList = result.getMappedResults(); + + assertThat(resultList, is(notNullValue())); + assertThat((String) resultList.get(0).get("_id"), is(product.id)); + assertThat((String) resultList.get(0).get("name"), is(product.name)); + assertThat((Double) resultList.get(0).get("netPricePlus1"), is(product.netPrice + 1)); + assertThat((Double) resultList.get(0).get("netPriceMinus1"), is(product.netPrice - 1)); + assertThat((Double) resultList.get(0).get("netPriceMul2"), is(product.netPrice * 2)); + assertThat((Double) resultList.get(0).get("netPriceDiv119"), is(product.netPrice / 1.19)); + assertThat((Integer) resultList.get(0).get("spaceUnitsMod2"), is(product.spaceUnits % 2)); + assertThat((Integer) resultList.get(0).get("spaceUnitsPlusSpaceUnits"), + is(product.spaceUnits + product.spaceUnits)); + assertThat((Integer) resultList.get(0).get("spaceUnitsMinusSpaceUnits"), + is(product.spaceUnits - product.spaceUnits)); + assertThat((Integer) resultList.get(0).get("spaceUnitsMultiplySpaceUnits"), + is(product.spaceUnits * product.spaceUnits)); + assertThat((Double) resultList.get(0).get("spaceUnitsDivideSpaceUnits"), + is((double) (product.spaceUnits / product.spaceUnits))); + assertThat((Integer) resultList.get(0).get("spaceUnitsModSpaceUnits"), is(product.spaceUnits % product.spaceUnits)); + } + + @Test // DATAMONGO-774 + public void expressionsInProjectionExample() { + + Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); + mongoTemplate.insert(product); + + TypedAggregation agg = newAggregation(Product.class, // + project("name", "netPrice") // + .andExpression("netPrice + 1").as("netPricePlus1") // + .andExpression("netPrice - 1").as("netPriceMinus1") // + .andExpression("netPrice / 2").as("netPriceDiv2") // + .andExpression("netPrice * 1.19").as("grossPrice") // + .andExpression("spaceUnits % 2").as("spaceUnitsMod2") // + .andExpression("(netPrice * 0.8 + 1.2) * 1.19").as("grossPriceIncludingDiscountAndCharge") // + + ); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + List resultList = result.getMappedResults(); + + assertThat(resultList, is(notNullValue())); + assertThat((String) resultList.get(0).get("_id"), is(product.id)); + assertThat((String) resultList.get(0).get("name"), is(product.name)); + assertThat((Double) resultList.get(0).get("netPricePlus1"), is(product.netPrice + 1)); + assertThat((Double) resultList.get(0).get("netPriceMinus1"), is(product.netPrice - 1)); + assertThat((Double) resultList.get(0).get("netPriceDiv2"), is(product.netPrice / 2)); + assertThat((Double) resultList.get(0).get("grossPrice"), is(product.netPrice * 1.19)); + assertThat((Integer) resultList.get(0).get("spaceUnitsMod2"), is(product.spaceUnits % 2)); + assertThat((Double) resultList.get(0).get("grossPriceIncludingDiscountAndCharge"), + is((product.netPrice * 0.8 + 1.2) * 1.19)); + } + + @Test // DATAMONGO-774 + public void stringExpressionsInProjectionExample() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); + + Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); + mongoTemplate.insert(product); + + TypedAggregation agg = newAggregation(Product.class, // + project("name", "netPrice") // + .andExpression("concat(name, '_bubu')").as("name_bubu") // + ); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + List resultList = result.getMappedResults(); + + assertThat(resultList, is(notNullValue())); + assertThat((String) resultList.get(0).get("_id"), is(product.id)); + assertThat((String) resultList.get(0).get("name"), is(product.name)); + assertThat((String) resultList.get(0).get("name_bubu"), is(product.name + "_bubu")); + } + + @Test // DATAMONGO-774 + public void expressionsInProjectionExampleShowcase() { + + Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); + mongoTemplate.insert(product); + + double shippingCosts = 1.2; + + TypedAggregation agg = newAggregation(Product.class, // + project("name", "netPrice") // + .andExpression("(netPrice * (1-discountRate) + [0]) * (1+taxRate)", shippingCosts).as("salesPrice") // + ); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + List resultList = result.getMappedResults(); + + assertThat(resultList, is(notNullValue())); + DBObject firstItem = resultList.get(0); + assertThat((String) firstItem.get("_id"), is(product.id)); + assertThat((String) firstItem.get("name"), is(product.name)); + assertThat((Double) firstItem.get("salesPrice"), + is((product.netPrice * (1 - product.discountRate) + shippingCosts) * (1 + product.taxRate))); + } + + @Test + public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressionsInProjection() { + + exception.expect(MappingException.class); + exception.expectMessage("unknown"); + + Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); + mongoTemplate.insert(product); + + TypedAggregation agg = newAggregation(Product.class, // + project("name", "netPrice") // + .andExpression("unknown + 1").as("netPricePlus1") // + ); + + mongoTemplate.aggregate(agg, DBObject.class); + } + + /** + * @see Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation + */ + @Test // DATAMONGO-753 + public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { + + mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); + mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("B", 1), new PD("B", 1), new PD("C", 1))); + + TypedAggregation agg = newAggregation(DATAMONGO753.class, // + unwind("pd"), // + group("pd.pDch") // the nested field expression + .sum("pd.up").as("uplift"), // + project("_id", "uplift")); + + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + List stats = result.getMappedResults(); + + assertThat(stats.size(), is(3)); + assertThat(stats.get(0).get("_id").toString(), is("C")); + assertThat((Integer) stats.get(0).get("uplift"), is(2)); + assertThat(stats.get(1).get("_id").toString(), is("B")); + assertThat((Integer) stats.get(1).get("uplift"), is(3)); + assertThat(stats.get(2).get("_id").toString(), is("A")); + assertThat((Integer) stats.get(2).get("uplift"), is(1)); + } + + /** + * @see Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation + */ + @Test // DATAMONGO-753 + public void aliasesNestedFieldInProjectionImmediately() { + + mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); + mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("B", 1), new PD("B", 1), new PD("C", 1))); + + TypedAggregation agg = newAggregation(DATAMONGO753.class, // + unwind("pd"), // + project().and("pd.up").as("up")); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + List mappedResults = results.getMappedResults(); + + assertThat(mappedResults, hasSize(6)); + for (DBObject element : mappedResults) { + assertThat(element.get("up"), is((Object) 1)); + } + } + + @Test // DATAMONGO-774 + public void shouldPerformDateProjectionOperatorsCorrectly() throws ParseException { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); + + Data data = new Data(); + data.stringValue = "ABC"; + mongoTemplate.insert(data); + + TypedAggregation agg = newAggregation(Data.class, + project() // + .andExpression("concat(stringValue, 'DE')").as("concat") // + .andExpression("strcasecmp(stringValue,'XYZ')").as("strcasecmp") // + .andExpression("substr(stringValue,1,1)").as("substr") // + .andExpression("toLower(stringValue)").as("toLower") // + .andExpression("toUpper(toLower(stringValue))").as("toUpper") // + ); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + DBObject dbo = results.getUniqueMappedResult(); + + assertThat(dbo, is(notNullValue())); + assertThat((String) dbo.get("concat"), is("ABCDE")); + assertThat((Integer) dbo.get("strcasecmp"), is(-1)); + assertThat((String) dbo.get("substr"), is("B")); + assertThat((String) dbo.get("toLower"), is("abc")); + assertThat((String) dbo.get("toUpper"), is("ABC")); + } + + @Test // DATAMONGO-774 + public void shouldPerformStringProjectionOperatorsCorrectly() throws ParseException { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); + + Data data = new Data(); + data.dateValue = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSSZ").parse("29.08.1983 12:34:56.789+0000"); + mongoTemplate.insert(data); + + TypedAggregation agg = newAggregation(Data.class, + project() // + .andExpression("dayOfYear(dateValue)").as("dayOfYear") // + .andExpression("dayOfMonth(dateValue)").as("dayOfMonth") // + .andExpression("dayOfWeek(dateValue)").as("dayOfWeek") // + .andExpression("year(dateValue)").as("year") // + .andExpression("month(dateValue)").as("month") // + .andExpression("week(dateValue)").as("week") // + .andExpression("hour(dateValue)").as("hour") // + .andExpression("minute(dateValue)").as("minute") // + .andExpression("second(dateValue)").as("second") // + .andExpression("millisecond(dateValue)").as("millisecond") // + ); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + DBObject dbo = results.getUniqueMappedResult(); + + assertThat(dbo, is(notNullValue())); + assertThat((Integer) dbo.get("dayOfYear"), is(241)); + assertThat((Integer) dbo.get("dayOfMonth"), is(29)); + assertThat((Integer) dbo.get("dayOfWeek"), is(2)); + assertThat((Integer) dbo.get("year"), is(1983)); + assertThat((Integer) dbo.get("month"), is(8)); + assertThat((Integer) dbo.get("week"), is(35)); + assertThat((Integer) dbo.get("hour"), is(12)); + assertThat((Integer) dbo.get("minute"), is(34)); + assertThat((Integer) dbo.get("second"), is(56)); + assertThat((Integer) dbo.get("millisecond"), is(789)); + } + + @Test // DATAMONGO-1550 + public void shouldPerformReplaceRootOperatorCorrectly() throws ParseException { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Data data = new Data(); + DataItem dataItem = new DataItem(); + dataItem.primitiveIntValue = 42; + data.item = dataItem; + mongoTemplate.insert(data); + + TypedAggregation agg = newAggregation(Data.class, project("item"), // + replaceRoot("item"), // + project().and("primitiveIntValue").as("my_primitiveIntValue")); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + DBObject dbo = results.getUniqueMappedResult(); + + assertThat(dbo, is(notNullValue())); + assertThat((Integer) dbo.get("my_primitiveIntValue"), is(42)); + assertThat((Integer) dbo.keySet().size(), is(1)); + } + + @Test // DATAMONGO-788 + public void referencesToGroupIdsShouldBeRenderedProperly() { + + mongoTemplate.insert(new DATAMONGO788(1, 1)); + mongoTemplate.insert(new DATAMONGO788(1, 1)); + mongoTemplate.insert(new DATAMONGO788(1, 1)); + mongoTemplate.insert(new DATAMONGO788(2, 1)); + mongoTemplate.insert(new DATAMONGO788(2, 1)); + + AggregationOperation projectFirst = Aggregation.project("x", "y").and("xField").as("x").and("yField").as("y"); + AggregationOperation group = Aggregation.group("x", "y").count().as("xPerY"); + AggregationOperation project = Aggregation.project("xPerY", "x", "y").andExclude("_id"); + + TypedAggregation aggregation = Aggregation.newAggregation(DATAMONGO788.class, projectFirst, group, + project); + AggregationResults aggResults = mongoTemplate.aggregate(aggregation, DBObject.class); + List items = aggResults.getMappedResults(); + + assertThat(items.size(), is(2)); + assertThat((Integer) items.get(0).get("xPerY"), is(2)); + assertThat((Integer) items.get(0).get("x"), is(2)); + assertThat((Integer) items.get(0).get("y"), is(1)); + assertThat((Integer) items.get(1).get("xPerY"), is(3)); + assertThat((Integer) items.get(1).get("x"), is(1)); + assertThat((Integer) items.get(1).get("y"), is(1)); + } + + @Test // DATAMONGO-806 + public void shouldAllowGroupByIdFields() { + + mongoTemplate.dropCollection(User.class); + + LocalDateTime now = new LocalDateTime(); + + User user1 = new User("u1", new PushMessage("1", "aaa", now.toDate())); + User user2 = new User("u2", new PushMessage("2", "bbb", now.minusDays(2).toDate())); + User user3 = new User("u3", new PushMessage("3", "ccc", now.minusDays(1).toDate())); + + mongoTemplate.save(user1); + mongoTemplate.save(user2); + mongoTemplate.save(user3); + + Aggregation agg = newAggregation( // + project("id", "msgs"), // + unwind("msgs"), // + match(where("msgs.createDate").gt(now.minusDays(1).toDate())), // + group("id").push("msgs").as("msgs") // + ); + + AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); + + List mappedResults = results.getMappedResults(); + + DBObject firstItem = mappedResults.get(0); + assertThat(firstItem.get("_id"), is(notNullValue())); + assertThat(String.valueOf(firstItem.get("_id")), is("u1")); + } - @Test // DATAMONGO-774 - public void stringExpressionsInProjectionExample() { + @Test // DATAMONGO-840 + public void shouldAggregateOrderDataToAnInvoice() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); + mongoTemplate.dropCollection(Order.class); - Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); - mongoTemplate.insert(product); + double taxRate = 0.19; - TypedAggregation agg = newAggregation(Product.class, // - project("name", "netPrice") // - .andExpression("concat(name, '_bubu')").as("name_bubu") // - ); + LineItem product1 = new LineItem("1", "p1", 1.23); + LineItem product2 = new LineItem("2", "p2", 0.87, 2); + LineItem product3 = new LineItem("3", "p3", 5.33); - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - List resultList = result.getMappedResults(); + Order order = new Order("o4711", "c42", new Date()).addItem(product1).addItem(product2).addItem(product3); - assertThat(resultList, is(notNullValue())); - assertThat((String) resultList.get(0).get("_id"), is(product.id)); - assertThat((String) resultList.get(0).get("name"), is(product.name)); - assertThat((String) resultList.get(0).get("name_bubu"), is(product.name + "_bubu")); - } + mongoTemplate.save(order); - @Test // DATAMONGO-774 - public void expressionsInProjectionExampleShowcase() { + AggregationResults results = mongoTemplate.aggregate(newAggregation(Order.class, // + match(where("id").is(order.getId())), unwind("items"), // + project("id", "customerId", "items") // + .andExpression("items.price * items.quantity").as("lineTotal"), // + group("id") // + .sum("lineTotal").as("netAmount") // + .addToSet("items").as("items"), // + project("id", "items", "netAmount") // + .and("orderId").previousOperation() // + .andExpression("netAmount * [0]", taxRate).as("taxAmount") // + .andExpression("netAmount * (1 + [0])", taxRate).as("totalAmount") // + ), Invoice.class); - Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); - mongoTemplate.insert(product); + Invoice invoice = results.getUniqueMappedResult(); - double shippingCosts = 1.2; + assertThat(invoice, is(notNullValue())); + assertThat(invoice.getOrderId(), is(order.getId())); + assertThat(invoice.getNetAmount(), is(closeTo(8.3, 000001))); + assertThat(invoice.getTaxAmount(), is(closeTo(1.577, 000001))); + assertThat(invoice.getTotalAmount(), is(closeTo(9.877, 000001))); + } - TypedAggregation agg = newAggregation(Product.class, // - project("name", "netPrice") // - .andExpression("(netPrice * (1-discountRate) + [0]) * (1+taxRate)", shippingCosts).as("salesPrice") // - ); + @Test // DATAMONGO-924 + public void shouldAllowGroupingByAliasedFieldDefinedInFormerAggregationStage() { - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - List resultList = result.getMappedResults(); + mongoTemplate.dropCollection(CarPerson.class); - assertThat(resultList, is(notNullValue())); - DBObject firstItem = resultList.get(0); - assertThat((String) firstItem.get("_id"), is(product.id)); - assertThat((String) firstItem.get("name"), is(product.name)); - assertThat((Double) firstItem.get("salesPrice"), - is((product.netPrice * (1 - product.discountRate) + shippingCosts) * (1 + product.taxRate))); - } + CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000), + new CarDescriptor.Entry("MAKE1", "MODEL2", 2001), new CarDescriptor.Entry("MAKE2", "MODEL3", 2010), + new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); - @Test - public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressionsInProjection() { + CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); - exception.expect(MappingException.class); - exception.expectMessage("unknown"); + CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2011)); - Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19); - mongoTemplate.insert(product); + mongoTemplate.save(person1); + mongoTemplate.save(person2); + mongoTemplate.save(person3); - TypedAggregation agg = newAggregation(Product.class, // - project("name", "netPrice") // - .andExpression("unknown + 1").as("netPricePlus1") // - ); + TypedAggregation agg = Aggregation.newAggregation(CarPerson.class, + unwind("descriptors.carDescriptor.entries"), // + project() // + .and("descriptors.carDescriptor.entries.make").as("make") // + .and("descriptors.carDescriptor.entries.model").as("model") // + .and("firstName").as("firstName") // + .and("lastName").as("lastName"), // + group("make")); - mongoTemplate.aggregate(agg, DBObject.class); - } + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - /** - * @see Spring - * Data MongoDB - Aggregation Framework - invalid reference in group Operation - */ - @Test // DATAMONGO-753 - public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { - - mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); - mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("B", 1), new PD("B", 1), new PD("C", 1))); - - TypedAggregation agg = newAggregation(DATAMONGO753.class, // - unwind("pd"), // - group("pd.pDch") // the nested field expression - .sum("pd.up").as("uplift"), // - project("_id", "uplift")); - - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - List stats = result.getMappedResults(); - - assertThat(stats.size(), is(3)); - assertThat(stats.get(0).get("_id").toString(), is("C")); - assertThat((Integer) stats.get(0).get("uplift"), is(2)); - assertThat(stats.get(1).get("_id").toString(), is("B")); - assertThat((Integer) stats.get(1).get("uplift"), is(3)); - assertThat(stats.get(2).get("_id").toString(), is("A")); - assertThat((Integer) stats.get(2).get("uplift"), is(1)); - } + assertThat(result.getMappedResults(), hasSize(3)); + } - /** - * @see Spring - * Data MongoDB - Aggregation Framework - invalid reference in group Operation - */ - @Test // DATAMONGO-753 - public void aliasesNestedFieldInProjectionImmediately() { + @Test // DATAMONGO-960 + public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabled() { - mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("A", 1), new PD("B", 1), new PD("C", 1))); - mongoTemplate.insert(new DATAMONGO753().withPDs(new PD("B", 1), new PD("B", 1), new PD("C", 1))); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - TypedAggregation agg = newAggregation(DATAMONGO753.class, // - unwind("pd"), // - project().and("pd.up").as("up")); + createUserWithLikesDocuments(); - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - List mappedResults = results.getMappedResults(); + TypedAggregation agg = createUsersWithCommonLikesAggregation() // + .withOptions(newAggregationOptions().allowDiskUse(true).build()); - assertThat(mappedResults, hasSize(6)); - for (DBObject element : mappedResults) { - assertThat(element.get("up"), is((Object) 1)); - } - } - - @Test // DATAMONGO-774 - public void shouldPerformDateProjectionOperatorsCorrectly() throws ParseException { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); - - Data data = new Data(); - data.stringValue = "ABC"; - mongoTemplate.insert(data); - - TypedAggregation agg = newAggregation(Data.class, - project() // - .andExpression("concat(stringValue, 'DE')").as("concat") // - .andExpression("strcasecmp(stringValue,'XYZ')").as("strcasecmp") // - .andExpression("substr(stringValue,1,1)").as("substr") // - .andExpression("toLower(stringValue)").as("toLower") // - .andExpression("toUpper(toLower(stringValue))").as("toUpper") // - ); - - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - DBObject dbo = results.getUniqueMappedResult(); - - assertThat(dbo, is(notNullValue())); - assertThat((String) dbo.get("concat"), is("ABCDE")); - assertThat((Integer) dbo.get("strcasecmp"), is(-1)); - assertThat((String) dbo.get("substr"), is("B")); - assertThat((String) dbo.get("toLower"), is("abc")); - assertThat((String) dbo.get("toUpper"), is("ABC")); - } - - @Test // DATAMONGO-774 - public void shouldPerformStringProjectionOperatorsCorrectly() throws ParseException { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_FOUR)); - - Data data = new Data(); - data.dateValue = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSSZ").parse("29.08.1983 12:34:56.789+0000"); - mongoTemplate.insert(data); - - TypedAggregation agg = newAggregation(Data.class, - project() // - .andExpression("dayOfYear(dateValue)").as("dayOfYear") // - .andExpression("dayOfMonth(dateValue)").as("dayOfMonth") // - .andExpression("dayOfWeek(dateValue)").as("dayOfWeek") // - .andExpression("year(dateValue)").as("year") // - .andExpression("month(dateValue)").as("month") // - .andExpression("week(dateValue)").as("week") // - .andExpression("hour(dateValue)").as("hour") // - .andExpression("minute(dateValue)").as("minute") // - .andExpression("second(dateValue)").as("second") // - .andExpression("millisecond(dateValue)").as("millisecond") // - ); - - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - DBObject dbo = results.getUniqueMappedResult(); - - assertThat(dbo, is(notNullValue())); - assertThat((Integer) dbo.get("dayOfYear"), is(241)); - assertThat((Integer) dbo.get("dayOfMonth"), is(29)); - assertThat((Integer) dbo.get("dayOfWeek"), is(2)); - assertThat((Integer) dbo.get("year"), is(1983)); - assertThat((Integer) dbo.get("month"), is(8)); - assertThat((Integer) dbo.get("week"), is(35)); - assertThat((Integer) dbo.get("hour"), is(12)); - assertThat((Integer) dbo.get("minute"), is(34)); - assertThat((Integer) dbo.get("second"), is(56)); - assertThat((Integer) dbo.get("millisecond"), is(789)); - } - - @Test // DATAMONGO-1550 - public void shouldPerformReplaceRootOperatorCorrectly() throws ParseException { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - - Data data = new Data(); - DataItem dataItem = new DataItem(); - dataItem.primitiveIntValue = 42; - data.item = dataItem; - mongoTemplate.insert(data); - - TypedAggregation agg = newAggregation(Data.class, project("item"), // - replaceRoot("item"), // - project().and("primitiveIntValue").as("my_primitiveIntValue")); - - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - DBObject dbo = results.getUniqueMappedResult(); - - assertThat(dbo, is(notNullValue())); - assertThat((Integer) dbo.get("my_primitiveIntValue"), is(42)); - assertThat((Integer) dbo.keySet().size(), is(1)); - } - - @Test // DATAMONGO-788 - public void referencesToGroupIdsShouldBeRenderedProperly() { - - mongoTemplate.insert(new DATAMONGO788(1, 1)); - mongoTemplate.insert(new DATAMONGO788(1, 1)); - mongoTemplate.insert(new DATAMONGO788(1, 1)); - mongoTemplate.insert(new DATAMONGO788(2, 1)); - mongoTemplate.insert(new DATAMONGO788(2, 1)); - - AggregationOperation projectFirst = Aggregation.project("x", "y").and("xField").as("x").and("yField").as("y"); - AggregationOperation group = Aggregation.group("x", "y").count().as("xPerY"); - AggregationOperation project = Aggregation.project("xPerY", "x", "y").andExclude("_id"); - - TypedAggregation aggregation = Aggregation.newAggregation(DATAMONGO788.class, projectFirst, group, - project); - AggregationResults aggResults = mongoTemplate.aggregate(aggregation, DBObject.class); - List items = aggResults.getMappedResults(); - - assertThat(items.size(), is(2)); - assertThat((Integer) items.get(0).get("xPerY"), is(2)); - assertThat((Integer) items.get(0).get("x"), is(2)); - assertThat((Integer) items.get(0).get("y"), is(1)); - assertThat((Integer) items.get(1).get("xPerY"), is(3)); - assertThat((Integer) items.get(1).get("x"), is(1)); - assertThat((Integer) items.get(1).get("y"), is(1)); - } + assertThat(agg, is(notNullValue())); + assertThat(agg.toString(), is(notNullValue())); - @Test // DATAMONGO-806 - public void shouldAllowGroupByIdFields() { + AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); + assertThat(result, is(notNullValue())); + assertThat(result.getMappedResults(), is(notNullValue())); + assertThat(result.getMappedResults().size(), is(5)); - mongoTemplate.dropCollection(User.class); - - LocalDateTime now = new LocalDateTime(); - - User user1 = new User("u1", new PushMessage("1", "aaa", now.toDate())); - User user2 = new User("u2", new PushMessage("2", "bbb", now.minusDays(2).toDate())); - User user3 = new User("u3", new PushMessage("3", "ccc", now.minusDays(1).toDate())); - - mongoTemplate.save(user1); - mongoTemplate.save(user2); - mongoTemplate.save(user3); - - Aggregation agg = newAggregation( // - project("id", "msgs"), // - unwind("msgs"), // - match(where("msgs.createDate").gt(now.minusDays(1).toDate())), // - group("id").push("msgs").as("msgs") // - ); - - AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); - - List mappedResults = results.getMappedResults(); - - DBObject firstItem = mappedResults.get(0); - assertThat(firstItem.get("_id"), is(notNullValue())); - assertThat(String.valueOf(firstItem.get("_id")), is("u1")); - } - - @Test // DATAMONGO-840 - public void shouldAggregateOrderDataToAnInvoice() { - - mongoTemplate.dropCollection(Order.class); - - double taxRate = 0.19; - - LineItem product1 = new LineItem("1", "p1", 1.23); - LineItem product2 = new LineItem("2", "p2", 0.87, 2); - LineItem product3 = new LineItem("3", "p3", 5.33); - - Order order = new Order("o4711", "c42", new Date()).addItem(product1).addItem(product2).addItem(product3); - - mongoTemplate.save(order); - - AggregationResults results = mongoTemplate.aggregate(newAggregation(Order.class, // - match(where("id").is(order.getId())), unwind("items"), // - project("id", "customerId", "items") // - .andExpression("items.price * items.quantity").as("lineTotal"), // - group("id") // - .sum("lineTotal").as("netAmount") // - .addToSet("items").as("items"), // - project("id", "items", "netAmount") // - .and("orderId").previousOperation() // - .andExpression("netAmount * [0]", taxRate).as("taxAmount") // - .andExpression("netAmount * (1 + [0])", taxRate).as("totalAmount") // - ), Invoice.class); - - Invoice invoice = results.getUniqueMappedResult(); - - assertThat(invoice, is(notNullValue())); - assertThat(invoice.getOrderId(), is(order.getId())); - assertThat(invoice.getNetAmount(), is(closeTo(8.3, 000001))); - assertThat(invoice.getTaxAmount(), is(closeTo(1.577, 000001))); - assertThat(invoice.getTotalAmount(), is(closeTo(9.877, 000001))); - } - - @Test // DATAMONGO-924 - public void shouldAllowGroupingByAliasedFieldDefinedInFormerAggregationStage() { - - mongoTemplate.dropCollection(CarPerson.class); - - CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000), - new CarDescriptor.Entry("MAKE1", "MODEL2", 2001), new CarDescriptor.Entry("MAKE2", "MODEL3", 2010), - new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); - - CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014)); - - CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2011)); - - mongoTemplate.save(person1); - mongoTemplate.save(person2); - mongoTemplate.save(person3); - - TypedAggregation agg = Aggregation.newAggregation(CarPerson.class, - unwind("descriptors.carDescriptor.entries"), // - project() // - .and("descriptors.carDescriptor.entries.make").as("make") // - .and("descriptors.carDescriptor.entries.model").as("model") // - .and("firstName").as("firstName") // - .and("lastName").as("lastName"), // - group("make")); - - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - - assertThat(result.getMappedResults(), hasSize(3)); - } - - @Test // DATAMONGO-960 - public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabled() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - - createUserWithLikesDocuments(); - - TypedAggregation agg = createUsersWithCommonLikesAggregation() // - .withOptions(newAggregationOptions().allowDiskUse(true).build()); - - assertThat(agg, is(notNullValue())); - assertThat(agg.toString(), is(notNullValue())); - - AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); - assertThat(result, is(notNullValue())); - assertThat(result.getMappedResults(), is(notNullValue())); - assertThat(result.getMappedResults().size(), is(5)); - - assertLikeStats(result.getMappedResults().get(0), "a", 4); - assertLikeStats(result.getMappedResults().get(1), "b", 2); - assertLikeStats(result.getMappedResults().get(2), "c", 4); - assertLikeStats(result.getMappedResults().get(3), "d", 2); - assertLikeStats(result.getMappedResults().get(4), "e", 3); - } + assertLikeStats(result.getMappedResults().get(0), "a", 4); + assertLikeStats(result.getMappedResults().get(1), "b", 2); + assertLikeStats(result.getMappedResults().get(2), "c", 4); + assertLikeStats(result.getMappedResults().get(3), "d", 2); + assertLikeStats(result.getMappedResults().get(4), "e", 3); + } @Test // DATAMONGO-960 public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabledWhileStreaming() { @@ -1303,247 +1303,247 @@ public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOp @Test // DATAMONGO-960 public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithExplainOptionEnabled() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - createUserWithLikesDocuments(); + createUserWithLikesDocuments(); - TypedAggregation agg = createUsersWithCommonLikesAggregation() // - .withOptions(newAggregationOptions().explain(true).build()); + TypedAggregation agg = createUsersWithCommonLikesAggregation() // + .withOptions(newAggregationOptions().explain(true).build()); - AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); + AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); - assertThat(result.getMappedResults(), is(empty())); + assertThat(result.getMappedResults(), is(empty())); - DBObject rawResult = result.getRawResults(); + DBObject rawResult = result.getRawResults(); - assertThat(rawResult, is(notNullValue())); - assertThat(rawResult.containsField("stages"), is(true)); - } + assertThat(rawResult, is(notNullValue())); + assertThat(rawResult.containsField("stages"), is(true)); + } - @Test // DATAMONGO-954 - public void shouldSupportReturningCurrentAggregationRoot() { + @Test // DATAMONGO-954 + public void shouldSupportReturningCurrentAggregationRoot() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - mongoTemplate.save(new Person("p1_first", "p1_last", 25)); - mongoTemplate.save(new Person("p2_first", "p2_last", 32)); - mongoTemplate.save(new Person("p3_first", "p3_last", 25)); - mongoTemplate.save(new Person("p4_first", "p4_last", 15)); + mongoTemplate.save(new Person("p1_first", "p1_last", 25)); + mongoTemplate.save(new Person("p2_first", "p2_last", 32)); + mongoTemplate.save(new Person("p3_first", "p3_last", 25)); + mongoTemplate.save(new Person("p4_first", "p4_last", 15)); - List personsWithAge25 = mongoTemplate.find(Query.query(where("age").is(25)), DBObject.class, - mongoTemplate.getCollectionName(Person.class)); + List personsWithAge25 = mongoTemplate.find(Query.query(where("age").is(25)), DBObject.class, + mongoTemplate.getCollectionName(Person.class)); - Aggregation agg = newAggregation(group("age").push(Aggregation.ROOT).as("users")); - AggregationResults result = mongoTemplate.aggregate(agg, Person.class, DBObject.class); + Aggregation agg = newAggregation(group("age").push(Aggregation.ROOT).as("users")); + AggregationResults result = mongoTemplate.aggregate(agg, Person.class, DBObject.class); - assertThat(result.getMappedResults(), hasSize(3)); - DBObject o = (DBObject) result.getMappedResults().get(2); + assertThat(result.getMappedResults(), hasSize(3)); + DBObject o = (DBObject) result.getMappedResults().get(2); - assertThat(o.get("_id"), is((Object) 25)); - assertThat((List) o.get("users"), hasSize(2)); - assertThat((List) o.get("users"), is(contains(personsWithAge25.toArray()))); - } + assertThat(o.get("_id"), is((Object) 25)); + assertThat((List) o.get("users"), hasSize(2)); + assertThat((List) o.get("users"), is(contains(personsWithAge25.toArray()))); + } - /** - * {@link http://stackoverflow.com/questions/24185987/using-root-inside-spring-data-mongodb-for-retrieving-whole-document} - */ - @Test // DATAMONGO-954 - public void shouldSupportReturningCurrentAggregationRootInReference() { + /** + * {@link http://stackoverflow.com/questions/24185987/using-root-inside-spring-data-mongodb-for-retrieving-whole-document} + */ + @Test // DATAMONGO-954 + public void shouldSupportReturningCurrentAggregationRootInReference() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - mongoTemplate.save(new Reservation("0123", "42", 100)); - mongoTemplate.save(new Reservation("0360", "43", 200)); - mongoTemplate.save(new Reservation("0360", "44", 300)); + mongoTemplate.save(new Reservation("0123", "42", 100)); + mongoTemplate.save(new Reservation("0360", "43", 200)); + mongoTemplate.save(new Reservation("0360", "44", 300)); - Aggregation agg = newAggregation( // - match(where("hotelCode").is("0360")), // - sort(Direction.DESC, "confirmationNumber", "timestamp"), // - group("confirmationNumber") // - .first("timestamp").as("timestamp") // - .first(Aggregation.ROOT).as("reservationImage") // - ); - AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); + Aggregation agg = newAggregation( // + match(where("hotelCode").is("0360")), // + sort(Direction.DESC, "confirmationNumber", "timestamp"), // + group("confirmationNumber") // + .first("timestamp").as("timestamp") // + .first(Aggregation.ROOT).as("reservationImage") // + ); + AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); - assertThat(result.getMappedResults(), hasSize(2)); - } + assertThat(result.getMappedResults(), hasSize(2)); + } - @Test // DATAMONGO-1549 - public void shouldApplyCountCorrectly() { + @Test // DATAMONGO-1549 + public void shouldApplyCountCorrectly() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - mongoTemplate.save(new Reservation("0123", "42", 100)); - mongoTemplate.save(new Reservation("0360", "43", 200)); - mongoTemplate.save(new Reservation("0360", "44", 300)); + mongoTemplate.save(new Reservation("0123", "42", 100)); + mongoTemplate.save(new Reservation("0360", "43", 200)); + mongoTemplate.save(new Reservation("0360", "44", 300)); - Aggregation agg = newAggregation( // - count().as("documents"), // - project("documents") // - .andExpression("documents * 2").as("twice")); - AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); + Aggregation agg = newAggregation( // + count().as("documents"), // + project("documents") // + .andExpression("documents * 2").as("twice")); + AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); - assertThat(result.getMappedResults(), hasSize(1)); + assertThat(result.getMappedResults(), hasSize(1)); - DBObject dbObject = result.getMappedResults().get(0); - assertThat(dbObject, isBsonObject().containing("documents", 3).containing("twice", 6)); - } + DBObject dbObject = result.getMappedResults().get(0); + assertThat(dbObject, isBsonObject().containing("documents", 3).containing("twice", 6)); + } - @Test // DATAMONGO-975 - public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception { - - mongoTemplate.dropCollection(ObjectWithDate.class); - - DateTime dateTime = new DateTime() // - .withYear(2014) // - .withMonthOfYear(2) // - .withDayOfMonth(7) // - .withTime(3, 4, 5, 6).toDateTime(DateTimeZone.UTC).toDateTimeISO(); - - ObjectWithDate owd = new ObjectWithDate(dateTime.toDate()); - mongoTemplate.insert(owd); - - ProjectionOperation dateProjection = Aggregation.project() // - .and("dateValue").extractHour().as("hour") // - .and("dateValue").extractMinute().as("min") // - .and("dateValue").extractSecond().as("second") // - .and("dateValue").extractMillisecond().as("millis") // - .and("dateValue").extractYear().as("year") // - .and("dateValue").extractMonth().as("month") // - .and("dateValue").extractWeek().as("week") // - .and("dateValue").extractDayOfYear().as("dayOfYear") // - .and("dateValue").extractDayOfMonth().as("dayOfMonth") // - .and("dateValue").extractDayOfWeek().as("dayOfWeek") // - .andExpression("dateValue + 86400000").extractDayOfYear().as("dayOfYearPlus1Day") // - .andExpression("dateValue + 86400000").project("dayOfYear").as("dayOfYearPlus1DayManually") // - ; - - Aggregation agg = newAggregation(dateProjection); - AggregationResults result = mongoTemplate.aggregate(agg, ObjectWithDate.class, DBObject.class); - - assertThat(result.getMappedResults(), hasSize(1)); - DBObject dbo = result.getMappedResults().get(0); - - assertThat(dbo.get("hour"), is((Object) dateTime.getHourOfDay())); - assertThat(dbo.get("min"), is((Object) dateTime.getMinuteOfHour())); - assertThat(dbo.get("second"), is((Object) dateTime.getSecondOfMinute())); - assertThat(dbo.get("millis"), is((Object) dateTime.getMillisOfSecond())); - assertThat(dbo.get("year"), is((Object) dateTime.getYear())); - assertThat(dbo.get("month"), is((Object) dateTime.getMonthOfYear())); - // dateTime.getWeekOfWeekyear()) returns 6 since for MongoDB the week starts on sunday and not on monday. - assertThat(dbo.get("week"), is((Object) 5)); - assertThat(dbo.get("dayOfYear"), is((Object) dateTime.getDayOfYear())); - assertThat(dbo.get("dayOfMonth"), is((Object) dateTime.getDayOfMonth())); - - // dateTime.getDayOfWeek() - assertThat(dbo.get("dayOfWeek"), is((Object) 6)); - assertThat(dbo.get("dayOfYearPlus1Day"), is((Object) dateTime.plusDays(1).getDayOfYear())); - assertThat(dbo.get("dayOfYearPlus1DayManually"), is((Object) dateTime.plusDays(1).getDayOfYear())); - } + @Test // DATAMONGO-975 + public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception { - @Test // DATAMONGO-1127 - public void shouldSupportGeoNearQueriesForAggregationWithDistanceField() { + mongoTemplate.dropCollection(ObjectWithDate.class); - mongoTemplate.insert(new Venue("Penn Station", -73.99408, 40.75057)); - mongoTemplate.insert(new Venue("10gen Office", -73.99171, 40.738868)); - mongoTemplate.insert(new Venue("Flatiron Building", -73.988135, 40.741404)); + DateTime dateTime = new DateTime() // + .withYear(2014) // + .withMonthOfYear(2) // + .withDayOfMonth(7) // + .withTime(3, 4, 5, 6).toDateTime(DateTimeZone.UTC).toDateTimeISO(); - mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location")); + ObjectWithDate owd = new ObjectWithDate(dateTime.toDate()); + mongoTemplate.insert(owd); - NearQuery geoNear = NearQuery.near(-73, 40, Metrics.KILOMETERS).num(10).maxDistance(150); + ProjectionOperation dateProjection = Aggregation.project() // + .and("dateValue").extractHour().as("hour") // + .and("dateValue").extractMinute().as("min") // + .and("dateValue").extractSecond().as("second") // + .and("dateValue").extractMillisecond().as("millis") // + .and("dateValue").extractYear().as("year") // + .and("dateValue").extractMonth().as("month") // + .and("dateValue").extractWeek().as("week") // + .and("dateValue").extractDayOfYear().as("dayOfYear") // + .and("dateValue").extractDayOfMonth().as("dayOfMonth") // + .and("dateValue").extractDayOfWeek().as("dayOfWeek") // + .andExpression("dateValue + 86400000").extractDayOfYear().as("dayOfYearPlus1Day") // + .andExpression("dateValue + 86400000").project("dayOfYear").as("dayOfYearPlus1DayManually") // + ; - Aggregation agg = newAggregation(Aggregation.geoNear(geoNear, "distance")); - AggregationResults result = mongoTemplate.aggregate(agg, Venue.class, DBObject.class); + Aggregation agg = newAggregation(dateProjection); + AggregationResults result = mongoTemplate.aggregate(agg, ObjectWithDate.class, DBObject.class); - assertThat(result.getMappedResults(), hasSize(3)); + assertThat(result.getMappedResults(), hasSize(1)); + DBObject dbo = result.getMappedResults().get(0); - DBObject firstResult = result.getMappedResults().get(0); - assertThat(firstResult.containsField("distance"), is(true)); - assertThat((Double) firstResult.get("distance"), closeTo(117.620092203928, 0.00001)); - } + assertThat(dbo.get("hour"), is((Object) dateTime.getHourOfDay())); + assertThat(dbo.get("min"), is((Object) dateTime.getMinuteOfHour())); + assertThat(dbo.get("second"), is((Object) dateTime.getSecondOfMinute())); + assertThat(dbo.get("millis"), is((Object) dateTime.getMillisOfSecond())); + assertThat(dbo.get("year"), is((Object) dateTime.getYear())); + assertThat(dbo.get("month"), is((Object) dateTime.getMonthOfYear())); + // dateTime.getWeekOfWeekyear()) returns 6 since for MongoDB the week starts on sunday and not on monday. + assertThat(dbo.get("week"), is((Object) 5)); + assertThat(dbo.get("dayOfYear"), is((Object) dateTime.getDayOfYear())); + assertThat(dbo.get("dayOfMonth"), is((Object) dateTime.getDayOfMonth())); - @Test // DATAMONGO-1133 - public void shouldHonorFieldAliasesForFieldReferences() { + // dateTime.getDayOfWeek() + assertThat(dbo.get("dayOfWeek"), is((Object) 6)); + assertThat(dbo.get("dayOfYearPlus1Day"), is((Object) dateTime.plusDays(1).getDayOfYear())); + assertThat(dbo.get("dayOfYearPlus1DayManually"), is((Object) dateTime.plusDays(1).getDayOfYear())); + } - mongoTemplate.insert(new MeterData("m1", "counter1", 42)); - mongoTemplate.insert(new MeterData("m1", "counter1", 13)); - mongoTemplate.insert(new MeterData("m1", "counter1", 45)); + @Test // DATAMONGO-1127 + public void shouldSupportGeoNearQueriesForAggregationWithDistanceField() { - TypedAggregation agg = newAggregation(MeterData.class, // - match(where("resourceId").is("m1")), // - group("counterName").sum("counterVolume").as("totalValue")); + mongoTemplate.insert(new Venue("Penn Station", -73.99408, 40.75057)); + mongoTemplate.insert(new Venue("10gen Office", -73.99171, 40.738868)); + mongoTemplate.insert(new Venue("Flatiron Building", -73.988135, 40.741404)); - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + mongoTemplate.indexOps(Venue.class).ensureIndex(new GeospatialIndex("location")); - assertThat(results.getMappedResults(), hasSize(1)); - DBObject result = results.getMappedResults().get(0); + NearQuery geoNear = NearQuery.near(-73, 40, Metrics.KILOMETERS).num(10).maxDistance(150); - assertThat(result.get("_id"), is(equalTo((Object) "counter1"))); - assertThat(result.get("totalValue"), is(equalTo((Object) 100.0))); - } + Aggregation agg = newAggregation(Aggregation.geoNear(geoNear, "distance")); + AggregationResults result = mongoTemplate.aggregate(agg, Venue.class, DBObject.class); - @Test // DATAMONGO-1326 - public void shouldLookupPeopleCorectly() { + assertThat(result.getMappedResults(), hasSize(3)); - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + DBObject firstResult = result.getMappedResults().get(0); + assertThat(firstResult.containsField("distance"), is(true)); + assertThat((Double) firstResult.get("distance"), closeTo(117.620092203928, 0.00001)); + } - createUsersWithReferencedPersons(); + @Test // DATAMONGO-1133 + public void shouldHonorFieldAliasesForFieldReferences() { - TypedAggregation agg = newAggregation(User.class, // - lookup("person", "_id", "firstname", "linkedPerson"), // - sort(ASC, "id")); + mongoTemplate.insert(new MeterData("m1", "counter1", 42)); + mongoTemplate.insert(new MeterData("m1", "counter1", 13)); + mongoTemplate.insert(new MeterData("m1", "counter1", 45)); - AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); + TypedAggregation agg = newAggregation(MeterData.class, // + match(where("resourceId").is("m1")), // + group("counterName").sum("counterVolume").as("totalValue")); - List mappedResults = results.getMappedResults(); + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - DBObject firstItem = mappedResults.get(0); + assertThat(results.getMappedResults(), hasSize(1)); + DBObject result = results.getMappedResults().get(0); - assertThat(firstItem, isBsonObject().containing("_id", "u1")); - assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); - } + assertThat(result.get("_id"), is(equalTo((Object) "counter1"))); + assertThat(result.get("totalValue"), is(equalTo((Object) 100.0))); + } - @Test // DATAMONGO-1326 - public void shouldGroupByAndLookupPeopleCorectly() { + @Test // DATAMONGO-1326 + public void shouldLookupPeopleCorectly() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - createUsersWithReferencedPersons(); + createUsersWithReferencedPersons(); - TypedAggregation agg = newAggregation(User.class, // - group().min("id").as("foreignKey"), // - lookup("person", "foreignKey", "firstname", "linkedPerson"), // - sort(ASC, "foreignKey", "linkedPerson.firstname")); + TypedAggregation agg = newAggregation(User.class, // + lookup("person", "_id", "firstname", "linkedPerson"), // + sort(ASC, "id")); - AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); + AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); - List mappedResults = results.getMappedResults(); + List mappedResults = results.getMappedResults(); - DBObject firstItem = mappedResults.get(0); + DBObject firstItem = mappedResults.get(0); - assertThat(firstItem, isBsonObject().containing("foreignKey", "u1")); - assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); - } + assertThat(firstItem, isBsonObject().containing("_id", "u1")); + assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); + } - @Test // DATAMONGO-1418 - public void shouldCreateOutputCollection() { + @Test // DATAMONGO-1326 + public void shouldGroupByAndLookupPeopleCorectly() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - mongoTemplate.save(new Person("Anna", "Ivanova", 21, Person.Sex.FEMALE)); - mongoTemplate.save(new Person("Pavel", "Sidorov", 36, Person.Sex.MALE)); - mongoTemplate.save(new Person("Anastasia", "Volochkova", 29, Person.Sex.FEMALE)); - mongoTemplate.save(new Person("Igor", "Stepanov", 31, Person.Sex.MALE)); - mongoTemplate.save(new Person("Leoniv", "Yakubov", 55, Person.Sex.MALE)); + createUsersWithReferencedPersons(); - String tempOutCollection = "personQueryTemp"; - TypedAggregation agg = newAggregation(Person.class, // - group("sex").count().as("count"), // - sort(DESC, "count"), // - out(tempOutCollection)); + TypedAggregation agg = newAggregation(User.class, // + group().min("id").as("foreignKey"), // + lookup("person", "foreignKey", "firstname", "linkedPerson"), // + sort(ASC, "foreignKey", "linkedPerson.firstname")); + + AggregationResults results = mongoTemplate.aggregate(agg, User.class, DBObject.class); + + List mappedResults = results.getMappedResults(); + + DBObject firstItem = mappedResults.get(0); + + assertThat(firstItem, isBsonObject().containing("foreignKey", "u1")); + assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); + } + + @Test // DATAMONGO-1418 + public void shouldCreateOutputCollection() { - AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); - assertThat(results.getMappedResults(), is(empty())); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + + mongoTemplate.save(new Person("Anna", "Ivanova", 21, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Pavel", "Sidorov", 36, Person.Sex.MALE)); + mongoTemplate.save(new Person("Anastasia", "Volochkova", 29, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Igor", "Stepanov", 31, Person.Sex.MALE)); + mongoTemplate.save(new Person("Leoniv", "Yakubov", 55, Person.Sex.MALE)); + + String tempOutCollection = "personQueryTemp"; + TypedAggregation agg = newAggregation(Person.class, // + group("sex").count().as("count"), // + sort(DESC, "count"), // + out(tempOutCollection)); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + assertThat(results.getMappedResults(), is(empty())); List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); @@ -1576,532 +1576,532 @@ public void shouldCreateOutputCollectionWhileStreaming() { List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); - assertThat(list, hasSize(2)); - assertThat(list.get(0), isBsonObject().containing("_id", "MALE").containing("count", 3)); - assertThat(list.get(1), isBsonObject().containing("_id", "FEMALE").containing("count", 2)); - - mongoTemplate.dropCollection(tempOutCollection); - } - - @Test(expected = IllegalArgumentException.class) // DATAMONGO-1418 - public void outShouldOutBeTheLastOperation() { - - newAggregation(match(new Criteria()), // - group("field1").count().as("totalCount"), // - out("collection1"), // - skip(100L)); - } - - @Test // DATAMONGO-1457 - public void sliceShouldBeAppliedCorrectly() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - - createUserWithLikesDocuments(); - - TypedAggregation agg = newAggregation(UserWithLikes.class, match(new Criteria()), - project().and("likes").slice(2)); - - AggregationResults result = mongoTemplate.aggregate(agg, UserWithLikes.class); - - assertThat(result.getMappedResults(), hasSize(9)); - for (UserWithLikes user : result) { - assertThat(user.likes.size() <= 2, is(true)); - } - } - - @Test // DATAMONGO-1491 - public void filterShouldBeAppliedCorrectly() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - - Item item43 = Item.builder().itemId("43").quantity(2).price(2L).build(); - Item item2 = Item.builder().itemId("2").quantity(1).price(240L).build(); - Sales sales1 = Sales.builder().id("0") - .items(Arrays.asList( // - item43, item2)) // - .build(); - - Item item23 = Item.builder().itemId("23").quantity(3).price(110L).build(); - Item item103 = Item.builder().itemId("103").quantity(4).price(5L).build(); - Item item38 = Item.builder().itemId("38").quantity(1).price(300L).build(); - Sales sales2 = Sales.builder().id("1").items(Arrays.asList( // - item23, item103, item38)).build(); - - Item item4 = Item.builder().itemId("4").quantity(1).price(23L).build(); - Sales sales3 = Sales.builder().id("2").items(Arrays.asList( // - item4)).build(); - - mongoTemplate.insert(Arrays.asList(sales1, sales2, sales3), Sales.class); - - TypedAggregation agg = newAggregation(Sales.class, project().and("items") - .filter("item", AggregationFunctionExpressions.GTE.of(field("item.price"), 100)).as("items")); - - assertThat(mongoTemplate.aggregate(agg, Sales.class).getMappedResults(), - contains(Sales.builder().id("0").items(Collections.singletonList(item2)).build(), - Sales.builder().id("1").items(Arrays.asList(item23, item38)).build(), - Sales.builder().id("2").items(Collections. emptyList()).build())); - } - - @Test // DATAMONGO-1538 - public void letShouldBeAppliedCorrectly() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - - Sales2 sales1 = Sales2.builder().id("1").price(10).tax(0.5F).applyDiscount(true).build(); - Sales2 sales2 = Sales2.builder().id("2").price(10).tax(0.25F).applyDiscount(false).build(); - - mongoTemplate.insert(Arrays.asList(sales1, sales2), Sales2.class); - - ExpressionVariable total = ExpressionVariable.newVariable("total") - .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))); - ExpressionVariable discounted = ExpressionVariable.newVariable("discounted") - .forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); - - TypedAggregation agg = Aggregation.newAggregation(Sales2.class, - Aggregation.project() - .and(VariableOperators.Let.define(total, discounted).andApply( - AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) - .as("finalTotal")); - - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - assertThat(result.getMappedResults(), - contains(new BasicDBObjectBuilder().add("_id", "1").add("finalTotal", 9.450000000000001D).get(), - new BasicDBObjectBuilder().add("_id", "2").add("finalTotal", 10.25D).get())); - } - - @Test // DATAMONGO-1551 - public void graphLookupShouldBeAppliedCorrectly() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - - Employee em1 = Employee.builder().id(1).name("Dev").build(); - Employee em2 = Employee.builder().id(2).name("Eliot").reportsTo("Dev").build(); - Employee em4 = Employee.builder().id(4).name("Andrew").reportsTo("Eliot").build(); - - mongoTemplate.insert(Arrays.asList(em1, em2, em4), Employee.class); - - TypedAggregation agg = Aggregation.newAggregation(Employee.class, - match(Criteria.where("name").is("Andrew")), // - Aggregation.graphLookup("employee") // - .startWith("reportsTo") // - .connectFrom("reportsTo") // - .connectTo("name") // - .depthField("depth") // - .maxDepth(5) // - .as("reportingHierarchy")); - - AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - - DBObject object = result.getUniqueMappedResult(); - BasicDBList list = (BasicDBList) object.get("reportingHierarchy"); - - assertThat(object, isBsonObject().containing("reportingHierarchy", List.class)); - assertThat((DBObject) list.get(0), isBsonObject().containing("name", "Dev").containing("depth", 1L)); - assertThat((DBObject) list.get(1), isBsonObject().containing("name", "Eliot").containing("depth", 0L)); - } - - @Test // DATAMONGO-1552 - public void bucketShouldCollectDocumentsIntoABucket() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - - Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); - Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); - Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); - Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); - - mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); - - TypedAggregation aggregation = newAggregation(Art.class, // - bucket("price") // - .withBoundaries(0, 100, 200) // - .withDefaultBucket("other") // - .andOutputCount().as("count") // - .andOutput("title").push().as("titles") // - .andOutputExpression("price * 10").sum().as("sum")); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(3)); - - // { "_id" : 0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} - DBObject bound0 = result.getMappedResults().get(0); - assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer")); - assertThat((Double) bound0.get("sum"), is(closeTo(760.40, 0.1))); - - // { "_id" : 100 , "count" : 2 , "titles" : [ "The Pillars of Society" , "The Great Wave off Kanagawa"] , "sum" : - // 3672.9} - DBObject bound100 = result.getMappedResults().get(1); - assertThat(bound100, isBsonObject().containing("count", 2).containing("_id", 100)); - assertThat((List) bound100.get("titles"), - hasItems("The Pillars of Society", "The Great Wave off Kanagawa")); - assertThat((Double) bound100.get("sum"), is(closeTo(3672.9, 0.1))); - } - - @Test // DATAMONGO-1552 - public void bucketAutoShouldCollectDocumentsIntoABucket() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - - Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); - Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); - Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); - Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); - - mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); - - TypedAggregation aggregation = newAggregation(Art.class, // - bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // - .withGranularity(Granularities.E12) // - .andOutputCount().as("count") // - .andOutput("title").push().as("titles") // - .andOutputExpression("price * 10").sum().as("sum")); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(3)); - - // { "min" : 680.0 , "max" : 820.0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} - DBObject bound0 = result.getMappedResults().get(0); - assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer").containing("min", 680.0) - .containing("max")); + assertThat(list, hasSize(2)); + assertThat(list.get(0), isBsonObject().containing("_id", "MALE").containing("count", 3)); + assertThat(list.get(1), isBsonObject().containing("_id", "FEMALE").containing("count", 2)); - // { "min" : 820.0 , "max" : 1800.0 , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : 1673.0} - DBObject bound1 = result.getMappedResults().get(1); - assertThat(bound1, isBsonObject().containing("count", 1).containing("min", 820.0)); - assertThat((List) bound1.get("titles"), hasItems("The Great Wave off Kanagawa")); - assertThat((Double) bound1.get("sum"), is(closeTo(1673.0, 0.1))); - } + mongoTemplate.dropCollection(tempOutCollection); + } - @Test // DATAMONGO-1552 - public void facetShouldCreateFacets() { - - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - - Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); - Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); - Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); - Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); - - mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); - - BucketAutoOperation bucketPrice = bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // - .withGranularity(Granularities.E12) // - .andOutputCount().as("count") // - .andOutput("title").push().as("titles") // - .andOutputExpression("price * 10") // - .sum().as("sum"); - - TypedAggregation aggregation = newAggregation(Art.class, // - project("title", "artist", "year", "price"), // - facet(bucketPrice).as("categorizeByPrice") // - .and(bucketAuto("year", 3)).as("categorizeByYear")); - - AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); - assertThat(result.getMappedResults().size(), is(1)); - - DBObject mappedResult = result.getUniqueMappedResult(); - - // [ { "_id" : { "min" : 680.0 , "max" : 820.0} , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} - // , - // { "_id" : { "min" : 820.0 , "max" : 1800.0} , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : - // 1673.0} , - // { "_id" : { "min" : 1800.0 , "max" : 3300.0} , "count" : 2 , "titles" : [ "The Pillars of Society" , "Melancholy - // III"] , "sum" : 4799.9}] - BasicDBList categorizeByPrice = (BasicDBList) mappedResult.get("categorizeByPrice"); - assertThat(categorizeByPrice, hasSize(3)); - - // [ { "_id" : { "min" : null , "max" : 1902} , "count" : 1} , - // { "_id" : { "min" : 1902 , "max" : 1925} , "count" : 1} , - // { "_id" : { "min" : 1925 , "max" : 1926} , "count" : 2}] - BasicDBList categorizeByYear = (BasicDBList) mappedResult.get("categorizeByYear"); - assertThat(categorizeByYear, hasSize(3)); - } + @Test(expected = IllegalArgumentException.class) // DATAMONGO-1418 + public void outShouldOutBeTheLastOperation() { - private void createUsersWithReferencedPersons() { + newAggregation(match(new Criteria()), // + group("field1").count().as("totalCount"), // + out("collection1"), // + skip(100L)); + } - mongoTemplate.dropCollection(User.class); - mongoTemplate.dropCollection(Person.class); + @Test // DATAMONGO-1457 + public void sliceShouldBeAppliedCorrectly() { - User user1 = new User("u1"); - User user2 = new User("u2"); - User user3 = new User("u3"); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - mongoTemplate.save(user1); - mongoTemplate.save(user2); - mongoTemplate.save(user3); + createUserWithLikesDocuments(); - Person person1 = new Person("u1", "User 1"); - Person person2 = new Person("u2", "User 2"); + TypedAggregation agg = newAggregation(UserWithLikes.class, match(new Criteria()), + project().and("likes").slice(2)); - mongoTemplate.save(person1); - mongoTemplate.save(person2); - mongoTemplate.save(user3); - } + AggregationResults result = mongoTemplate.aggregate(agg, UserWithLikes.class); - private void assertLikeStats(LikeStats like, String id, long count) { + assertThat(result.getMappedResults(), hasSize(9)); + for (UserWithLikes user : result) { + assertThat(user.likes.size() <= 2, is(true)); + } + } - assertThat(like, is(notNullValue())); - assertThat(like.id, is(id)); - assertThat(like.count, is(count)); - } + @Test // DATAMONGO-1491 + public void filterShouldBeAppliedCorrectly() { - private void createUserWithLikesDocuments() { - mongoTemplate.insert(new UserWithLikes("u1", new Date(), "a", "b", "c")); - mongoTemplate.insert(new UserWithLikes("u2", new Date(), "a")); - mongoTemplate.insert(new UserWithLikes("u3", new Date(), "b", "c")); - mongoTemplate.insert(new UserWithLikes("u4", new Date(), "c", "d", "e")); - mongoTemplate.insert(new UserWithLikes("u5", new Date(), "a", "e", "c")); - mongoTemplate.insert(new UserWithLikes("u6", new Date())); - mongoTemplate.insert(new UserWithLikes("u7", new Date(), "a")); - mongoTemplate.insert(new UserWithLikes("u8", new Date(), "x", "e")); - mongoTemplate.insert(new UserWithLikes("u9", new Date(), "y", "d")); - } + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - private void createTagDocuments() { + Item item43 = Item.builder().itemId("43").quantity(2).price(2L).build(); + Item item2 = Item.builder().itemId("2").quantity(1).price(240L).build(); + Sales sales1 = Sales.builder().id("0") + .items(Arrays.asList( // + item43, item2)) // + .build(); - DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); + Item item23 = Item.builder().itemId("23").quantity(3).price(110L).build(); + Item item103 = Item.builder().itemId("103").quantity(4).price(5L).build(); + Item item38 = Item.builder().itemId("38").quantity(1).price(300L).build(); + Sales sales2 = Sales.builder().id("1").items(Arrays.asList( // + item23, item103, item38)).build(); - coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); - coll.insert(createDocument("Doc2", "spring", "mongodb")); - coll.insert(createDocument("Doc3", "spring")); - } + Item item4 = Item.builder().itemId("4").quantity(1).price(23L).build(); + Sales sales3 = Sales.builder().id("2").items(Arrays.asList( // + item4)).build(); - private static DBObject createDocument(String title, String... tags) { + mongoTemplate.insert(Arrays.asList(sales1, sales2, sales3), Sales.class); - DBObject doc = new BasicDBObject("title", title); - List tagList = new ArrayList(); + TypedAggregation agg = newAggregation(Sales.class, project().and("items") + .filter("item", AggregationFunctionExpressions.GTE.of(field("item.price"), 100)).as("items")); - for (String tag : tags) { - tagList.add(tag); - } + assertThat(mongoTemplate.aggregate(agg, Sales.class).getMappedResults(), + contains(Sales.builder().id("0").items(Collections.singletonList(item2)).build(), + Sales.builder().id("1").items(Arrays.asList(item23, item38)).build(), + Sales.builder().id("2").items(Collections. emptyList()).build())); + } - doc.put("tags", tagList); - return doc; - } + @Test // DATAMONGO-1538 + public void letShouldBeAppliedCorrectly() { - private static void assertTagCount(String tag, int n, TagCount tagCount) { + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); - assertThat(tagCount.getTag(), is(tag)); - assertThat(tagCount.getN(), is(n)); - } + Sales2 sales1 = Sales2.builder().id("1").price(10).tax(0.5F).applyDiscount(true).build(); + Sales2 sales2 = Sales2.builder().id("2").price(10).tax(0.25F).applyDiscount(false).build(); - static class DATAMONGO753 { - PD[] pd; + mongoTemplate.insert(Arrays.asList(sales1, sales2), Sales2.class); - DATAMONGO753 withPDs(PD... pds) { - this.pd = pds; - return this; - } - } + ExpressionVariable total = ExpressionVariable.newVariable("total") + .forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))); + ExpressionVariable discounted = ExpressionVariable.newVariable("discounted") + .forExpression(ConditionalOperators.Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)); - static class PD { - String pDch; - @org.springframework.data.mongodb.core.mapping.Field("alias") int up; + TypedAggregation agg = Aggregation.newAggregation(Sales2.class, + Aggregation.project() + .and(VariableOperators.Let.define(total, discounted).andApply( + AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) + .as("finalTotal")); - public PD(String pDch, int up) { - this.pDch = pDch; - this.up = up; - } - } + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); + assertThat(result.getMappedResults(), + contains(new BasicDBObjectBuilder().add("_id", "1").add("finalTotal", 9.450000000000001D).get(), + new BasicDBObjectBuilder().add("_id", "2").add("finalTotal", 10.25D).get())); + } - static class DATAMONGO788 { + @Test // DATAMONGO-1551 + public void graphLookupShouldBeAppliedCorrectly() { - int x; - int y; - int xField; - int yField; + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); - public DATAMONGO788() {} + Employee em1 = Employee.builder().id(1).name("Dev").build(); + Employee em2 = Employee.builder().id(2).name("Eliot").reportsTo("Dev").build(); + Employee em4 = Employee.builder().id(4).name("Andrew").reportsTo("Eliot").build(); - public DATAMONGO788(int x, int y) { - this.x = x; - this.xField = x; - this.y = y; - this.yField = y; - } - } + mongoTemplate.insert(Arrays.asList(em1, em2, em4), Employee.class); - // DATAMONGO-806 - static class User { + TypedAggregation agg = Aggregation.newAggregation(Employee.class, + match(Criteria.where("name").is("Andrew")), // + Aggregation.graphLookup("employee") // + .startWith("reportsTo") // + .connectFrom("reportsTo") // + .connectTo("name") // + .depthField("depth") // + .maxDepth(5) // + .as("reportingHierarchy")); - @Id String id; - List msgs; + AggregationResults result = mongoTemplate.aggregate(agg, DBObject.class); - public User() {} + DBObject object = result.getUniqueMappedResult(); + BasicDBList list = (BasicDBList) object.get("reportingHierarchy"); - public User(String id, PushMessage... msgs) { - this.id = id; - this.msgs = Arrays.asList(msgs); - } - } + assertThat(object, isBsonObject().containing("reportingHierarchy", List.class)); + assertThat((DBObject) list.get(0), isBsonObject().containing("name", "Dev").containing("depth", 1L)); + assertThat((DBObject) list.get(1), isBsonObject().containing("name", "Eliot").containing("depth", 0L)); + } - // DATAMONGO-806 - static class PushMessage { + @Test // DATAMONGO-1552 + public void bucketShouldCollectDocumentsIntoABucket() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + TypedAggregation aggregation = newAggregation(Art.class, // + bucket("price") // + .withBoundaries(0, 100, 200) // + .withDefaultBucket("other") // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10").sum().as("sum")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); + + // { "_id" : 0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + DBObject bound0 = result.getMappedResults().get(0); + assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer")); + assertThat((Double) bound0.get("sum"), is(closeTo(760.40, 0.1))); + + // { "_id" : 100 , "count" : 2 , "titles" : [ "The Pillars of Society" , "The Great Wave off Kanagawa"] , "sum" : + // 3672.9} + DBObject bound100 = result.getMappedResults().get(1); + assertThat(bound100, isBsonObject().containing("count", 2).containing("_id", 100)); + assertThat((List) bound100.get("titles"), + hasItems("The Pillars of Society", "The Great Wave off Kanagawa")); + assertThat((Double) bound100.get("sum"), is(closeTo(3672.9, 0.1))); + } + + @Test // DATAMONGO-1552 + public void bucketAutoShouldCollectDocumentsIntoABucket() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + TypedAggregation aggregation = newAggregation(Art.class, // + bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // + .withGranularity(Granularities.E12) // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10").sum().as("sum")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(3)); + + // { "min" : 680.0 , "max" : 820.0 , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + DBObject bound0 = result.getMappedResults().get(0); + assertThat(bound0, isBsonObject().containing("count", 1).containing("titles.[0]", "Dancer").containing("min", 680.0) + .containing("max")); + + // { "min" : 820.0 , "max" : 1800.0 , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : 1673.0} + DBObject bound1 = result.getMappedResults().get(1); + assertThat(bound1, isBsonObject().containing("count", 1).containing("min", 820.0)); + assertThat((List) bound1.get("titles"), hasItems("The Great Wave off Kanagawa")); + assertThat((Double) bound1.get("sum"), is(closeTo(1673.0, 0.1))); + } + + @Test // DATAMONGO-1552 + public void facetShouldCreateFacets() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + BucketAutoOperation bucketPrice = bucketAuto(ArithmeticOperators.Multiply.valueOf("price").multiplyBy(10), 3) // + .withGranularity(Granularities.E12) // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10") // + .sum().as("sum"); + + TypedAggregation aggregation = newAggregation(Art.class, // + project("title", "artist", "year", "price"), // + facet(bucketPrice).as("categorizeByPrice") // + .and(bucketAuto("year", 3)).as("categorizeByYear")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(1)); + + DBObject mappedResult = result.getUniqueMappedResult(); + + // [ { "_id" : { "min" : 680.0 , "max" : 820.0} , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + // , + // { "_id" : { "min" : 820.0 , "max" : 1800.0} , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : + // 1673.0} , + // { "_id" : { "min" : 1800.0 , "max" : 3300.0} , "count" : 2 , "titles" : [ "The Pillars of Society" , "Melancholy + // III"] , "sum" : 4799.9}] + BasicDBList categorizeByPrice = (BasicDBList) mappedResult.get("categorizeByPrice"); + assertThat(categorizeByPrice, hasSize(3)); + + // [ { "_id" : { "min" : null , "max" : 1902} , "count" : 1} , + // { "_id" : { "min" : 1902 , "max" : 1925} , "count" : 1} , + // { "_id" : { "min" : 1925 , "max" : 1926} , "count" : 2}] + BasicDBList categorizeByYear = (BasicDBList) mappedResult.get("categorizeByYear"); + assertThat(categorizeByYear, hasSize(3)); + } + + private void createUsersWithReferencedPersons() { + + mongoTemplate.dropCollection(User.class); + mongoTemplate.dropCollection(Person.class); + + User user1 = new User("u1"); + User user2 = new User("u2"); + User user3 = new User("u3"); + + mongoTemplate.save(user1); + mongoTemplate.save(user2); + mongoTemplate.save(user3); + + Person person1 = new Person("u1", "User 1"); + Person person2 = new Person("u2", "User 2"); + + mongoTemplate.save(person1); + mongoTemplate.save(person2); + mongoTemplate.save(user3); + } - @Id String id; - String content; - Date createDate; + private void assertLikeStats(LikeStats like, String id, long count) { + + assertThat(like, is(notNullValue())); + assertThat(like.id, is(id)); + assertThat(like.count, is(count)); + } + + private void createUserWithLikesDocuments() { + mongoTemplate.insert(new UserWithLikes("u1", new Date(), "a", "b", "c")); + mongoTemplate.insert(new UserWithLikes("u2", new Date(), "a")); + mongoTemplate.insert(new UserWithLikes("u3", new Date(), "b", "c")); + mongoTemplate.insert(new UserWithLikes("u4", new Date(), "c", "d", "e")); + mongoTemplate.insert(new UserWithLikes("u5", new Date(), "a", "e", "c")); + mongoTemplate.insert(new UserWithLikes("u6", new Date())); + mongoTemplate.insert(new UserWithLikes("u7", new Date(), "a")); + mongoTemplate.insert(new UserWithLikes("u8", new Date(), "x", "e")); + mongoTemplate.insert(new UserWithLikes("u9", new Date(), "y", "d")); + } + + private void createTagDocuments() { + + DBCollection coll = mongoTemplate.getCollection(INPUT_COLLECTION); + + coll.insert(createDocument("Doc1", "spring", "mongodb", "nosql")); + coll.insert(createDocument("Doc2", "spring", "mongodb")); + coll.insert(createDocument("Doc3", "spring")); + } + + private static DBObject createDocument(String title, String... tags) { + + DBObject doc = new BasicDBObject("title", title); + List tagList = new ArrayList(); - public PushMessage() {} + for (String tag : tags) { + tagList.add(tag); + } - public PushMessage(String id, String content, Date createDate) { - this.id = id; - this.content = content; - this.createDate = createDate; - } - } + doc.put("tags", tagList); + return doc; + } - @org.springframework.data.mongodb.core.mapping.Document - static class CarPerson { + private static void assertTagCount(String tag, int n, TagCount tagCount) { - @Id private String id; - private String firstName; - private String lastName; - private Descriptors descriptors; + assertThat(tagCount.getTag(), is(tag)); + assertThat(tagCount.getN(), is(n)); + } - public CarPerson(String firstname, String lastname, Entry... entries) { - this.firstName = firstname; - this.lastName = lastname; + static class DATAMONGO753 { + PD[] pd; - this.descriptors = new Descriptors(); + DATAMONGO753 withPDs(PD... pds) { + this.pd = pds; + return this; + } + } - this.descriptors.carDescriptor = new CarDescriptor(entries); - } - } + static class PD { + String pDch; + @org.springframework.data.mongodb.core.mapping.Field("alias") int up; - @SuppressWarnings("unused") - static class Descriptors { - private CarDescriptor carDescriptor; - } + public PD(String pDch, int up) { + this.pDch = pDch; + this.up = up; + } + } - static class CarDescriptor { + static class DATAMONGO788 { - private List entries = new ArrayList(); + int x; + int y; + int xField; + int yField; - public CarDescriptor(Entry... entries) { + public DATAMONGO788() {} - for (Entry entry : entries) { - this.entries.add(entry); - } - } + public DATAMONGO788(int x, int y) { + this.x = x; + this.xField = x; + this.y = y; + this.yField = y; + } + } - @SuppressWarnings("unused") - static class Entry { - private String make; - private String model; - private int year; + // DATAMONGO-806 + static class User { - public Entry() {} + @Id String id; + List msgs; - public Entry(String make, String model, int year) { - this.make = make; - this.model = model; - this.year = year; - } - } - } - - static class Reservation { + public User() {} - String hotelCode; - String confirmationNumber; - int timestamp; - - public Reservation() {} - - public Reservation(String hotelCode, String confirmationNumber, int timestamp) { - this.hotelCode = hotelCode; - this.confirmationNumber = confirmationNumber; - this.timestamp = timestamp; - } - } - - static class ObjectWithDate { - - Date dateValue; - - public ObjectWithDate(Date dateValue) { - this.dateValue = dateValue; - } - } + public User(String id, PushMessage... msgs) { + this.id = id; + this.msgs = Arrays.asList(msgs); + } + } - // DATAMONGO-861 - @Document(collection = "inventory") - static class InventoryItem { + // DATAMONGO-806 + static class PushMessage { - int id; - String item; - String description; - int qty; + @Id String id; + String content; + Date createDate; - public InventoryItem() {} + public PushMessage() {} - public InventoryItem(int id, String item, int qty) { + public PushMessage(String id, String content, Date createDate) { + this.id = id; + this.content = content; + this.createDate = createDate; + } + } - this.id = id; - this.item = item; - this.qty = qty; - } + @org.springframework.data.mongodb.core.mapping.Document + static class CarPerson { - public InventoryItem(int id, String item, String description, int qty) { + @Id private String id; + private String firstName; + private String lastName; + private Descriptors descriptors; - this.id = id; - this.item = item; - this.description = description; - this.qty = qty; - } - } + public CarPerson(String firstname, String lastname, Entry... entries) { + this.firstName = firstname; + this.lastName = lastname; - // DATAMONGO-1491 - @lombok.Data - @Builder - static class Sales { + this.descriptors = new Descriptors(); - @Id String id; - List items; - } + this.descriptors.carDescriptor = new CarDescriptor(entries); + } + } - // DATAMONGO-1491 - @lombok.Data - @Builder - static class Item { + @SuppressWarnings("unused") + static class Descriptors { + private CarDescriptor carDescriptor; + } - @org.springframework.data.mongodb.core.mapping.Field("item_id") // - String itemId; - Integer quantity; - Long price; - } + static class CarDescriptor { - // DATAMONGO-1538 - @lombok.Data - @Builder - static class Sales2 { + private List entries = new ArrayList(); - String id; - Integer price; - Float tax; - boolean applyDiscount; - } + public CarDescriptor(Entry... entries) { - // DATAMONGO-1551 - @lombok.Data - @Builder - static class Employee { + for (Entry entry : entries) { + this.entries.add(entry); + } + } - int id; - String name; - String reportsTo; - } + @SuppressWarnings("unused") + static class Entry { + private String make; + private String model; + private int year; - // DATAMONGO-1552 - @lombok.Data - @Builder - static class Art { + public Entry() {} - int id; - String title; - String artist; - Integer year; - double price; - } -} + public Entry(String make, String model, int year) { + this.make = make; + this.model = model; + this.year = year; + } + } + } + + static class Reservation { + + String hotelCode; + String confirmationNumber; + int timestamp; + + public Reservation() {} + + public Reservation(String hotelCode, String confirmationNumber, int timestamp) { + this.hotelCode = hotelCode; + this.confirmationNumber = confirmationNumber; + this.timestamp = timestamp; + } + } + + static class ObjectWithDate { + + Date dateValue; + + public ObjectWithDate(Date dateValue) { + this.dateValue = dateValue; + } + } + + // DATAMONGO-861 + @Document(collection = "inventory") + static class InventoryItem { + + int id; + String item; + String description; + int qty; + + public InventoryItem() {} + + public InventoryItem(int id, String item, int qty) { + + this.id = id; + this.item = item; + this.qty = qty; + } + + public InventoryItem(int id, String item, String description, int qty) { + + this.id = id; + this.item = item; + this.description = description; + this.qty = qty; + } + } + + // DATAMONGO-1491 + @lombok.Data + @Builder + static class Sales { + + @Id String id; + List items; + } + + // DATAMONGO-1491 + @lombok.Data + @Builder + static class Item { + + @org.springframework.data.mongodb.core.mapping.Field("item_id") // + String itemId; + Integer quantity; + Long price; + } + + // DATAMONGO-1538 + @lombok.Data + @Builder + static class Sales2 { + + String id; + Integer price; + Float tax; + boolean applyDiscount; + } + + // DATAMONGO-1551 + @lombok.Data + @Builder + static class Employee { + + int id; + String name; + String reportsTo; + } + + // DATAMONGO-1552 + @lombok.Data + @Builder + static class Art { + + int id; + String title; + String artist; + Integer year; + double price; + } +} \ No newline at end of file From 031b68c3c13a59238684089e530b03665270d1c3 Mon Sep 17 00:00:00 2001 From: manindr Date: Fri, 3 Mar 2017 20:39:08 +0530 Subject: [PATCH 113/118] DATAMONGO-1637 formatting as per intellijformatting.jar . added support for streaming result of aggregation operations --- .../data/mongodb/core/MongoOperations.java | 249 ++++++++-------- .../data/mongodb/core/MongoTemplate.java | 165 ++++++----- .../core/aggregation/AggregationTests.java | 272 +++++++++--------- 3 files changed, 346 insertions(+), 340 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index 31f63dada6..113cc2ae3d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -48,7 +48,7 @@ * Interface that specifies a basic set of MongoDB operations. Implemented by {@link MongoTemplate}. Not often used but * a useful option for extensibility and testability (as it can be easily mocked, stubbed, or be the target of a JDK * proxy). - * + * * @author Thomas Risberg * @author Mark Pollack * @author Oliver Gierke @@ -56,12 +56,13 @@ * @author Chuong Ngo * @author Christoph Strobl * @author Thomas Darimont + * @author maninder */ public interface MongoOperations { /** * The collection name used for the specified class by this template. - * + * * @param entityClass must not be {@literal null}. * @return */ @@ -71,7 +72,7 @@ public interface MongoOperations { * Execute the a MongoDB command expressed as a JSON string. This will call the method JSON.parse that is part of the * MongoDB driver to convert the JSON string to a DBObject. Any errors that result from executing this command will be * converted into Spring's DAO exception hierarchy. - * + * * @param jsonCommand a MongoDB command expressed as a JSON string. */ CommandResult executeCommand(String jsonCommand); @@ -79,7 +80,7 @@ public interface MongoOperations { /** * Execute a MongoDB command. Any errors that result from executing this command will be converted into Spring's DAO * exception hierarchy. - * + * * @param command a MongoDB command */ CommandResult executeCommand(DBObject command); @@ -87,11 +88,11 @@ public interface MongoOperations { /** * Execute a MongoDB command. Any errors that result from executing this command will be converted into Spring's DAO * exception hierarchy. - * + * * @param command a MongoDB command * @param options query options to use * @deprecated since 1.7. Please use {@link #executeCommand(DBObject, ReadPreference)}, as the MongoDB Java driver - * version 3 no longer supports this operation. + * version 3 no longer supports this operation. */ @Deprecated CommandResult executeCommand(DBObject command, int options); @@ -99,7 +100,7 @@ public interface MongoOperations { /** * Execute a MongoDB command. Any errors that result from executing this command will be converted into Spring's data * access exception hierarchy. - * + * * @param command a MongoDB command, must not be {@literal null}. * @param readPreference read preferences to use, can be {@literal null}. * @return @@ -109,9 +110,9 @@ public interface MongoOperations { /** * Execute a MongoDB query and iterate over the query results on a per-document basis with a DocumentCallbackHandler. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param collectionName name of the collection to retrieve the objects from * @param dch the handler that will extract results, one document at a time */ @@ -121,7 +122,7 @@ public interface MongoOperations { * Executes a {@link DbCallback} translating any exceptions as necessary. *

                                        * Allows for returning a result object, that is a domain object or a collection of domain objects. - * + * * @param return type * @param action callback object that specifies the MongoDB actions to perform on the passed in DB instance. * @return a result object returned by the action or null @@ -132,7 +133,7 @@ public interface MongoOperations { * Executes the given {@link CollectionCallback} on the entity collection of the specified class. *

                                        * Allows for returning a result object, that is a domain object or a collection of domain objects. - * + * * @param entityClass class that determines the collection to use * @param return type * @param action callback object that specifies the MongoDB action @@ -144,7 +145,7 @@ public interface MongoOperations { * Executes the given {@link CollectionCallback} on the collection of the given name. *

                                        * Allows for returning a result object, that is a domain object or a collection of domain objects. - * + * * @param return type * @param collectionName the name of the collection that specifies which DBCollection instance will be passed into * @param action callback object that specifies the MongoDB action the callback action. @@ -158,12 +159,12 @@ public interface MongoOperations { * href=http://www.mongodb.org/display/DOCS/Java+Driver+Concurrency>Java Driver Concurrency} *

                                        * Allows for returning a result object, that is a domain object or a collection of domain objects. - * + * * @param return type * @param action callback that specified the MongoDB actions to perform on the DB instance * @return a result object returned by the action or null * @deprecated since 1.7 as the MongoDB Java driver version 3 does not longer support request boundaries via - * {@link DB#requestStart()} and {@link DB#requestDone()}. + * {@link DB#requestStart()} and {@link DB#requestDone()}. */ @Deprecated T executeInSession(DbCallback action); @@ -171,9 +172,8 @@ public interface MongoOperations { /** * Executes the given {@link Query} on the entity collection of the specified {@code entityType} backed by a Mongo DB * {@link Cursor}. - *

                                        * Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. - * + * * @param element return type * @param query must not be {@literal null}. * @param entityType must not be {@literal null}. @@ -185,9 +185,8 @@ public interface MongoOperations { /** * Executes the given {@link Query} on the entity collection of the specified {@code entityType} and collection backed * by a Mongo DB {@link Cursor}. - *

                                        * Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. - * + * * @param element return type * @param query must not be {@literal null}. * @param entityType must not be {@literal null}. @@ -199,7 +198,7 @@ public interface MongoOperations { /** * Create an uncapped collection with a name based on the provided entity class. - * + * * @param entityClass class that determines the collection to create * @return the created collection */ @@ -207,7 +206,7 @@ public interface MongoOperations { /** * Create a collection with a name based on the provided entity class using the options. - * + * * @param entityClass class that determines the collection to create * @param collectionOptions options to use when creating the collection. * @return the created collection @@ -216,7 +215,7 @@ public interface MongoOperations { /** * Create an uncapped collection with the provided name. - * + * * @param collectionName name of the collection * @return the created collection */ @@ -224,7 +223,7 @@ public interface MongoOperations { /** * Create a collection with the provided name and options. - * + * * @param collectionName name of the collection * @param collectionOptions options to use when creating the collection. * @return the created collection @@ -233,7 +232,7 @@ public interface MongoOperations { /** * A set of collection names. - * + * * @return list of collection names */ Set getCollectionNames(); @@ -242,7 +241,7 @@ public interface MongoOperations { * Get a collection by name, creating it if it doesn't exist. *

                                        * Translate any exceptions as necessary. - * + * * @param collectionName name of the collection * @return an existing collection or a newly created one. */ @@ -252,7 +251,7 @@ public interface MongoOperations { * Check to see if a collection with a name indicated by the entity class exists. *

                                        * Translate any exceptions as necessary. - * + * * @param entityClass class that determines the name of the collection * @return true if a collection with the given name is found, false otherwise. */ @@ -262,7 +261,7 @@ public interface MongoOperations { * Check to see if a collection with a given name exists. *

                                        * Translate any exceptions as necessary. - * + * * @param collectionName name of the collection * @return true if a collection with the given name is found, false otherwise. */ @@ -272,7 +271,7 @@ public interface MongoOperations { * Drop the collection with the name indicated by the entity class. *

                                        * Translate any exceptions as necessary. - * + * * @param entityClass class that determines the collection to drop/delete. */ void dropCollection(Class entityClass); @@ -281,28 +280,28 @@ public interface MongoOperations { * Drop the collection with the given name. *

                                        * Translate any exceptions as necessary. - * + * * @param collectionName name of the collection to drop/delete. */ void dropCollection(String collectionName); /** * Returns the operations that can be performed on indexes - * + * * @return index operations on the named collection */ IndexOperations indexOps(String collectionName); /** * Returns the operations that can be performed on indexes - * + * * @return index operations on the named collection associated with the given entity class */ IndexOperations indexOps(Class entityClass); /** * Returns the {@link ScriptOperations} that can be performed on {@link com.mongodb.DB} level. - * + * * @return * @since 1.7 */ @@ -310,7 +309,7 @@ public interface MongoOperations { /** * Returns a new {@link BulkOperations} for the given collection. - * + * * @param mode the {@link BulkMode} to use for bulk operations, must not be {@literal null}. * @param collectionName the name of the collection to work on, must not be {@literal null} or empty. * @return {@link BulkOperations} on the named collection @@ -319,7 +318,7 @@ public interface MongoOperations { /** * Returns a new {@link BulkOperations} for the given entity type. - * + * * @param mode the {@link BulkMode} to use for bulk operations, must not be {@literal null}. * @param entityType the name of the entity class, must not be {@literal null}. * @return {@link BulkOperations} on the named collection associated of the given entity class. @@ -328,7 +327,7 @@ public interface MongoOperations { /** * Returns a new {@link BulkOperations} for the given entity type and collection name. - * + * * @param mode the {@link BulkMode} to use for bulk operations, must not be {@literal null}. * @param entityClass the name of the entity class, must not be {@literal null}. * @param collectionName the name of the collection to work on, must not be {@literal null} or empty. @@ -344,7 +343,7 @@ public interface MongoOperations { *

                                        * If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way * to map objects since the test for class type is done in the client and not on the server. - * + * * @param entityClass the parameterized type of the returned list * @return the converted collection */ @@ -358,7 +357,7 @@ public interface MongoOperations { *

                                        * If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way * to map objects since the test for class type is done in the client and not on the server. - * + * * @param entityClass the parameterized type of the returned list. * @param collectionName name of the collection to retrieve the objects from * @return the converted collection @@ -368,12 +367,12 @@ public interface MongoOperations { /** * Execute a group operation over the entire collection. The group operation entity class should match the 'shape' of * the returned object that takes int account the initial document structure as well as any finalize functions. - * + * * @param criteria The criteria that restricts the row that are considered for grouping. If not specified all rows are - * considered. + * considered. * @param inputCollectionName the collection where the group operation will read from * @param groupBy the conditions under which the group operation will be performed, e.g. keys, initial document, - * reduce function. + * reduce function. * @param entityClass The parameterized type of the returned list * @return The results of the group operation */ @@ -383,12 +382,12 @@ public interface MongoOperations { * Execute a group operation restricting the rows to those which match the provided Criteria. The group operation * entity class should match the 'shape' of the returned object that takes int account the initial document structure * as well as any finalize functions. - * + * * @param criteria The criteria that restricts the row that are considered for grouping. If not specified all rows are - * considered. + * considered. * @param inputCollectionName the collection where the group operation will read from * @param groupBy the conditions under which the group operation will be performed, e.g. keys, initial document, - * reduce function. + * reduce function. * @param entityClass The parameterized type of the returned list * @return The results of the group operation */ @@ -398,9 +397,9 @@ public interface MongoOperations { /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. The name of the * inputCollection is derived from the inputType of the aggregation. - * + * * @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be - * {@literal null}. + * {@literal null}. * @param collectionName The name of the input collection to use for the aggreation. * @param outputType The parameterized type of the returned list, must not be {@literal null}. * @return The results of the aggregation operation. @@ -411,9 +410,9 @@ public interface MongoOperations { /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. The name of the * inputCollection is derived from the inputType of the aggregation. - * + * * @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be - * {@literal null}. + * {@literal null}. * @param outputType The parameterized type of the returned list, must not be {@literal null}. * @return The results of the aggregation operation. * @since 1.3 @@ -426,7 +425,7 @@ public interface MongoOperations { * inputCollection is derived from the inputType of the aggregation. * * @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be - * {@literal null}. + * {@literal null}. * @param outputType The parameterized type of the returned list, must not be {@literal null}. * @return The results of the aggregation operation. * @since 1.11.0 @@ -439,11 +438,11 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. - * + * * @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be - * {@literal null}. + * {@literal null}. * @param inputType the inputType where the aggregation operation will read from, must not be {@literal null} or - * empty. + * empty. * @param outputType The parameterized type of the returned list, must not be {@literal null}. * @return The results of the aggregation operation. * @since 1.3 @@ -454,11 +453,11 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. - * + * * @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be - * {@literal null}. + * {@literal null}. * @param collectionName the collection where the aggregation operation will read from, must not be {@literal null} or - * empty. + * empty. * @param outputType The parameterized type of the returned list, must not be {@literal null}. * @return The results of the aggregation operation. * @since 1.3 @@ -467,7 +466,7 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String /** * Execute a map-reduce operation. The map-reduce operation will be formed with an output type of INLINE - * + * * @param inputCollectionName the collection where the map-reduce will read from * @param mapFunction The JavaScript map function * @param reduceFunction The JavaScript reduce function @@ -476,11 +475,11 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String * @return The results of the map reduce operation */ MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - Class entityClass); + Class entityClass); /** * Execute a map-reduce operation that takes additional map-reduce options. - * + * * @param inputCollectionName the collection where the map-reduce will read from * @param mapFunction The JavaScript map function * @param reduceFunction The JavaScript reduce function @@ -489,12 +488,12 @@ MapReduceResults mapReduce(String inputCollectionName, String mapFunction * @return The results of the map reduce operation */ MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - MapReduceOptions mapReduceOptions, Class entityClass); + MapReduceOptions mapReduceOptions, Class entityClass); /** * Execute a map-reduce operation that takes a query. The map-reduce operation will be formed with an output type of * INLINE - * + * * @param query The query to use to select the data for the map phase * @param inputCollectionName the collection where the map-reduce will read from * @param mapFunction The JavaScript map function @@ -504,11 +503,11 @@ MapReduceResults mapReduce(String inputCollectionName, String mapFunction * @return The results of the map reduce operation */ MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, String reduceFunction, - Class entityClass); + Class entityClass); /** * Execute a map-reduce operation that takes a query and additional map-reduce options - * + * * @param query The query to use to select the data for the map phase * @param inputCollectionName the collection where the map-reduce will read from * @param mapFunction The JavaScript map function @@ -518,14 +517,14 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * @return The results of the map reduce operation */ MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, String reduceFunction, - MapReduceOptions mapReduceOptions, Class entityClass); + MapReduceOptions mapReduceOptions, Class entityClass); /** * Returns {@link GeoResults} for all entities matching the given {@link NearQuery}. Will consider entity mapping * information to determine the collection the query is ran against. Note, that MongoDB limits the number of results * by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect a particular number of * results. - * + * * @param near must not be {@literal null}. * @param entityClass must not be {@literal null}. * @return @@ -536,11 +535,11 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * Returns {@link GeoResults} for all entities matching the given {@link NearQuery}. Note, that MongoDB limits the * number of results by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect a * particular number of results. - * + * * @param near must not be {@literal null}. * @param entityClass must not be {@literal null}. * @param collectionName the collection to trigger the query against. If no collection name is given the entity class - * will be inspected. + * will be inspected. * @return */ GeoResults geoNear(NearQuery near, Class entityClass, String collectionName); @@ -554,9 +553,9 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @return the converted object */ @@ -571,9 +570,9 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @param collectionName name of the collection to retrieve the objects from * @return the converted object @@ -582,7 +581,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Determine result of given {@link Query} contains at least one element. - * + * * @param query the {@link Query} class that specifies the criteria used to find a record. * @param collectionName name of the collection to check for objects. * @return @@ -591,7 +590,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Determine result of given {@link Query} contains at least one element. - * + * * @param query the {@link Query} class that specifies the criteria used to find a record. * @param entityClass the parameterized type. * @return @@ -600,7 +599,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Determine result of given {@link Query} contains at least one element. - * + * * @param query the {@link Query} class that specifies the criteria used to find a record. * @param entityClass the parameterized type. * @param collectionName name of the collection to check for objects. @@ -616,9 +615,9 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @return the List of converted objects */ @@ -632,9 +631,9 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @param collectionName name of the collection to retrieve the objects from * @return the List of converted objects @@ -644,7 +643,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Returns a document with the given id mapped onto the given class. The collection the query is ran against will be * derived from the given target class as well. - * + * * @param * @param id the id of the document to return. * @param entityClass the type the document shall be converted into. @@ -654,7 +653,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Returns the document with the given id from the given collection mapped onto the given target class. - * + * * @param id the id of the document to return * @param entityClass the type to convert the document to * @param collectionName the collection to query for the document @@ -666,9 +665,9 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. - * + * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional - * fields specification. + * fields specification. * @param update the {@link Update} to apply on matching documents. * @param entityClass the parameterized type. * @return @@ -678,9 +677,9 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. - * + * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional - * fields specification. + * fields specification. * @param update the {@link Update} to apply on matching documents. * @param entityClass the parameterized type. * @param collectionName the collection to query. @@ -692,9 +691,9 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. - * + * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional - * fields specification. + * fields specification. * @param update the {@link Update} to apply on matching documents. * @param options the {@link FindAndModifyOptions} holding additional information. * @param entityClass the parameterized type. @@ -706,9 +705,9 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. - * + * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional - * fields specification. + * fields specification. * @param update the {@link Update} to apply on matching documents. * @param options the {@link FindAndModifyOptions} holding additional information. * @param entityClass the parameterized type. @@ -716,7 +715,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * @return */ T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass, - String collectionName); + String collectionName); /** * Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the @@ -727,9 +726,9 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @return the converted object */ @@ -744,9 +743,9 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @param collectionName name of the collection to retrieve the objects from * @return the converted object @@ -755,7 +754,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Returns the number of documents for the given {@link Query} by querying the collection of the given entity class. - * + * * @param query * @param entityClass must not be {@literal null}. * @return @@ -766,7 +765,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * Returns the number of documents for the given {@link Query} querying the given collection. The given {@link Query} * must solely consist of document field references as we lack type information to map potential property references * onto document fields. TO make sure the query gets mapped, use {@link #count(Query, Class, String)}. - * + * * @param query * @param collectionName must not be {@literal null} or empty. * @return @@ -777,7 +776,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Returns the number of documents for the given {@link Query} by querying the given collection using the given entity * class to map the given {@link Query}. - * + * * @param query * @param entityClass must not be {@literal null}. * @param collectionName must not be {@literal null} or empty. @@ -798,7 +797,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl *

                                        *

                                        * Insert is used to initially store the object into the database. To update an existing object use the save method. - * + * * @param objectToSave the object to store in the collection. */ void insert(Object objectToSave); @@ -810,7 +809,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * configured otherwise, an instance of MappingMongoConverter will be used. *

                                        * Insert is used to initially store the object into the database. To update an existing object use the save method. - * + * * @param objectToSave the object to store in the collection * @param collectionName name of the collection to store the object in */ @@ -818,7 +817,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Insert a Collection of objects into a collection in a single batch write to the database. - * + * * @param batchToSave the list of objects to save. * @param entityClass class that determines the collection to use */ @@ -826,7 +825,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Insert a list of objects into the specified collection in a single batch write to the database. - * + * * @param batchToSave the list of objects to save. * @param collectionName name of the collection to store the object in */ @@ -835,7 +834,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Insert a mixed Collection of objects into a database collection determining the collection name to use based on the * class. - * + * * @param collectionToSave the list of objects to save. */ void insertAll(Collection objectsToSave); @@ -852,7 +851,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See * * Spring's Type Conversion" for more details. - * + * * @param objectToSave the object to store in the collection */ void save(Object objectToSave); @@ -869,7 +868,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * property type will be handled by Spring's BeanWrapper class that leverages Type Cobnversion API. See Spring's * Type Conversion" for more details. - * + * * @param objectToSave the object to store in the collection * @param collectionName name of the collection to store the object in */ @@ -878,7 +877,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by * combining the query document and the update document. - * + * * @param query the query document that specifies the criteria used to select a record to be upserted * @param update the update document that contains the updated object or $ operators to manipulate the existing object * @param entityClass class that determines the collection to use @@ -889,10 +888,10 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by * combining the query document and the update document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param collectionName name of the collection to update the object in * @return the WriteResult which lets you access the results of the previous write. */ @@ -901,7 +900,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by * combining the query document and the update document. - * + * * @param query the query document that specifies the criteria used to select a record to be upserted * @param update the update document that contains the updated object or $ operators to manipulate the existing object * @param entityClass class of the pojo to be operated on @@ -913,10 +912,10 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates the first object that is found in the collection of the entity class that matches the query document with * the provided update document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param entityClass class that determines the collection to use * @return the WriteResult which lets you access the results of the previous write. */ @@ -925,10 +924,10 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates the first object that is found in the specified collection that matches the query document criteria with * the provided updated document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param collectionName name of the collection to update the object in * @return the WriteResult which lets you access the results of the previous write. */ @@ -937,10 +936,10 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates the first object that is found in the specified collection that matches the query document criteria with * the provided updated document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param entityClass class of the pojo to be operated on * @param collectionName name of the collection to update the object in * @return the WriteResult which lets you access the results of the previous write. @@ -950,10 +949,10 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates all objects that are found in the collection for the entity class that matches the query document criteria * with the provided updated document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param entityClass class that determines the collection to use * @return the WriteResult which lets you access the results of the previous write. */ @@ -962,10 +961,10 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates all objects that are found in the specified collection that matches the query document criteria with the * provided updated document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param collectionName name of the collection to update the object in * @return the WriteResult which lets you access the results of the previous write. */ @@ -974,10 +973,10 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates all objects that are found in the collection for the entity class that matches the query document criteria * with the provided updated document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param entityClass class of the pojo to be operated on * @param collectionName name of the collection to update the object in * @return the WriteResult which lets you access the results of the previous write. @@ -986,14 +985,14 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Remove the given object from the collection by id. - * + * * @param object */ WriteResult remove(Object object); /** * Removes the given object from the given collection. - * + * * @param object * @param collection must not be {@literal null} or empty. */ @@ -1002,7 +1001,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Remove all documents that match the provided query document criteria from the the collection used to store the * entityClass. The Class parameter is also used to help convert the Id of the object if it is present in the query. - * + * * @param query * @param entityClass */ @@ -1011,7 +1010,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Remove all documents that match the provided query document criteria from the the collection used to store the * entityClass. The Class parameter is also used to help convert the Id of the object if it is present in the query. - * + * * @param query * @param entityClass * @param collectionName @@ -1021,7 +1020,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Remove all documents from the specified collection that match the provided query document criteria. There is no * conversion/mapping done for any criteria using the id field. - * + * * @param query the query document that specifies the criteria used to remove a record * @param collectionName name of the collection where the objects will removed */ @@ -1031,7 +1030,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Returns and removes all documents form the specified collection that match the provided query. - * + * * @param query * @param collectionName * @return @@ -1041,7 +1040,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Returns and removes all documents matching the given query form the collection used to store the entityClass. - * + * * @param query * @param entityClass * @return @@ -1053,7 +1052,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * Returns and removes all documents that match the provided query document criteria from the the collection used to * store the entityClass. The Class parameter is also used to help convert the Id of the object if it is present in * the query. - * + * * @param query * @param entityClass * @param collectionName @@ -1064,7 +1063,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Returns the underlying {@link MongoConverter}. - * + * * @return */ MongoConverter getConverter(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index a8ca776b23..2ebbf678dc 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -93,6 +93,7 @@ * @author Niko Schmuck * @author Mark Paluch * @author Laszlo Csontos + * @author maninder */ @SuppressWarnings("deprecation") public class MongoTemplate implements MongoOperations, ApplicationContextAware { @@ -382,11 +383,11 @@ public void executeQuery(Query query, String collectionName, DocumentCallbackHan * {@link DocumentCallbackHandler} using the provided CursorPreparer. * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification, must not be {@literal null}. + * specification, must not be {@literal null}. * @param collectionName name of the collection to retrieve the objects from * @param dch the handler that will extract results, one document at a time * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply - * limits, skips and so on). + * limits, skips and so on). */ protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, CursorPreparer preparer) { @@ -1294,7 +1295,7 @@ public WriteResult doInCollection(DBCollection collection) throws MongoException if (LOGGER.isDebugEnabled()) { LOGGER.debug("Remove using query: {} in collection: {}.", - new Object[] { serializeToJsonSafely(dboq), collectionName }); + new Object[]{serializeToJsonSafely(dboq), collectionName}); } WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) @@ -1436,14 +1437,14 @@ public AggregationResults aggregate(TypedAggregation aggregation, Clas return aggregate(aggregation, determineCollectionName(aggregation.getInputType()), outputType); } - @Override - public CloseableIterator aggregateStream(TypedAggregation aggregation, Class outputType) { - return aggregateStream(aggregation, determineCollectionName(aggregation.getInputType()), outputType); - } + @Override + public CloseableIterator aggregateStream(TypedAggregation aggregation, Class outputType) { + return aggregateStream(aggregation, determineCollectionName(aggregation.getInputType()), outputType); + } - @Override - public AggregationResults aggregate(TypedAggregation aggregation, String inputCollectionName, - Class outputType) { + @Override + public AggregationResults aggregate(TypedAggregation aggregation, String inputCollectionName, + Class outputType) { Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); @@ -1452,52 +1453,52 @@ public AggregationResults aggregate(TypedAggregation aggregation, Stri return aggregate(aggregation, inputCollectionName, outputType, context); } - @Override - public CloseableIterator aggregateStream(TypedAggregation aggregation, String inputCollectionName, - Class outputType) { + @Override + public CloseableIterator aggregateStream(TypedAggregation aggregation, String inputCollectionName, + Class outputType) { - Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); + Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), - mappingContext, queryMapper); - return aggregateStream(aggregation, inputCollectionName, outputType, context); - } + AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(), + mappingContext, queryMapper); + return aggregateStream(aggregation, inputCollectionName, outputType, context); + } - @Override - public AggregationResults aggregate(Aggregation aggregation, Class inputType, Class outputType) { + @Override + public AggregationResults aggregate(Aggregation aggregation, Class inputType, Class outputType) { return aggregate(aggregation, determineCollectionName(inputType), outputType, new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); } - @Override - public CloseableIterator aggregateStream(Aggregation aggregation, Class inputType, Class outputType) { + @Override + public CloseableIterator aggregateStream(Aggregation aggregation, Class inputType, Class outputType) { - return aggregateStream(aggregation, determineCollectionName(inputType), outputType, - new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); - } + return aggregateStream(aggregation, determineCollectionName(inputType), outputType, + new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper)); + } - @Override - public AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType) { - return aggregate(aggregation, collectionName, outputType, null); - } + @Override + public AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType) { + return aggregate(aggregation, collectionName, outputType, null); + } - @Override - public CloseableIterator aggregateStream(Aggregation aggregation, String collectionName, Class outputType) { - return aggregateStream(aggregation, collectionName, outputType, null); - } + @Override + public CloseableIterator aggregateStream(Aggregation aggregation, String collectionName, Class outputType) { + return aggregateStream(aggregation, collectionName, outputType, null); + } - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String) - */ - @Override - public List findAllAndRemove(Query query, String collectionName) { - return findAndRemove(query, null, collectionName); - } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String) + */ + @Override + public List findAllAndRemove(Query query, String collectionName) { + return findAndRemove(query, null, collectionName); + } /* * (non-Javadoc) @@ -1559,59 +1560,59 @@ protected AggregationResults aggregate(Aggregation aggregation, String co commandResult); } - protected CloseableIterator aggregateStream(final Aggregation aggregation, final String collectionName, final Class outputType, - AggregationOperationContext context) { + protected CloseableIterator aggregateStream(final Aggregation aggregation, final String collectionName, final Class outputType, + AggregationOperationContext context) { - Assert.hasText(collectionName, "Collection name must not be null or empty!"); - Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); - Assert.notNull(outputType, "Output type must not be null!"); + Assert.hasText(collectionName, "Collection name must not be null or empty!"); + Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); + Assert.notNull(outputType, "Output type must not be null!"); - AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; + AggregationOperationContext rootContext = context == null ? Aggregation.DEFAULT_CONTEXT : context; - final DBObject command = aggregation.toDbObject(collectionName, rootContext); + final DBObject command = aggregation.toDbObject(collectionName, rootContext); - Assert.isNull(command.get(CURSOR), "Custom options not allowed while streaming"); - Assert.isNull(command.get(EXPLAIN), "Explain option can't be used while streaming"); + Assert.isNull(command.get(CURSOR), "Custom options not allowed while streaming"); + Assert.isNull(command.get(EXPLAIN), "Explain option can't be used while streaming"); - return execute(collectionName, new CollectionCallback>() { + return execute(collectionName, new CollectionCallback>() { - @Override - public CloseableIterator doInCollection(DBCollection collection) throws MongoException, DataAccessException { + @Override + public CloseableIterator doInCollection(DBCollection collection) throws MongoException, DataAccessException { - List pipeline = (List) command.get("pipeline"); - Cursor cursor = collection.aggregate(pipeline, getNativeAggregationOptionsFromCommand(command)); + List pipeline = (List) command.get("pipeline"); + Cursor cursor = collection.aggregate(pipeline, getNativeAggregationOptionsFromCommand(command)); - ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, outputType, collectionName); + ReadDbObjectCallback readCallback = new ReadDbObjectCallback(mongoConverter, outputType, collectionName); - return new CloseableIterableCursorAdapter(cursor, exceptionTranslator, readCallback); - } + return new CloseableIterableCursorAdapter(cursor, exceptionTranslator, readCallback); + } - private AggregationOptions getNativeAggregationOptionsFromCommand(DBObject command) { - AggregationOptions.Builder builder = AggregationOptions.builder(); - Object allowDiskUse = command.get(ALLOW_DISK_USE); - if(allowDiskUse !=null && String.valueOf(allowDiskUse).equals("true")){ - builder.allowDiskUse(true); - } - return builder.build(); - } - }); + private AggregationOptions getNativeAggregationOptionsFromCommand(DBObject command) { + AggregationOptions.Builder builder = AggregationOptions.builder(); + Object allowDiskUse = command.get(ALLOW_DISK_USE); + if (allowDiskUse != null && String.valueOf(allowDiskUse).equals("true")) { + builder.allowDiskUse(true); + } + return builder.build(); + } + }); /* - Query query = new BasicQuery(command); + Query query = new BasicQuery(command); return stream(query, outputType); */ - } + } - /** - * Returns the potentially mapped results of the given {@commandResult} contained some. - * - * @param outputType - * @param commandResult - * @return - */ - private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, - String collectionName) { + /** + * Returns the potentially mapped results of the given {@commandResult} contained some. + * + * @param outputType + * @param commandResult + * @return + */ + private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, + String collectionName) { @SuppressWarnings("unchecked") Iterable resultSet = (Iterable) commandResult.get("result"); @@ -1785,7 +1786,7 @@ protected List doFind(String collectionName, DBObject query, DBObject fie * @param fields the document that specifies the fields to be returned. * @param entityClass the parameterized type of the returned list. * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply - * limits, skips and so on). + * limits, skips and so on). * @return the {@link List} of converted objects. */ protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, @@ -1993,7 +1994,6 @@ private List executeFindMultiInternal(CollectionCallback collec } return result; - } finally { if (cursor != null) { @@ -2023,13 +2023,11 @@ private void executeQueryInternal(CollectionCallback collectionCallbac DBObject dbobject = cursor.next(); callbackHandler.processDocument(dbobject); } - } finally { if (cursor != null) { cursor.close(); } } - } catch (RuntimeException e) { throw potentiallyConvertRuntimeException(e, exceptionTranslator); } @@ -2447,7 +2445,6 @@ public DBCursor prepare(DBCursor cursor) { } } } - } catch (RuntimeException e) { throw potentiallyConvertRuntimeException(e, exceptionTranslator); } @@ -2495,8 +2492,8 @@ public GeoResult doWith(DBObject object) { /** * A {@link CloseableIterator} that is backed by a MongoDB {@link Cursor}. * - * @since 1.7 * @author Thomas Darimont + * @since 1.7 */ static class CloseableIterableCursorAdapter implements CloseableIterator { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index b630cb4c98..ace4949ed2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -77,6 +77,7 @@ * @author Christoph Strobl * @author Mark Paluch * @author Nikolay Bogdanov + * @author maninder */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") @@ -224,39 +225,39 @@ public void shouldAggregate() { assertTagCount("nosql", 1, tagCount.get(2)); } - @Test // DATAMONGO-586 - public void shouldAggregateAndStream() { + @Test // DATAMONGO-1637 + public void shouldAggregateAndStream() { - createTagDocuments(); + createTagDocuments(); - Aggregation agg = newAggregation( // - project("tags"), // - unwind("tags"), // - group("tags") // - .count().as("n"), // - project("n") // - .and("tag").previousOperation(), // - sort(DESC, "n") // - ); + Aggregation agg = newAggregation( // + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("n"), // + project("n") // + .and("tag").previousOperation(), // + sort(DESC, "n") // + ); - CloseableIterator iterator = mongoTemplate.aggregateStream(agg, INPUT_COLLECTION, TagCount.class); + CloseableIterator iterator = mongoTemplate.aggregateStream(agg, INPUT_COLLECTION, TagCount.class); - assertThat(iterator, is(notNullValue())); - List tagCount = new ArrayList(); - while (iterator.hasNext()) { - tagCount.add(iterator.next()); - } + assertThat(iterator, is(notNullValue())); + List tagCount = new ArrayList(); + while (iterator.hasNext()) { + tagCount.add(iterator.next()); + } - assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(3)); + assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(3)); - assertTagCount("spring", 3, tagCount.get(0)); - assertTagCount("mongodb", 2, tagCount.get(1)); - assertTagCount("nosql", 1, tagCount.get(2)); - } + assertTagCount("spring", 3, tagCount.get(0)); + assertTagCount("mongodb", 2, tagCount.get(1)); + assertTagCount("nosql", 1, tagCount.get(2)); + } - @Test // DATAMONGO-586 - public void shouldAggregateEmptyCollection() { + @Test // DATAMONGO-586 + public void shouldAggregateEmptyCollection() { Aggregation aggregation = newAggregation(// project("tags"), // @@ -278,34 +279,34 @@ public void shouldAggregateEmptyCollection() { assertThat(tagCount.size(), is(0)); } - @Test // DATAMONGO-586 - public void shouldAggregateEmptyCollectionAndStream() { + @Test // DATAMONGO-1637 + public void shouldAggregateEmptyCollectionAndStream() { - Aggregation aggregation = newAggregation(// - project("tags"), // - unwind("tags"), // - group("tags") // - .count().as("n"), // - project("n") // - .and("tag").previousOperation(), // - sort(DESC, "n") // - ); + Aggregation aggregation = newAggregation(// + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("n"), // + project("n") // + .and("tag").previousOperation(), // + sort(DESC, "n") // + ); - CloseableIterator results = mongoTemplate.aggregateStream(aggregation, INPUT_COLLECTION, TagCount.class); + CloseableIterator results = mongoTemplate.aggregateStream(aggregation, INPUT_COLLECTION, TagCount.class); - assertThat(results, is(notNullValue())); + assertThat(results, is(notNullValue())); - List tagCount = new ArrayList(); - while (results.hasNext()){ - tagCount.add(results.next()); - } + List tagCount = new ArrayList(); + while (results.hasNext()) { + tagCount.add(results.next()); + } - // assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(0)); - } + // assertThat(tagCount, is(notNullValue())); + assertThat(tagCount.size(), is(0)); + } - @Test // DATAMONGO-1391 - public void shouldUnwindWithIndex() { + @Test // DATAMONGO-1391 + public void shouldUnwindWithIndex() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO)); @@ -386,36 +387,36 @@ public void shouldDetectResultMismatch() { } - @Test // DATAMONGO-586 - public void shouldDetectResultMismatchWhileStreaming() { + @Test // DATAMONGO-1637 + public void shouldDetectResultMismatchWhileStreaming() { - createTagDocuments(); + createTagDocuments(); - Aggregation aggregation = newAggregation( // - project("tags"), // - unwind("tags"), // - group("tags") // - .count().as("count"), // count field not present - limit(2) // - ); + Aggregation aggregation = newAggregation( // + project("tags"), // + unwind("tags"), // + group("tags") // + .count().as("count"), // count field not present + limit(2) // + ); - CloseableIterator results = mongoTemplate.aggregateStream(aggregation, INPUT_COLLECTION, TagCount.class); + CloseableIterator results = mongoTemplate.aggregateStream(aggregation, INPUT_COLLECTION, TagCount.class); - assertThat(results, is(notNullValue())); + assertThat(results, is(notNullValue())); - List tagCount = new ArrayList(); - while (results.hasNext()){ - tagCount.add(results.next()); - } + List tagCount = new ArrayList(); + while (results.hasNext()) { + tagCount.add(results.next()); + } // assertThat(tagCount, is(notNullValue())); - assertThat(tagCount.size(), is(2)); - assertTagCount(null, 0, tagCount.get(0)); - assertTagCount(null, 0, tagCount.get(1)); - } + assertThat(tagCount.size(), is(2)); + assertTagCount(null, 0, tagCount.get(0)); + assertTagCount(null, 0, tagCount.get(1)); + } - @Test // DATAMONGO-586 - public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { + @Test // DATAMONGO-586 + public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { /* //complex mongodb aggregation framework example from https://docs.mongodb.org/manual/tutorial/aggregation-examples/#largest-and-smallest-cities-by-state db.zipInfo.aggregate( @@ -570,7 +571,7 @@ public void findStatesWithPopulationOver10MillionAggregationExample() { /** * @see MongoDB Aggregation - * Framework: $cond + * Framework: $cond */ @Test // DATAMONGO-861 public void aggregationUsingConditionalProjectionToCalculateDiscount() { @@ -623,7 +624,7 @@ public void aggregationUsingConditionalProjectionToCalculateDiscount() { /** * @see MongoDB Aggregation - * Framework: $ifNull + * Framework: $ifNull */ @Test // DATAMONGO-861 public void aggregationUsingIfNullToProjectSaneDefaults() { @@ -803,8 +804,8 @@ public void shouldAllowGroupingUsingConditionalExpressions() { /** * @see Return - * the Five Most Common “Likes” + * "https://docs.mongodb.com/manual/tutorial/aggregation-with-user-preference-data/#return-the-five-most-common-likes">Return + * the Five Most Common “Likes” */ @Test // DATAMONGO-586 public void returnFiveMostCommonLikesAggregationFrameworkExample() { @@ -977,8 +978,8 @@ public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressio /** * @see Spring - * Data MongoDB - Aggregation Framework - invalid reference in group Operation + * "http://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group-operati">Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation */ @Test // DATAMONGO-753 public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { @@ -1006,8 +1007,8 @@ public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { /** * @see Spring - * Data MongoDB - Aggregation Framework - invalid reference in group Operation + * "http://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group-operati">Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation */ @Test // DATAMONGO-753 public void aliasesNestedFieldInProjectionImmediately() { @@ -1270,38 +1271,38 @@ public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOp assertLikeStats(result.getMappedResults().get(4), "e", 3); } - @Test // DATAMONGO-960 - public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabledWhileStreaming() { + @Test // DATAMONGO-1637 + public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabledWhileStreaming() { - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - createUserWithLikesDocuments(); + createUserWithLikesDocuments(); - TypedAggregation agg = createUsersWithCommonLikesAggregation() // - .withOptions(newAggregationOptions().allowDiskUse(true).build()); + TypedAggregation agg = createUsersWithCommonLikesAggregation() // + .withOptions(newAggregationOptions().allowDiskUse(true).build()); - assertThat(agg, is(notNullValue())); - assertThat(agg.toString(), is(notNullValue())); + assertThat(agg, is(notNullValue())); + assertThat(agg.toString(), is(notNullValue())); - CloseableIterator iterator = mongoTemplate.aggregateStream(agg, LikeStats.class); - List result=new ArrayList(); - while (iterator.hasNext()){ - result.add(iterator.next()); - } - assertThat(result, is(notNullValue())); - assertThat(result, is(notNullValue())); - assertThat(result.size(), is(5)); + CloseableIterator iterator = mongoTemplate.aggregateStream(agg, LikeStats.class); + List result = new ArrayList(); + while (iterator.hasNext()) { + result.add(iterator.next()); + } + assertThat(result, is(notNullValue())); + assertThat(result, is(notNullValue())); + assertThat(result.size(), is(5)); - assertLikeStats(result.get(0), "a", 4); - assertLikeStats(result.get(1), "b", 2); - assertLikeStats(result.get(2), "c", 4); - assertLikeStats(result.get(3), "d", 2); - assertLikeStats(result.get(4), "e", 3); - } + assertLikeStats(result.get(0), "a", 4); + assertLikeStats(result.get(1), "b", 2); + assertLikeStats(result.get(2), "c", 4); + assertLikeStats(result.get(3), "d", 2); + assertLikeStats(result.get(4), "e", 3); + } - @Test // DATAMONGO-960 - public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithExplainOptionEnabled() { + @Test // DATAMONGO-960 + public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithExplainOptionEnabled() { assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); @@ -1545,36 +1546,35 @@ public void shouldCreateOutputCollection() { AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); assertThat(results.getMappedResults(), is(empty())); - List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); - - assertThat(list, hasSize(2)); - assertThat(list.get(0), isBsonObject().containing("_id", "MALE").containing("count", 3)); - assertThat(list.get(1), isBsonObject().containing("_id", "FEMALE").containing("count", 2)); + List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); - mongoTemplate.dropCollection(tempOutCollection); - } + assertThat(list, hasSize(2)); + assertThat(list.get(0), isBsonObject().containing("_id", "MALE").containing("count", 3)); + assertThat(list.get(1), isBsonObject().containing("_id", "FEMALE").containing("count", 2)); - @Test // DATAMONGO-1418 - public void shouldCreateOutputCollectionWhileStreaming() { + mongoTemplate.dropCollection(tempOutCollection); + } - assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + @Test // DATAMONGO-1637 + public void shouldCreateOutputCollectionWhileStreaming() { - mongoTemplate.save(new Person("Anna", "Ivanova", 21, Person.Sex.FEMALE)); - mongoTemplate.save(new Person("Pavel", "Sidorov", 36, Person.Sex.MALE)); - mongoTemplate.save(new Person("Anastasia", "Volochkova", 29, Person.Sex.FEMALE)); - mongoTemplate.save(new Person("Igor", "Stepanov", 31, Person.Sex.MALE)); - mongoTemplate.save(new Person("Leoniv", "Yakubov", 55, Person.Sex.MALE)); + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); - String tempOutCollection = "personQueryTemp"; - TypedAggregation agg = newAggregation(Person.class, // - group("sex").count().as("count"), // - sort(DESC, "count"), // - out(tempOutCollection)); + mongoTemplate.save(new Person("Anna", "Ivanova", 21, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Pavel", "Sidorov", 36, Person.Sex.MALE)); + mongoTemplate.save(new Person("Anastasia", "Volochkova", 29, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Igor", "Stepanov", 31, Person.Sex.MALE)); + mongoTemplate.save(new Person("Leoniv", "Yakubov", 55, Person.Sex.MALE)); - CloseableIterator iterator = mongoTemplate.aggregateStream(agg, DBObject.class); + String tempOutCollection = "personQueryTemp"; + TypedAggregation agg = newAggregation(Person.class, // + group("sex").count().as("count"), // + sort(DESC, "count"), // + out(tempOutCollection)); + CloseableIterator iterator = mongoTemplate.aggregateStream(agg, DBObject.class); - List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); + List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); assertThat(list, hasSize(2)); assertThat(list.get(0), isBsonObject().containing("_id", "MALE").containing("count", 3)); @@ -1640,7 +1640,7 @@ public void filterShouldBeAppliedCorrectly() { assertThat(mongoTemplate.aggregate(agg, Sales.class).getMappedResults(), contains(Sales.builder().id("0").items(Collections.singletonList(item2)).build(), Sales.builder().id("1").items(Arrays.asList(item23, item38)).build(), - Sales.builder().id("2").items(Collections. emptyList()).build())); + Sales.builder().id("2").items(Collections.emptyList()).build())); } @Test // DATAMONGO-1538 @@ -1886,6 +1886,7 @@ private static void assertTagCount(String tag, int n, TagCount tagCount) { } static class DATAMONGO753 { + PD[] pd; DATAMONGO753 withPDs(PD... pds) { @@ -1895,6 +1896,7 @@ DATAMONGO753 withPDs(PD... pds) { } static class PD { + String pDch; @org.springframework.data.mongodb.core.mapping.Field("alias") int up; @@ -1911,7 +1913,8 @@ static class DATAMONGO788 { int xField; int yField; - public DATAMONGO788() {} + public DATAMONGO788() { + } public DATAMONGO788(int x, int y) { this.x = x; @@ -1927,7 +1930,8 @@ static class User { @Id String id; List msgs; - public User() {} + public User() { + } public User(String id, PushMessage... msgs) { this.id = id; @@ -1942,7 +1946,8 @@ static class PushMessage { String content; Date createDate; - public PushMessage() {} + public PushMessage() { + } public PushMessage(String id, String content, Date createDate) { this.id = id; @@ -1971,6 +1976,7 @@ public CarPerson(String firstname, String lastname, Entry... entries) { @SuppressWarnings("unused") static class Descriptors { + private CarDescriptor carDescriptor; } @@ -1987,11 +1993,13 @@ public CarDescriptor(Entry... entries) { @SuppressWarnings("unused") static class Entry { + private String make; private String model; private int year; - public Entry() {} + public Entry() { + } public Entry(String make, String model, int year) { this.make = make; @@ -2007,7 +2015,8 @@ static class Reservation { String confirmationNumber; int timestamp; - public Reservation() {} + public Reservation() { + } public Reservation(String hotelCode, String confirmationNumber, int timestamp) { this.hotelCode = hotelCode; @@ -2034,7 +2043,8 @@ static class InventoryItem { String description; int qty; - public InventoryItem() {} + public InventoryItem() { + } public InventoryItem(int id, String item, int qty) { From bd31fbfb06073c52db099127c700e905a3f8f205 Mon Sep 17 00:00:00 2001 From: manindr Date: Mon, 6 Mar 2017 12:32:13 +0530 Subject: [PATCH 114/118] DATAMONGO-1637 formatting using eclipse settings --- .../data/mongodb/core/MongoOperations.java | 107 +++++++++--------- .../data/mongodb/core/MongoTemplate.java | 76 ++++++------- .../core/aggregation/AggregationTests.java | 49 ++++---- 3 files changed, 108 insertions(+), 124 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index 113cc2ae3d..a275074937 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -92,7 +92,7 @@ public interface MongoOperations { * @param command a MongoDB command * @param options query options to use * @deprecated since 1.7. Please use {@link #executeCommand(DBObject, ReadPreference)}, as the MongoDB Java driver - * version 3 no longer supports this operation. + * version 3 no longer supports this operation. */ @Deprecated CommandResult executeCommand(DBObject command, int options); @@ -112,7 +112,7 @@ public interface MongoOperations { * Execute a MongoDB query and iterate over the query results on a per-document basis with a DocumentCallbackHandler. * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param collectionName name of the collection to retrieve the objects from * @param dch the handler that will extract results, one document at a time */ @@ -164,15 +164,15 @@ public interface MongoOperations { * @param action callback that specified the MongoDB actions to perform on the DB instance * @return a result object returned by the action or null * @deprecated since 1.7 as the MongoDB Java driver version 3 does not longer support request boundaries via - * {@link DB#requestStart()} and {@link DB#requestDone()}. + * {@link DB#requestStart()} and {@link DB#requestDone()}. */ @Deprecated T executeInSession(DbCallback action); /** * Executes the given {@link Query} on the entity collection of the specified {@code entityType} backed by a Mongo DB - * {@link Cursor}. - * Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. + * {@link Cursor}. Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be + * closed. * * @param element return type * @param query must not be {@literal null}. @@ -184,8 +184,8 @@ public interface MongoOperations { /** * Executes the given {@link Query} on the entity collection of the specified {@code entityType} and collection backed - * by a Mongo DB {@link Cursor}. - * Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. + * by a Mongo DB {@link Cursor}. Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that + * needs to be closed. * * @param element return type * @param query must not be {@literal null}. @@ -369,10 +369,10 @@ public interface MongoOperations { * the returned object that takes int account the initial document structure as well as any finalize functions. * * @param criteria The criteria that restricts the row that are considered for grouping. If not specified all rows are - * considered. + * considered. * @param inputCollectionName the collection where the group operation will read from * @param groupBy the conditions under which the group operation will be performed, e.g. keys, initial document, - * reduce function. + * reduce function. * @param entityClass The parameterized type of the returned list * @return The results of the group operation */ @@ -384,22 +384,21 @@ public interface MongoOperations { * as well as any finalize functions. * * @param criteria The criteria that restricts the row that are considered for grouping. If not specified all rows are - * considered. + * considered. * @param inputCollectionName the collection where the group operation will read from * @param groupBy the conditions under which the group operation will be performed, e.g. keys, initial document, - * reduce function. + * reduce function. * @param entityClass The parameterized type of the returned list * @return The results of the group operation */ GroupByResults group(Criteria criteria, String inputCollectionName, GroupBy groupBy, Class entityClass); - /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. The name of the * inputCollection is derived from the inputType of the aggregation. * * @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be - * {@literal null}. + * {@literal null}. * @param collectionName The name of the input collection to use for the aggreation. * @param outputType The parameterized type of the returned list, must not be {@literal null}. * @return The results of the aggregation operation. @@ -412,37 +411,35 @@ public interface MongoOperations { * inputCollection is derived from the inputType of the aggregation. * * @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be - * {@literal null}. + * {@literal null}. * @param outputType The parameterized type of the returned list, must not be {@literal null}. * @return The results of the aggregation operation. * @since 1.3 */ AggregationResults aggregate(TypedAggregation aggregation, Class outputType); - /** - * Execute an aggregation operation. The raw results will be mapped to the given entity class and are returned as stream. The name of the - * inputCollection is derived from the inputType of the aggregation. + * Execute an aggregation operation. The raw results will be mapped to the given entity class and are returned as + * stream. The name of the inputCollection is derived from the inputType of the aggregation. * * @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be - * {@literal null}. + * {@literal null}. * @param outputType The parameterized type of the returned list, must not be {@literal null}. * @return The results of the aggregation operation. * @since 1.11.0 */ CloseableIterator aggregateStream(TypedAggregation aggregation, Class outputType); - CloseableIterator aggregateStream(TypedAggregation aggregation, String inputCollectionName, - Class outputType); + Class outputType); /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. * * @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be - * {@literal null}. + * {@literal null}. * @param inputType the inputType where the aggregation operation will read from, must not be {@literal null} or - * empty. + * empty. * @param outputType The parameterized type of the returned list, must not be {@literal null}. * @return The results of the aggregation operation. * @since 1.3 @@ -455,9 +452,9 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String * Execute an aggregation operation. The raw results will be mapped to the given entity class. * * @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be - * {@literal null}. + * {@literal null}. * @param collectionName the collection where the aggregation operation will read from, must not be {@literal null} or - * empty. + * empty. * @param outputType The parameterized type of the returned list, must not be {@literal null}. * @return The results of the aggregation operation. * @since 1.3 @@ -475,7 +472,7 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String * @return The results of the map reduce operation */ MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - Class entityClass); + Class entityClass); /** * Execute a map-reduce operation that takes additional map-reduce options. @@ -488,7 +485,7 @@ MapReduceResults mapReduce(String inputCollectionName, String mapFunction * @return The results of the map reduce operation */ MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - MapReduceOptions mapReduceOptions, Class entityClass); + MapReduceOptions mapReduceOptions, Class entityClass); /** * Execute a map-reduce operation that takes a query. The map-reduce operation will be formed with an output type of @@ -503,7 +500,7 @@ MapReduceResults mapReduce(String inputCollectionName, String mapFunction * @return The results of the map reduce operation */ MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, String reduceFunction, - Class entityClass); + Class entityClass); /** * Execute a map-reduce operation that takes a query and additional map-reduce options @@ -517,7 +514,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * @return The results of the map reduce operation */ MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, String reduceFunction, - MapReduceOptions mapReduceOptions, Class entityClass); + MapReduceOptions mapReduceOptions, Class entityClass); /** * Returns {@link GeoResults} for all entities matching the given {@link NearQuery}. Will consider entity mapping @@ -539,7 +536,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * @param near must not be {@literal null}. * @param entityClass must not be {@literal null}. * @param collectionName the collection to trigger the query against. If no collection name is given the entity class - * will be inspected. + * will be inspected. * @return */ GeoResults geoNear(NearQuery near, Class entityClass, String collectionName); @@ -555,7 +552,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * feature rich {@link Query}. * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @return the converted object */ @@ -572,7 +569,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * feature rich {@link Query}. * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @param collectionName name of the collection to retrieve the objects from * @return the converted object @@ -617,7 +614,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * feature rich {@link Query}. * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @return the List of converted objects */ @@ -633,7 +630,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * feature rich {@link Query}. * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @param collectionName name of the collection to retrieve the objects from * @return the List of converted objects @@ -663,11 +660,11 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin T findById(Object id, Class entityClass, String collectionName); /** - * Triggers findAndModify - * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. + * Triggers findAndModify + * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional - * fields specification. + * fields specification. * @param update the {@link Update} to apply on matching documents. * @param entityClass the parameterized type. * @return @@ -675,11 +672,11 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin T findAndModify(Query query, Update update, Class entityClass); /** - * Triggers findAndModify - * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. + * Triggers findAndModify + * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional - * fields specification. + * fields specification. * @param update the {@link Update} to apply on matching documents. * @param entityClass the parameterized type. * @param collectionName the collection to query. @@ -688,12 +685,12 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin T findAndModify(Query query, Update update, Class entityClass, String collectionName); /** - * Triggers findAndModify - * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking + * Triggers findAndModify + * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional - * fields specification. + * fields specification. * @param update the {@link Update} to apply on matching documents. * @param options the {@link FindAndModifyOptions} holding additional information. * @param entityClass the parameterized type. @@ -702,12 +699,12 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass); /** - * Triggers findAndModify - * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking + * Triggers findAndModify + * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional - * fields specification. + * fields specification. * @param update the {@link Update} to apply on matching documents. * @param options the {@link FindAndModifyOptions} holding additional information. * @param entityClass the parameterized type. @@ -715,7 +712,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * @return */ T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass, - String collectionName); + String collectionName); /** * Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the @@ -728,7 +725,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * feature rich {@link Query}. * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @return the converted object */ @@ -745,7 +742,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * feature rich {@link Query}. * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification + * specification * @param entityClass the parameterized type of the returned list. * @param collectionName name of the collection to retrieve the objects from * @return the converted object @@ -891,7 +888,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param collectionName name of the collection to update the object in * @return the WriteResult which lets you access the results of the previous write. */ @@ -915,7 +912,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param entityClass class that determines the collection to use * @return the WriteResult which lets you access the results of the previous write. */ @@ -927,7 +924,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param collectionName name of the collection to update the object in * @return the WriteResult which lets you access the results of the previous write. */ @@ -939,7 +936,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param entityClass class of the pojo to be operated on * @param collectionName name of the collection to update the object in * @return the WriteResult which lets you access the results of the previous write. @@ -952,7 +949,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param entityClass class that determines the collection to use * @return the WriteResult which lets you access the results of the previous write. */ @@ -964,7 +961,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param collectionName name of the collection to update the object in * @return the WriteResult which lets you access the results of the previous write. */ @@ -976,7 +973,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing - * object. + * object. * @param entityClass class of the pojo to be operated on * @param collectionName name of the collection to update the object in * @return the WriteResult which lets you access the results of the previous write. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 2ebbf678dc..62973692ea 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -383,14 +383,14 @@ public void executeQuery(Query query, String collectionName, DocumentCallbackHan * {@link DocumentCallbackHandler} using the provided CursorPreparer. * * @param query the query class that specifies the criteria used to find a record and also an optional fields - * specification, must not be {@literal null}. + * specification, must not be {@literal null}. * @param collectionName name of the collection to retrieve the objects from * @param dch the handler that will extract results, one document at a time * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply - * limits, skips and so on). + * limits, skips and so on). */ protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch, - CursorPreparer preparer) { + CursorPreparer preparer) { Assert.notNull(query, "Query must not be null!"); @@ -683,7 +683,7 @@ public T findAndModify(Query query, Update update, FindAndModifyOptions opti } public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass, - String collectionName) { + String collectionName) { return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(), getMappedSortObject(query, entityClass), entityClass, update, options); } @@ -721,7 +721,7 @@ public long count(Query query, Class entityClass, String collectionName) { final DBObject dbObject = query == null ? null : queryMapper.getMappedObject(query.getQueryObject(), - entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); + entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); return execute(collectionName, new CollectionCallback() { public Long doInCollection(DBCollection collection) throws MongoException, DataAccessException { @@ -1100,7 +1100,7 @@ public WriteResult updateMulti(final Query query, final Update update, Class } protected WriteResult doUpdate(final String collectionName, final Query query, final Update update, - final Class entityClass, final boolean upsert, final boolean multi) { + final Class entityClass, final boolean upsert, final boolean multi) { return execute(collectionName, new CollectionCallback() { public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { @@ -1295,7 +1295,7 @@ public WriteResult doInCollection(DBCollection collection) throws MongoException if (LOGGER.isDebugEnabled()) { LOGGER.debug("Remove using query: {} in collection: {}.", - new Object[]{serializeToJsonSafely(dboq), collectionName}); + new Object[] { serializeToJsonSafely(dboq), collectionName }); } WriteResult wr = writeConcernToUse == null ? collection.remove(dboq) @@ -1320,24 +1320,24 @@ public List findAll(Class entityClass, String collectionName) { } public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - Class entityClass) { + Class entityClass) { return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), entityClass); } public MapReduceResults mapReduce(String inputCollectionName, String mapFunction, String reduceFunction, - MapReduceOptions mapReduceOptions, Class entityClass) { + MapReduceOptions mapReduceOptions, Class entityClass) { return mapReduce(null, inputCollectionName, mapFunction, reduceFunction, mapReduceOptions, entityClass); } public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, - String reduceFunction, Class entityClass) { + String reduceFunction, Class entityClass) { return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(), entityClass); } public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction, - String reduceFunction, MapReduceOptions mapReduceOptions, Class entityClass) { + String reduceFunction, MapReduceOptions mapReduceOptions, Class entityClass) { String mapFunc = replaceWithResourceIfNecessary(mapFunction); String reduceFunc = replaceWithResourceIfNecessary(reduceFunction); @@ -1376,7 +1376,7 @@ public GroupByResults group(String inputCollectionName, GroupBy groupBy, } public GroupByResults group(Criteria criteria, String inputCollectionName, GroupBy groupBy, - Class entityClass) { + Class entityClass) { DBObject dbo = groupBy.getGroupByObject(); dbo.put("ns", inputCollectionName); @@ -1444,7 +1444,7 @@ public CloseableIterator aggregateStream(TypedAggregation aggregation, @Override public AggregationResults aggregate(TypedAggregation aggregation, String inputCollectionName, - Class outputType) { + Class outputType) { Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); @@ -1455,7 +1455,7 @@ public AggregationResults aggregate(TypedAggregation aggregation, Stri @Override public CloseableIterator aggregateStream(TypedAggregation aggregation, String inputCollectionName, - Class outputType) { + Class outputType) { Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); @@ -1478,19 +1478,16 @@ public CloseableIterator aggregateStream(Aggregation aggregation, Class AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType) { return aggregate(aggregation, collectionName, outputType, null); } - @Override public CloseableIterator aggregateStream(Aggregation aggregation, String collectionName, Class outputType) { return aggregateStream(aggregation, collectionName, outputType, null); } - /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String) @@ -1540,7 +1537,7 @@ protected List doFindAndDelete(String collectionName, Query query, Class< } protected AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType, - AggregationOperationContext context) { + AggregationOperationContext context) { Assert.hasText(collectionName, "Collection name must not be null or empty!"); Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); @@ -1560,8 +1557,8 @@ protected AggregationResults aggregate(Aggregation aggregation, String co commandResult); } - protected CloseableIterator aggregateStream(final Aggregation aggregation, final String collectionName, final Class outputType, - AggregationOperationContext context) { + protected CloseableIterator aggregateStream(final Aggregation aggregation, final String collectionName, + final Class outputType, AggregationOperationContext context) { Assert.hasText(collectionName, "Collection name must not be null or empty!"); Assert.notNull(aggregation, "Aggregation pipeline must not be null!"); @@ -1597,13 +1594,12 @@ private AggregationOptions getNativeAggregationOptionsFromCommand(DBObject comma } }); -/* - Query query = new BasicQuery(command); - return stream(query, outputType); -*/ + /* + Query query = new BasicQuery(command); + return stream(query, outputType); + */ } - /** * Returns the potentially mapped results of the given {@commandResult} contained some. * @@ -1612,7 +1608,7 @@ private AggregationOptions getNativeAggregationOptionsFromCommand(DBObject comma * @return */ private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, - String collectionName) { + String collectionName) { @SuppressWarnings("unchecked") Iterable resultSet = (Iterable) commandResult.get("result"); @@ -1660,7 +1656,7 @@ protected String replaceWithResourceIfNecessary(String function) { } private void copyMapReduceOptionsToCommand(Query query, MapReduceOptions mapReduceOptions, - MapReduceCommand mapReduceCommand) { + MapReduceCommand mapReduceCommand) { if (query != null) { if (query.getSkip() != 0 || query.getFieldsObject() != null) { @@ -1786,17 +1782,17 @@ protected List doFind(String collectionName, DBObject query, DBObject fie * @param fields the document that specifies the fields to be returned. * @param entityClass the parameterized type of the returned list. * @param preparer allows for customization of the {@link DBCursor} used when iterating over the result set, (apply - * limits, skips and so on). + * limits, skips and so on). * @return the {@link List} of converted objects. */ protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, - CursorPreparer preparer) { + CursorPreparer preparer) { return doFind(collectionName, query, fields, entityClass, preparer, new ReadDbObjectCallback(mongoConverter, entityClass, collectionName)); } protected List doFind(String collectionName, DBObject query, DBObject fields, Class entityClass, - CursorPreparer preparer, DbObjectCallback objectCallback) { + CursorPreparer preparer, DbObjectCallback objectCallback) { MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); @@ -1840,7 +1836,7 @@ protected DBObject convertToDbObject(CollectionOptions collectionOptions) { * @return the List of converted objects. */ protected T doFindAndRemove(String collectionName, DBObject query, DBObject fields, DBObject sort, - Class entityClass) { + Class entityClass) { EntityReader readerToUse = this.mongoConverter; @@ -1856,7 +1852,7 @@ protected T doFindAndRemove(String collectionName, DBObject query, DBObject } protected T doFindAndModify(String collectionName, DBObject query, DBObject fields, DBObject sort, - Class entityClass, Update update, FindAndModifyOptions options) { + Class entityClass, Update update, FindAndModifyOptions options) { EntityReader readerToUse = this.mongoConverter; @@ -1942,7 +1938,7 @@ private DBCollection getAndPrepareCollection(DB db, String collectionName) { * @return */ private T executeFindOneInternal(CollectionCallback collectionCallback, - DbObjectCallback objectCallback, String collectionName) { + DbObjectCallback objectCallback, String collectionName) { try { T result = objectCallback @@ -1972,7 +1968,7 @@ private T executeFindOneInternal(CollectionCallback collectionCall * @return */ private List executeFindMultiInternal(CollectionCallback collectionCallback, CursorPreparer preparer, - DbObjectCallback objectCallback, String collectionName) { + DbObjectCallback objectCallback, String collectionName) { try { @@ -2006,7 +2002,7 @@ private List executeFindMultiInternal(CollectionCallback collec } private void executeQueryInternal(CollectionCallback collectionCallback, CursorPreparer preparer, - DocumentCallbackHandler callbackHandler, String collectionName) { + DocumentCallbackHandler callbackHandler, String collectionName) { try { @@ -2155,7 +2151,7 @@ private DBObject getMappedSortObject(Query query, Class type) { * @return */ private static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex, - PersistenceExceptionTranslator exceptionTranslator) { + PersistenceExceptionTranslator exceptionTranslator) { RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex); return resolved == null ? ex : resolved; } @@ -2281,7 +2277,7 @@ private static class FindAndModifyCallback implements CollectionCallback extends ReadDbObjectCallback { public UnwrapAndReadDbObjectCallback(EntityReader reader, Class type, - String collectionName) { + String collectionName) { super(reader, type, collectionName); } @@ -2509,7 +2505,7 @@ static class CloseableIterableCursorAdapter implements CloseableIterator { * @param objectReadCallback */ public CloseableIterableCursorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator, - DbObjectCallback objectReadCallback) { + DbObjectCallback objectReadCallback) { this.cursor = cursor; this.exceptionTranslator = exceptionTranslator; @@ -2561,4 +2557,4 @@ public void close() { } } } -} \ No newline at end of file +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index ace4949ed2..aaadc6cc3b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -301,7 +301,7 @@ public void shouldAggregateEmptyCollectionAndStream() { tagCount.add(results.next()); } - // assertThat(tagCount, is(notNullValue())); + // assertThat(tagCount, is(notNullValue())); assertThat(tagCount.size(), is(0)); } @@ -386,7 +386,6 @@ public void shouldDetectResultMismatch() { assertTagCount(null, 0, tagCount.get(1)); } - @Test // DATAMONGO-1637 public void shouldDetectResultMismatchWhileStreaming() { @@ -408,13 +407,12 @@ public void shouldDetectResultMismatchWhileStreaming() { while (results.hasNext()) { tagCount.add(results.next()); } -// assertThat(tagCount, is(notNullValue())); + // assertThat(tagCount, is(notNullValue())); assertThat(tagCount.size(), is(2)); assertTagCount(null, 0, tagCount.get(0)); assertTagCount(null, 0, tagCount.get(1)); } - @Test // DATAMONGO-586 public void complexAggregationFrameworkUsageLargestAndSmallestCitiesByState() { /* @@ -571,7 +569,7 @@ public void findStatesWithPopulationOver10MillionAggregationExample() { /** * @see MongoDB Aggregation - * Framework: $cond + * Framework: $cond */ @Test // DATAMONGO-861 public void aggregationUsingConditionalProjectionToCalculateDiscount() { @@ -624,7 +622,7 @@ public void aggregationUsingConditionalProjectionToCalculateDiscount() { /** * @see MongoDB Aggregation - * Framework: $ifNull + * Framework: $ifNull */ @Test // DATAMONGO-861 public void aggregationUsingIfNullToProjectSaneDefaults() { @@ -804,8 +802,8 @@ public void shouldAllowGroupingUsingConditionalExpressions() { /** * @see Return - * the Five Most Common “Likes” + * "https://docs.mongodb.com/manual/tutorial/aggregation-with-user-preference-data/#return-the-five-most-common-likes">Return + * the Five Most Common “Likes” */ @Test // DATAMONGO-586 public void returnFiveMostCommonLikesAggregationFrameworkExample() { @@ -978,8 +976,8 @@ public void shouldThrowExceptionIfUnknownFieldIsReferencedInArithmenticExpressio /** * @see Spring - * Data MongoDB - Aggregation Framework - invalid reference in group Operation + * "http://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group-operati">Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation */ @Test // DATAMONGO-753 public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { @@ -1007,8 +1005,8 @@ public void allowsNestedFieldReferencesAsGroupIdsInGroupExpressions() { /** * @see Spring - * Data MongoDB - Aggregation Framework - invalid reference in group Operation + * "http://stackoverflow.com/questions/18653574/spring-data-mongodb-aggregation-framework-invalid-reference-in-group-operati">Spring + * Data MongoDB - Aggregation Framework - invalid reference in group Operation */ @Test // DATAMONGO-753 public void aliasesNestedFieldInProjectionImmediately() { @@ -1300,7 +1298,6 @@ public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOp assertLikeStats(result.get(4), "e", 3); } - @Test // DATAMONGO-960 public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithExplainOptionEnabled() { @@ -1417,7 +1414,7 @@ public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception { .and("dateValue").extractDayOfWeek().as("dayOfWeek") // .andExpression("dateValue + 86400000").extractDayOfYear().as("dayOfYearPlus1Day") // .andExpression("dateValue + 86400000").project("dayOfYear").as("dayOfYearPlus1DayManually") // - ; + ; Aggregation agg = newAggregation(dateProjection); AggregationResults result = mongoTemplate.aggregate(agg, ObjectWithDate.class, DBObject.class); @@ -1640,7 +1637,7 @@ public void filterShouldBeAppliedCorrectly() { assertThat(mongoTemplate.aggregate(agg, Sales.class).getMappedResults(), contains(Sales.builder().id("0").items(Collections.singletonList(item2)).build(), Sales.builder().id("1").items(Arrays.asList(item23, item38)).build(), - Sales.builder().id("2").items(Collections.emptyList()).build())); + Sales.builder().id("2").items(Collections. emptyList()).build())); } @Test // DATAMONGO-1538 @@ -1913,8 +1910,7 @@ static class DATAMONGO788 { int xField; int yField; - public DATAMONGO788() { - } + public DATAMONGO788() {} public DATAMONGO788(int x, int y) { this.x = x; @@ -1930,8 +1926,7 @@ static class User { @Id String id; List msgs; - public User() { - } + public User() {} public User(String id, PushMessage... msgs) { this.id = id; @@ -1946,8 +1941,7 @@ static class PushMessage { String content; Date createDate; - public PushMessage() { - } + public PushMessage() {} public PushMessage(String id, String content, Date createDate) { this.id = id; @@ -1998,8 +1992,7 @@ static class Entry { private String model; private int year; - public Entry() { - } + public Entry() {} public Entry(String make, String model, int year) { this.make = make; @@ -2015,8 +2008,7 @@ static class Reservation { String confirmationNumber; int timestamp; - public Reservation() { - } + public Reservation() {} public Reservation(String hotelCode, String confirmationNumber, int timestamp) { this.hotelCode = hotelCode; @@ -2043,8 +2035,7 @@ static class InventoryItem { String description; int qty; - public InventoryItem() { - } + public InventoryItem() {} public InventoryItem(int id, String item, int qty) { @@ -2077,7 +2068,7 @@ static class Sales { static class Item { @org.springframework.data.mongodb.core.mapping.Field("item_id") // - String itemId; + String itemId; Integer quantity; Long price; } @@ -2114,4 +2105,4 @@ static class Art { Integer year; double price; } -} \ No newline at end of file +} From f97c70365851c61c7c57e54c944eeb5bbc35ed6c Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 6 Mar 2017 16:19:11 +0100 Subject: [PATCH 115/118] DATAMONGO-1639 - Make sure BeforeConvertEvent sees new version for updates. The changes for DATAMONGO-1617 subtley changed the behavior for entity updates in terms of the version value they see for entities using optimistic locking. Previously the updates already saw the new version value, where after the fix for DATAMONGO-1617 it saw the old one. That caused BeforeConvertEvent listeners not being able to distinguish between an original insert and the first update anymore. This change is now rolled back and we introduced a test case that encodes this expectation explicitly. --- .../data/mongodb/core/MongoTemplate.java | 14 ++++----- .../mongodb/core/MongoTemplateUnitTests.java | 30 +++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 1f3fd76fe8..6cd1588209 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -1001,16 +1001,12 @@ private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity doInsert(collectionName, objectToSave, this.mongoConverter); } else { - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); - assertUpdateableIdIfNotSet(objectToSave); - - // Create query for entity with the id and old version - Object id = convertingAccessor.getProperty(idProperty); - Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version)); - // Bump version number convertingAccessor.setProperty(versionProperty, versionNumber.longValue() + 1); + maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); + assertUpdateableIdIfNotSet(objectToSave); + BasicDBObject dbObject = new BasicDBObject(); this.mongoConverter.write(objectToSave, dbObject); @@ -1018,6 +1014,10 @@ private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbObject, collectionName)); Update update = Update.fromDBObject(dbObject, ID_FIELD); + // Create query for entity with the id and old version + Object id = convertingAccessor.getProperty(idProperty); + Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version)); + doUpdate(collectionName, query, update, objectToSave.getClass(), false, false); maybeEmitEvent(new AfterSaveEvent(objectToSave, dbObject, collectionName)); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java index a4bc0595ca..dcb8b97020 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java @@ -52,6 +52,8 @@ import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; +import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Criteria; @@ -72,6 +74,7 @@ import com.mongodb.Mongo; import com.mongodb.MongoException; import com.mongodb.ReadPreference; +import com.mongodb.WriteResult; /** * Unit tests for {@link MongoTemplate}. @@ -465,6 +468,33 @@ public void mapReduceShouldPickUpLimitFromOptionsEvenWhenQueryDefinesItDifferent assertThat(captor.getValue().getLimit(), is(1000)); } + @Test // DATAMONGO-1639 + public void beforeConvertEventForUpdateSeesNextVersion() { + + final VersionedEntity entity = new VersionedEntity(); + entity.id = 1; + entity.version = 0; + + GenericApplicationContext context = new GenericApplicationContext(); + context.refresh(); + context.addApplicationListener(new AbstractMongoEventListener() { + + @Override + public void onBeforeConvert(BeforeConvertEvent event) { + assertThat(event.getSource().version, is(1)); + } + }); + + template.setApplicationContext(context); + + MongoTemplate spy = Mockito.spy(template); + + doReturn(mock(WriteResult.class)).when(spy).doUpdate(anyString(), Mockito.any(Query.class), + Mockito.any(Update.class), Mockito.any(Class.class), anyBoolean(), anyBoolean()); + + spy.save(entity); + } + class AutogenerateableId { @Id BigInteger id; From bfe55c7cbe6785b9eccceb9c683f8c55b701fd50 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 6 Mar 2017 16:19:36 +0100 Subject: [PATCH 116/118] DATAMONGO-1639 - Polishing. Formatting in MongoTemplateUnitTests. --- .../mongodb/core/MongoTemplateUnitTests.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java index dcb8b97020..292a1315b4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java @@ -328,8 +328,8 @@ public void processDocument(DBObject dbObject) throws MongoException, DataAccess @Test // DATAMONGO-1166 public void aggregateShouldHonorReadPreferenceWhenSet() { - when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( - mock(CommandResult.class)); + when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))) + .thenReturn(mock(CommandResult.class)); when(db.command(Mockito.any(DBObject.class))).thenReturn(mock(CommandResult.class)); template.setReadPreference(ReadPreference.secondary()); @@ -341,8 +341,8 @@ public void aggregateShouldHonorReadPreferenceWhenSet() { @Test // DATAMONGO-1166 public void aggregateShouldIgnoreReadPreferenceWhenNotSet() { - when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( - mock(CommandResult.class)); + when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))) + .thenReturn(mock(CommandResult.class)); when(db.command(Mockito.any(DBObject.class))).thenReturn(mock(CommandResult.class)); template.aggregate(Aggregation.newAggregation(Aggregation.unwind("foo")), "collection-1", Wrapper.class); @@ -353,8 +353,8 @@ public void aggregateShouldIgnoreReadPreferenceWhenNotSet() { @Test // DATAMONGO-1166 public void geoNearShouldHonorReadPreferenceWhenSet() { - when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( - mock(CommandResult.class)); + when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))) + .thenReturn(mock(CommandResult.class)); when(db.command(Mockito.any(DBObject.class))).thenReturn(mock(CommandResult.class)); template.setReadPreference(ReadPreference.secondary()); @@ -367,8 +367,8 @@ public void geoNearShouldHonorReadPreferenceWhenSet() { @Test // DATAMONGO-1166 public void geoNearShouldIgnoreReadPreferenceWhenNotSet() { - when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))).thenReturn( - mock(CommandResult.class)); + when(db.command(Mockito.any(DBObject.class), Mockito.any(ReadPreference.class))) + .thenReturn(mock(CommandResult.class)); when(db.command(Mockito.any(DBObject.class))).thenReturn(mock(CommandResult.class)); NearQuery query = NearQuery.near(new Point(1, 1)); From 1ca5f74895c0fbd95d3bbed141e01e2afbb5e6e0 Mon Sep 17 00:00:00 2001 From: manindr Date: Tue, 7 Mar 2017 13:25:35 +0530 Subject: [PATCH 117/118] DATAMONGO-1637 formatting issues fixed --- .../data/mongodb/core/MongoOperations.java | 39 +++--- .../data/mongodb/core/MongoTemplate.java | 125 ++++++++++-------- 2 files changed, 95 insertions(+), 69 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index a275074937..799daa1d42 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -171,8 +171,9 @@ public interface MongoOperations { /** * Executes the given {@link Query} on the entity collection of the specified {@code entityType} backed by a Mongo DB - * {@link Cursor}. Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be - * closed. + * {@link Cursor}. + *

                                        + * Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. * * @param element return type * @param query must not be {@literal null}. @@ -184,8 +185,9 @@ public interface MongoOperations { /** * Executes the given {@link Query} on the entity collection of the specified {@code entityType} and collection backed - * by a Mongo DB {@link Cursor}. Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that - * needs to be closed. + * by a Mongo DB {@link Cursor}. + *

                                        + * Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. * * @param element return type * @param query must not be {@literal null}. @@ -406,6 +408,9 @@ public interface MongoOperations { */ AggregationResults aggregate(TypedAggregation aggregation, String collectionName, Class outputType); + CloseableIterator aggregateStream(TypedAggregation aggregation, String inputCollectionName, + Class outputType); + /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. The name of the * inputCollection is derived from the inputType of the aggregation. @@ -430,8 +435,6 @@ public interface MongoOperations { */ CloseableIterator aggregateStream(TypedAggregation aggregation, Class outputType); - CloseableIterator aggregateStream(TypedAggregation aggregation, String inputCollectionName, - Class outputType); /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. @@ -461,6 +464,9 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String */ AggregationResults aggregate(Aggregation aggregation, String collectionName, Class outputType); + CloseableIterator aggregateStream(Aggregation aggregation, String collectionName, Class outputType); + + /** * Execute a map-reduce operation. The map-reduce operation will be formed with an output type of INLINE * @@ -660,9 +666,9 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin T findById(Object id, Class entityClass, String collectionName); /** - * Triggers findAndModify - * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. - * + * Triggers findAndModify + * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. + * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional * fields specification. * @param update the {@link Update} to apply on matching documents. @@ -672,9 +678,9 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin T findAndModify(Query query, Update update, Class entityClass); /** - * Triggers findAndModify - * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. - * + * Triggers findAndModify + * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}. + * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional * fields specification. * @param update the {@link Update} to apply on matching documents. @@ -685,8 +691,8 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin T findAndModify(Query query, Update update, Class entityClass, String collectionName); /** - * Triggers findAndModify - * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking + * Triggers findAndModify + * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional @@ -699,8 +705,8 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass); /** - * Triggers findAndModify - * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking + * Triggers findAndModify + * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional @@ -1023,7 +1029,6 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl */ WriteResult remove(Query query, String collectionName); - CloseableIterator aggregateStream(Aggregation aggregation, String collectionName, Class outputType); /** * Returns and removes all documents form the specified collection that match the provided query. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 62973692ea..b6c71f95a4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -15,10 +15,23 @@ */ package org.springframework.data.mongodb.core; -import com.mongodb.AggregationOptions; -import com.mongodb.*; -import com.mongodb.util.JSON; -import com.mongodb.util.JSONParseException; +import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.SerializationUtils.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Scanner; +import java.util.Set; + import org.bson.types.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,15 +78,24 @@ import org.springframework.util.*; import org.springframework.util.StringUtils; -import java.io.IOException; -import java.util.*; -import java.util.Map.Entry; - -import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.ALLOW_DISK_USE; -import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.CURSOR; -import static org.springframework.data.mongodb.core.aggregation.AggregationOptions.EXPLAIN; -import static org.springframework.data.mongodb.core.query.Criteria.where; -import static org.springframework.data.mongodb.core.query.SerializationUtils.serializeToJsonSafely; +import com.mongodb.BasicDBObject; +import com.mongodb.Bytes; +import com.mongodb.CommandResult; +import com.mongodb.Cursor; +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.DBCursor; +import com.mongodb.DBObject; +import com.mongodb.MapReduceCommand; +import com.mongodb.MapReduceOutput; +import com.mongodb.Mongo; +import com.mongodb.MongoException; +import com.mongodb.ReadPreference; +import com.mongodb.WriteConcern; +import com.mongodb.WriteResult; +import com.mongodb.util.JSON; +import com.mongodb.util.JSONParseException; +import com.mongodb.AggregationOptions; /** * Primary implementation of {@link MongoOperations}. @@ -953,16 +975,12 @@ private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity doInsert(collectionName, objectToSave, this.mongoConverter); } else { - maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); - assertUpdateableIdIfNotSet(objectToSave); - - // Create query for entity with the id and old version - Object id = convertingAccessor.getProperty(idProperty); - Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version)); - // Bump version number convertingAccessor.setProperty(versionProperty, versionNumber.longValue() + 1); + maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName)); + assertUpdateableIdIfNotSet(objectToSave); + BasicDBObject dbObject = new BasicDBObject(); this.mongoConverter.write(objectToSave, dbObject); @@ -970,6 +988,10 @@ private void doSaveVersioned(T objectToSave, MongoPersistentEntity entity maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbObject, collectionName)); Update update = Update.fromDBObject(dbObject, ID_FIELD); + // Create query for entity with the id and old version + Object id = convertingAccessor.getProperty(idProperty); + Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version)); + doUpdate(collectionName, query, update, objectToSave.getClass(), false, false); maybeEmitEvent(new AfterSaveEvent(objectToSave, dbObject, collectionName)); } @@ -1557,6 +1579,32 @@ protected AggregationResults aggregate(Aggregation aggregation, String co commandResult); } + /** + * Returns the potentially mapped results of the given {@commandResult} contained some. + * + * @param outputType + * @param commandResult + * @return + */ + private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, + String collectionName) { + + @SuppressWarnings("unchecked") + Iterable resultSet = (Iterable) commandResult.get("result"); + if (resultSet == null) { + return Collections.emptyList(); + } + + DbObjectCallback callback = new UnwrapAndReadDbObjectCallback(mongoConverter, outputType, collectionName); + + List mappedResults = new ArrayList(); + for (DBObject dbObject : resultSet) { + mappedResults.add(callback.doWith(dbObject)); + } + + return mappedResults; + } + protected CloseableIterator aggregateStream(final Aggregation aggregation, final String collectionName, final Class outputType, AggregationOperationContext context) { @@ -1593,37 +1641,6 @@ private AggregationOptions getNativeAggregationOptionsFromCommand(DBObject comma return builder.build(); } }); - - /* - Query query = new BasicQuery(command); - return stream(query, outputType); - */ - } - - /** - * Returns the potentially mapped results of the given {@commandResult} contained some. - * - * @param outputType - * @param commandResult - * @return - */ - private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult, - String collectionName) { - - @SuppressWarnings("unchecked") - Iterable resultSet = (Iterable) commandResult.get("result"); - if (resultSet == null) { - return Collections.emptyList(); - } - - DbObjectCallback callback = new UnwrapAndReadDbObjectCallback(mongoConverter, outputType, collectionName); - - List mappedResults = new ArrayList(); - for (DBObject dbObject : resultSet) { - mappedResults.add(callback.doWith(dbObject)); - } - - return mappedResults; } protected String replaceWithResourceIfNecessary(String function) { @@ -1990,6 +2007,7 @@ private List executeFindMultiInternal(CollectionCallback collec } return result; + } finally { if (cursor != null) { @@ -2019,11 +2037,13 @@ private void executeQueryInternal(CollectionCallback collectionCallbac DBObject dbobject = cursor.next(); callbackHandler.processDocument(dbobject); } + } finally { if (cursor != null) { cursor.close(); } } + } catch (RuntimeException e) { throw potentiallyConvertRuntimeException(e, exceptionTranslator); } @@ -2441,6 +2461,7 @@ public DBCursor prepare(DBCursor cursor) { } } } + } catch (RuntimeException e) { throw potentiallyConvertRuntimeException(e, exceptionTranslator); } @@ -2488,8 +2509,8 @@ public GeoResult doWith(DBObject object) { /** * A {@link CloseableIterator} that is backed by a MongoDB {@link Cursor}. * - * @author Thomas Darimont * @since 1.7 + * @author Thomas Darimont */ static class CloseableIterableCursorAdapter implements CloseableIterator { From fe689eeb0ce97c94bccc9dcdf6735263e96290ad Mon Sep 17 00:00:00 2001 From: manindr Date: Tue, 7 Mar 2017 13:40:28 +0530 Subject: [PATCH 118/118] DATAMONGO-1637 formatting space issues fixed --- .../data/mongodb/core/MongoOperations.java | 169 +++++++++--------- .../data/mongodb/core/MongoTemplate.java | 41 ++++- 2 files changed, 119 insertions(+), 91 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index 799daa1d42..0fd30bd1e9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -48,7 +48,7 @@ * Interface that specifies a basic set of MongoDB operations. Implemented by {@link MongoTemplate}. Not often used but * a useful option for extensibility and testability (as it can be easily mocked, stubbed, or be the target of a JDK * proxy). - * + * * @author Thomas Risberg * @author Mark Pollack * @author Oliver Gierke @@ -62,7 +62,7 @@ public interface MongoOperations { /** * The collection name used for the specified class by this template. - * + * * @param entityClass must not be {@literal null}. * @return */ @@ -72,7 +72,7 @@ public interface MongoOperations { * Execute the a MongoDB command expressed as a JSON string. This will call the method JSON.parse that is part of the * MongoDB driver to convert the JSON string to a DBObject. Any errors that result from executing this command will be * converted into Spring's DAO exception hierarchy. - * + * * @param jsonCommand a MongoDB command expressed as a JSON string. */ CommandResult executeCommand(String jsonCommand); @@ -80,7 +80,7 @@ public interface MongoOperations { /** * Execute a MongoDB command. Any errors that result from executing this command will be converted into Spring's DAO * exception hierarchy. - * + * * @param command a MongoDB command */ CommandResult executeCommand(DBObject command); @@ -88,7 +88,7 @@ public interface MongoOperations { /** * Execute a MongoDB command. Any errors that result from executing this command will be converted into Spring's DAO * exception hierarchy. - * + * * @param command a MongoDB command * @param options query options to use * @deprecated since 1.7. Please use {@link #executeCommand(DBObject, ReadPreference)}, as the MongoDB Java driver @@ -100,7 +100,7 @@ public interface MongoOperations { /** * Execute a MongoDB command. Any errors that result from executing this command will be converted into Spring's data * access exception hierarchy. - * + * * @param command a MongoDB command, must not be {@literal null}. * @param readPreference read preferences to use, can be {@literal null}. * @return @@ -110,7 +110,7 @@ public interface MongoOperations { /** * Execute a MongoDB query and iterate over the query results on a per-document basis with a DocumentCallbackHandler. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields * specification * @param collectionName name of the collection to retrieve the objects from @@ -122,7 +122,7 @@ public interface MongoOperations { * Executes a {@link DbCallback} translating any exceptions as necessary. *

                                        * Allows for returning a result object, that is a domain object or a collection of domain objects. - * + * * @param return type * @param action callback object that specifies the MongoDB actions to perform on the passed in DB instance. * @return a result object returned by the action or null @@ -133,7 +133,7 @@ public interface MongoOperations { * Executes the given {@link CollectionCallback} on the entity collection of the specified class. *

                                        * Allows for returning a result object, that is a domain object or a collection of domain objects. - * + * * @param entityClass class that determines the collection to use * @param return type * @param action callback object that specifies the MongoDB action @@ -145,7 +145,7 @@ public interface MongoOperations { * Executes the given {@link CollectionCallback} on the collection of the given name. *

                                        * Allows for returning a result object, that is a domain object or a collection of domain objects. - * + * * @param return type * @param collectionName the name of the collection that specifies which DBCollection instance will be passed into * @param action callback object that specifies the MongoDB action the callback action. @@ -159,7 +159,7 @@ public interface MongoOperations { * href=http://www.mongodb.org/display/DOCS/Java+Driver+Concurrency>Java Driver Concurrency} *

                                        * Allows for returning a result object, that is a domain object or a collection of domain objects. - * + * * @param return type * @param action callback that specified the MongoDB actions to perform on the DB instance * @return a result object returned by the action or null @@ -174,7 +174,7 @@ public interface MongoOperations { * {@link Cursor}. *

                                        * Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. - * + * * @param element return type * @param query must not be {@literal null}. * @param entityType must not be {@literal null}. @@ -188,7 +188,7 @@ public interface MongoOperations { * by a Mongo DB {@link Cursor}. *

                                        * Returns a {@link CloseableIterator} that wraps the a Mongo DB {@link Cursor} that needs to be closed. - * + * * @param element return type * @param query must not be {@literal null}. * @param entityType must not be {@literal null}. @@ -200,7 +200,7 @@ public interface MongoOperations { /** * Create an uncapped collection with a name based on the provided entity class. - * + * * @param entityClass class that determines the collection to create * @return the created collection */ @@ -208,7 +208,7 @@ public interface MongoOperations { /** * Create a collection with a name based on the provided entity class using the options. - * + * * @param entityClass class that determines the collection to create * @param collectionOptions options to use when creating the collection. * @return the created collection @@ -217,7 +217,7 @@ public interface MongoOperations { /** * Create an uncapped collection with the provided name. - * + * * @param collectionName name of the collection * @return the created collection */ @@ -225,7 +225,7 @@ public interface MongoOperations { /** * Create a collection with the provided name and options. - * + * * @param collectionName name of the collection * @param collectionOptions options to use when creating the collection. * @return the created collection @@ -234,7 +234,7 @@ public interface MongoOperations { /** * A set of collection names. - * + * * @return list of collection names */ Set getCollectionNames(); @@ -243,7 +243,7 @@ public interface MongoOperations { * Get a collection by name, creating it if it doesn't exist. *

                                        * Translate any exceptions as necessary. - * + * * @param collectionName name of the collection * @return an existing collection or a newly created one. */ @@ -253,7 +253,7 @@ public interface MongoOperations { * Check to see if a collection with a name indicated by the entity class exists. *

                                        * Translate any exceptions as necessary. - * + * * @param entityClass class that determines the name of the collection * @return true if a collection with the given name is found, false otherwise. */ @@ -263,7 +263,7 @@ public interface MongoOperations { * Check to see if a collection with a given name exists. *

                                        * Translate any exceptions as necessary. - * + * * @param collectionName name of the collection * @return true if a collection with the given name is found, false otherwise. */ @@ -273,7 +273,7 @@ public interface MongoOperations { * Drop the collection with the name indicated by the entity class. *

                                        * Translate any exceptions as necessary. - * + * * @param entityClass class that determines the collection to drop/delete. */ void dropCollection(Class entityClass); @@ -282,21 +282,21 @@ public interface MongoOperations { * Drop the collection with the given name. *

                                        * Translate any exceptions as necessary. - * + * * @param collectionName name of the collection to drop/delete. */ void dropCollection(String collectionName); /** * Returns the operations that can be performed on indexes - * + * * @return index operations on the named collection */ IndexOperations indexOps(String collectionName); /** * Returns the operations that can be performed on indexes - * + * * @return index operations on the named collection associated with the given entity class */ IndexOperations indexOps(Class entityClass); @@ -311,7 +311,7 @@ public interface MongoOperations { /** * Returns a new {@link BulkOperations} for the given collection. - * + * * @param mode the {@link BulkMode} to use for bulk operations, must not be {@literal null}. * @param collectionName the name of the collection to work on, must not be {@literal null} or empty. * @return {@link BulkOperations} on the named collection @@ -320,7 +320,7 @@ public interface MongoOperations { /** * Returns a new {@link BulkOperations} for the given entity type. - * + * * @param mode the {@link BulkMode} to use for bulk operations, must not be {@literal null}. * @param entityType the name of the entity class, must not be {@literal null}. * @return {@link BulkOperations} on the named collection associated of the given entity class. @@ -329,7 +329,7 @@ public interface MongoOperations { /** * Returns a new {@link BulkOperations} for the given entity type and collection name. - * + * * @param mode the {@link BulkMode} to use for bulk operations, must not be {@literal null}. * @param entityClass the name of the entity class, must not be {@literal null}. * @param collectionName the name of the collection to work on, must not be {@literal null} or empty. @@ -345,7 +345,7 @@ public interface MongoOperations { *

                                        * If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way * to map objects since the test for class type is done in the client and not on the server. - * + * * @param entityClass the parameterized type of the returned list * @return the converted collection */ @@ -359,7 +359,7 @@ public interface MongoOperations { *

                                        * If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way * to map objects since the test for class type is done in the client and not on the server. - * + * * @param entityClass the parameterized type of the returned list. * @param collectionName name of the collection to retrieve the objects from * @return the converted collection @@ -369,7 +369,7 @@ public interface MongoOperations { /** * Execute a group operation over the entire collection. The group operation entity class should match the 'shape' of * the returned object that takes int account the initial document structure as well as any finalize functions. - * + * * @param criteria The criteria that restricts the row that are considered for grouping. If not specified all rows are * considered. * @param inputCollectionName the collection where the group operation will read from @@ -384,7 +384,7 @@ public interface MongoOperations { * Execute a group operation restricting the rows to those which match the provided Criteria. The group operation * entity class should match the 'shape' of the returned object that takes int account the initial document structure * as well as any finalize functions. - * + * * @param criteria The criteria that restricts the row that are considered for grouping. If not specified all rows are * considered. * @param inputCollectionName the collection where the group operation will read from @@ -398,7 +398,7 @@ public interface MongoOperations { /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. The name of the * inputCollection is derived from the inputType of the aggregation. - * + * * @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be * {@literal null}. * @param collectionName The name of the input collection to use for the aggreation. @@ -414,7 +414,7 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. The name of the * inputCollection is derived from the inputType of the aggregation. - * + * * @param aggregation The {@link TypedAggregation} specification holding the aggregation operations, must not be * {@literal null}. * @param outputType The parameterized type of the returned list, must not be {@literal null}. @@ -438,7 +438,7 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. - * + * * @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be * {@literal null}. * @param inputType the inputType where the aggregation operation will read from, must not be {@literal null} or @@ -453,7 +453,7 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String /** * Execute an aggregation operation. The raw results will be mapped to the given entity class. - * + * * @param aggregation The {@link Aggregation} specification holding the aggregation operations, must not be * {@literal null}. * @param collectionName the collection where the aggregation operation will read from, must not be {@literal null} or @@ -469,7 +469,7 @@ CloseableIterator aggregateStream(TypedAggregation aggregation, String /** * Execute a map-reduce operation. The map-reduce operation will be formed with an output type of INLINE - * + * * @param inputCollectionName the collection where the map-reduce will read from * @param mapFunction The JavaScript map function * @param reduceFunction The JavaScript reduce function @@ -482,7 +482,7 @@ MapReduceResults mapReduce(String inputCollectionName, String mapFunction /** * Execute a map-reduce operation that takes additional map-reduce options. - * + * * @param inputCollectionName the collection where the map-reduce will read from * @param mapFunction The JavaScript map function * @param reduceFunction The JavaScript reduce function @@ -496,7 +496,7 @@ MapReduceResults mapReduce(String inputCollectionName, String mapFunction /** * Execute a map-reduce operation that takes a query. The map-reduce operation will be formed with an output type of * INLINE - * + * * @param query The query to use to select the data for the map phase * @param inputCollectionName the collection where the map-reduce will read from * @param mapFunction The JavaScript map function @@ -510,7 +510,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Execute a map-reduce operation that takes a query and additional map-reduce options - * + * * @param query The query to use to select the data for the map phase * @param inputCollectionName the collection where the map-reduce will read from * @param mapFunction The JavaScript map function @@ -527,7 +527,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * information to determine the collection the query is ran against. Note, that MongoDB limits the number of results * by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect a particular number of * results. - * + * * @param near must not be {@literal null}. * @param entityClass must not be {@literal null}. * @return @@ -538,7 +538,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * Returns {@link GeoResults} for all entities matching the given {@link NearQuery}. Note, that MongoDB limits the * number of results by default. Make sure to add an explicit limit to the {@link NearQuery} if you expect a * particular number of results. - * + * * @param near must not be {@literal null}. * @param entityClass must not be {@literal null}. * @param collectionName the collection to trigger the query against. If no collection name is given the entity class @@ -556,7 +556,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields * specification * @param entityClass the parameterized type of the returned list. @@ -573,7 +573,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields * specification * @param entityClass the parameterized type of the returned list. @@ -584,7 +584,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Determine result of given {@link Query} contains at least one element. - * + * * @param query the {@link Query} class that specifies the criteria used to find a record. * @param collectionName name of the collection to check for objects. * @return @@ -593,7 +593,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Determine result of given {@link Query} contains at least one element. - * + * * @param query the {@link Query} class that specifies the criteria used to find a record. * @param entityClass the parameterized type. * @return @@ -602,7 +602,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Determine result of given {@link Query} contains at least one element. - * + * * @param query the {@link Query} class that specifies the criteria used to find a record. * @param entityClass the parameterized type. * @param collectionName name of the collection to check for objects. @@ -618,7 +618,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields * specification * @param entityClass the parameterized type of the returned list. @@ -634,7 +634,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields * specification * @param entityClass the parameterized type of the returned list. @@ -646,7 +646,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Returns a document with the given id mapped onto the given class. The collection the query is ran against will be * derived from the given target class as well. - * + * * @param * @param id the id of the document to return. * @param entityClass the type the document shall be converted into. @@ -656,7 +656,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin /** * Returns the document with the given id from the given collection mapped onto the given target class. - * + * * @param id the id of the document to return * @param entityClass the type to convert the document to * @param collectionName the collection to query for the document @@ -694,7 +694,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. - * + * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional * fields specification. * @param update the {@link Update} to apply on matching documents. @@ -708,7 +708,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * Triggers findAndModify * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking * {@link FindAndModifyOptions} into account. - * + * * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional * fields specification. * @param update the {@link Update} to apply on matching documents. @@ -718,7 +718,7 @@ MapReduceResults mapReduce(Query query, String inputCollectionName, Strin * @return */ T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass, - String collectionName); + String collectionName); /** * Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the @@ -729,7 +729,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields * specification * @param entityClass the parameterized type of the returned list. @@ -746,7 +746,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl *

                                        * The query is specified as a {@link Query} which can be created either using the {@link BasicQuery} or the more * feature rich {@link Query}. - * + * * @param query the query class that specifies the criteria used to find a record and also an optional fields * specification * @param entityClass the parameterized type of the returned list. @@ -757,7 +757,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Returns the number of documents for the given {@link Query} by querying the collection of the given entity class. - * + * * @param query * @param entityClass must not be {@literal null}. * @return @@ -768,7 +768,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * Returns the number of documents for the given {@link Query} querying the given collection. The given {@link Query} * must solely consist of document field references as we lack type information to map potential property references * onto document fields. TO make sure the query gets mapped, use {@link #count(Query, Class, String)}. - * + * * @param query * @param collectionName must not be {@literal null} or empty. * @return @@ -779,7 +779,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Returns the number of documents for the given {@link Query} by querying the given collection using the given entity * class to map the given {@link Query}. - * + * * @param query * @param entityClass must not be {@literal null}. * @param collectionName must not be {@literal null} or empty. @@ -800,7 +800,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl *

                                        *

                                        * Insert is used to initially store the object into the database. To update an existing object use the save method. - * + * * @param objectToSave the object to store in the collection. */ void insert(Object objectToSave); @@ -812,7 +812,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * configured otherwise, an instance of MappingMongoConverter will be used. *

                                        * Insert is used to initially store the object into the database. To update an existing object use the save method. - * + * * @param objectToSave the object to store in the collection * @param collectionName name of the collection to store the object in */ @@ -820,7 +820,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Insert a Collection of objects into a collection in a single batch write to the database. - * + * * @param batchToSave the list of objects to save. * @param entityClass class that determines the collection to use */ @@ -828,7 +828,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Insert a list of objects into the specified collection in a single batch write to the database. - * + * * @param batchToSave the list of objects to save. * @param collectionName name of the collection to store the object in */ @@ -837,7 +837,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Insert a mixed Collection of objects into a database collection determining the collection name to use based on the * class. - * + * * @param collectionToSave the list of objects to save. */ void insertAll(Collection objectsToSave); @@ -854,7 +854,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * property type will be handled by Spring's BeanWrapper class that leverages Type Conversion API. See * * Spring's Type Conversion" for more details. - * + * * @param objectToSave the object to store in the collection */ void save(Object objectToSave); @@ -871,7 +871,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * property type will be handled by Spring's BeanWrapper class that leverages Type Cobnversion API. See Spring's * Type Conversion" for more details. - * + * * @param objectToSave the object to store in the collection * @param collectionName name of the collection to store the object in */ @@ -880,7 +880,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by * combining the query document and the update document. - * + * * @param query the query document that specifies the criteria used to select a record to be upserted * @param update the update document that contains the updated object or $ operators to manipulate the existing object * @param entityClass class that determines the collection to use @@ -891,7 +891,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by * combining the query document and the update document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing * object. @@ -903,7 +903,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by * combining the query document and the update document. - * + * * @param query the query document that specifies the criteria used to select a record to be upserted * @param update the update document that contains the updated object or $ operators to manipulate the existing object * @param entityClass class of the pojo to be operated on @@ -915,7 +915,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates the first object that is found in the collection of the entity class that matches the query document with * the provided update document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing * object. @@ -927,7 +927,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates the first object that is found in the specified collection that matches the query document criteria with * the provided updated document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing * object. @@ -939,7 +939,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates the first object that is found in the specified collection that matches the query document criteria with * the provided updated document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing * object. @@ -952,7 +952,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates all objects that are found in the collection for the entity class that matches the query document criteria * with the provided updated document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing * object. @@ -964,7 +964,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates all objects that are found in the specified collection that matches the query document criteria with the * provided updated document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing * object. @@ -976,7 +976,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Updates all objects that are found in the collection for the entity class that matches the query document criteria * with the provided updated document. - * + * * @param query the query document that specifies the criteria used to select a record to be updated * @param update the update document that contains the updated object or $ operators to manipulate the existing * object. @@ -988,14 +988,14 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Remove the given object from the collection by id. - * + * * @param object */ WriteResult remove(Object object); /** * Removes the given object from the given collection. - * + * * @param object * @param collection must not be {@literal null} or empty. */ @@ -1004,7 +1004,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Remove all documents that match the provided query document criteria from the the collection used to store the * entityClass. The Class parameter is also used to help convert the Id of the object if it is present in the query. - * + * * @param query * @param entityClass */ @@ -1013,7 +1013,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Remove all documents that match the provided query document criteria from the the collection used to store the * entityClass. The Class parameter is also used to help convert the Id of the object if it is present in the query. - * + * * @param query * @param entityClass * @param collectionName @@ -1023,16 +1023,15 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Remove all documents from the specified collection that match the provided query document criteria. There is no * conversion/mapping done for any criteria using the id field. - * + * * @param query the query document that specifies the criteria used to remove a record * @param collectionName name of the collection where the objects will removed */ WriteResult remove(Query query, String collectionName); - /** * Returns and removes all documents form the specified collection that match the provided query. - * + * * @param query * @param collectionName * @return @@ -1042,7 +1041,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Returns and removes all documents matching the given query form the collection used to store the entityClass. - * + * * @param query * @param entityClass * @return @@ -1054,7 +1053,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl * Returns and removes all documents that match the provided query document criteria from the the collection used to * store the entityClass. The Class parameter is also used to help convert the Id of the object if it is present in * the query. - * + * * @param query * @param entityClass * @param collectionName @@ -1065,7 +1064,7 @@ T findAndModify(Query query, Update update, FindAndModifyOptions options, Cl /** * Returns the underlying {@link MongoConverter}. - * + * * @return */ MongoConverter getConverter(); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index b6c71f95a4..4de7f0335e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -36,7 +36,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; -import org.springframework.context.*; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -57,25 +62,49 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.BulkOperations.BulkMode; -import org.springframework.data.mongodb.core.aggregation.*; -import org.springframework.data.mongodb.core.convert.*; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.aggregation.Fields; +import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext; +import org.springframework.data.mongodb.core.aggregation.TypedAggregation; +import org.springframework.data.mongodb.core.convert.DbRefResolver; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.convert.MongoWriter; +import org.springframework.data.mongodb.core.convert.QueryMapper; +import org.springframework.data.mongodb.core.convert.UpdateMapper; import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher; import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; -import org.springframework.data.mongodb.core.mapping.event.*; +import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent; +import org.springframework.data.mongodb.core.mapping.event.AfterDeleteEvent; +import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent; +import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent; +import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; +import org.springframework.data.mongodb.core.mapping.event.BeforeDeleteEvent; +import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent; +import org.springframework.data.mongodb.core.mapping.event.MongoMappingEvent; import org.springframework.data.mongodb.core.mapreduce.GroupBy; import org.springframework.data.mongodb.core.mapreduce.GroupByResults; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; import org.springframework.data.mongodb.core.mapreduce.MapReduceResults; -import org.springframework.data.mongodb.core.query.*; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Meta; +import org.springframework.data.mongodb.core.query.NearQuery; +import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.util.MongoClientVersion; import org.springframework.data.util.CloseableIterator; import org.springframework.jca.cci.core.ConnectionCallback; -import org.springframework.util.*; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; import com.mongodb.BasicDBObject;