Skip to content

Commit dd7d25c

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 0ddd7e3 commit dd7d25c

File tree

8 files changed

+1239
-111
lines changed

8 files changed

+1239
-111
lines changed

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

Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,32 @@
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
/**
4451
* Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression.
4552
*
4653
* @author Thomas Darimont
54+
* @author Christoph Strobl
4755
*/
4856
class SpelExpressionTransformer implements AggregationExpressionTransformer {
4957

@@ -65,6 +73,8 @@ public SpelExpressionTransformer() {
6573
conversions.add(new PropertyOrFieldReferenceNodeConversion(this));
6674
conversions.add(new CompoundExpressionNodeConversion(this));
6775
conversions.add(new MethodReferenceNodeConversion(this));
76+
conversions.add(new NotOperatorNodeConversion(this));
77+
conversions.add(new ValueRetrievingNodeConversion(this));
6878

6979
this.conversions = Collections.unmodifiableList(conversions);
7080
}
@@ -231,8 +241,17 @@ public OperatorNodeConversion(AggregationExpressionTransformer transformer) {
231241
protected Object convert(AggregationExpressionTransformationContext<OperatorNode> context) {
232242

233243
OperatorNode currentNode = context.getCurrentNode();
234-
235244
Document operationObject = createOperationObjectAndAddToPreviousArgumentsIfNecessary(context, currentNode);
245+
246+
if (currentNode.isLogicalOperator()) {
247+
248+
for (ExpressionNode expressionNode : currentNode) {
249+
transform(expressionNode, currentNode, operationObject, context);
250+
}
251+
252+
return operationObject;
253+
}
254+
236255
Object leftResult = transform(currentNode.getLeft(), currentNode, operationObject, context);
237256

238257
if (currentNode.isUnaryMinus()) {
@@ -286,7 +305,7 @@ private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<Operat
286305
*/
287306
@Override
288307
protected boolean supports(ExpressionNode node) {
289-
return node.isMathematicalOperation();
308+
return node.isMathematicalOperation() || node.isLogicalOperator();
290309
}
291310
}
292311

@@ -459,13 +478,31 @@ public MethodReferenceNodeConversion(AggregationExpressionTransformer transforme
459478
protected Object convert(AggregationExpressionTransformationContext<MethodReferenceNode> context) {
460479

461480
MethodReferenceNode node = context.getCurrentNode();
462-
List<Object> args = new ArrayList<Object>();
481+
AggregationMethodReference methodReference = node.getMethodReference();
463482

464-
for (ExpressionNode childNode : node) {
465-
args.add(transform(childNode, context));
483+
Object args = null;
484+
485+
if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.SINGLE)) {
486+
args = transform(node.getChild(0), context);
487+
} else if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.MAP)) {
488+
489+
Document dbo = new Document();
490+
for (int i = 0; i < methodReference.getArgumentMap().length; i++) {
491+
dbo.put(methodReference.getArgumentMap()[i], transform(node.getChild(i), context));
492+
}
493+
args = dbo;
494+
} else {
495+
496+
List<Object> argList = new ArrayList<Object>();
497+
498+
for (ExpressionNode childNode : node) {
499+
argList.add(transform(childNode, context));
500+
}
501+
502+
args = argList;
466503
}
467504

468-
return context.addToPreviousOrReturn(new Document(node.getMethodName(), args));
505+
return context.addToPreviousOrReturn(new Document(methodReference.getMongoOperator(), args));
469506
}
470507
}
471508

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

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)