Skip to content

Commit a85bff3

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. Original Pull Request: #410
1 parent 537f760 commit a85bff3

File tree

11 files changed

+1312
-121
lines changed

11 files changed

+1312
-121
lines changed

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

Lines changed: 153 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.
@@ -22,32 +22,42 @@
2222
import java.util.List;
2323

2424
import org.springframework.core.GenericTypeResolver;
25+
import org.springframework.data.mongodb.core.spel.ConstructorReferenceNode;
2526
import org.springframework.data.mongodb.core.spel.ExpressionNode;
2627
import org.springframework.data.mongodb.core.spel.ExpressionTransformationContextSupport;
28+
import org.springframework.data.mongodb.core.spel.InlineListNode;
29+
import org.springframework.data.mongodb.core.spel.InlineMapNode;
2730
import org.springframework.data.mongodb.core.spel.LiteralNode;
2831
import org.springframework.data.mongodb.core.spel.MethodReferenceNode;
32+
import org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference;
33+
import org.springframework.data.mongodb.core.spel.NotOperatorNode;
2934
import org.springframework.data.mongodb.core.spel.OperatorNode;
3035
import org.springframework.expression.spel.ExpressionState;
3136
import org.springframework.expression.spel.SpelNode;
3237
import org.springframework.expression.spel.SpelParserConfiguration;
3338
import org.springframework.expression.spel.ast.CompoundExpression;
3439
import org.springframework.expression.spel.ast.Indexer;
3540
import org.springframework.expression.spel.ast.InlineList;
41+
import org.springframework.expression.spel.ast.InlineMap;
42+
import org.springframework.expression.spel.ast.OperatorNot;
3643
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
3744
import org.springframework.expression.spel.standard.SpelExpression;
3845
import org.springframework.expression.spel.standard.SpelExpressionParser;
3946
import org.springframework.expression.spel.support.StandardEvaluationContext;
4047
import org.springframework.util.Assert;
48+
import org.springframework.util.ClassUtils;
4149
import org.springframework.util.NumberUtils;
4250

4351
import com.mongodb.BasicDBList;
4452
import com.mongodb.BasicDBObject;
4553
import com.mongodb.DBObject;
54+
import org.springframework.util.ObjectUtils;
4655

4756
/**
4857
* Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression.
4958
*
5059
* @author Thomas Darimont
60+
* @author Christoph Strobl
5161
*/
5262
class SpelExpressionTransformer implements AggregationExpressionTransformer {
5363

@@ -69,6 +79,9 @@ public SpelExpressionTransformer() {
6979
conversions.add(new PropertyOrFieldReferenceNodeConversion(this));
7080
conversions.add(new CompoundExpressionNodeConversion(this));
7181
conversions.add(new MethodReferenceNodeConversion(this));
82+
conversions.add(new NotOperatorrNodeConversion(this));
83+
conversions.add(new ConstructorReferenceNodeConversion(this));
84+
conversions.add(new ValueRetrievingNodeConversion(this));
7285

7386
this.conversions = Collections.unmodifiableList(conversions);
7487
}
@@ -131,8 +144,8 @@ private ExpressionNodeConversion<ExpressionNode> lookupConversionFor(ExpressionN
131144
* @author Thomas Darimont
132145
* @author Oliver Gierke
133146
*/
134-
private static abstract class ExpressionNodeConversion<T extends ExpressionNode> implements
135-
AggregationExpressionTransformer {
147+
private static abstract class ExpressionNodeConversion<T extends ExpressionNode>
148+
implements AggregationExpressionTransformer {
136149

137150
private final AggregationExpressionTransformer transformer;
138151
private final Class<? extends ExpressionNode> nodeType;
@@ -235,8 +248,17 @@ public OperatorNodeConversion(AggregationExpressionTransformer transformer) {
235248
protected Object convert(AggregationExpressionTransformationContext<OperatorNode> context) {
236249

237250
OperatorNode currentNode = context.getCurrentNode();
238-
239251
DBObject operationObject = createOperationObjectAndAddToPreviousArgumentsIfNecessary(context, currentNode);
252+
253+
if (currentNode.isConjunctionOperator()) {
254+
255+
for (ExpressionNode expressionNode : currentNode) {
256+
transform(expressionNode, currentNode, operationObject, context);
257+
}
258+
259+
return operationObject;
260+
}
261+
240262
Object leftResult = transform(currentNode.getLeft(), currentNode, operationObject, context);
241263

242264
if (currentNode.isUnaryMinus()) {
@@ -271,7 +293,8 @@ private DBObject createOperationObjectAndAddToPreviousArgumentsIfNecessary(
271293
return nextDbObject;
272294
}
273295

274-
private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<OperatorNode> context, Object leftResult) {
296+
private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<OperatorNode> context,
297+
Object leftResult) {
275298

276299
Object result = leftResult instanceof Number ? leftResult
277300
: new BasicDBObject("$multiply", dbList(-1, leftResult));
@@ -289,7 +312,7 @@ private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<Operat
289312
*/
290313
@Override
291314
protected boolean supports(ExpressionNode node) {
292-
return node.isMathematicalOperation();
315+
return node.isMathematicalOperation() || node.isConjunctionOperator();
293316
}
294317
}
295318

@@ -462,13 +485,31 @@ public MethodReferenceNodeConversion(AggregationExpressionTransformer transforme
462485
protected Object convert(AggregationExpressionTransformationContext<MethodReferenceNode> context) {
463486

464487
MethodReferenceNode node = context.getCurrentNode();
465-
List<Object> args = new ArrayList<Object>();
488+
AggregationMethodReference methodReference = node.getMethodReference();
466489

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

471-
return context.addToPreviousOrReturn(new BasicDBObject(node.getMethodName(), dbList(args.toArray())));
512+
return context.addToPreviousOrReturn(new BasicDBObject(node.getMethodName(), args));
472513
}
473514
}
474515

@@ -510,4 +551,106 @@ protected boolean supports(ExpressionNode node) {
510551
return node.isOfType(CompoundExpression.class);
511552
}
512553
}
554+
555+
/**
556+
* @author Christoph Strobl
557+
* @since 1.10
558+
*/
559+
static class NotOperatorrNodeConversion extends ExpressionNodeConversion<NotOperatorNode> {
560+
561+
/**
562+
* Creates a new {@link ExpressionNodeConversion}.
563+
*
564+
* @param transformer must not be {@literal null}.
565+
*/
566+
public NotOperatorrNodeConversion(AggregationExpressionTransformer transformer) {
567+
super(transformer);
568+
}
569+
570+
/*
571+
* (non-Javadoc)
572+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
573+
*/
574+
@Override
575+
protected Object convert(AggregationExpressionTransformationContext<NotOperatorNode> context) {
576+
577+
NotOperatorNode node = context.getCurrentNode();
578+
List<Object> args = new ArrayList<Object>();
579+
580+
for (ExpressionNode childNode : node) {
581+
args.add(transform(childNode, context));
582+
}
583+
584+
return context.addToPreviousOrReturn(new BasicDBObject(node.getMongoOperator(), dbList(args.toArray())));
585+
}
586+
587+
/*
588+
* (non-Javadoc)
589+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
590+
*/
591+
@Override
592+
protected boolean supports(ExpressionNode node) {
593+
return node.isOfType(OperatorNot.class);
594+
}
595+
}
596+
597+
/**
598+
* @author Christoph Strobl
599+
* @since 1.10
600+
*/
601+
static class ConstructorReferenceNodeConversion extends ExpressionNodeConversion<ConstructorReferenceNode> {
602+
603+
/**
604+
* Creates a new {@link ExpressionNodeConversion}.
605+
*
606+
* @param transformer must not be {@literal null}.
607+
*/
608+
public ConstructorReferenceNodeConversion(AggregationExpressionTransformer transformer) {
609+
super(transformer);
610+
}
611+
612+
/*
613+
* (non-Javadoc)
614+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
615+
*/
616+
@Override
617+
protected Object convert(AggregationExpressionTransformationContext<ConstructorReferenceNode> context) {
618+
return context.getCurrentNode().getValue();
619+
}
620+
621+
/*
622+
* (non-Javadoc)
623+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
624+
*/
625+
@Override
626+
protected boolean supports(ExpressionNode node) {
627+
return ClassUtils.isAssignable(ConstructorReferenceNode.class, node.getClass());
628+
}
629+
}
630+
631+
/**
632+
* @author Christoph Strobl
633+
* @since 1.10
634+
*/
635+
static class ValueRetrievingNodeConversion extends ExpressionNodeConversion<ExpressionNode> {
636+
637+
/**
638+
* Creates a new {@link ExpressionNodeConversion}.
639+
*
640+
* @param transformer must not be {@literal null}.
641+
*/
642+
public ValueRetrievingNodeConversion(AggregationExpressionTransformer transformer) {
643+
super(transformer);
644+
}
645+
646+
@Override
647+
protected Object convert(AggregationExpressionTransformationContext<ExpressionNode> context) {
648+
return context.getCurrentNode().getValue();
649+
}
650+
651+
@Override
652+
protected boolean supports(ExpressionNode node) {
653+
return ClassUtils.isAssignable(InlineMapNode.class, node.getClass()) || ClassUtils.isAssignable(InlineListNode.class, node.getClass());
654+
}
655+
}
513656
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2016. the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.spel;
17+
18+
import org.springframework.expression.spel.ExpressionState;
19+
import org.springframework.expression.spel.SpelNode;
20+
import org.springframework.expression.spel.ast.ConstructorReference;
21+
22+
/**
23+
* @author Christoph Strobl
24+
* @since 1.10
25+
*/
26+
public class ConstructorReferenceNode extends ExpressionNode {
27+
28+
/**
29+
* Creates a new {@link ExpressionNode} from the given {@link SpelNode} and {@link ExpressionState}.
30+
*
31+
* @param node must not be {@literal null}.
32+
* @param state must not be {@literal null}.
33+
*/
34+
protected ConstructorReferenceNode(ConstructorReference node, ExpressionState state) {
35+
super(node, state);
36+
}
37+
38+
}

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

Lines changed: 42 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.
@@ -20,15 +20,20 @@
2020

2121
import org.springframework.expression.spel.ExpressionState;
2222
import org.springframework.expression.spel.SpelNode;
23+
import org.springframework.expression.spel.ast.ConstructorReference;
24+
import org.springframework.expression.spel.ast.InlineList;
25+
import org.springframework.expression.spel.ast.InlineMap;
2326
import org.springframework.expression.spel.ast.Literal;
2427
import org.springframework.expression.spel.ast.MethodReference;
2528
import org.springframework.expression.spel.ast.Operator;
29+
import org.springframework.expression.spel.ast.OperatorNot;
2630
import org.springframework.util.Assert;
2731

2832
/**
2933
* A value object for nodes in an expression. Allows iterating ove potentially available child {@link ExpressionNode}s.
3034
*
3135
* @author Oliver Gierke
36+
* @author Christoph Strobl
3237
*/
3338
public class ExpressionNode implements Iterable<ExpressionNode> {
3439

@@ -79,6 +84,22 @@ public static ExpressionNode from(SpelNode node, ExpressionState state) {
7984
return new LiteralNode((Literal) node, state);
8085
}
8186

87+
if (node instanceof OperatorNot) {
88+
return new NotOperatorNode((OperatorNot) node, state);
89+
}
90+
91+
if(node instanceof InlineList) {
92+
return new InlineListNode((InlineList)node, state);
93+
}
94+
95+
if(node instanceof InlineMap) {
96+
return new InlineMapNode((InlineMap)node, state);
97+
}
98+
99+
if (node instanceof ConstructorReference) {
100+
return new ConstructorReferenceNode((ConstructorReference) node, state);
101+
}
102+
82103
return new ExpressionNode(node, state);
83104
}
84105

@@ -122,6 +143,26 @@ public boolean isMathematicalOperation() {
122143
return false;
123144
}
124145

146+
/**
147+
* Returns whether the {@link ExpressionNode} is a logical conjunction operation like {@code &&, ||}.
148+
*
149+
* @return
150+
* @since 1.10
151+
*/
152+
public boolean isConjunctionOperator() {
153+
return false;
154+
}
155+
156+
/**
157+
* Returns whether the {@link ExpressionNode} is a negating operation.
158+
*
159+
* @return
160+
* @since 1.10
161+
*/
162+
public boolean isNotOperator() {
163+
return false;
164+
}
165+
125166
/**
126167
* Returns whether the {@link ExpressionNode} is a literal.
127168
*

0 commit comments

Comments
 (0)