Skip to content

Commit 52c93b1

Browse files
committedMar 20, 2023
Increase scope of regex pattern cache for the SpEL matches operator
Prior to this commit, the pattern cache for the SpEL `matches` operator only applied to expressions such as the following where the same `matches` operator is invoked multiple times with different input: "map.keySet().?[#this matches '.+xyz']" The pattern cache did not apply to expressions such as the following where the same pattern ('.+xyz') is used in multiple `matches` operations: "foo matches '.+xyz' AND bar matches '.+xyz'" This commit addresses this by moving the instance of the pattern cache map from OperatorMatches to InternalSpelExpressionParser so that the cache can be reused for all `matches` operations for the given parser. Closes gh-30148
1 parent 9c6cb74 commit 52c93b1

File tree

2 files changed

+28
-6
lines changed

2 files changed

+28
-6
lines changed
 

‎spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java

+20-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -36,19 +36,35 @@
3636
*
3737
* @author Andy Clement
3838
* @author Juergen Hoeller
39+
* @author Sam Brannen
3940
* @since 3.0
4041
*/
4142
public class OperatorMatches extends Operator {
4243

4344
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
4445

45-
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
46+
private final ConcurrentMap<String, Pattern> patternCache;
4647

4748

49+
/**
50+
* Create a new {@link OperatorMatches} instance.
51+
* @deprecated as of Spring Framework 5.2.23 in favor of invoking
52+
* {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)}
53+
* with a shared pattern cache instead
54+
*/
55+
@Deprecated
4856
public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) {
49-
super("matches", startPos, endPos, operands);
57+
this(new ConcurrentHashMap<>(), startPos, endPos, operands);
5058
}
5159

60+
/**
61+
* Create a new {@link OperatorMatches} instance with a shared pattern cache.
62+
* @since 5.2.23
63+
*/
64+
public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) {
65+
super("matches", startPos, endPos, operands);
66+
this.patternCache = patternCache;
67+
}
5268

5369
/**
5470
* Check the first operand matches the regex specified as the second operand.
@@ -63,7 +79,7 @@ public BooleanTypedValue getValueInternal(ExpressionState state) throws Evaluati
6379
SpelNodeImpl leftOp = getLeftOperand();
6480
SpelNodeImpl rightOp = getRightOperand();
6581
String left = leftOp.getValue(state, String.class);
66-
Object right = getRightOperand().getValue(state);
82+
Object right = rightOp.getValue(state);
6783

6884
if (left == null) {
6985
throw new SpelEvaluationException(leftOp.getStartPosition(),

‎spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -21,6 +21,8 @@
2121
import java.util.Collections;
2222
import java.util.Deque;
2323
import java.util.List;
24+
import java.util.concurrent.ConcurrentHashMap;
25+
import java.util.concurrent.ConcurrentMap;
2426
import java.util.regex.Pattern;
2527

2628
import org.springframework.expression.ParseException;
@@ -83,6 +85,7 @@
8385
* @author Andy Clement
8486
* @author Juergen Hoeller
8587
* @author Phillip Webb
88+
* @author Sam Brannen
8689
* @since 3.0
8790
*/
8891
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
@@ -95,6 +98,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
9598
// For rules that build nodes, they are stacked here for return
9699
private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>();
97100

101+
// Shared cache for compiled regex patterns
102+
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
103+
98104
// The expression being parsed
99105
private String expressionString = "";
100106

@@ -248,7 +254,7 @@ private SpelNodeImpl eatRelationalExpression() {
248254
}
249255

250256
if (tk == TokenKind.MATCHES) {
251-
return new OperatorMatches(t.startPos, t.endPos, expr, rhExpr);
257+
return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr);
252258
}
253259

254260
Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");

0 commit comments

Comments
 (0)