Skip to content

Commit e5105f0

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 e5105f0

File tree

9 files changed

+515
-33
lines changed

9 files changed

+515
-33
lines changed

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

Lines changed: 99 additions & 6 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,22 +22,26 @@
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;
2728
import org.springframework.data.mongodb.core.spel.LiteralNode;
2829
import org.springframework.data.mongodb.core.spel.MethodReferenceNode;
30+
import org.springframework.data.mongodb.core.spel.NotOperatorNode;
2931
import org.springframework.data.mongodb.core.spel.OperatorNode;
3032
import org.springframework.expression.spel.ExpressionState;
3133
import org.springframework.expression.spel.SpelNode;
3234
import org.springframework.expression.spel.SpelParserConfiguration;
3335
import org.springframework.expression.spel.ast.CompoundExpression;
3436
import org.springframework.expression.spel.ast.Indexer;
3537
import org.springframework.expression.spel.ast.InlineList;
38+
import org.springframework.expression.spel.ast.OperatorNot;
3639
import org.springframework.expression.spel.ast.PropertyOrFieldReference;
3740
import org.springframework.expression.spel.standard.SpelExpression;
3841
import org.springframework.expression.spel.standard.SpelExpressionParser;
3942
import org.springframework.expression.spel.support.StandardEvaluationContext;
4043
import org.springframework.util.Assert;
44+
import org.springframework.util.ClassUtils;
4145
import org.springframework.util.NumberUtils;
4246

4347
import com.mongodb.BasicDBList;
@@ -48,6 +52,7 @@
4852
* Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression.
4953
*
5054
* @author Thomas Darimont
55+
* @author Christoph Strobl
5156
*/
5257
class SpelExpressionTransformer implements AggregationExpressionTransformer {
5358

@@ -69,6 +74,8 @@ public SpelExpressionTransformer() {
6974
conversions.add(new PropertyOrFieldReferenceNodeConversion(this));
7075
conversions.add(new CompoundExpressionNodeConversion(this));
7176
conversions.add(new MethodReferenceNodeConversion(this));
77+
conversions.add(new NotOperatorrNodeConversion(this));
78+
conversions.add(new ConstructorReferenceNodeConversion(this));
7279

7380
this.conversions = Collections.unmodifiableList(conversions);
7481
}
@@ -131,8 +138,8 @@ private ExpressionNodeConversion<ExpressionNode> lookupConversionFor(ExpressionN
131138
* @author Thomas Darimont
132139
* @author Oliver Gierke
133140
*/
134-
private static abstract class ExpressionNodeConversion<T extends ExpressionNode> implements
135-
AggregationExpressionTransformer {
141+
private static abstract class ExpressionNodeConversion<T extends ExpressionNode>
142+
implements AggregationExpressionTransformer {
136143

137144
private final AggregationExpressionTransformer transformer;
138145
private final Class<? extends ExpressionNode> nodeType;
@@ -235,8 +242,17 @@ public OperatorNodeConversion(AggregationExpressionTransformer transformer) {
235242
protected Object convert(AggregationExpressionTransformationContext<OperatorNode> context) {
236243

237244
OperatorNode currentNode = context.getCurrentNode();
238-
239245
DBObject operationObject = createOperationObjectAndAddToPreviousArgumentsIfNecessary(context, currentNode);
246+
247+
if (currentNode.isConjunctionOperator()) {
248+
249+
for (ExpressionNode expressionNode : currentNode) {
250+
transform(expressionNode, currentNode, operationObject, context);
251+
}
252+
253+
return operationObject;
254+
}
255+
240256
Object leftResult = transform(currentNode.getLeft(), currentNode, operationObject, context);
241257

242258
if (currentNode.isUnaryMinus()) {
@@ -271,7 +287,8 @@ private DBObject createOperationObjectAndAddToPreviousArgumentsIfNecessary(
271287
return nextDbObject;
272288
}
273289

274-
private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<OperatorNode> context, Object leftResult) {
290+
private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<OperatorNode> context,
291+
Object leftResult) {
275292

276293
Object result = leftResult instanceof Number ? leftResult
277294
: new BasicDBObject("$multiply", dbList(-1, leftResult));
@@ -289,7 +306,7 @@ private Object convertUnaryMinusOp(ExpressionTransformationContextSupport<Operat
289306
*/
290307
@Override
291308
protected boolean supports(ExpressionNode node) {
292-
return node.isMathematicalOperation();
309+
return node.isMathematicalOperation() || node.isConjunctionOperator();
293310
}
294311
}
295312

@@ -510,4 +527,80 @@ protected boolean supports(ExpressionNode node) {
510527
return node.isOfType(CompoundExpression.class);
511528
}
512529
}
530+
531+
/**
532+
* @author Christoph Strobl
533+
* @since 1.10
534+
*/
535+
static class NotOperatorrNodeConversion extends ExpressionNodeConversion<NotOperatorNode> {
536+
537+
/**
538+
* Creates a new {@link ExpressionNodeConversion}.
539+
*
540+
* @param transformer must not be {@literal null}.
541+
*/
542+
public NotOperatorrNodeConversion(AggregationExpressionTransformer transformer) {
543+
super(transformer);
544+
}
545+
546+
/*
547+
* (non-Javadoc)
548+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
549+
*/
550+
@Override
551+
protected Object convert(AggregationExpressionTransformationContext<NotOperatorNode> context) {
552+
553+
NotOperatorNode node = context.getCurrentNode();
554+
List<Object> args = new ArrayList<Object>();
555+
556+
for (ExpressionNode childNode : node) {
557+
args.add(transform(childNode, context));
558+
}
559+
560+
return context.addToPreviousOrReturn(new BasicDBObject(node.getMongoOperator(), dbList(args.toArray())));
561+
}
562+
563+
/*
564+
* (non-Javadoc)
565+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
566+
*/
567+
@Override
568+
protected boolean supports(ExpressionNode node) {
569+
return node.isOfType(OperatorNot.class);
570+
}
571+
}
572+
573+
/**
574+
* @author Christoph Strobl
575+
* @since 1.10
576+
*/
577+
static class ConstructorReferenceNodeConversion extends ExpressionNodeConversion<ConstructorReferenceNode> {
578+
579+
/**
580+
* Creates a new {@link ExpressionNodeConversion}.
581+
*
582+
* @param transformer must not be {@literal null}.
583+
*/
584+
public ConstructorReferenceNodeConversion(AggregationExpressionTransformer transformer) {
585+
super(transformer);
586+
}
587+
588+
/*
589+
* (non-Javadoc)
590+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext)
591+
*/
592+
@Override
593+
protected Object convert(AggregationExpressionTransformationContext<ConstructorReferenceNode> context) {
594+
return context.getCurrentNode().getValue();
595+
}
596+
597+
/*
598+
* (non-Javadoc)
599+
* @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode)
600+
*/
601+
@Override
602+
protected boolean supports(ExpressionNode node) {
603+
return ClassUtils.isAssignable(ConstructorReferenceNode.class, node.getClass());
604+
}
605+
}
513606
}
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: 32 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,18 @@
2020

2121
import org.springframework.expression.spel.ExpressionState;
2222
import org.springframework.expression.spel.SpelNode;
23+
import org.springframework.expression.spel.ast.ConstructorReference;
2324
import org.springframework.expression.spel.ast.Literal;
2425
import org.springframework.expression.spel.ast.MethodReference;
2526
import org.springframework.expression.spel.ast.Operator;
27+
import org.springframework.expression.spel.ast.OperatorNot;
2628
import org.springframework.util.Assert;
2729

2830
/**
2931
* A value object for nodes in an expression. Allows iterating ove potentially available child {@link ExpressionNode}s.
3032
*
3133
* @author Oliver Gierke
34+
* @author Christoph Strobl
3235
*/
3336
public class ExpressionNode implements Iterable<ExpressionNode> {
3437

@@ -79,6 +82,14 @@ public static ExpressionNode from(SpelNode node, ExpressionState state) {
7982
return new LiteralNode((Literal) node, state);
8083
}
8184

85+
if (node instanceof OperatorNot) {
86+
return new NotOperatorNode((OperatorNot) node, state);
87+
}
88+
89+
if (node instanceof ConstructorReference) {
90+
return new ConstructorReferenceNode((ConstructorReference) node, state);
91+
}
92+
8293
return new ExpressionNode(node, state);
8394
}
8495

@@ -122,6 +133,26 @@ public boolean isMathematicalOperation() {
122133
return false;
123134
}
124135

136+
/**
137+
* Returns whether the {@link ExpressionNode} is a logical conjunction operation like {@code &&, ||}.
138+
*
139+
* @return
140+
* @since 1.10
141+
*/
142+
public boolean isConjunctionOperator() {
143+
return false;
144+
}
145+
146+
/**
147+
* Returns whether the {@link ExpressionNode} is a negating operation.
148+
*
149+
* @return
150+
* @since 1.10
151+
*/
152+
public boolean isNotOperator() {
153+
return false;
154+
}
155+
125156
/**
126157
* Returns whether the {@link ExpressionNode} is a literal.
127158
*

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)