Browse Source

DATAMONGO-774 - Support SpEL expressions to define projection operations in the aggregation framework.

ProjectionOperations can now be built using SpEL expressions as this significantly shortens the code needed to express the project, especially for slightly more complex mathematical expressions.

Projection now has an ….andExpression(…) method that takes a SpEL expression and optional arguments that can be referred to via their index, i.e. a SpEl expression "5 + [0]" can be expanded using ….andExpression("5 + [0]", 7).… so that the projection can be prepared and dynamically get values bound.

Original pull request: #81.
pull/85/head
Thomas Darimont 12 years ago committed by Oliver Gierke
parent
commit
7e471e2301
  1. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java
  2. 81
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java
  3. 668
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionToMongoExpressionTransformer.java
  4. 33
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/DBObjectUtils.java
  5. 20
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/package-info.java
  6. 4
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java
  7. 235
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java
  8. 31
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/Data.java
  9. 23
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DataItem.java
  10. 9
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsUnitTests.java
  11. 4
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java
  12. 24
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java
  13. 52
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java
  14. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SortOperationUnitTests.java
  15. 70
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionToMongoExpressionTransformerIntegrationTests.java
  16. 179
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionToMongoExpressionTransformerTests.java
  17. 4
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java
  18. 6
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java
  19. 4
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java
  20. 6
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java
  21. 6
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java

@ -340,7 +340,11 @@ public class ExposedFields implements Iterable<ExposedField> {
* @return * @return
*/ */
public String getRaw() { public String getRaw() {
String target = field.getTarget(); String target = field.getTarget();
if (target.startsWith("$")) {
target = target.substring(1);
}
return field.synthetic ? target : String.format("%s.%s", Fields.UNDERSCORE_ID, target); return field.synthetic ? target : String.format("%s.%s", Fields.UNDERSCORE_ID, target);
} }

81
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java

@ -116,6 +116,10 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
return new ProjectionOperationBuilder(name, this, null); return new ProjectionOperationBuilder(name, this, null);
} }
public ExpressionProjectionOperationBuilder andExpression(String expression, Object... params) {
return new ExpressionProjectionOperationBuilder(expression, this, params);
}
/** /**
* Excludes the given fields from the projection. * Excludes the given fields from the projection.
* *
@ -188,13 +192,80 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
return new BasicDBObject("$project", fieldObject); return new BasicDBObject("$project", fieldObject);
} }
/**
* @author Thomas Darimont
*/
public static abstract class AbstractProjectionOperationBuilder implements AggregationOperation {
protected final Object value;
protected final ProjectionOperation operation;
public AbstractProjectionOperationBuilder(Object value, ProjectionOperation 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;
}
public abstract ProjectionOperation as(String alias);
/* (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 this.operation.toDBObject(context);
}
}
/**
* @author Thomas Darimont
*/
public static class ExpressionProjectionOperationBuilder extends AbstractProjectionOperationBuilder {
private Object[] params;
public ExpressionProjectionOperationBuilder(Object value, ProjectionOperation operation, Object[] params) {
super(value, operation);
this.params = params;
}
public ProjectionOperation as(String alias) {
return this.operation.and(new ExpressionProjection(Fields.field(alias, "expr"), this.value.toString(), params));
}
static class ExpressionProjection extends Projection {
private String expression;
private Object[] params;
public ExpressionProjection(Field field, String expression, Object[] params) {
super(field);
this.expression = expression;
this.params = params;
}
/* (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) {
return new BasicDBObject(getExposedField().getName(),
SpelExpressionToMongoExpressionTransformer.INSTANCE.transform(expression, context, params));
}
}
}
/** /**
* Builder for {@link ProjectionOperation}s on a field. * Builder for {@link ProjectionOperation}s on a field.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont * @author Thomas Darimont
*/ */
public static class ProjectionOperationBuilder implements AggregationOperation { public static class ProjectionOperationBuilder extends AbstractProjectionOperationBuilder {
private final String name; private final String name;
private final ProjectionOperation operation; private final ProjectionOperation operation;
@ -209,9 +280,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
* @param previousProjection the previous operation projection, may be {@literal null}. * @param previousProjection the previous operation projection, may be {@literal null}.
*/ */
public ProjectionOperationBuilder(String name, ProjectionOperation operation, OperationProjection previousProjection) { public ProjectionOperationBuilder(String name, ProjectionOperation operation, OperationProjection previousProjection) {
super(name, operation);
Assert.hasText(name, "Field name must not be null or empty!");
Assert.notNull(operation, "ProjectionOperation must not be null!");
this.name = name; this.name = name;
this.operation = operation; this.operation = operation;
@ -248,8 +317,8 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
*/ */
public ProjectionOperation as(String alias) { public ProjectionOperation as(String alias) {
if (previousProjection != null) { if (this.previousProjection != null) {
return this.operation.andReplaceLastOneWith(previousProjection.withAlias(alias)); return this.operation.andReplaceLastOneWith(this.previousProjection.withAlias(alias));
} else { } else {
return this.operation.and(new FieldProjection(Fields.field(alias, name), null)); return this.operation.and(new FieldProjection(Fields.field(alias, name), null));
} }

668
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionToMongoExpressionTransformer.java

@ -0,0 +1,668 @@
/*
* Copyright 2013 the original author 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.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.data.mongodb.util.DBObjectUtils;
import org.springframework.expression.spel.ExpressionState;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.ast.CompoundExpression;
import org.springframework.expression.spel.ast.FloatLiteral;
import org.springframework.expression.spel.ast.Indexer;
import org.springframework.expression.spel.ast.InlineList;
import org.springframework.expression.spel.ast.IntLiteral;
import org.springframework.expression.spel.ast.Literal;
import org.springframework.expression.spel.ast.LongLiteral;
import org.springframework.expression.spel.ast.MethodReference;
import org.springframework.expression.spel.ast.NullLiteral;
import org.springframework.expression.spel.ast.OpDivide;
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.OpPlus;
import org.springframework.expression.spel.ast.Operator;
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
import org.springframework.expression.spel.ast.RealLiteral;
import org.springframework.expression.spel.ast.StringLiteral;
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 com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression.
*
* @author Thomas Darimont
*/
enum SpelExpressionToMongoExpressionTransformer {
INSTANCE;
private List<SpelNodeConversion<? extends SpelNode>> conversions;
/**
* Creates a new {@link SpelExpressionToMongoExpressionTransformer}.
*/
private SpelExpressionToMongoExpressionTransformer() {
this.conversions = new ArrayList<SpelNodeConversion<? extends SpelNode>>();
this.conversions.add(new OperatorNodeConversion());
this.conversions.add(new LiteralNodeConversion());
this.conversions.add(new IndexerNodeConversion());
this.conversions.add(new InlineListNodeConversion());
this.conversions.add(new PropertyOrFieldReferenceNodeConversion());
this.conversions.add(new CompoundExpressionNodeConversion());
this.conversions.add(new MethodReferenceNodeConversion());
}
/**
* Transforms the given SpEL expression string to a corresponding MongoDB Expression.
*
* @param expression must be a SpEL expression.
* @return
*/
public Object transform(String expression) {
return transform(expression, Aggregation.DEFAULT_CONTEXT, new Object[0]);
}
/**
* Transforms the given SpEL expression string to a corresponding MongoDB expression against the
* {@link Aggregation#DEFAULT_CONTEXT}.
* <p>
* Exposes the given @{code params} as <code>[0] ... [n]</code>.
*
* @param expression must be a SpEL expression.
* @param params must not be {@literal null}
* @return
*/
public Object transform(String expression, Object... params) {
return transform(expression, Aggregation.DEFAULT_CONTEXT, params);
}
/**
* Transforms the given SpEL expression string to a corresponding MongoDB expression against the given
* {@link AggregationOperationContext} {@code context}.
* <p>
* Exposes the given @{code params} as <code>[0] ... [n]</code>.
*
* @param expression must not be {@literal null}
* @param context must not be {@literal null}
* @param params must not be {@literal null}
* @return
*/
public Object transform(String expression, AggregationOperationContext context, Object[] params) {
Assert.notNull(expression, "expression must not be null!");
return transform((SpelExpression) new SpelExpressionParser().parseExpression(expression), context, params);
}
/**
* Transforms the given SpEL expression to a corresponding MongoDB expression against the given
* {@link AggregationOperationContext} {@code context}.
* <p>
* Exposes the given @{code params} as <code>[0] ... [n]</code>.
*
* @param expression must not be {@literal null}
* @param context must not be {@literal null}
* @param params must not be {@literal null}
* @return
*/
public Object transform(SpelExpression expression, AggregationOperationContext context, Object[] params) {
Assert.notNull(params, "params must not be null!");
return transform(expression, context, new ExpressionState(new StandardEvaluationContext(params)));
}
/**
* Transforms the given SpEL expression to a corresponding MongoDB expression against the given
* {@link AggregationOperationContext} {@code context} and the given {@link ExpressionState}.
*
* @param expression
* @param aggregationContext
* @param expressionState
* @return
*/
public Object transform(SpelExpression expression, AggregationOperationContext aggregationContext,
ExpressionState expressionState) {
Assert.notNull(expression, "expression must not be null!");
Assert.notNull(aggregationContext, "aggregationContext must not be null!");
Assert.notNull(expressionState, "expressionState must not be null!");
ExpressionTransformationContext expressionContext = new ExpressionTransformationContext(expression.getAST(), null,
null, aggregationContext, expressionState);
return doTransform(expressionContext);
}
/**
* @param spelNode
* @param context
* @return
*/
private Object doTransform(ExpressionTransformationContext context) {
return lookupConversionFor(context.getCurrentNode()).convert(context);
}
/**
* Returns an appropriate {@link SpelNodeConversion} for the given {@code node}. Throws an
* {@link IllegalArgumentException} if no conversion could be found.
*
* @param node
* @return the appropriate {@link SpelNodeConversion} for the given {@link SpelNode}.
*/
private SpelNodeConversion<? extends SpelNode> lookupConversionFor(SpelNode node) {
for (SpelNodeConversion<? extends SpelNode> candidate : conversions) {
if (candidate.supports(node)) {
return candidate;
}
}
throw new IllegalArgumentException("Unsupported Element: " + node + " Type: " + node.getClass()
+ " You probably have a syntax error in your SpEL expression!");
}
/**
* Holds information about the current transformation context.
*
* @author Thomas Darimont
*/
private static class ExpressionTransformationContext {
private final SpelNode currentNode;
private final SpelNode parentNode;
private final Object previousOperationObject;
private final AggregationOperationContext aggregationContext;
private final ExpressionState expressionState;
/**
* Creates a <code>ExpressionConversionContext<code>
*
* @param currentNode, must not be {@literal null}
* @param parentNode
* @param previousOperationObject
* @param aggregationContext, must not be {@literal null}
* @param expressionState, must not be {@literal null}
*/
public ExpressionTransformationContext(SpelNode currentNode, SpelNode parentNode, Object previousOperationObject,
AggregationOperationContext aggregationContext, ExpressionState expressionState) {
Assert.notNull(currentNode, "currentNode must not be null!");
Assert.notNull(aggregationContext, "aggregationContext must not be null!");
Assert.notNull(expressionState, "expressionState must not be null!");
this.currentNode = currentNode;
this.parentNode = parentNode;
this.previousOperationObject = previousOperationObject;
this.aggregationContext = aggregationContext;
this.expressionState = expressionState;
}
/**
* Creates a {@link ExpressionTransformationContext}.
*
* @param child, must not be {@literal null}
* @param context, must not be {@literal null}
*/
public ExpressionTransformationContext(SpelNode currentNode, ExpressionTransformationContext context) {
this(currentNode, context.getParentNode(), context.getPreviousOperationObject(), context.getAggregationContext(),
context.getExpressionState());
}
public SpelNode getCurrentNode() {
return currentNode;
}
public SpelNode getParentNode() {
return parentNode;
}
public Object getPreviousOperationObject() {
return previousOperationObject;
}
public AggregationOperationContext getAggregationContext() {
return aggregationContext;
}
public ExpressionState getExpressionState() {
return expressionState;
}
public boolean isPreviousOperationPresent() {
return getPreviousOperationObject() != null;
}
/**
* Returns a {@link FieldReference} for the given {@code fieldName}. Checks whether a field with the given
* {@code fieldName} can be found in the {@link AggregationOperationContext}.
*
* @param fieldName
* @return
*/
private FieldReference getFieldReference(String fieldName) {
if (aggregationContext == null) {
return null;
}
return aggregationContext.getReference(fieldName);
}
}
/**
* Abstract base class for {@link SpelNode} to (Db)-object conversions.
*
* @author Thomas Darimont
*/
static abstract class SpelNodeConversion<T extends SpelNode> {
protected final Class<T> nodeType;
public SpelNodeConversion(Class<T> nodeType) {
this.nodeType = nodeType;
}
/**
* @param node
* @return true if {@literal this} conversion can be applied to the given {@code node}.
*/
protected boolean supports(SpelNode node) {
return nodeType.isAssignableFrom(node.getClass());
}
/**
* Performs the actual conversion from {@link SpelNode} to the corresponding representation for MongoDB.
*
* @param context
* @return
*/
abstract Object convert(ExpressionTransformationContext context);
/**
* Extracts the argument list from the given {@code context}.
*
* @param context
* @return
*/
protected static BasicDBList extractArgumentListFrom(DBObject context) {
return (BasicDBList) context.get(context.keySet().iterator().next());
}
protected SpelExpressionToMongoExpressionTransformer getTransformer() {
return INSTANCE;
}
}
/**
* A {@link SpelNodeConversion} that converts arithmetic operations.
*
* @author Thomas Darimont
*/
static class OperatorNodeConversion extends SpelNodeConversion<Operator> {
private Map<String, String> arithmeticOperatorsSpelToMongoConversion = new HashMap<String, String>() {
private static final long serialVersionUID = 1L;
{
put("+", "$add");
put("-", "$subtract");
put("*", "$multiply");
put("/", "$divide");
put("%", "$mod");
}
};
public OperatorNodeConversion() {
super(Operator.class);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.ExpressionConversionContext)
*/
@Override
Object convert(ExpressionTransformationContext context) {
Operator currentNode = Operator.class.cast(context.getCurrentNode());
boolean unaryOperator = currentNode.getRightOperand() == null;
Object operationObject = createOperationObjectAndAddToPreviousArgumentsIfNecessary(context, currentNode,
unaryOperator);
Object leftResult = convertPart(currentNode.getLeftOperand(), context, currentNode, operationObject);
if (unaryOperator && currentNode instanceof OpMinus) {
return convertUnaryMinusOp(context, leftResult);
}
// we deliberately ignore the RHS result
convertPart(currentNode.getRightOperand(), context, currentNode, operationObject);
return operationObject;
}
private Object convertPart(SpelNode currentNode, ExpressionTransformationContext context, Operator parentNode,
Object operationObject) {
return getTransformer().doTransform(
new ExpressionTransformationContext(currentNode, parentNode, operationObject,
context.getAggregationContext(), context.getExpressionState()));
}
private Object createOperationObjectAndAddToPreviousArgumentsIfNecessary(ExpressionTransformationContext context,
Operator currentNode, boolean unaryOperator) {
Object nextDbObject = new BasicDBObject(getOp(currentNode), new BasicDBList());
if (context.isPreviousOperationPresent()) {
if (currentNode.getClass().equals(context.getParentNode().getClass())) {
// same operator applied in a row e.g. 1 + 2 + 3 carry on with the operation and render as $add: [1, 2 ,3]
nextDbObject = context.getPreviousOperationObject();
} else if (!unaryOperator) {
// different operator -> add context object for next level to list if arguments of previous expression
extractArgumentListFrom((DBObject) context.getPreviousOperationObject()).add(nextDbObject);
}
}
return nextDbObject;
}
private Object convertUnaryMinusOp(ExpressionTransformationContext context, Object leftResult) {
Object result = leftResult instanceof Number ? leftResult : new BasicDBObject("$multiply", DBObjectUtils.dbList(
-1, leftResult));
if (leftResult != null && context.getPreviousOperationObject() != null) {
extractArgumentListFrom((DBObject) context.getPreviousOperationObject()).add(result);
}
return result;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.SpelNodeWrapper#supports(java.lang.Class)
*/
@Override
protected boolean supports(SpelNode node) {
return node instanceof OpMinus || node instanceof OpPlus || node instanceof OpMultiply
|| node instanceof OpDivide || node instanceof OpModulus;
}
private String getOp(SpelNode node) {
return supports(node) ? toMongoOperator((Operator) node) : null;
}
private String toMongoOperator(Operator operator) {
return arithmeticOperatorsSpelToMongoConversion.get(operator.getOperatorName());
}
}
/**
* A {@link SpelNodeConversion} that converts indexed expressions.
*
* @author Thomas Darimont
*/
static class IndexerNodeConversion extends SpelNodeConversion<Indexer> {
public IndexerNodeConversion() {
super(Indexer.class);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.ExpressionConversionContext)
*/
@Override
Object convert(ExpressionTransformationContext context) {
Indexer currentNode = Indexer.class.cast(context.getCurrentNode());
Object value = currentNode.getValue(context.getExpressionState());
if (context.isPreviousOperationPresent()) {
extractArgumentListFrom((DBObject) context.getPreviousOperationObject()).add(value);
return context.getPreviousOperationObject();
}
return value;
}
}
/**
* A {@link SpelNodeConversion} that converts in-line list expressions.
*
* @author Thomas Darimont
*/
static class InlineListNodeConversion extends SpelNodeConversion<InlineList> {
public InlineListNodeConversion() {
super(InlineList.class);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.ExpressionConversionContext)
*/
@Override
Object convert(ExpressionTransformationContext context) {
InlineList currentNode = InlineList.class.cast(context.getCurrentNode());
if (currentNode.getChildCount() == 0) {
return null;
}
// just take the first item
ExpressionTransformationContext nestedExpressionContext = new ExpressionTransformationContext(
currentNode.getChild(0), currentNode, null, context.getAggregationContext(), context.getExpressionState());
return INSTANCE.doTransform(nestedExpressionContext);
}
}
/**
* A {@link SpelNodeConversion} that converts property or field reference expressions.
*
* @author Thomas Darimont
*/
static class PropertyOrFieldReferenceNodeConversion extends SpelNodeConversion<PropertyOrFieldReference> {
public PropertyOrFieldReferenceNodeConversion() {
super(PropertyOrFieldReference.class);
}
@Override
Object convert(ExpressionTransformationContext context) {
PropertyOrFieldReference currentNode = PropertyOrFieldReference.class.cast(context.getCurrentNode());
FieldReference fieldReference = context.getFieldReference(currentNode.getName());
if (context.isPreviousOperationPresent()) {
extractArgumentListFrom((DBObject) context.getPreviousOperationObject()).add(fieldReference.toString());
return context.getPreviousOperationObject();
}
return fieldReference.toString();
}
}
/**
* A {@link SpelNodeConversion} that converts literal expressions.
*
* @author Thomas Darimont
*/
static class LiteralNodeConversion extends SpelNodeConversion<Literal> {
public LiteralNodeConversion() {
super(Literal.class);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.ExpressionConversionContext)
*/
@Override
Object convert(ExpressionTransformationContext context) {
Literal currentNode = Literal.class.cast(context.getCurrentNode());
Object value = currentNode.getLiteralValue().getValue();
if (context.isPreviousOperationPresent()) {
if (context.getParentNode() instanceof OpMinus && ((OpMinus) context.getParentNode()).getRightOperand() == null) {
// unary minus operator
return NumberUtils.convertNumberToTargetClass(((Number) value).doubleValue() * -1,
(Class<Number>) value.getClass()); // retain type, e.g. int to -int
}
extractArgumentListFrom((DBObject) context.getPreviousOperationObject()).add(value);
return context.getPreviousOperationObject();
}
return value;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.SpelNodeWrapper#supports(org.springframework.expression.spel.SpelNode)
*/
@Override
protected boolean supports(SpelNode node) {
return node instanceof FloatLiteral || node instanceof RealLiteral || node instanceof IntLiteral
|| node instanceof LongLiteral || node instanceof StringLiteral || node instanceof NullLiteral;
}
}
/**
* A {@link SpelNodeConversion} that converts method reference expressions.
*
* @author Thomas Darimont
*/
static class MethodReferenceNodeConversion extends SpelNodeConversion<MethodReference> {
private Map<String, String> namedFunctionToMongoExpressionMap = new HashMap<String, String>() {
private static final long serialVersionUID = 1L;
{
put("concat", "$concat"); // Concatenates two strings.
put("strcasecmp", "$strcasecmp"); // Compares two strings and returns an integer that reflects the comparison.
put("substr", "$substr"); // Takes a string and returns portion of that string.
put("toLower", "$toLower"); // Converts a string to lowercase.
put("toUpper", "$toUpper"); // Converts a string to uppercase.
put("dayOfYear", "$dayOfYear"); // Converts a date to a number between 1 and 366.
put("dayOfMonth", "$dayOfMonth"); // Converts a date to a number between 1 and 31.
put("dayOfWeek", "$dayOfWeek"); // Converts a date to a number between 1 and 7.
put("year", "$year"); // Converts a date to the full year.
put("month", "$month"); // Converts a date into a number between 1 and 12.
put("week", "$week"); // Converts a date into a number between 0 and 53
put("hour", "$hour"); // Converts a date into a number between 0 and 23.
put("minute", "$minute"); // Converts a date into a number between 0 and 59.
put("second", "$second"); // Converts a date into a number between 0 and 59. May be 60 to account for leap
// seconds.
put("millisecond", "$millisecond"); // Returns the millisecond portion of a date as an integer between 0 and
// 999.
}
};
public MethodReferenceNodeConversion() {
super(MethodReference.class);
}
private String getMongoFunctionFor(String methodName) {
return namedFunctionToMongoExpressionMap.get(methodName);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.ExpressionConversionContext)
*/
@Override
Object convert(ExpressionTransformationContext context) {
MethodReference currentNode = MethodReference.class.cast(context.getCurrentNode());
String stringAST = currentNode.toStringAST();
String methodName = stringAST.substring(0, stringAST.indexOf('('));
String mongoFunction = getMongoFunctionFor(methodName);
List<Object> args = new ArrayList<Object>();
for (int i = 0; i < currentNode.getChildCount(); i++) {
args.add(getTransformer().doTransform(new ExpressionTransformationContext(currentNode.getChild(i), context)));
}
BasicDBObject functionObject = new BasicDBObject(mongoFunction, DBObjectUtils.dbList(args.toArray()));
if (context.isPreviousOperationPresent()) {
extractArgumentListFrom((DBObject) context.getPreviousOperationObject()).add(functionObject);
return context.getPreviousOperationObject();
}
return functionObject;
}
}
/**
* A {@link SpelNodeConversion} that converts method compound expressions.
*
* @author Thomas Darimont
*/
static class CompoundExpressionNodeConversion extends SpelNodeConversion<CompoundExpression> {
public CompoundExpressionNodeConversion() {
super(CompoundExpression.class);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionToMongoExpressionTransformer.ExpressionConversionContext)
*/
@Override
Object convert(ExpressionTransformationContext context) {
CompoundExpression currentNode = CompoundExpression.class.cast(context.getCurrentNode());
if (currentNode.getChildCount() > 0 && !(currentNode.getChild(0) instanceof Indexer)) {
// we have a property path expression like: foo.bar -> render as reference
return context.getFieldReference(currentNode.toStringAST()).toString();
}
Object value = currentNode.getValue(context.getExpressionState());
if (context.isPreviousOperationPresent()) {
extractArgumentListFrom((DBObject) context.getPreviousOperationObject()).add(value);
return context.getPreviousOperationObject();
}
return value;
}
}
}

33
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/DBObjectUtils.java

@ -0,0 +1,33 @@
/*
* Copyright 2013 the original author 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.util;
import com.mongodb.BasicDBList;
/**
* @author Thomas Darimont
*/
public class DBObjectUtils {
public static BasicDBList dbList(Object... items) {
BasicDBList list = new BasicDBList();
for (Object item : items) {
list.add(item);
}
return list;
}
}

20
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/package-info.java

@ -0,0 +1,20 @@
/*
* Copyright 2013 the original author 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.
*/
/**
*
* @author Thomas Darimont
*/
package org.springframework.data.mongodb.util;

4
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectUtils.java → spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DBObjectTestUtils.java

@ -26,9 +26,9 @@ import com.mongodb.DBObject;
* *
* @author Oliver Gierke * @author Oliver Gierke
*/ */
public abstract class DBObjectUtils { public abstract class DBObjectTestUtils {
private DBObjectUtils() { private DBObjectTestUtils() {
} }

235
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java

@ -22,20 +22,26 @@ import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Criteria.*;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Scanner; import java.util.Scanner;
import org.junit.After; import org.junit.After;
import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.CollectionCallback; import org.springframework.data.mongodb.core.CollectionCallback;
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Query;
@ -68,13 +74,25 @@ public class AggregationTests {
@Autowired MongoTemplate mongoTemplate; @Autowired MongoTemplate mongoTemplate;
@Rule public ExpectedException exception = ExpectedException.none();
private static String mongoVersion;
@Before @Before
public void setUp() { public void setUp() {
queryMongoVersionIfNecessary();
cleanDb(); cleanDb();
initSampleDataIfNecessary(); initSampleDataIfNecessary();
} }
private void queryMongoVersionIfNecessary() {
if (mongoVersion == null) {
CommandResult result = mongoTemplate.executeCommand("{ buildInfo: 1 }");
mongoVersion = result.get("version").toString();
}
}
@After @After
public void cleanUp() { public void cleanUp() {
cleanDb(); cleanDb();
@ -85,6 +103,7 @@ public class AggregationTests {
mongoTemplate.dropCollection(Product.class); mongoTemplate.dropCollection(Product.class);
mongoTemplate.dropCollection(UserWithLikes.class); mongoTemplate.dropCollection(UserWithLikes.class);
mongoTemplate.dropCollection(DATAMONGO753.class); mongoTemplate.dropCollection(DATAMONGO753.class);
mongoTemplate.dropCollection(Data.class);
} }
/** /**
@ -97,9 +116,7 @@ public class AggregationTests {
if (!initialized) { if (!initialized) {
CommandResult result = mongoTemplate.executeCommand("{ buildInfo: 1 }"); LOGGER.debug("Server uses MongoDB Version: {}", mongoVersion);
Object version = result.get("version");
LOGGER.debug("Server uses MongoDB Version: {}", version);
mongoTemplate.dropCollection(ZipInfo.class); mongoTemplate.dropCollection(ZipInfo.class);
mongoTemplate.execute(ZipInfo.class, new CollectionCallback<Void>() { mongoTemplate.execute(ZipInfo.class, new CollectionCallback<Void>() {
@ -425,13 +442,8 @@ public class AggregationTests {
@Test @Test
public void arithmenticOperatorsInProjectionExample() { public void arithmenticOperatorsInProjectionExample() {
double taxRate = 0.19; Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19);
double netPrice = 1.99; mongoTemplate.insert(product);
double discountRate = 0.05;
int spaceUnits = 3;
String productId = "P1";
String productName = "A";
mongoTemplate.insert(new Product(productId, productName, netPrice, spaceUnits, discountRate, taxRate));
TypedAggregation<Product> agg = newAggregation(Product.class, // TypedAggregation<Product> agg = newAggregation(Product.class, //
project("name", "netPrice") // project("name", "netPrice") //
@ -451,18 +463,125 @@ public class AggregationTests {
List<DBObject> resultList = result.getMappedResults(); List<DBObject> resultList = result.getMappedResults();
assertThat(resultList, is(notNullValue())); assertThat(resultList, is(notNullValue()));
assertThat((String) resultList.get(0).get("_id"), is(productId)); assertThat((String) resultList.get(0).get("_id"), is(product.id));
assertThat((String) resultList.get(0).get("name"), is(productName)); assertThat((String) resultList.get(0).get("name"), is(product.name));
assertThat((Double) resultList.get(0).get("netPricePlus1"), is(netPrice + 1)); assertThat((Double) resultList.get(0).get("netPricePlus1"), is(product.netPrice + 1));
assertThat((Double) resultList.get(0).get("netPriceMinus1"), is(netPrice - 1)); assertThat((Double) resultList.get(0).get("netPriceMinus1"), is(product.netPrice - 1));
assertThat((Double) resultList.get(0).get("netPriceMul2"), is(netPrice * 2)); assertThat((Double) resultList.get(0).get("netPriceMul2"), is(product.netPrice * 2));
assertThat((Double) resultList.get(0).get("netPriceDiv119"), is(netPrice / 1.19)); assertThat((Double) resultList.get(0).get("netPriceDiv119"), is(product.netPrice / 1.19));
assertThat((Integer) resultList.get(0).get("spaceUnitsMod2"), is(spaceUnits % 2)); assertThat((Integer) resultList.get(0).get("spaceUnitsMod2"), is(product.spaceUnits % 2));
assertThat((Integer) resultList.get(0).get("spaceUnitsPlusSpaceUnits"), is(spaceUnits + spaceUnits)); assertThat((Integer) resultList.get(0).get("spaceUnitsPlusSpaceUnits"), is(product.spaceUnits + product.spaceUnits));
assertThat((Integer) resultList.get(0).get("spaceUnitsMinusSpaceUnits"), is(spaceUnits - spaceUnits)); assertThat((Integer) resultList.get(0).get("spaceUnitsMinusSpaceUnits"),
assertThat((Integer) resultList.get(0).get("spaceUnitsMultiplySpaceUnits"), is(spaceUnits * spaceUnits)); is(product.spaceUnits - product.spaceUnits));
assertThat((Double) resultList.get(0).get("spaceUnitsDivideSpaceUnits"), is((double) (spaceUnits / spaceUnits))); assertThat((Integer) resultList.get(0).get("spaceUnitsMultiplySpaceUnits"), is(product.spaceUnits
assertThat((Integer) resultList.get(0).get("spaceUnitsModSpaceUnits"), is(spaceUnits % 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));
}
/**
* @see DATAMONGO-774
*/
@Test
public void expressionsInProjectionExample() {
Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19);
mongoTemplate.insert(product);
TypedAggregation<Product> 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<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
List<DBObject> 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));
}
/**
* @see DATAMONGO-774
*/
@Test
public void stringExpressionsInProjectionExample() {
Assume.assumeTrue(mongoVersion.startsWith("2.4"));
Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19);
mongoTemplate.insert(product);
TypedAggregation<Product> agg = newAggregation(Product.class, //
project("name", "netPrice") //
.andExpression("concat(name, '_bubu')").as("name_bubu") //
);
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
List<DBObject> 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"));
}
/**
* @see DATAMONGO-774
*/
@Test
public void expressionsInProjectionExampleShowcase() {
Product product = new Product("P1", "A", 1.99, 3, 0.05, 0.19);
mongoTemplate.insert(product);
double shippingCosts = 1.2;
TypedAggregation<Product> agg = newAggregation(Product.class, //
project("name", "netPrice") //
.andExpression("(netPrice * (1-discountRate) + [0]) * (1+taxRate)", shippingCosts).as("salesPrice") //
);
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
List<DBObject> 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<Product> agg = newAggregation(Product.class, //
project("name", "netPrice") //
.andExpression("unknown + 1").as("netPricePlus1") //
);
mongoTemplate.aggregate(agg, DBObject.class);
} }
/** /**
@ -520,6 +639,78 @@ public class AggregationTests {
} }
} }
/**
* @DATAMONGO-774
*/
@Test
public void shouldPerformDateProjectionOperatorsCorrectly() throws ParseException {
Assume.assumeTrue(mongoVersion.startsWith("2.4"));
Data data = new Data();
data.stringValue = "ABC";
mongoTemplate.insert(data);
TypedAggregation<Data> 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<DBObject> 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"));
}
/**
* @DATAMONGO-774
*/
@Test
public void shouldPerformStringProjectionOperatorsCorrectly() throws ParseException {
Assume.assumeTrue(mongoVersion.startsWith("2.4"));
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<Data> 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<DBObject> 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));
}
private void assertLikeStats(LikeStats like, String id, long count) { private void assertLikeStats(LikeStats like, String id, long count) {
assertThat(like, is(notNullValue())); assertThat(like, is(notNullValue()));

31
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/Data.java

@ -0,0 +1,31 @@
/*
* Copyright 2013 the original author 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.Date;
/**
* @author Thomas Darimont
*/
class Data {
public long primitiveLongValue;
public double primitiveDoubleValue;
public Double doubleValue;
public Date dateValue;
public String stringValue;
public DataItem item;
}

23
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DataItem.java

@ -0,0 +1,23 @@
/*
* Copyright 2013 the original author 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;
/**
* @author Thomas Darimont
*/
class DataItem {
int primitiveIntValue;
}

9
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ExposedFieldsUnitTests.java

@ -25,6 +25,7 @@ import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedFi
* Unit tests for {@link ExposedFields}. * Unit tests for {@link ExposedFields}.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Thomas Darimont
*/ */
public class ExposedFieldsUnitTests { public class ExposedFieldsUnitTests {
@ -43,6 +44,14 @@ public class ExposedFieldsUnitTests {
ExposedFields.nonSynthetic(null); ExposedFields.nonSynthetic(null);
} }
@Test
public void mitigateLeadingDollarSignInFieldName() {
ExposedFields fields = ExposedFields.synthetic(Fields.fields("$foo"));
assertThat(fields.iterator().next().getName(), is("$foo"));
assertThat(fields.iterator().next().getTarget(), is("$foo"));
}
@Test @Test
public void exposesSingleField() { public void exposesSingleField() {

4
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GeoNearOperationUnitTests.java

@ -19,7 +19,7 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.mongodb.core.DBObjectUtils; import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.NearQuery;
import com.mongodb.DBObject; import com.mongodb.DBObject;
@ -38,7 +38,7 @@ public class GeoNearOperationUnitTests {
GeoNearOperation operation = new GeoNearOperation(query); GeoNearOperation operation = new GeoNearOperation(query);
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject nearClause = DBObjectUtils.getAsDBObject(dbObject, "$geoNear"); DBObject nearClause = DBObjectTestUtils.getAsDBObject(dbObject, "$geoNear");
assertThat(nearClause, is(query.toDBObject())); assertThat(nearClause, is(query.toDBObject()));
} }
} }

24
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GroupOperationUnitTests.java

@ -20,7 +20,7 @@ import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.aggregation.Fields.*; import static org.springframework.data.mongodb.core.aggregation.Fields.*;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.mongodb.core.DBObjectUtils; import org.springframework.data.mongodb.core.DBObjectTestUtils;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
import com.mongodb.DBObject; import com.mongodb.DBObject;
@ -85,7 +85,7 @@ public class GroupOperationUnitTests {
GroupOperation operation = new GroupOperation(fields("a").and("b", "c")); GroupOperation operation = new GroupOperation(fields("a").and("b", "c"));
DBObject groupClause = extractDbObjectFromGroupOperation(operation); DBObject groupClause = extractDbObjectFromGroupOperation(operation);
DBObject idClause = DBObjectUtils.getAsDBObject(groupClause, UNDERSCORE_ID); DBObject idClause = DBObjectTestUtils.getAsDBObject(groupClause, UNDERSCORE_ID);
assertThat(idClause.get("a"), is((Object) "$a")); assertThat(idClause.get("a"), is((Object) "$a"));
assertThat(idClause.get("b"), is((Object) "$c")); assertThat(idClause.get("b"), is((Object) "$c"));
@ -98,7 +98,7 @@ public class GroupOperationUnitTests {
.sum("e").as("e"); .sum("e").as("e");
DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation);
DBObject eOp = DBObjectUtils.getAsDBObject(groupClause, "e"); DBObject eOp = DBObjectTestUtils.getAsDBObject(groupClause, "e");
assertThat(eOp, is((DBObject) new BasicDBObject("$sum", "$e"))); assertThat(eOp, is((DBObject) new BasicDBObject("$sum", "$e")));
} }
@ -109,7 +109,7 @@ public class GroupOperationUnitTests {
.sum("e").as("ee"); .sum("e").as("ee");
DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation);
DBObject eOp = DBObjectUtils.getAsDBObject(groupClause, "ee"); DBObject eOp = DBObjectTestUtils.getAsDBObject(groupClause, "ee");
assertThat(eOp, is((DBObject) new BasicDBObject("$sum", "$e"))); assertThat(eOp, is((DBObject) new BasicDBObject("$sum", "$e")));
} }
@ -120,7 +120,7 @@ public class GroupOperationUnitTests {
.count().as("count"); .count().as("count");
DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation);
DBObject eOp = DBObjectUtils.getAsDBObject(groupClause, "count"); DBObject eOp = DBObjectTestUtils.getAsDBObject(groupClause, "count");
assertThat(eOp, is((DBObject) new BasicDBObject("$sum", 1))); assertThat(eOp, is((DBObject) new BasicDBObject("$sum", 1)));
} }
@ -132,10 +132,10 @@ public class GroupOperationUnitTests {
.min("e").as("min"); // .min("e").as("min"); //
DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation);
DBObject sum = DBObjectUtils.getAsDBObject(groupClause, "sum"); DBObject sum = DBObjectTestUtils.getAsDBObject(groupClause, "sum");
assertThat(sum, is((DBObject) new BasicDBObject("$sum", "$e"))); assertThat(sum, is((DBObject) new BasicDBObject("$sum", "$e")));
DBObject min = DBObjectUtils.getAsDBObject(groupClause, "min"); DBObject min = DBObjectTestUtils.getAsDBObject(groupClause, "min");
assertThat(min, is((DBObject) new BasicDBObject("$min", "$e"))); assertThat(min, is((DBObject) new BasicDBObject("$min", "$e")));
} }
@ -145,7 +145,7 @@ public class GroupOperationUnitTests {
GroupOperation groupOperation = Aggregation.group("a", "b").push(1).as("x"); GroupOperation groupOperation = Aggregation.group("a", "b").push(1).as("x");
DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation);
DBObject push = DBObjectUtils.getAsDBObject(groupClause, "x"); DBObject push = DBObjectTestUtils.getAsDBObject(groupClause, "x");
assertThat(push, is((DBObject) new BasicDBObject("$push", 1))); assertThat(push, is((DBObject) new BasicDBObject("$push", 1)));
} }
@ -156,7 +156,7 @@ public class GroupOperationUnitTests {
GroupOperation groupOperation = Aggregation.group("a", "b").push("ref").as("x"); GroupOperation groupOperation = Aggregation.group("a", "b").push("ref").as("x");
DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation);
DBObject push = DBObjectUtils.getAsDBObject(groupClause, "x"); DBObject push = DBObjectTestUtils.getAsDBObject(groupClause, "x");
assertThat(push, is((DBObject) new BasicDBObject("$push", "$ref"))); assertThat(push, is((DBObject) new BasicDBObject("$push", "$ref")));
} }
@ -167,7 +167,7 @@ public class GroupOperationUnitTests {
GroupOperation groupOperation = Aggregation.group("a", "b").addToSet("ref").as("x"); GroupOperation groupOperation = Aggregation.group("a", "b").addToSet("ref").as("x");
DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation);
DBObject push = DBObjectUtils.getAsDBObject(groupClause, "x"); DBObject push = DBObjectTestUtils.getAsDBObject(groupClause, "x");
assertThat(push, is((DBObject) new BasicDBObject("$addToSet", "$ref"))); assertThat(push, is((DBObject) new BasicDBObject("$addToSet", "$ref")));
} }
@ -178,14 +178,14 @@ public class GroupOperationUnitTests {
GroupOperation groupOperation = Aggregation.group("a", "b").addToSet(42).as("x"); GroupOperation groupOperation = Aggregation.group("a", "b").addToSet(42).as("x");
DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation); DBObject groupClause = extractDbObjectFromGroupOperation(groupOperation);
DBObject push = DBObjectUtils.getAsDBObject(groupClause, "x"); DBObject push = DBObjectTestUtils.getAsDBObject(groupClause, "x");
assertThat(push, is((DBObject) new BasicDBObject("$addToSet", 42))); assertThat(push, is((DBObject) new BasicDBObject("$addToSet", 42)));
} }
private DBObject extractDbObjectFromGroupOperation(GroupOperation groupOperation) { private DBObject extractDbObjectFromGroupOperation(GroupOperation groupOperation) {
DBObject dbObject = groupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = groupOperation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject groupClause = DBObjectUtils.getAsDBObject(dbObject, "$group"); DBObject groupClause = DBObjectTestUtils.getAsDBObject(dbObject, "$group");
return groupClause; return groupClause;
} }
} }

52
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java

@ -17,11 +17,12 @@ package org.springframework.data.mongodb.core.aggregation;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.springframework.data.mongodb.util.DBObjectUtils.*;
import java.util.Arrays; import java.util.Arrays;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.mongodb.core.DBObjectUtils; import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder;
import com.mongodb.BasicDBList; import com.mongodb.BasicDBList;
@ -55,7 +56,7 @@ public class ProjectionOperationUnitTests {
operation = operation.and("prop").previousOperation(); operation = operation.and("prop").previousOperation();
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
assertThat(projectClause.get("prop"), is((Object) Fields.UNDERSCORE_ID_REF)); assertThat(projectClause.get("prop"), is((Object) Fields.UNDERSCORE_ID_REF));
} }
@ -65,7 +66,7 @@ public class ProjectionOperationUnitTests {
ProjectionOperation operation = new ProjectionOperation(Fields.fields("foo").and("bar", "foobar")); ProjectionOperation operation = new ProjectionOperation(Fields.fields("foo").and("bar", "foobar"));
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
assertThat((Integer) projectClause.get("foo"), is(1)); assertThat((Integer) projectClause.get("foo"), is(1));
assertThat(projectClause.get("bar"), is((Object) "$foobar")); assertThat(projectClause.get("bar"), is((Object) "$foobar"));
@ -77,7 +78,7 @@ public class ProjectionOperationUnitTests {
ProjectionOperation operation = new ProjectionOperation(); ProjectionOperation operation = new ProjectionOperation();
DBObject dbObject = operation.and("foo").as("bar").toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.and("foo").as("bar").toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
assertThat(projectClause.get("bar"), is((Object) "$foo")); assertThat(projectClause.get("bar"), is((Object) "$foo"));
} }
@ -88,9 +89,9 @@ public class ProjectionOperationUnitTests {
ProjectionOperation operation = new ProjectionOperation(); ProjectionOperation operation = new ProjectionOperation();
DBObject dbObject = operation.and("foo").plus(41).as("bar").toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.and("foo").plus(41).as("bar").toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
DBObject barClause = DBObjectUtils.getAsDBObject(projectClause, "bar"); DBObject barClause = DBObjectTestUtils.getAsDBObject(projectClause, "bar");
BasicDBList addClause = DBObjectUtils.getAsDBList(barClause, "$add"); BasicDBList addClause = DBObjectTestUtils.getAsDBList(barClause, "$add");
assertThat(addClause, hasSize(2)); assertThat(addClause, hasSize(2));
assertThat(addClause.get(0), is((Object) "$foo")); assertThat(addClause.get(0), is((Object) "$foo"));
@ -102,7 +103,7 @@ public class ProjectionOperationUnitTests {
String fieldName = "a"; String fieldName = "a";
ProjectionOperationBuilder operation = new ProjectionOperation().and(fieldName).plus(1); ProjectionOperationBuilder operation = new ProjectionOperation().and(fieldName).plus(1);
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
DBObject oper = exctractOperation(fieldName, projectClause); DBObject oper = exctractOperation(fieldName, projectClause);
assertThat(oper.containsField(ADD), is(true)); assertThat(oper.containsField(ADD), is(true));
@ -116,7 +117,7 @@ public class ProjectionOperationUnitTests {
String fieldAlias = "b"; String fieldAlias = "b";
ProjectionOperation operation = new ProjectionOperation().and(fieldName).plus(1).as(fieldAlias); ProjectionOperation operation = new ProjectionOperation().and(fieldName).plus(1).as(fieldAlias);
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
DBObject oper = exctractOperation(fieldAlias, projectClause); DBObject oper = exctractOperation(fieldAlias, projectClause);
assertThat(oper.containsField(ADD), is(true)); assertThat(oper.containsField(ADD), is(true));
@ -130,7 +131,7 @@ public class ProjectionOperationUnitTests {
String fieldAlias = "b"; String fieldAlias = "b";
ProjectionOperation operation = new ProjectionOperation().and(fieldName).minus(1).as(fieldAlias); ProjectionOperation operation = new ProjectionOperation().and(fieldName).minus(1).as(fieldAlias);
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
DBObject oper = exctractOperation(fieldAlias, projectClause); DBObject oper = exctractOperation(fieldAlias, projectClause);
assertThat(oper.containsField(SUBTRACT), is(true)); assertThat(oper.containsField(SUBTRACT), is(true));
@ -144,7 +145,7 @@ public class ProjectionOperationUnitTests {
String fieldAlias = "b"; String fieldAlias = "b";
ProjectionOperation operation = new ProjectionOperation().and(fieldName).multiply(1).as(fieldAlias); ProjectionOperation operation = new ProjectionOperation().and(fieldName).multiply(1).as(fieldAlias);
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
DBObject oper = exctractOperation(fieldAlias, projectClause); DBObject oper = exctractOperation(fieldAlias, projectClause);
assertThat(oper.containsField(MULTIPLY), is(true)); assertThat(oper.containsField(MULTIPLY), is(true));
@ -158,7 +159,7 @@ public class ProjectionOperationUnitTests {
String fieldAlias = "b"; String fieldAlias = "b";
ProjectionOperation operation = new ProjectionOperation().and(fieldName).divide(1).as(fieldAlias); ProjectionOperation operation = new ProjectionOperation().and(fieldName).divide(1).as(fieldAlias);
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
DBObject oper = exctractOperation(fieldAlias, projectClause); DBObject oper = exctractOperation(fieldAlias, projectClause);
assertThat(oper.containsField(DIVIDE), is(true)); assertThat(oper.containsField(DIVIDE), is(true));
@ -178,7 +179,7 @@ public class ProjectionOperationUnitTests {
String fieldAlias = "b"; String fieldAlias = "b";
ProjectionOperation operation = new ProjectionOperation().and(fieldName).mod(3).as(fieldAlias); ProjectionOperation operation = new ProjectionOperation().and(fieldName).mod(3).as(fieldAlias);
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
DBObject oper = exctractOperation(fieldAlias, projectClause); DBObject oper = exctractOperation(fieldAlias, projectClause);
assertThat(oper.containsField(MOD), is(true)); assertThat(oper.containsField(MOD), is(true));
@ -202,7 +203,7 @@ public class ProjectionOperationUnitTests {
ProjectionOperation projectionOp = new ProjectionOperation().andExclude(Fields.UNDERSCORE_ID); ProjectionOperation projectionOp = new ProjectionOperation().andExclude(Fields.UNDERSCORE_ID);
DBObject dbObject = projectionOp.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = projectionOp.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
assertThat((Integer) projectClause.get(Fields.UNDERSCORE_ID), is(0)); assertThat((Integer) projectClause.get(Fields.UNDERSCORE_ID), is(0));
} }
@ -216,7 +217,7 @@ public class ProjectionOperationUnitTests {
.andExclude("_id"); .andExclude("_id");
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
assertThat(projectClause.get("foo"), is((Object) 1)); // implicit assertThat(projectClause.get("foo"), is((Object) 1)); // implicit
assertThat(projectClause.get("bar"), is((Object) "$foobar")); // explicit assertThat(projectClause.get("bar"), is((Object) "$foobar")); // explicit
@ -245,7 +246,7 @@ public class ProjectionOperationUnitTests {
.and("foo").mod("bar").as("fooModBar"); .and("foo").mod("bar").as("fooModBar");
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
assertThat((BasicDBObject) projectClause.get("fooPlusBar"), // assertThat((BasicDBObject) projectClause.get("fooPlusBar"), //
is(new BasicDBObject("$add", dbList("$foo", "$bar")))); is(new BasicDBObject("$add", dbList("$foo", "$bar"))));
@ -259,13 +260,20 @@ public class ProjectionOperationUnitTests {
is(new BasicDBObject("$mod", dbList("$foo", "$bar")))); is(new BasicDBObject("$mod", dbList("$foo", "$bar"))));
} }
public static BasicDBList dbList(Object... items) { /**
* @see DATAMONGO-774
*/
@Test
public void projectionExpressions() {
BasicDBList list = new BasicDBList(); ProjectionOperation operation = Aggregation.project() //
for (Object item : items) { .andExpression("(netPrice + surCharge) * taxrate * [0]", 2).as("grossSalesPrice") //
list.add(item); .and("foo").as("bar"); //
}
return list; DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
assertThat(
dbObject.toString(),
is("{ \"$project\" : { \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"bar\" : \"$foo\"}}"));
} }
private static DBObject exctractOperation(String field, DBObject fromProjectClause) { private static DBObject exctractOperation(String field, DBObject fromProjectClause) {

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SortOperationUnitTests.java

@ -17,7 +17,7 @@ package org.springframework.data.mongodb.core.aggregation;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.DBObjectUtils.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;

70
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionToMongoExpressionTransformerIntegrationTests.java

@ -0,0 +1,70 @@
/*
* Copyright 2013 the original author 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.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.data.mapping.model.MappingException;
import org.springframework.data.mongodb.MongoDbFactory;
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 org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration tests for {@link SpelExpressionToMongoExpressionTransformer}.
*
* @author Thomas Darimont
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
public class SpelExpressionToMongoExpressionTransformerIntegrationTests {
@Autowired MongoDbFactory mongoDbFactory;
@Rule public ExpectedException exception = ExpectedException.none();
SpelExpressionToMongoExpressionTransformer transformer = SpelExpressionToMongoExpressionTransformer.INSTANCE;
@Test
public void shouldConvertCompoundExpressionToPropertyPath() {
MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory, new MongoMappingContext());
TypeBasedAggregationOperationContext ctxt = new TypeBasedAggregationOperationContext(Data.class,
new MongoMappingContext(), new QueryMapper(converter));
assertThat(transformer.transform("item.primitiveIntValue", ctxt, new Object[0]).toString(),
is("$item.primitiveIntValue"));
}
@Test
public void shouldThrowExceptionIfNestedPropertyCannotBeFound() {
exception.expect(MappingException.class);
exception.expectMessage("value2");
MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory, new MongoMappingContext());
TypeBasedAggregationOperationContext ctxt = new TypeBasedAggregationOperationContext(Data.class,
new MongoMappingContext(), new QueryMapper(converter));
assertThat(transformer.transform("item.value2", ctxt, new Object[0]).toString(), is("$item.value2"));
}
}

179
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionToMongoExpressionTransformerTests.java

@ -0,0 +1,179 @@
/*
* Copyright 2013 the original author 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.Before;
import org.junit.Ignore;
import org.junit.Test;
/**
* Unit tests for {@link SpelExpressionToMongoExpressionTransformer}.
*
* @author Thomas Darimont
*/
public class SpelExpressionToMongoExpressionTransformerTests {
SpelExpressionToMongoExpressionTransformer transformer = SpelExpressionToMongoExpressionTransformer.INSTANCE;
Data data;
@Before
public void setup() {
this.data = new Data();
this.data.primitiveLongValue = 42;
this.data.primitiveDoubleValue = 1.2345;
this.data.doubleValue = 23.0;
this.data.item = new DataItem();
this.data.item.primitiveIntValue = 21;
}
@Test
public void shouldRenderConstantExpression() {
assertThat(transformer.transform("1").toString(), is("1"));
assertThat(transformer.transform("-1").toString(), is("-1"));
assertThat(transformer.transform("1.0").toString(), is("1.0"));
assertThat(transformer.transform("-1.0").toString(), is("-1.0"));
assertThat(String.valueOf(transformer.transform("null")), is("null"));
}
@Test
public void shouldSupportKnownOperands() {
assertThat(transformer.transform("a + b").toString(), is("{ \"$add\" : [ \"$a\" , \"$b\"]}"));
assertThat(transformer.transform("a - b").toString(), is("{ \"$subtract\" : [ \"$a\" , \"$b\"]}"));
assertThat(transformer.transform("a * b").toString(), is("{ \"$multiply\" : [ \"$a\" , \"$b\"]}"));
assertThat(transformer.transform("a / b").toString(), is("{ \"$divide\" : [ \"$a\" , \"$b\"]}"));
assertThat(transformer.transform("a % b").toString(), is("{ \"$mod\" : [ \"$a\" , \"$b\"]}"));
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnUnknownOperand() {
transformer.transform("a ^ 1");
}
@Test
public void shouldRenderSumExpression() {
assertThat(transformer.transform("a + 1").toString(), is("{ \"$add\" : [ \"$a\" , 1]}"));
}
@Test
public void shouldRenderFormula() {
assertThat(
transformer.transform("(netPrice + surCharge) * taxrate + 42").toString(),
is("{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}"));
}
@Test
public void shouldRenderFormulaInCurlyBrackets() {
assertThat(
transformer.transform("{(netPrice + surCharge) * taxrate + 42}").toString(),
is("{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}"));
}
@Test
public void shouldRenderFieldReference() {
assertThat(transformer.transform("foo").toString(), is("$foo"));
assertThat(transformer.transform("$foo").toString(), is("$foo"));
}
@Test
public void shouldRenderNestedFieldReference() {
assertThat(transformer.transform("foo.bar").toString(), is("$foo.bar"));
assertThat(transformer.transform("$foo.bar").toString(), is("$foo.bar"));
}
@Test
@Ignore
public void shouldRenderNestedIndexedFieldReference() {
// TODO add support for rendering nested indexed field references
assertThat(transformer.transform("foo[3].bar").toString(), is("$foo[3].bar"));
}
@Test
public void shouldRenderConsecutiveOperation() {
assertThat(transformer.transform("1 + 1 + 1").toString(), is("{ \"$add\" : [ 1 , 1 , 1]}"));
}
@Test
public void shouldRenderComplexExpression0() {
assertThat(transformer.transform("-(1 + q)").toString(),
is("{ \"$multiply\" : [ -1 , { \"$add\" : [ 1 , \"$q\"]}]}"));
}
@Test
public void shouldRenderComplexExpression1() {
assertThat(transformer.transform("1 + (q + 1) / (q - 1)").toString(),
is("{ \"$add\" : [ 1 , { \"$divide\" : [ { \"$add\" : [ \"$q\" , 1]} , { \"$subtract\" : [ \"$q\" , 1]}]}]}"));
}
@Test
public void shouldRenderComplexExpression2() {
assertThat(
transformer.transform("(q + 1 + 4 - 5) / (q + 1 + 3 + 4)").toString(),
is("{ \"$divide\" : [ { \"$subtract\" : [ { \"$add\" : [ \"$q\" , 1 , 4]} , 5]} , { \"$add\" : [ \"$q\" , 1 , 3 , 4]}]}"));
}
@Test
public void shouldRenderBinaryExpressionWithMixedSignsCorrectly() {
assertThat(transformer.transform("-4 + 1").toString(), is("{ \"$add\" : [ -4 , 1]}"));
assertThat(transformer.transform("1 + -4").toString(), is("{ \"$add\" : [ 1 , -4]}"));
}
@Test
public void shouldRenderConsecutiveOperationsInComplexExpression() {
assertThat(transformer.transform("1 + 1 + (1 + 1 + 1) / q").toString(),
is("{ \"$add\" : [ 1 , 1 , { \"$divide\" : [ { \"$add\" : [ 1 , 1 , 1]} , \"$q\"]}]}"));
}
@Test
public void shouldRenderParameterExpressionResults() {
assertThat(transformer.transform("[0] + [1] + [2]", 1, 2, 3).toString(), is("{ \"$add\" : [ 1 , 2 , 3]}"));
}
@Test
public void shouldRenderNestedParameterExpressionResults() {
assertThat(
transformer.transform("[0].primitiveLongValue + [0].primitiveDoubleValue + [0].doubleValue.longValue()", data)
.toString(), is("{ \"$add\" : [ 42 , 1.2345 , 23]}"));
}
@Test
public void shouldRenderNestedParameterExpressionResultsInNestedExpressions() {
assertThat(
transformer.transform(
"((1 + [0].primitiveLongValue) + [0].primitiveDoubleValue) * [0].doubleValue.longValue()", data).toString(),
is("{ \"$multiply\" : [ { \"$add\" : [ 1 , 42 , 1.2345]} , 23]}"));
}
@Test
public void shouldRenderStringFunctions() {
assertThat(transformer.transform("concat(a, b)").toString(), is("{ \"$concat\" : [ \"$a\" , \"$b\"]}"));
assertThat(transformer.transform("substr(a, 1, 2)").toString(), is("{ \"$substr\" : [ \"$a\" , 1 , 2]}"));
assertThat(transformer.transform("strcasecmp(a, b)").toString(), is("{ \"$strcasecmp\" : [ \"$a\" , \"$b\"]}"));
assertThat(transformer.transform("toLower(a)").toString(), is("{ \"$toLower\" : [ \"$a\"]}"));
assertThat(transformer.transform("toUpper(a)").toString(), is("{ \"$toUpper\" : [ \"$a\"]}"));
assertThat(transformer.transform("toUpper(toLower(a))").toString(),
is("{ \"$toUpper\" : [ { \"$toLower\" : [ \"$a\"]}]}"));
}
}

4
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DBObjectAccessorUnitTests.java

@ -19,7 +19,7 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.mongodb.core.DBObjectUtils; import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
@ -49,7 +49,7 @@ public class DBObjectAccessorUnitTests {
DBObjectAccessor accessor = new DBObjectAccessor(dbObject); DBObjectAccessor accessor = new DBObjectAccessor(dbObject);
accessor.put(fooProperty, "FooBar"); accessor.put(fooProperty, "FooBar");
DBObject aDbObject = DBObjectUtils.getAsDBObject(dbObject, "a"); DBObject aDbObject = DBObjectTestUtils.getAsDBObject(dbObject, "a");
assertThat(aDbObject.get("b"), is((Object) "FooBar")); assertThat(aDbObject.get("b"), is((Object) "FooBar"));
} }

6
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultMongoTypeMapperUnitTests.java

@ -26,7 +26,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.convert.ConfigurableTypeInformationMapper; import org.springframework.data.convert.ConfigurableTypeInformationMapper;
import org.springframework.data.convert.SimpleTypeInformationMapper; import org.springframework.data.convert.SimpleTypeInformationMapper;
import org.springframework.data.mongodb.core.DBObjectUtils; import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
import com.mongodb.BasicDBList; import com.mongodb.BasicDBList;
@ -119,8 +119,8 @@ public class DefaultMongoTypeMapperUnitTests {
typeMapper = new DefaultMongoTypeMapper(); typeMapper = new DefaultMongoTypeMapper();
typeMapper.writeTypeRestrictions(result, Collections.<Class<?>> singleton(String.class)); typeMapper.writeTypeRestrictions(result, Collections.<Class<?>> singleton(String.class));
DBObject typeInfo = DBObjectUtils.getAsDBObject(result, DefaultMongoTypeMapper.DEFAULT_TYPE_KEY); DBObject typeInfo = DBObjectTestUtils.getAsDBObject(result, DefaultMongoTypeMapper.DEFAULT_TYPE_KEY);
List<Object> aliases = DBObjectUtils.getAsDBList(typeInfo, "$in"); List<Object> aliases = DBObjectTestUtils.getAsDBList(typeInfo, "$in");
assertThat(aliases, hasSize(1)); assertThat(aliases, hasSize(1));
assertThat(aliases.get(0), is((Object) String.class.getName())); assertThat(aliases.get(0), is((Object) String.class.getName()));
} }

4
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java

@ -53,7 +53,7 @@ import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.model.MappingInstantiationException; import org.springframework.data.mapping.model.MappingInstantiationException;
import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.DBObjectUtils; import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.convert.DBObjectAccessorUnitTests.NestedType; import org.springframework.data.mongodb.core.convert.DBObjectAccessorUnitTests.NestedType;
import org.springframework.data.mongodb.core.convert.DBObjectAccessorUnitTests.ProjectingType; import org.springframework.data.mongodb.core.convert.DBObjectAccessorUnitTests.ProjectingType;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
@ -1446,7 +1446,7 @@ public class MappingMongoConverterUnitTests {
converter.write(type, result); converter.write(type, result);
assertThat(result.get("name"), is((Object) "name")); assertThat(result.get("name"), is((Object) "name"));
DBObject aValue = DBObjectUtils.getAsDBObject(result, "a"); DBObject aValue = DBObjectTestUtils.getAsDBObject(result, "a");
assertThat(aValue.get("b"), is((Object) "bar")); assertThat(aValue.get("b"), is((Object) "bar"));
assertThat(aValue.get("c"), is((Object) "C")); assertThat(aValue.get("c"), is((Object) "C"));
} }

6
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java

@ -17,7 +17,7 @@ package org.springframework.data.mongodb.core.convert;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.springframework.data.mongodb.core.DBObjectUtils.*; import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*; import static org.springframework.data.mongodb.core.query.Query.*;
@ -35,7 +35,7 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.DBObjectUtils; import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.Person; import org.springframework.data.mongodb.core.Person;
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.DBRef;
@ -333,7 +333,7 @@ public class QueryMapperUnitTests {
Query query = query(where("reference").in(first, second)); Query query = query(where("reference").in(first, second));
DBObject result = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(WithDBRef.class)); DBObject result = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(WithDBRef.class));
DBObject reference = DBObjectUtils.getAsDBObject(result, "reference"); DBObject reference = DBObjectTestUtils.getAsDBObject(result, "reference");
BasicDBList inClause = getAsDBList(reference, "$in"); BasicDBList inClause = getAsDBList(reference, "$in");
assertThat(inClause, hasSize(2)); assertThat(inClause, hasSize(2));

6
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java

@ -26,7 +26,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.DBObjectUtils; import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.core.query.Update;
@ -62,8 +62,8 @@ public class UpdateMapperUnitTests {
DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(), DBObject mappedObject = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(ParentClass.class)); context.getPersistentEntity(ParentClass.class));
DBObject push = DBObjectUtils.getAsDBObject(mappedObject, "$push"); DBObject push = DBObjectTestUtils.getAsDBObject(mappedObject, "$push");
DBObject list = DBObjectUtils.getAsDBObject(push, "list"); DBObject list = DBObjectTestUtils.getAsDBObject(push, "list");
assertThat(list.get("_class"), is((Object) ConcreteChildClass.class.getName())); assertThat(list.get("_class"), is((Object) ConcreteChildClass.class.getName()));
} }

Loading…
Cancel
Save