diff --git a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc index 45997fd2846..1db4a58bf3e 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/evaluation.adoc @@ -488,17 +488,23 @@ Kotlin:: It is possible to configure the SpEL expression parser by using a parser configuration object (`org.springframework.expression.spel.SpelParserConfiguration`). The configuration -object controls the behavior of some of the expression components. For example, if you -index into a collection and the element at the specified index is `null`, SpEL can -automatically create the element. This is useful when using expressions made up of a -chain of property references. Similarly, if you index into a collection and specify an -index that is greater than the current size of the collection, SpEL can automatically -grow the collection to accommodate that index. In order to add an element at the -specified index, SpEL will try to create the element using the element type's default -constructor before setting the specified value. If the element type does not have a -default constructor, `null` will be added to the collection. If there is no built-in -converter or custom converter that knows how to set the value, `null` will remain in the -collection at the specified index. The following example demonstrates how to +object controls the behavior of some of the expression components. To create a +`SpelParserConfiguration` instance, favor `SpelParserConfiguration.builder()` over the +numerous constructors in `SpelParserConfiguration`, since the builder only requires +configuration of the properties that need to deviate from their sensible defaults -- +or use `SpelParserConfiguration.withDefaults()` if none of those defaults need to be +overridden. + +For example, if you index into a collection and the element at the specified index is +`null`, SpEL can automatically create the element. This is useful when using expressions +made up of a chain of property references. Similarly, if you index into a collection and +specify an index that is greater than the current size of the collection, SpEL can +automatically grow the collection to accommodate that index. In order to add an element +at the specified index, SpEL will try to create the element using the element type's +default constructor before setting the specified value. If the element type does not +have a default constructor, `null` will be added to the collection. If there is no +built-in converter or custom converter that knows how to set the value, `null` will +remain in the collection at the specified index. The following example demonstrates how to automatically grow a `List`. [tabs] @@ -511,10 +517,10 @@ Java:: public List list; } - // Turn on: - // - auto null reference initialization - // - auto collection growing - SpelParserConfiguration config = new SpelParserConfiguration(true, true); + SpelParserConfiguration config = SpelParserConfiguration.builder() + .autoGrowNullReferences() + .autoGrowCollections() + .build(); ExpressionParser parser = new SpelExpressionParser(config); @@ -536,10 +542,10 @@ Kotlin:: var list: List? = null } - // Turn on: - // - auto null reference initialization - // - auto collection growing - val config = SpelParserConfiguration(true, true) + val config = SpelParserConfiguration.builder() + .autoGrowNullReferences() + .autoGrowCollections() + .build() val parser = SpelExpressionParser(config) @@ -556,7 +562,8 @@ Kotlin:: By default, a SpEL expression cannot contain more than 10,000 characters; however, the `maxExpressionLength` is configurable. If you create a `SpelExpressionParser` -programmatically, you can specify a custom `maxExpressionLength` when creating the +programmatically, you can specify a custom `maxExpressionLength` via +`SpelParserConfiguration.builder().maximumExpressionLength(...)` when creating the `SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you wish to set the `maxExpressionLength` used for parsing SpEL expressions within an `ApplicationContext` -- for example, in XML bean definitions, `@Value`, etc. -- you can @@ -567,12 +574,14 @@ xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]). Similarly, the number of operations performed during the evaluation of a SpEL expression cannot exceed 10,000 by default; however, the `maxOperations` value is configurable. If you create a `SpelExpressionParser` programmatically (the recommend approach), you can -specify a custom `maxOperations` value when creating the `SpelParserConfiguration` that -you provide to the `SpelExpressionParser`. If you are not able to configure an explicit -value for `maxOperations` via `SpelParserConfiguration`, you can set a JVM system -property or Spring property named `spring.expression.maxOperations` to the maximum number -of operations required by your application (see -xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]). +specify a custom `maxOperations` value via +`SpelParserConfiguration.builder().maximumOperations(...)` when creating the +`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. If you are not +able to configure an explicit value for `maxOperations` via `SpelParserConfiguration`, +you can set a JVM system property or Spring property named +`spring.expression.maxOperations` to the maximum number of operations required by your +application (see xref:appendix.adoc#appendix-spring-properties[Supported Spring +Properties]). In addition, the result of a `BigDecimal` or `BigInteger` power operation within a SpEL expression cannot exceed 1,000,000 bits by default – approximately equivalent to a @@ -580,13 +589,14 @@ decimal number with 300,000 digits. Power operations involving large base values exponents can be computationally expensive, and this limit ensures that evaluations remain bounded; however, the `maximumBigPowerBits` value is configurable. If you create a `SpelExpressionParser` programmatically (the recommended approach), you can specify a -custom `maximumBigPowerBits` value when creating the `SpelParserConfiguration` that you -provide to the `SpelExpressionParser`. To remove this limit entirely, pass -`Integer.MAX_VALUE` as the `maximumBigPowerBits` value. If you are not able to configure -an explicit value for `maximumBigPowerBits` via `SpelParserConfiguration`, you can set a -JVM system property or Spring property named `spring.expression.maxBigPowerBits` to the -maximum result size in bits (see xref:appendix.adoc#appendix-spring-properties[Supported -Spring Properties]). +custom `maximumBigPowerBits` value via +`SpelParserConfiguration.builder().maximumBigPowerBits(...)` when creating the +`SpelParserConfiguration` that you provide to the `SpelExpressionParser`. To remove this +limit entirely, pass `Integer.MAX_VALUE` as the `maximumBigPowerBits` value. If you are +not able to configure an explicit value for `maximumBigPowerBits` via +`SpelParserConfiguration`, you can set a JVM system property or Spring property named +`spring.expression.maxBigPowerBits` to the maximum result size in bits (see +xref:appendix.adoc#appendix-spring-properties[Supported Spring Properties]). [[expressions-spel-compilation]] == SpEL Compilation @@ -669,8 +679,10 @@ Java:: + [source,java,indent=0,subs="verbatim,quotes"] ---- - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, - this.getClass().getClassLoader()); + SpelParserConfiguration config = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.IMMEDIATE) + .compilerClassLoader(getClass().getClassLoader()) + .build(); SpelExpressionParser parser = new SpelExpressionParser(config); @@ -685,8 +697,10 @@ Kotlin:: + [source,kotlin,indent=0,subs="verbatim,quotes"] ---- - val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, - this.javaClass.classLoader) + val config = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.IMMEDIATE) + .compilerClassLoader(javaClass.classLoader) + .build() val parser = SpelExpressionParser(config) diff --git a/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java b/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java index 798d954e577..ea98cd2ad28 100644 --- a/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java +++ b/spring-context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java @@ -118,8 +118,10 @@ public class StandardBeanExpressionResolver implements BeanExpressionResolver { * @param beanClassLoader the factory's bean class loader */ public StandardBeanExpressionResolver(@Nullable ClassLoader beanClassLoader) { - SpelParserConfiguration parserConfig = new SpelParserConfiguration( - null, beanClassLoader, false, false, Integer.MAX_VALUE, retrieveMaxExpressionLength()); + SpelParserConfiguration parserConfig = SpelParserConfiguration.builder() + .compilerClassLoader(beanClassLoader) + .maximumExpressionLength(retrieveMaxExpressionLength()) + .build(); this.expressionParser = new SpelExpressionParser(parserConfig); } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java index b3669fd4fa2..849f259b694 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ExpressionState.java @@ -75,7 +75,7 @@ public class ExpressionState { public ExpressionState(EvaluationContext context) { - this(context, context.getRootObject(), new SpelParserConfiguration(false, false)); + this(context, context.getRootObject(), SpelParserConfiguration.withDefaults()); } public ExpressionState(EvaluationContext context, SpelParserConfiguration configuration) { @@ -83,7 +83,7 @@ public class ExpressionState { } public ExpressionState(EvaluationContext context, TypedValue rootObject) { - this(context, rootObject, new SpelParserConfiguration(false, false)); + this(context, rootObject, SpelParserConfiguration.withDefaults()); } public ExpressionState(EvaluationContext context, TypedValue rootObject, SpelParserConfiguration configuration) { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java index 73e218ec5f8..e868c316e77 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelParserConfiguration.java @@ -27,12 +27,22 @@ import org.springframework.util.StringUtils; /** * Configuration object for the SpEL expression parser. * + *

Rather than using one of the numerous constructors in this class, it is + * strongly recommended that you use the {@linkplain #builder() builder API} to + * configure and create a {@code SpelParserConfiguration} instance, since the + * builder only requires configuration of the properties that need to deviate from + * their sensible defaults — or use {@link #withDefaults()} if none of those + * defaults need to be overridden. Note that the constructors in this class are + * planned to be deprecated in favor of the builder as of Spring Framework 7.1. + * * @author Juergen Hoeller * @author Phillip Webb * @author Andy Clement * @author Sam Brannen * @since 3.0 * @see org.springframework.expression.spel.standard.SpelExpressionParser#SpelExpressionParser(SpelParserConfiguration) + * @see #withDefaults() + * @see #builder() */ public class SpelParserConfiguration { @@ -62,10 +72,10 @@ public class SpelParserConfiguration { /** * System property to configure the default compiler mode for SpEL expression parsers: {@value}. *

NOTE: Instead of relying on a global default, applications - * and frameworks should ideally set an explicit custom value via the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int)} - * constructor which provides complete configuration control and the ability - * to override global defaults per use case. + * and frameworks should ideally set an explicit custom value via + * {@link Builder#compilerMode(SpelCompilerMode)}, which provides complete + * configuration control and the ability to override global defaults per + * use case. *

Can also be configured via the {@link SpringProperties} mechanism. */ public static final String SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME = "spring.expression.compiler.mode"; @@ -74,10 +84,10 @@ public class SpelParserConfiguration { * System property to configure the default maximum number of operations permitted * during SpEL expression evaluation: {@value}. *

NOTE: Instead of relying on a global default, applications - * and frameworks should ideally set an explicit custom value via the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor which provides complete configuration control and the ability - * to override global defaults per use case. + * and frameworks should ideally set an explicit custom value via + * {@link Builder#maximumOperations(int)}, which provides complete + * configuration control and the ability to override global defaults per + * use case. *

Can also be configured via the {@link SpringProperties} mechanism. * @since 6.2.19 * @see #DEFAULT_MAX_OPERATIONS @@ -89,10 +99,10 @@ public class SpelParserConfiguration { * result of a {@link java.math.BigDecimal} or {@link java.math.BigInteger} power * operation within a SpEL expression: {@value}. *

NOTE: Instead of relying on a global default, applications - * and frameworks should ideally set an explicit custom value via the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor which provides complete configuration control and the ability - * to override global defaults per use case. + * and frameworks should ideally set an explicit custom value via + * {@link Builder#maximumBigPowerBits(int)}, which provides complete + * configuration control and the ability to override global defaults per + * use case. *

Can also be configured via the {@link SpringProperties} mechanism. * @since 7.0.9 * @see #DEFAULT_MAX_BIG_POWER_BITS @@ -127,12 +137,35 @@ public class SpelParserConfiguration { private final int maximumBigPowerBits; + /** + * Create a new {@code SpelParserConfiguration} instance with the same defaults + * applied by {@link #builder()}. + *

This is shorthand for {@code SpelParserConfiguration.builder().build()}, + * for use whenever none of the defaults need to be overridden. + * @since 7.0.10 + * @see #builder() + */ + public static SpelParserConfiguration withDefaults() { + return builder().build(); + } + + /** + * Create a new {@link Builder} for configuring a {@code SpelParserConfiguration}. + *

The builder only requires configuration of the properties that need + * to deviate from their sensible defaults. See {@link Builder} for details + * on those defaults. + * @since 7.0.10 + * @see #withDefaults() + */ + public static Builder builder() { + return new Builder(); + } + /** * Create a new {@code SpelParserConfiguration} instance with default settings. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME * @see #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME @@ -143,10 +176,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param compilerMode the compiler mode that parsers using this configuration * should use; or {@code null} to use the default mode * @param compilerClassLoader the {@code ClassLoader} to use as the basis for @@ -161,10 +193,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow * @see #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME @@ -177,10 +208,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param autoGrowNullReferences if null references should automatically grow * @param autoGrowCollections if collections should automatically grow * @param maximumAutoGrowSize the maximum size to which a collection can auto grow @@ -194,10 +224,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param compilerMode the compiler mode that parsers using this configuration * should use; or {@code null} to use the default mode * @param compilerClassLoader the {@code ClassLoader} to use as the basis for @@ -218,10 +247,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param compilerMode the compiler mode that parsers using this configuration * should use; or {@code null} to use the default mode * @param compilerClassLoader the {@code ClassLoader} to use as the basis for @@ -245,10 +273,9 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. - *

NOTE: Favor the - * {@link #SpelParserConfiguration(SpelCompilerMode, ClassLoader, boolean, boolean, int, int, int, int)} - * constructor for complete configuration control and the ability to override - * global defaults per use case. + *

NOTE: Favor {@link #builder()} for complete + * configuration control and the ability to override global defaults + * per use case. * @param compilerMode the compiler mode that parsers using this configuration * should use; must not be {@code null} * @param compilerClassLoader the {@code ClassLoader} to use as the basis for @@ -273,6 +300,8 @@ public class SpelParserConfiguration { /** * Create a new {@code SpelParserConfiguration} instance. + *

NOTE: Favor {@link #builder()} for a more readable + * way to override global defaults per use case. * @param compilerMode the compiler mode that parsers using this configuration * should use; must not be {@code null} * @param compilerClassLoader the {@code ClassLoader} to use as the basis for @@ -406,4 +435,172 @@ public class SpelParserConfiguration { } } + + /** + * Fluent builder API for {@link SpelParserConfiguration}. + *

Each property defaults to the same value as the corresponding + * property in a {@code SpelParserConfiguration} created via the + * {@linkplain SpelParserConfiguration#SpelParserConfiguration() no-arg constructor} + * — including honoring the system properties or Spring properties named + * {@value #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME}, + * {@value #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME}, and + * {@value #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME} — with + * one exception: {@link #maximumAutoGrowSize(int) maximumAutoGrowSize} + * defaults to {@code 256} rather than {@link Integer#MAX_VALUE}, for + * consistency with the default auto-grow limit used for Spring's data + * binding support (see {@code DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT}). + *

Only configure the properties that need to deviate from those defaults. + * @since 7.0.10 + * @see SpelParserConfiguration#builder() + */ + public static final class Builder { + + // Aligned with DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT, unlike the + // constructors in the enclosing class, which default to Integer.MAX_VALUE + // for backward compatibility. + private static final int DEFAULT_MAX_AUTO_GROW_SIZE = 256; + + private SpelCompilerMode compilerMode = defaultCompilerMode; + + private @Nullable ClassLoader compilerClassLoader; + + private boolean autoGrowNullReferences = false; + + private boolean autoGrowCollections = false; + + private int maximumAutoGrowSize = DEFAULT_MAX_AUTO_GROW_SIZE; + + private int maximumExpressionLength = DEFAULT_MAX_EXPRESSION_LENGTH; + + private @Nullable Integer maximumOperations; + + private @Nullable Integer maximumBigPowerBits; + + private Builder() { + } + + /** + * Set the compiler mode that parsers using this configuration should use. + *

By default, set to the value configured via {@link SpringProperties} + * for a system property or Spring property named + * {@value #SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME}, or + * {@link SpelCompilerMode#OFF} if that property is not set. + * @param compilerMode the compiler mode to use; must not be {@code null} + */ + public Builder compilerMode(SpelCompilerMode compilerMode) { + Assert.notNull(compilerMode, "'compilerMode' must not be null"); + this.compilerMode = compilerMode; + return this; + } + + /** + * Set the {@code ClassLoader} to use as the basis for expression compilation. + *

By default, set to {@code null}, indicating that the default + * {@code ClassLoader} should be used. + * @param compilerClassLoader the {@code ClassLoader} to use + */ + public Builder compilerClassLoader(@Nullable ClassLoader compilerClassLoader) { + this.compilerClassLoader = compilerClassLoader; + return this; + } + + /** + * Enable automatic growth of {@code null} references encountered while + * traversing a property path. + *

By default, this is disabled. + */ + public Builder autoGrowNullReferences() { + this.autoGrowNullReferences = true; + return this; + } + + /** + * Enable automatic growth of collections and arrays encountered while + * traversing a property path. + *

By default, this is disabled. + * @see #maximumAutoGrowSize(int) + */ + public Builder autoGrowCollections() { + this.autoGrowCollections = true; + return this; + } + + /** + * Set the maximum size to which a collection or array can automatically grow. + *

By default, set to {@code 256}, for consistency with the default + * auto-grow limit used for Spring's data binding support (see + * {@code DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT}) — unlike + * the constructors in {@link SpelParserConfiguration}, which default to + * {@link Integer#MAX_VALUE} (effectively unbounded) for backward + * compatibility. + * @param maximumAutoGrowSize the maximum auto-grow size; must not be + * negative, and a value of {@code 0} effectively disables growing a + * collection or array beyond its current size + * @see #autoGrowCollections() + */ + public Builder maximumAutoGrowSize(int maximumAutoGrowSize) { + Assert.isTrue(maximumAutoGrowSize >= 0, "'maximumAutoGrowSize' must not be negative"); + this.maximumAutoGrowSize = maximumAutoGrowSize; + return this; + } + + /** + * Set the maximum length permitted for a SpEL expression. + *

By default, set to {@link #DEFAULT_MAX_EXPRESSION_LENGTH}. + * @param maximumExpressionLength the maximum expression length; must be + * a positive number + */ + public Builder maximumExpressionLength(int maximumExpressionLength) { + Assert.isTrue(maximumExpressionLength > 0, "'maximumExpressionLength' must be a positive number"); + this.maximumExpressionLength = maximumExpressionLength; + return this; + } + + /** + * Set the maximum number of operations permitted during SpEL expression evaluation. + *

By default, set to the value configured via {@link SpringProperties} + * for a system property or Spring property named + * {@value #SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME}, or + * {@link #DEFAULT_MAX_OPERATIONS} if that property is not set. + * @param maximumOperations the maximum number of operations; must be a + * positive number + */ + public Builder maximumOperations(int maximumOperations) { + Assert.isTrue(maximumOperations > 0, "'maximumOperations' must be a positive number"); + this.maximumOperations = maximumOperations; + return this; + } + + /** + * Set the maximum number of bits permitted in the result of a + * {@link java.math.BigDecimal} or {@link java.math.BigInteger} power + * operation within a SpEL expression. + *

By default, set to the value configured via {@link SpringProperties} + * for a system property or Spring property named + * {@value #SPRING_EXPRESSION_MAX_BIG_POWER_BITS_PROPERTY_NAME}, or + * {@link #DEFAULT_MAX_BIG_POWER_BITS} if that property is not set. + * @param maximumBigPowerBits the maximum number of bits; must be a + * positive number; use {@link Integer#MAX_VALUE} for no limit + */ + public Builder maximumBigPowerBits(int maximumBigPowerBits) { + Assert.isTrue(maximumBigPowerBits > 0, "'maximumBigPowerBits' must be a positive number"); + this.maximumBigPowerBits = maximumBigPowerBits; + return this; + } + + /** + * Build the {@link SpelParserConfiguration} configured via this builder. + */ + public SpelParserConfiguration build() { + int maximumOperations = (this.maximumOperations != null ? + this.maximumOperations : retrieveMaxOperations()); + int maximumBigPowerBits = (this.maximumBigPowerBits != null ? + this.maximumBigPowerBits : retrieveMaxBigPowerBits()); + return new SpelParserConfiguration(this.compilerMode, this.compilerClassLoader, + this.autoGrowNullReferences, this.autoGrowCollections, this.maximumAutoGrowSize, + this.maximumExpressionLength, maximumOperations, maximumBigPowerBits); + } + + } + } diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java index 7322f20ef99..0e1950a3323 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/SpelExpressionParser.java @@ -41,7 +41,7 @@ public class SpelExpressionParser extends TemplateAwareExpressionParser { * Create a parser with default settings. */ public SpelExpressionParser() { - this.configuration = new SpelParserConfiguration(); + this.configuration = SpelParserConfiguration.withDefaults(); } /** diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java index 494cd7856ac..5d44810cd8b 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java @@ -86,8 +86,9 @@ class EvaluationTests extends AbstractExpressionTests { String expression = "'%s'".formatted("Y".repeat(19_998)); assertThat(expression).hasSize(maximumExpressionLength); - SpelParserConfiguration configuration = - new SpelParserConfiguration(null, null, false, false, 0, maximumExpressionLength); + SpelParserConfiguration configuration = SpelParserConfiguration.builder() + .maximumExpressionLength(maximumExpressionLength) + .build(); ExpressionParser parser = new SpelExpressionParser(configuration); Expression expr = parser.parseExpression(expression); @@ -111,8 +112,11 @@ class EvaluationTests extends AbstractExpressionTests { Expression expr1 = parser.parseExpression(expression); assertThat(expr1.getValue(Boolean.class)).isTrue(); - SpelParserConfiguration configuration = - new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, maxLength, maxOperations); + SpelParserConfiguration configuration = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .maximumExpressionLength(maxLength) + .maximumOperations(maxOperations) + .build(); parser = new SpelExpressionParser(configuration); Expression expr2 = parser.parseExpression(expression); assertThatExceptionOfType(SpelEvaluationException.class) @@ -167,13 +171,20 @@ class EvaluationTests extends AbstractExpressionTests { SpringProperties.setProperty(SPRING_EXPRESSION_MAX_OPERATIONS_PROPERTY_NAME, "" + maxOperations / 2); // maxOperations + 1 should override maxOperations / 2 - SpelParserConfiguration configuration = - new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, maxLength, maxOperations + 1); + SpelParserConfiguration configuration = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .maximumExpressionLength(maxLength) + .maximumOperations(maxOperations + 1) + .build(); parser = new SpelExpressionParser(configuration); expr1 = parser.parseExpression(expression); assertThat(expr1.getValue(Boolean.class)).isTrue(); - configuration = new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, maxLength, maxOperations); + configuration = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .maximumExpressionLength(maxLength) + .maximumOperations(maxOperations) + .build(); parser = new SpelExpressionParser(configuration); Expression expr2 = parser.parseExpression(expression); assertThatExceptionOfType(SpelEvaluationException.class) @@ -191,7 +202,8 @@ class EvaluationTests extends AbstractExpressionTests { @Test void createListsOnAttemptToIndexNull01() throws EvaluationException, ParseException { - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e = parser.parseExpression("list[0]"); TestClass testClass = new TestClass(); @@ -213,7 +225,8 @@ class EvaluationTests extends AbstractExpressionTests { void createMapsOnAttemptToIndexNull() { TestClass testClass = new TestClass(); StandardEvaluationContext ctx = new StandardEvaluationContext(testClass); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Object o = parser.parseExpression("map['a']").getValue(ctx); assertThat(o).isNull(); @@ -230,7 +243,8 @@ class EvaluationTests extends AbstractExpressionTests { void createObjectsOnAttemptToReferenceNull() { TestClass testClass = new TestClass(); StandardEvaluationContext ctx = new StandardEvaluationContext(testClass); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Object o = parser.parseExpression("wibble.bar").getValue(ctx); assertThat(o).isEqualTo("hello"); @@ -385,7 +399,7 @@ class EvaluationTests extends AbstractExpressionTests { void initializingCollectionElementsOnWrite() { TestPerson person = new TestPerson(); EvaluationContext context = new StandardEvaluationContext(person); - SpelParserConfiguration config = new SpelParserConfiguration(true, true); + SpelParserConfiguration config = SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build(); ExpressionParser parser = new SpelExpressionParser(config); Expression e = parser.parseExpression("name"); e.setValue(context, "Oleg"); @@ -452,7 +466,8 @@ class EvaluationTests extends AbstractExpressionTests { // Add a new element to the list StandardEvaluationContext ctx = new StandardEvaluationContext(instance); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e = parser.parseExpression("listOfStrings[++index3]='def'"); e.getValue(ctx); assertThat(instance.listOfStrings).hasSize(2); @@ -460,7 +475,8 @@ class EvaluationTests extends AbstractExpressionTests { // Check reference beyond end of collection ctx = new StandardEvaluationContext(instance); - parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); e = parser.parseExpression("listOfStrings[0]"); String value = e.getValue(ctx, String.class); assertThat(value).isEqualTo("abc"); @@ -473,7 +489,7 @@ class EvaluationTests extends AbstractExpressionTests { // Now turn off growing and reference off the end StandardEvaluationContext failCtx = new StandardEvaluationContext(instance); - parser = new SpelExpressionParser(new SpelParserConfiguration(false, false)); + parser = new SpelExpressionParser(SpelParserConfiguration.withDefaults()); Expression failExp = parser.parseExpression("listOfStrings[3]"); assertThatExceptionOfType(SpelEvaluationException.class) .isThrownBy(() -> failExp.getValue(failCtx, String.class)) @@ -484,7 +500,12 @@ class EvaluationTests extends AbstractExpressionTests { void limitCollectionGrowing() { TestClass instance = new TestClass(); StandardEvaluationContext ctx = new StandardEvaluationContext(instance); - SpelExpressionParser parser = new SpelExpressionParser( new SpelParserConfiguration(true, true, 3)); + SpelParserConfiguration config = SpelParserConfiguration.builder() + .autoGrowNullReferences() + .autoGrowCollections() + .maximumAutoGrowSize(3) + .build(); + SpelExpressionParser parser = new SpelExpressionParser(config); Expression e = parser.parseExpression("foo[2]"); e.setValue(ctx, "2"); assertThat(instance.getFoo()).hasSize(3); @@ -819,9 +840,12 @@ class EvaluationTests extends AbstractExpressionTests { // Use a small limit (16 bits) to verify behavior in tests. private static final int TEST_MAX_RESULT_BITS = 16; - private final SpelExpressionParser limitedParser = new SpelExpressionParser( - new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, - 0, 10, 10, TEST_MAX_RESULT_BITS)); + private final SpelExpressionParser limitedParser = new SpelExpressionParser(SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .maximumExpressionLength(10) + .maximumOperations(10) + .maximumBigPowerBits(TEST_MAX_RESULT_BITS) + .build()); @Test @@ -1016,7 +1040,8 @@ class EvaluationTests extends AbstractExpressionTests { void increment01root() { Integer i = 42; StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e = parser.parseExpression("#this++"); assertThat(i).isEqualTo(42); assertThatExceptionOfType(SpelEvaluationException.class) @@ -1028,7 +1053,8 @@ class EvaluationTests extends AbstractExpressionTests { void increment02postfix() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // BigDecimal @@ -1081,7 +1107,8 @@ class EvaluationTests extends AbstractExpressionTests { void increment02prefix() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // BigDecimal @@ -1134,7 +1161,8 @@ class EvaluationTests extends AbstractExpressionTests { void increment03() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e1 = parser.parseExpression("m()++"); assertThatExceptionOfType(SpelEvaluationException.class) @@ -1151,7 +1179,8 @@ class EvaluationTests extends AbstractExpressionTests { void increment04() { Integer i = 42; StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e1 = parser.parseExpression("++1"); assertThatExceptionOfType(SpelEvaluationException.class) .isThrownBy(() -> e1.getValue(ctx, double.class)) @@ -1166,7 +1195,8 @@ class EvaluationTests extends AbstractExpressionTests { void decrement01root() { Integer i = 42; StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e = parser.parseExpression("#this--"); assertThat(i).isEqualTo(42); assertThatExceptionOfType(SpelEvaluationException.class) @@ -1178,7 +1208,8 @@ class EvaluationTests extends AbstractExpressionTests { void decrement02postfix() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // BigDecimal @@ -1231,7 +1262,8 @@ class EvaluationTests extends AbstractExpressionTests { void decrement02prefix() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // BigDecimal @@ -1284,7 +1316,8 @@ class EvaluationTests extends AbstractExpressionTests { void decrement03() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e1 = parser.parseExpression("m()--"); assertThatExceptionOfType(SpelEvaluationException.class) @@ -1301,7 +1334,8 @@ class EvaluationTests extends AbstractExpressionTests { void decrement04() { Integer i = 42; StandardEvaluationContext ctx = new StandardEvaluationContext(i); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e1 = parser.parseExpression("--1"); assertThatExceptionOfType(SpelEvaluationException.class) .isThrownBy(() -> e1.getValue(ctx, Integer.class)) @@ -1317,7 +1351,8 @@ class EvaluationTests extends AbstractExpressionTests { void incrementAndDecrementTogether() { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // index1 is 2 at the start - the 'intArray[#root.index1++]' should not be evaluated twice! @@ -1345,7 +1380,8 @@ class EvaluationTests extends AbstractExpressionTests { void incrementAllNodeTypes() throws SecurityException, NoSuchMethodException { Spr9751 helper = new Spr9751(); StandardEvaluationContext ctx = new StandardEvaluationContext(helper); - ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + ExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression e; // BooleanLiteral diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java index 618e3450506..b4e503b4d37 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java @@ -159,7 +159,8 @@ class IndexingTests { @Test void setPropertyContainingMapAutoGrow() { - SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, false)); + SpelExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().build()); Expression expression = parser.parseExpression("parameterizedMap"); assertThat(expression.getValueTypeDescriptor(this)).asString() .isEqualTo("java.util.Map"); @@ -204,7 +205,8 @@ class IndexingTests { void setGenericPropertyContainingListAutogrow() { List property = new ArrayList<>(); this.property = property; - SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + SpelExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); Expression expression = parser.parseExpression("property"); assertThat(expression.getValueTypeDescriptor(this)).asString() .isEqualTo("@%s java.util.ArrayList", FieldAnnotation.class.getCanonicalName()); @@ -219,7 +221,8 @@ class IndexingTests { @Test void autoGrowListOfElementsWithoutDefaultConstructor() { this.decimals = new ArrayList<>(); - SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + SpelExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); parser.parseExpression("decimals[0]").setValue(this, "123.4"); assertThat(decimals).containsExactly(BigDecimal.valueOf(123.4)); } @@ -229,7 +232,8 @@ class IndexingTests { this.decimals = new ArrayList<>(); this.decimals.add(null); this.decimals.add(BigDecimal.ONE); - SpelExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration(true, true)); + SpelExpressionParser parser = new SpelExpressionParser( + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build()); parser.parseExpression("decimals[0]").setValue(this, "9876.5"); assertThat(decimals).containsExactly(BigDecimal.valueOf(9876.5), BigDecimal.ONE); } @@ -280,7 +284,8 @@ class IndexingTests { @Test void indexIntoGenericPropertyContainingNullList() { - SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); + SpelParserConfiguration configuration = + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build(); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("property"); assertThat(expression.getValueTypeDescriptor(this)).asString() @@ -297,7 +302,8 @@ class IndexingTests { void indexIntoGenericPropertyContainingGrowingList() { List property = new ArrayList<>(); this.property = property; - SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); + SpelParserConfiguration configuration = + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build(); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("property"); assertThat(expression.getValueTypeDescriptor(this)).asString() @@ -314,7 +320,8 @@ class IndexingTests { void indexIntoGenericPropertyContainingGrowingList2() { List property2 = new ArrayList<>(); this.property2 = property2; - SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); + SpelParserConfiguration configuration = + SpelParserConfiguration.builder().autoGrowNullReferences().autoGrowCollections().build(); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("property2"); assertThat(expression.getValueTypeDescriptor(this)).asString().isEqualTo("java.util.ArrayList"); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java index c8c0553add0..046086b4080 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelCompilationCoverageTests.java @@ -255,8 +255,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void failsWhenSettingContextForExpression_SPR12326() { - SpelExpressionParser parser = new SpelExpressionParser( - new SpelParserConfiguration(SpelCompilerMode.OFF, getClass().getClassLoader())); + SpelExpressionParser parser = new SpelExpressionParser(SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .compilerClassLoader(getClass().getClassLoader()) + .build()); Person3 person = new Person3("foo", 1); SpelExpression expression = parser.parseRaw("#it?.age?.equals([0])"); StandardEvaluationContext context = new StandardEvaluationContext(new Object[] {1}); @@ -1232,8 +1234,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void opNe_SPR14863() { - SpelParserConfiguration configuration = - new SpelParserConfiguration(SpelCompilerMode.MIXED, ClassLoader.getSystemClassLoader()); + SpelParserConfiguration configuration = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.MIXED) + .compilerClassLoader(ClassLoader.getSystemClassLoader()) + .build(); SpelExpressionParser parser = new SpelExpressionParser(configuration); Expression expression = parser.parseExpression("data['my-key'] != 'my-value'"); @@ -1307,7 +1311,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void nullComparison_SPR22358() { - SpelParserConfiguration configuration = new SpelParserConfiguration(SpelCompilerMode.OFF, null); + SpelParserConfiguration configuration = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.OFF).build(); SpelExpressionParser parser = new SpelExpressionParser(configuration); StandardEvaluationContext ctx = new StandardEvaluationContext(); ctx.setRootObject(new Reg(1)); @@ -3460,8 +3464,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void compilationOfBasicNullSafeMethodReference() { - SpelExpressionParser parser = new SpelExpressionParser( - new SpelParserConfiguration(SpelCompilerMode.OFF, getClass().getClassLoader())); + SpelExpressionParser parser = new SpelExpressionParser(SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.OFF) + .compilerClassLoader(getClass().getClassLoader()) + .build()); SpelExpression expression = parser.parseRaw("#it?.equals(3)"); StandardEvaluationContext context = new StandardEvaluationContext(new Object[] {1}); context.setVariable("it", 3); @@ -4132,8 +4138,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { assertThat(expression.getValue(context, Boolean.class)).isTrue(); // Variant of above more like what was in the bug report: - SpelExpressionParser parser = new SpelExpressionParser( - new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, getClass().getClassLoader())); + SpelExpressionParser parser = new SpelExpressionParser(SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.IMMEDIATE) + .compilerClassLoader(getClass().getClassLoader()) + .build()); SpelExpression ex = parser.parseRaw("#it?.age.equals([0])"); context = new StandardEvaluationContext(new Object[] {person.getAge()}); @@ -5268,8 +5276,10 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void indexIntoMap_SPR12045() { - SpelParserConfiguration config = new SpelParserConfiguration( - SpelCompilerMode.IMMEDIATE,getClass().getClassLoader()); + SpelParserConfiguration config = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.IMMEDIATE) + .compilerClassLoader(getClass().getClassLoader()) + .build(); SpelExpressionParser parser = new SpelExpressionParser(config); expression = parser.parseExpression("headers[command]"); @@ -6069,7 +6079,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void ternaryWithMapAccess() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); expression = parser.parseExpression( @@ -6106,7 +6116,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test // gh-19758 void ternaryMiscellaneous() { - SpelParserConfiguration configuration = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration configuration = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); Expression exp; StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("map", Map.of("foo", "qux")); @@ -6225,7 +6235,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test // gh-19758 void elvisMiscellaneous() { - SpelParserConfiguration configuration = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration configuration = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); Expression exp; exp = new SpelExpressionParser(configuration).parseExpression("bar()"); @@ -6309,7 +6319,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests { @Test void elvis_SPR17214() { - SpelParserConfiguration spc = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration spc = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser sep = new SpelExpressionParser(spc); RecordHolder rh = null; diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelParserConfigurationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelParserConfigurationTests.java new file mode 100644 index 00000000000..f5825706d24 --- /dev/null +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelParserConfigurationTests.java @@ -0,0 +1,243 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.expression.spel; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link SpelParserConfiguration}. + * + * @author Sam Brannen + * @since 7.0.10 + */ +class SpelParserConfigurationTests { + + @Test + void builderAppliesSameDefaultsAsNoArgConstructor() { + SpelParserConfiguration expected = new SpelParserConfiguration(); + SpelParserConfiguration actual = SpelParserConfiguration.builder().build(); + + assertThat(actual.getCompilerMode()).isEqualTo(expected.getCompilerMode()); + assertThat(actual.getCompilerClassLoader()).isEqualTo(expected.getCompilerClassLoader()); + assertThat(actual.isAutoGrowNullReferences()).isEqualTo(expected.isAutoGrowNullReferences()); + assertThat(actual.isAutoGrowCollections()).isEqualTo(expected.isAutoGrowCollections()); + // maximumAutoGrowSize defaults actually differ, so not asserted here + assertThat(actual.getMaximumExpressionLength()).isEqualTo(expected.getMaximumExpressionLength()); + assertThat(actual.getMaximumOperations()).isEqualTo(expected.getMaximumOperations()); + assertThat(actual.getMaximumBigPowerBits()).isEqualTo(expected.getMaximumBigPowerBits()); + } + + @Test + void maximumAutoGrowSizeDefaults() { + // Unlike the no-arg constructor, which defaults to Integer.MAX_VALUE for + // backward compatibility, the builder defaults to 256 for consistency with + // DataBinder.DEFAULT_AUTO_GROW_COLLECTION_LIMIT. + assertThat(new SpelParserConfiguration().getMaximumAutoGrowSize()).isEqualTo(Integer.MAX_VALUE); + assertThat(SpelParserConfiguration.builder().build().getMaximumAutoGrowSize()).isEqualTo(256); + } + + @Test + void withDefaultsMatchesBuilderDefaults() { + SpelParserConfiguration expected = SpelParserConfiguration.builder().build(); + SpelParserConfiguration actual = SpelParserConfiguration.withDefaults(); + + assertThat(actual.getCompilerMode()).isEqualTo(expected.getCompilerMode()); + assertThat(actual.getCompilerClassLoader()).isEqualTo(expected.getCompilerClassLoader()); + assertThat(actual.isAutoGrowNullReferences()).isEqualTo(expected.isAutoGrowNullReferences()); + assertThat(actual.isAutoGrowCollections()).isEqualTo(expected.isAutoGrowCollections()); + assertThat(actual.getMaximumAutoGrowSize()).isEqualTo(expected.getMaximumAutoGrowSize()); + assertThat(actual.getMaximumExpressionLength()).isEqualTo(expected.getMaximumExpressionLength()); + assertThat(actual.getMaximumOperations()).isEqualTo(expected.getMaximumOperations()); + assertThat(actual.getMaximumBigPowerBits()).isEqualTo(expected.getMaximumBigPowerBits()); + } + + @Test + void builderAppliesCustomValues() { + ClassLoader classLoader = getClass().getClassLoader(); + + SpelParserConfiguration configuration = SpelParserConfiguration.builder() + .compilerMode(SpelCompilerMode.IMMEDIATE) + .compilerClassLoader(classLoader) + .autoGrowNullReferences() + .autoGrowCollections() + .maximumAutoGrowSize(99) + .maximumExpressionLength(100) + .maximumOperations(101) + .maximumBigPowerBits(102) + .build(); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.IMMEDIATE); + assertThat(configuration.getCompilerClassLoader()).isSameAs(classLoader); + assertThat(configuration.isAutoGrowNullReferences()).isTrue(); + assertThat(configuration.isAutoGrowCollections()).isTrue(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(99); + assertThat(configuration.getMaximumExpressionLength()).isEqualTo(100); + assertThat(configuration.getMaximumOperations()).isEqualTo(101); + assertThat(configuration.getMaximumBigPowerBits()).isEqualTo(102); + } + + @Test + void builderRejectsInvalidValues() { + SpelParserConfiguration.Builder builder = SpelParserConfiguration.builder(); + + assertThatIllegalArgumentException().isThrownBy(() -> builder.compilerMode(null)); + assertThatIllegalArgumentException().isThrownBy(() -> builder.maximumAutoGrowSize(-1)); + assertThatIllegalArgumentException().isThrownBy(() -> builder.maximumExpressionLength(0)); + assertThatIllegalArgumentException().isThrownBy(() -> builder.maximumOperations(0)); + assertThatIllegalArgumentException().isThrownBy(() -> builder.maximumBigPowerBits(0)); + } + + + /** + * Regression tests for the legacy constructors in {@link SpelParserConfiguration}. + *

Elsewhere in the test suite, we prefer {@link SpelParserConfiguration#builder()} + * over these constructors. Thus, this nested class exists so that constructor coverage + * remains in one place, which will also keep any future deprecation warnings for + * these constructors confined to this class. + */ + @Nested + class LegacyConstructorTests { + + @Test + void noArgConstructorAppliesDefaults() { + SpelParserConfiguration configuration = new SpelParserConfiguration(); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.OFF); + assertThat(configuration.getCompilerClassLoader()).isNull(); + assertThat(configuration.isAutoGrowNullReferences()).isFalse(); + assertThat(configuration.isAutoGrowCollections()).isFalse(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(Integer.MAX_VALUE); + assertThat(configuration.getMaximumExpressionLength()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_EXPRESSION_LENGTH); + assertThat(configuration.getMaximumOperations()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_OPERATIONS); + assertThat(configuration.getMaximumBigPowerBits()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_BIG_POWER_BITS); + } + + @Test + void compilerModeAndClassLoaderConstructor() { + ClassLoader classLoader = getClass().getClassLoader(); + SpelParserConfiguration configuration = + new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, classLoader); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.IMMEDIATE); + assertThat(configuration.getCompilerClassLoader()).isSameAs(classLoader); + assertThat(configuration.isAutoGrowNullReferences()).isFalse(); + assertThat(configuration.isAutoGrowCollections()).isFalse(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(Integer.MAX_VALUE); + } + + @Test + void autoGrowFlagsConstructor() { + SpelParserConfiguration configuration = new SpelParserConfiguration(true, true); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.OFF); + assertThat(configuration.isAutoGrowNullReferences()).isTrue(); + assertThat(configuration.isAutoGrowCollections()).isTrue(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(Integer.MAX_VALUE); + } + + @Test + void autoGrowFlagsAndMaximumAutoGrowSizeConstructor() { + SpelParserConfiguration configuration = new SpelParserConfiguration(true, true, 99); + + assertThat(configuration.isAutoGrowNullReferences()).isTrue(); + assertThat(configuration.isAutoGrowCollections()).isTrue(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(99); + } + + @Test + void fiveArgConstructorAppliesAllValues() { + ClassLoader classLoader = getClass().getClassLoader(); + SpelParserConfiguration configuration = new SpelParserConfiguration( + SpelCompilerMode.IMMEDIATE, classLoader, true, true, 99); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.IMMEDIATE); + assertThat(configuration.getCompilerClassLoader()).isSameAs(classLoader); + assertThat(configuration.isAutoGrowNullReferences()).isTrue(); + assertThat(configuration.isAutoGrowCollections()).isTrue(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(99); + assertThat(configuration.getMaximumExpressionLength()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_EXPRESSION_LENGTH); + } + + @Test + void sixArgConstructorAppliesMaximumExpressionLength() { + SpelParserConfiguration configuration = new SpelParserConfiguration( + SpelCompilerMode.IMMEDIATE, null, true, true, 99, 100); + + assertThat(configuration.getMaximumExpressionLength()).isEqualTo(100); + assertThat(configuration.getMaximumOperations()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_OPERATIONS); + } + + @Test + void sevenArgConstructorAppliesMaximumOperations() { + SpelParserConfiguration configuration = new SpelParserConfiguration( + SpelCompilerMode.IMMEDIATE, null, true, true, 99, 100, 101); + + assertThat(configuration.getMaximumOperations()).isEqualTo(101); + assertThat(configuration.getMaximumBigPowerBits()) + .isEqualTo(SpelParserConfiguration.DEFAULT_MAX_BIG_POWER_BITS); + } + + @Test + void canonicalConstructorAppliesAllValues() { + ClassLoader classLoader = getClass().getClassLoader(); + SpelParserConfiguration configuration = new SpelParserConfiguration( + SpelCompilerMode.IMMEDIATE, classLoader, true, true, 99, 100, 101, 102); + + assertThat(configuration.getCompilerMode()).isEqualTo(SpelCompilerMode.IMMEDIATE); + assertThat(configuration.getCompilerClassLoader()).isSameAs(classLoader); + assertThat(configuration.isAutoGrowNullReferences()).isTrue(); + assertThat(configuration.isAutoGrowCollections()).isTrue(); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(99); + assertThat(configuration.getMaximumExpressionLength()).isEqualTo(100); + assertThat(configuration.getMaximumOperations()).isEqualTo(101); + assertThat(configuration.getMaximumBigPowerBits()).isEqualTo(102); + } + + @Test + void canonicalConstructorRejectsInvalidValues() { + assertThatIllegalArgumentException().isThrownBy(() -> + new SpelParserConfiguration(null, null, false, false, 0, 1, 1, 1)); + assertThatIllegalArgumentException().isThrownBy(() -> + new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, 0, 1, 1)); + assertThatIllegalArgumentException().isThrownBy(() -> + new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, 1, 0, 1)); + assertThatIllegalArgumentException().isThrownBy(() -> + new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, 0, 1, 1, 0)); + } + + @Test + void canonicalConstructorDoesNotRejectNegativeMaximumAutoGrowSize() { + // Unlike SpelParserConfiguration.Builder#maximumAutoGrowSize(int), this + // constructor does not (yet) validate that maximumAutoGrowSize is not + // negative -- that precondition was only introduced on the 7.1 line. + SpelParserConfiguration configuration = + new SpelParserConfiguration(SpelCompilerMode.OFF, null, false, false, -1, 1, 1, 1); + assertThat(configuration.getMaximumAutoGrowSize()).isEqualTo(-1); + } + + } + +} diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java index 76a7654d21f..e27c7925605 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java @@ -696,8 +696,8 @@ class SpelReproTests extends AbstractExpressionTests { @Test void compiledExpressionForProxy_SPR16191() { - SpelExpressionParser expressionParser = - new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null)); + SpelExpressionParser expressionParser = new SpelExpressionParser( + SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build()); Expression expression = expressionParser.parseExpression("#target.process(#root)"); VarargsReceiver receiver = new VarargsReceiver(); @@ -1274,7 +1274,7 @@ class SpelReproTests extends AbstractExpressionTests { @Test void SPR10452() { - SpelParserConfiguration configuration = new SpelParserConfiguration(false, false); + SpelParserConfiguration configuration = SpelParserConfiguration.withDefaults(); ExpressionParser parser = new SpelExpressionParser(configuration); StandardEvaluationContext context = new StandardEvaluationContext(); @@ -1299,7 +1299,7 @@ class SpelReproTests extends AbstractExpressionTests { @Test void SPR9495() { - SpelParserConfiguration configuration = new SpelParserConfiguration(false, false); + SpelParserConfiguration configuration = SpelParserConfiguration.withDefaults(); ExpressionParser parser = new SpelExpressionParser(configuration); StandardEvaluationContext context = new StandardEvaluationContext(); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java index 67799dd7ab5..b19ebbf8fd7 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelCompilerTests.java @@ -46,7 +46,7 @@ class SpelCompilerTests { @Test // gh-24357 void expressionCompilesWhenMethodComesFromPublicInterface() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); OrderedComponent component = new OrderedComponent(); @@ -59,7 +59,7 @@ class SpelCompilerTests { @Test // gh-25706 void defaultMethodInvocation() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); StandardEvaluationContext context = new StandardEvaluationContext(); @@ -84,7 +84,7 @@ class SpelCompilerTests { @Test void simpleEvaluationContextBlocksCompilationByDefault() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); // "order" resides in the public Ordered interface and is therefore compilable, // so any non-compilation is attributable solely to the context's policy. @@ -102,7 +102,7 @@ class SpelCompilerTests { @Test void simpleEvaluationContextAllowsCompilationWhenSupported() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); // "order" resides in the public Ordered interface and is therefore compilable. Expression expression = parser.parseExpression("order"); @@ -120,7 +120,7 @@ class SpelCompilerTests { @Test void simpleEvaluationContextIgnoresPrecompiledExpressionByDefault() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); // "order" resides in the public Ordered interface and is therefore compilable. Expression expression = parser.parseExpression("order"); @@ -155,7 +155,7 @@ class SpelCompilerTests { */ @Test void simpleEvaluationContextSetAsDefaultBlocksCompilationForImplicitContextVariants() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); // "order" resides in the public Ordered interface and is therefore compilable, // so any non-compilation is attributable solely to the context's policy. @@ -193,7 +193,7 @@ class SpelCompilerTests { */ @Test void simpleEvaluationContextSetAsDefaultIgnoresPrecompiledExpressionForImplicitContextVariants() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.IMMEDIATE).build(); SpelExpressionParser parser = new SpelExpressionParser(config); // "order" resides in the public Ordered interface and is therefore compilable. SpelExpression expression = parser.parseRaw("order"); @@ -240,7 +240,7 @@ class SpelCompilerTests { @Test // gh-28043 void changingRegisteredVariableTypeDoesNotResultInFailureInMixedMode() { - SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.MIXED, null); + SpelParserConfiguration config = SpelParserConfiguration.builder().compilerMode(SpelCompilerMode.MIXED).build(); SpelExpressionParser parser = new SpelExpressionParser(config); Expression sharedExpression = parser.parseExpression("#bean.value"); StandardEvaluationContext context = new StandardEvaluationContext();