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-30140
This commit is contained in:
parent
4a3518b4d6
commit
935c29e3dd
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -43,13 +43,28 @@ public class OperatorMatches extends Operator {
|
||||||
|
|
||||||
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
|
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
|
||||||
|
|
||||||
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
|
private final ConcurrentMap<String, Pattern> patternCache;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link OperatorMatches} instance.
|
||||||
|
* @deprecated as of Spring Framework 5.3.26 in favor of invoking
|
||||||
|
* {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)}
|
||||||
|
* with a shared pattern cache instead
|
||||||
|
*/
|
||||||
|
@Deprecated(since = "5.3.26")
|
||||||
public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) {
|
public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) {
|
||||||
super("matches", startPos, endPos, operands);
|
this(new ConcurrentHashMap<>(), startPos, endPos, operands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link OperatorMatches} instance with a shared pattern cache.
|
||||||
|
* @since 5.3.26
|
||||||
|
*/
|
||||||
|
public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) {
|
||||||
|
super("matches", startPos, endPos, operands);
|
||||||
|
this.patternCache = patternCache;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the first operand matches the regex specified as the second operand.
|
* Check the first operand matches the regex specified as the second operand.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -21,6 +21,8 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.springframework.expression.ParseException;
|
import org.springframework.expression.ParseException;
|
||||||
|
|
@ -83,6 +85,7 @@ import org.springframework.util.StringUtils;
|
||||||
* @author Andy Clement
|
* @author Andy Clement
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
|
|
@ -95,6 +98,9 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
// For rules that build nodes, they are stacked here for return
|
// For rules that build nodes, they are stacked here for return
|
||||||
private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>();
|
private final Deque<SpelNodeImpl> constructedNodes = new ArrayDeque<>();
|
||||||
|
|
||||||
|
// Shared cache for compiled regex patterns
|
||||||
|
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
// The expression being parsed
|
// The expression being parsed
|
||||||
private String expressionString = "";
|
private String expressionString = "";
|
||||||
|
|
||||||
|
|
@ -248,7 +254,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tk == TokenKind.MATCHES) {
|
if (tk == TokenKind.MATCHES) {
|
||||||
return new OperatorMatches(t.startPos, t.endPos, expr, rhExpr);
|
return new OperatorMatches(this.patternCache, t.startPos, t.endPos, expr, rhExpr);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");
|
Assert.isTrue(tk == TokenKind.BETWEEN, "Between token expected");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue