Skip to content

Commit a741400

Browse files
DATAMONGO-1530 - Polishing.
Add missing transformations for ConstructorReference, OperatorNot, OpNE, OpEQ, OpGT, OpGE, OpLT, OpLE, OperatorPower, OpOr and OpAnd. This allows usage of logical operators &, || and ! as part of the expression, while ConstructorReference allows instantiating eg. arrays via an expression `new int[]{4,5,6}`. This can be useful eg. comparing arrays using $setEquals. More complex aggregation operators like $filter can be created by defining the variable references as string inside the expression like filter(a, 'num', '$$num' > 10). Commands like $let requires usage of InlineMap to pass in required arguments like eg. let({low:1, high:'$$low'}, gt('$$low', '$$high')). Original Pull Request: #410
1 parent b786b82 commit a741400

File tree

8 files changed

+1248
-123
lines changed

8 files changed

+1248
-123
lines changed

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

Lines changed: 125 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2014 the original author or authors.
2+
* Copyright 2013-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,19 +26,26 @@
2626
import org.springframework.data.mongodb.core.spel.ExpressionTransformationContextSupport;
2727
import org.springframework.data.mongodb.core.spel.LiteralNode;
2828
import org.springframework.data.mongodb.core.spel.MethodReferenceNode;
29+
import org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference;
30+
import org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference.ArgumentType;
31+
import org.springframework.data.mongodb.core.spel.NotOperatorNode;
2932
import org.springframework.data.mongodb.core.spel.OperatorNode;
3033
import org.springframework.expression.spel.ExpressionState;
3134
import org.springframework.expression.spel.SpelNode;
3235
import org.springframework.expression.spel.SpelParserConfiguration;
3336
import org.springframework.expression.spel.ast.CompoundExpression;
37+
import org.springframework.expression.spel.ast.ConstructorReference;
3438
import org.springframework.expression.spel.ast.Indexer;
3539
import org.springframework.expression.spel.ast.InlineList;
40+
import org.springframework.expression.spel.ast.InlineMap;
41+
import org.springframework.expression.spel.ast.OperatorNot;
3642
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
3743
import org.springframework.expression.spel.standard.SpelExpression;
3844
import org.springframework.expression.spel.standard.SpelExpressionParser;
3945
import org.springframework.expression.spel.support.StandardEvaluationContext;
4046
import org.springframework.util.Assert;
4147
import org.springframework.util.NumberUtils;
48+
import org.springframework.util.ObjectUtils;
4249

4350
import com.mongodb.BasicDBList;
4451
import com.mongodb.BasicDBObject;
@@ -48,6 +55,7 @@
4855
* Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression.
4956
*
5057
* @author Thomas Darimont
58+
* @author Christoph Strobl
5159
*/
5260
class SpelExpressionTransformer implements AggregationExpressionTransformer {
5361

@@ -69,6 +77,8 @@ public SpelExpressionTransformer() {
6977
conversions.add(new PropertyOrFieldReferenceNodeConversion(this));
7078
conversions.add(new CompoundExpressionNodeConversion(this));
7179
conversions.add(new MethodReferenceNodeConversion(this));
80+
conversions.add(new NotOperatorNodeConversion(this));
81+
conversions.add(new ValueRetrievingNodeConversion(this));
7282

7383
this.conversions = Collections.unmodifiableList(conversions);
7484
}
@@ -131,8 +141,8 @@ private ExpressionNodeConversion<ExpressionNode> lookupConversionFor(ExpressionN
131141
* @author Thomas Darimont
132142
* @author Oliver Gierke
133143
*/
134-
private static abstract class ExpressionNodeConversion<T extends ExpressionNode> implements
135-
AggregationExpressionTransformer {
144+
private static abstract class ExpressionNodeConversion<T extends ExpressionNode>
145+
implements AggregationExpressionTransformer {
136146

137147
private final AggregationExpressionTransformer transformer;
138148
private final Class<? extends ExpressionNode> nodeType;
@@ -235,8 +245,17 @@ public OperatorNodeConversion(AggregationExpressionTransformer transformer) {
235245
protected Object convert(AggregationExpressionTransformationContext<OperatorNode> context) {
236246

237247
OperatorNode currentNode = context.getCurrentNode();
238-
239248
DBObject operationObject = createOperationObjectAndAddToPreviousArgumentsIfNecessary(context, currentNode);
249+
250+
if (currentNode.isLogicalOperator()) {
251+
252+
for (ExpressionNode expressionNode : currentNode) {
253+
transform(expressionNode, currentNode, operationObject, context);
254+
}
255+
256+
return operationObject;
257+
}
258+
240259
Object leftResult = transform(currentNode.getLeft(), currentNode, operationObject, context);
241260

242261
if (currentNode.isUnaryMinus()) {
@@ -271,7 +290,8 @@ private DBObject createOperationObjectAndAddToPreviousArgumentsIfNecessary(
271290
return nextDbObject;
272291
}
273292

274-
private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<OperatorNode> context, Object leftResult) {
293+
private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<OperatorNode> context,
294+
Object leftResult) {
275295

276296
Object result = leftResult instanceof Number ? leftResult
277297
: new BasicDBObject("$multiply", dbList(-1, leftResult));
@@ -289,7 +309,7 @@ private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<Operat
289309
*/
290310
@Override
291311
protected boolean supports(ExpressionNode node) {
292-
return node.isMathematicalOperation();
312+
return node.isMathematicalOperation() || node.isLogicalOperator();
293313
}
294314
}
295315

@@ -462,13 +482,31 @@ public MethodReferenceNodeConversion(AggregationExpressionTransformer transforme
462482
protected Object convert(AggregationExpressionTransformationContext<MethodReferenceNode> context) {
463483

464484
MethodReferenceNode node = context.getCurrentNode();
465-
List<Object> args = new ArrayList<Object>();
485+
AggregationMethodReference methodReference = node.getMethodReference();
466486

467-
for (ExpressionNode childNode : node) {
468-
args.add(transform(childNode, context));
487+
Object args = null;
488+
489+
if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.SINGLE)) {
490+
args = transform(node.getChild(0), context);
491+
} else if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.MAP)) {
492+
493+
DBObject dbo = new BasicDBObject();
494+
for (int i = 0; i < methodReference.getArgumentMap().length; i++) {
495+
dbo.put(methodReference.getArgumentMap()[i], transform(node.getChild(i), context));
496+
}
497+
args = dbo;
498+
} else {
499+
500+
List<Object> argList = new ArrayList<Object>();
501+
502+
for (ExpressionNode childNode : node) {
503+
argList.add(transform(childNode, context));
504+
}
505+
506+
args = dbList(argList.toArray());
469507
}
470508

471-
return context.addToPreviousOrReturn(new BasicDBObject(node.getMethodName(), dbList(args.toArray())));
509+
return context.addToPreviousOrReturn(new BasicDBObject(methodReference.getMongoOperator(), args));
472510
}
473511
}
474512

@@ -510,4 +548,81 @@ protected boolean supports(ExpressionNode node) {
510548
return node.isOfType(CompoundExpression.class);
511549
}
512550
}
551+
552+
/**
553+
* @author Christoph Strobl
554+
* @since 1.10
555+
*/
556+
static class NotOperatorNodeConversion extends ExpressionNodeConversion<NotOperatorNode> {
557+
558+
/**
559+
* Creates a new {@link ExpressionNodeConversion}.
560+
*
561+
* @param transformer must not be {@literal null}.
562+
*/
563+
public NotOperatorNodeConversion(AggregationExpressionTransformer transformer) {
564+
super(transformer);
565+
}
566+
567+
/*
568+
* (non-Javadoc)
569+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
570+
*/
571+
@Override
572+
protected Object convert(AggregationExpressionTransformationContext<NotOperatorNode> context) {
573+
574+
NotOperatorNode node = context.getCurrentNode();
575+
List<Object> args = new ArrayList<Object>();
576+
577+
for (ExpressionNode childNode : node) {
578+
args.add(transform(childNode, context));
579+
}
580+
581+
return context.addToPreviousOrReturn(new BasicDBObject(node.getMongoOperator(), dbList(args.toArray())));
582+
}
583+
584+
/*
585+
* (non-Javadoc)
586+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
587+
*/
588+
@Override
589+
protected boolean supports(ExpressionNode node) {
590+
return node.isOfType(OperatorNot.class);
591+
}
592+
}
593+
594+
/**
595+
* @author Christoph Strobl
596+
* @since 1.10
597+
*/
598+
static class ValueRetrievingNodeConversion extends ExpressionNodeConversion<ExpressionNode> {
599+
600+
/**
601+
* Creates a new {@link ExpressionNodeConversion}.
602+
*
603+
* @param transformer must not be {@literal null}.
604+
*/
605+
public ValueRetrievingNodeConversion(AggregationExpressionTransformer transformer) {
606+
super(transformer);
607+
}
608+
609+
/*
610+
* (non-Javadoc)
611+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
612+
*/
613+
@Override
614+
protected Object convert(AggregationExpressionTransformationContext<ExpressionNode> context) {
615+
return context.getCurrentNode().getValue();
616+
}
617+
618+
/*
619+
* (non-Javadoc)
620+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
621+
*/
622+
@Override
623+
protected boolean supports(ExpressionNode node) {
624+
return node.isOfType(InlineMap.class) || node.isOfType(InlineList.class)
625+
|| node.isOfType(ConstructorReference.class);
626+
}
627+
}
513628
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013 the original author or authors.
2+
* Copyright 2013-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,12 +23,14 @@
2323
import org.springframework.expression.spel.ast.Literal;
2424
import org.springframework.expression.spel.ast.MethodReference;
2525
import org.springframework.expression.spel.ast.Operator;
26+
import org.springframework.expression.spel.ast.OperatorNot;
2627
import org.springframework.util.Assert;
2728

2829
/**
2930
* A value object for nodes in an expression. Allows iterating ove potentially available child {@link ExpressionNode}s.
3031
*
3132
* @author Oliver Gierke
33+
* @author Christoph Strobl
3234
*/
3335
public class ExpressionNode implements Iterable<ExpressionNode> {
3436

@@ -79,6 +81,10 @@ public static ExpressionNode from(SpelNode node, ExpressionState state) {
7981
return new LiteralNode((Literal) node, state);
8082
}
8183

84+
if (node instanceof OperatorNot) {
85+
return new NotOperatorNode((OperatorNot) node, state);
86+
}
87+
8288
return new ExpressionNode(node, state);
8389
}
8490

@@ -122,6 +128,16 @@ public boolean isMathematicalOperation() {
122128
return false;
123129
}
124130

131+
/**
132+
* Returns whether the {@link ExpressionNode} is a logical conjunction operation like {@code &&, ||}.
133+
*
134+
* @return
135+
* @since 1.10
136+
*/
137+
public boolean isLogicalOperator() {
138+
return false;
139+
}
140+
125141
/**
126142
* Returns whether the {@link ExpressionNode} is a literal.
127143
*

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013 the original author or authors.
2+
* Copyright 2013-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,7 +15,12 @@
1515
*/
1616
package org.springframework.data.mongodb.core.spel;
1717

18+
import java.util.Collections;
19+
import java.util.HashSet;
20+
import java.util.Set;
21+
1822
import org.springframework.expression.spel.ExpressionState;
23+
import org.springframework.expression.spel.ast.BooleanLiteral;
1924
import org.springframework.expression.spel.ast.FloatLiteral;
2025
import org.springframework.expression.spel.ast.IntLiteral;
2126
import org.springframework.expression.spel.ast.Literal;
@@ -26,13 +31,29 @@
2631

2732
/**
2833
* A node representing a literal in an expression.
29-
*
34+
*
3035
* @author Oliver Gierke
36+
* @author Christoph Strobl
3137
*/
3238
public class LiteralNode extends ExpressionNode {
3339

40+
private static final Set<Class<?>> SUPPORTED_LITERAL_TYPES;
3441
private final Literal literal;
3542

43+
static {
44+
45+
Set<Class<?>> supportedTypes = new HashSet<Class<?>>(7, 1);
46+
supportedTypes.add(BooleanLiteral.class);
47+
supportedTypes.add(FloatLiteral.class);
48+
supportedTypes.add(IntLiteral.class);
49+
supportedTypes.add(LongLiteral.class);
50+
supportedTypes.add(NullLiteral.class);
51+
supportedTypes.add(RealLiteral.class);
52+
supportedTypes.add(StringLiteral.class);
53+
54+
SUPPORTED_LITERAL_TYPES = Collections.unmodifiableSet(supportedTypes);
55+
}
56+
3657
/**
3758
* Creates a new {@link LiteralNode} from the given {@link Literal} and {@link ExpressionState}.
3859
*
@@ -66,7 +87,6 @@ public boolean isUnaryMinus(ExpressionNode parent) {
6687
*/
6788
@Override
6889
public boolean isLiteral() {
69-
return literal instanceof FloatLiteral || literal instanceof RealLiteral || literal instanceof IntLiteral
70-
|| literal instanceof LongLiteral || literal instanceof StringLiteral || literal instanceof NullLiteral;
90+
return SUPPORTED_LITERAL_TYPES.contains(literal.getClass());
7191
}
7292
}

0 commit comments

Comments
 (0)