Browse Source
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
21 changed files with 1385 additions and 80 deletions
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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")); |
||||||
|
} |
||||||
|
} |
||||||
@ -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\"]}]}")); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue