Skip to content

Commit ae19ced

Browse files
committed
Address review comments.
1 parent a6a9ff7 commit ae19ced

File tree

5 files changed

+87
-35
lines changed

5 files changed

+87
-35
lines changed

src/main/antora/modules/ROOT/pages/value-expressions.adoc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[[valueexpressions.fundamentals]]
22
= Value Expressions Fundamentals
33

4-
Value Expressions a composition of {spring-framework-docs}/core/expressions.html[Spring Expression Language (SpEL)] and {spring-framework-docs}/core/beans/environment.html#beans-placeholder-resolution-in-statements[Property Placeholder Resolution].
5-
Value Expressions a combine powerful evaluation of programmatic expressions with the simplicity to resort to property-placeholder resolution to obtain values from the `Environment` such as configuration properties.
4+
Value Expressions are a combination of {spring-framework-docs}/core/expressions.html[Spring Expression Language (SpEL)] and {spring-framework-docs}/core/beans/environment.html#beans-placeholder-resolution-in-statements[Property Placeholder Resolution].
5+
They combine powerful evaluation of programmatic expressions with the simplicity to resort to property-placeholder resolution to obtain values from the `Environment` such as configuration properties.
66

77
Expressions are expected to be defined by a trusted input such as an annotation value and not to be determined from user input.
88

@@ -35,12 +35,12 @@ orders-${tenant-config.suffix} <4>
3535
<1> Value Expression using a single SpEL Expression.
3636
<2> Value Expression using a static SpEL Expression evaluating to `2-hello-world`.
3737
<3> Value Expression using a single Property Placeholder.
38-
<4> Composite expression comprised from the literal `orders-` and the Property Placeholder `${tenant-config.suffix}`.
38+
<4> Composite expression comprised of the literal `orders-` and the Property Placeholder `${tenant-config.suffix}`.
3939
<5> Composite expression using SpEL, Property Placeholders and literals.
4040
====
4141

42-
Using value expressions introduces a lot of flexibility to your code.
43-
Keep in mind that using value expressions requires evaluation of the expression on each usage.
42+
NOTE: Using value expressions introduces a lot of flexibility to your code.
43+
Doing so requires evaluation of the expression on each usage and, therefore, value expression evaluation has an impact on the performance profile.
4444

4545
[[valueexpressions.api]]
4646
== Parsing and Evaluation

src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
import org.apache.commons.logging.LogFactory;
3636
import org.springframework.beans.BeanUtils;
3737
import org.springframework.beans.BeansException;
38+
import org.springframework.beans.factory.BeanFactory;
39+
import org.springframework.beans.factory.BeanFactoryAware;
3840
import org.springframework.beans.factory.InitializingBean;
41+
import org.springframework.beans.factory.ListableBeanFactory;
3942
import org.springframework.context.ApplicationContext;
4043
import org.springframework.context.ApplicationContextAware;
4144
import org.springframework.context.ApplicationEventPublisher;
@@ -91,8 +94,8 @@
9194
* @author Christoph Strobl
9295
*/
9396
public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?, P>, P extends PersistentProperty<P>>
94-
implements MappingContext<E, P>, ApplicationEventPublisherAware, ApplicationContextAware, InitializingBean,
95-
EnvironmentAware {
97+
implements MappingContext<E, P>, ApplicationEventPublisherAware, ApplicationContextAware, BeanFactoryAware,
98+
EnvironmentAware, InitializingBean {
9699

97100
private static final Log LOGGER = LogFactory.getLog(MappingContext.class);
98101

@@ -132,18 +135,44 @@ public void setApplicationEventPublisher(ApplicationEventPublisher applicationEv
132135
this.applicationEventPublisher = applicationEventPublisher;
133136
}
134137

138+
/**
139+
* Sets the application context. Sets also {@link ApplicationEventPublisher} and {@link Environment} if these weren't
140+
* already set.
141+
*
142+
* @param applicationContext the ApplicationContext object to be used by this object.
143+
* @throws BeansException
144+
*/
135145
@Override
136146
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
137147

138-
this.evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(applicationContext);
148+
setBeanFactory(applicationContext);
139149

140-
if (applicationEventPublisher == null) {
150+
if (this.applicationEventPublisher == null) {
141151
this.applicationEventPublisher = applicationContext;
142152
}
143153

144-
this.environment = applicationContext.getEnvironment();
154+
if (this.environment == null) {
155+
this.environment = applicationContext.getEnvironment();
156+
}
157+
}
158+
159+
/**
160+
* @param beanFactory owning BeanFactory.
161+
* @throws BeansException
162+
* @since 3.3
163+
*/
164+
@Override
165+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
166+
167+
if (beanFactory instanceof ListableBeanFactory lbf) {
168+
this.evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(lbf);
169+
}
145170
}
146171

172+
/**
173+
* @param environment the {@code Environment} that this component runs in.
174+
* @since 3.3
175+
*/
147176
@Override
148177
public void setEnvironment(Environment environment) {
149178
this.environment = environment;
@@ -210,6 +239,7 @@ public Collection<E> getPersistentEntities() {
210239
}
211240
}
212241

242+
@Override
213243
@Nullable
214244
public E getPersistentEntity(Class<?> type) {
215245
return getPersistentEntity(TypeInformation.of(type));

src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ public void setEvaluationContextProvider(EvaluationContextProvider provider) {
229229
this.evaluationContextProvider = provider;
230230
}
231231

232+
/**
233+
* @param environment the {@code Environment} that this component runs in.
234+
* @since 3.3
235+
*/
232236
@Override
233237
public void setEnvironment(Environment environment) {
234238
this.environment = environment;

src/main/java/org/springframework/data/spel/DefaultValueExpressionParser.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ private CompositeValueExpression parseComposite(String expression, int placerhol
8282
int endIndex = findPlaceholderEndIndex(expression, startIndex);
8383
if (endIndex != -1) {
8484

85-
String part = expression.substring(startIndex, endIndex + 1);
85+
int afterClosingParenthesisIndex = endIndex + 1;
86+
String part = expression.substring(startIndex, afterClosingParenthesisIndex);
8687

8788
if (part.startsWith(PLACEHOLDER_PREFIX)) {
8889
expressions.add(createPlaceholder(part));
@@ -96,9 +97,11 @@ private CompositeValueExpression parseComposite(String expression, int placerhol
9697
startIndex = getStartIndex(placerholderIndex, expressionIndex);
9798

9899
if (startIndex == -1) {
99-
expressions.add(new LiteralValueExpression(expression.substring(endIndex + 1)));
100+
// no next expression but we're capturing everything after the expression as literal.
101+
expressions.add(new LiteralValueExpression(expression.substring(afterClosingParenthesisIndex)));
100102
} else {
101-
expressions.add(new LiteralValueExpression(expression.substring(endIndex + 1, startIndex)));
103+
// capture literal after the expression ends and before the next starts.
104+
expressions.add(new LiteralValueExpression(expression.substring(afterClosingParenthesisIndex, startIndex)));
102105
}
103106
} else {
104107
expressions.add(new LiteralValueExpression(expression.substring(startIndex)));
@@ -127,6 +130,7 @@ private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
127130

128131
int index = startIndex + PLACEHOLDER_PREFIX_LENGTH;
129132
char quotationChar = 0;
133+
char nestingLevel = 0;
130134
boolean skipEscape = false;
131135

132136
while (index < buf.length()) {
@@ -149,11 +153,18 @@ private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
149153
quotationChar = 0;
150154
}
151155

152-
if (!skipEscape && quotationChar == 0 && c == SUFFIX) {
153-
return index;
154-
} else {
155-
index++;
156+
if (!skipEscape && quotationChar == 0) {
157+
158+
if (nestingLevel != 0 && c == SUFFIX) {
159+
nestingLevel--;
160+
} else if (c == '{') {
161+
nestingLevel++;
162+
} else if (nestingLevel == 0 && c == SUFFIX) {
163+
return index;
164+
}
156165
}
166+
167+
index++;
157168
}
158169

159170
return -1;

src/test/java/org/springframework/data/util/ValueEvaluationUnitTests.java renamed to src/test/java/org/springframework/data/spel/ValueEvaluationUnitTests.java

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023 the original author or authors.
2+
* Copyright 2024 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.
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.springframework.data.util;
16+
package org.springframework.data.spel;
1717

1818
import static org.assertj.core.api.Assertions.*;
1919

@@ -25,10 +25,9 @@
2525
import org.springframework.core.env.Environment;
2626
import org.springframework.core.env.MapPropertySource;
2727
import org.springframework.core.env.StandardEnvironment;
28-
import org.springframework.data.spel.ValueEvaluationContext;
29-
import org.springframework.data.spel.ValueExpressionParser;
30-
import org.springframework.data.spel.ValueParserConfiguration;
3128
import org.springframework.expression.EvaluationContext;
29+
import org.springframework.expression.EvaluationException;
30+
import org.springframework.expression.ParseException;
3231
import org.springframework.expression.spel.standard.SpelExpressionParser;
3332
import org.springframework.expression.spel.support.StandardEvaluationContext;
3433

@@ -45,7 +44,7 @@ public class ValueEvaluationUnitTests {
4544
@BeforeEach
4645
void setUp() {
4746

48-
MapPropertySource propertySource = new MapPropertySource("map", Map.of("key.one", "value"));
47+
MapPropertySource propertySource = new MapPropertySource("map", Map.of("env.key.one", "value", "env.key.two", 42L));
4948
StandardEnvironment environment = new StandardEnvironment();
5049
environment.getPropertySources().addFirst(propertySource);
5150

@@ -58,29 +57,34 @@ public Environment getEnvironment() {
5857
@Override
5958
public EvaluationContext getEvaluationContext() {
6059
StandardEvaluationContext context = new StandardEvaluationContext();
61-
context.setVariable("foo", "bar");
60+
context.setVariable("contextVar", "contextVal");
6261

6362
return context;
6463
}
6564
};
6665
}
6766

68-
@Test
67+
@Test // GH-2369
6968
void shouldParseAndEvaluateExpressions() {
7069

7170
assertThat(eval("foo")).isEqualTo("foo");
72-
assertThat(eval("${key.one}")).isEqualTo("value");
73-
assertThat(eval("${key.one")).isEqualTo("${key.one");
74-
assertThat(eval("#{'foo'}-${key.one")).isEqualTo("foo-${key.one");
71+
assertThat(eval("${env.key.one}")).isEqualTo("value");
72+
assertThat(eval("${env.key.two}")).isEqualTo("42");
73+
assertThat(eval("${env.key.one")).isEqualTo("${env.key.one");
74+
assertThat(eval("#{'foo'}-${env.key.one")).isEqualTo("foo-${env.key.one");
7575
assertThat(eval("#{'foo'}-")).isEqualTo("foo-");
7676
assertThat(eval("#{'fo\"o'}-")).isEqualTo("fo\"o-");
7777
assertThat(eval("#{\"foo\"}-")).isEqualTo("foo-");
7878
assertThat(eval("#{\"fo'o\"}-")).isEqualTo("fo'o-");
79-
assertThat(eval("${key.one}-")).isEqualTo("value-");
80-
assertThat(eval("#{#foo}")).isEqualTo("bar");
79+
assertThat(eval("${env.key.one}-")).isEqualTo("value-");
80+
assertThat(eval("#{#contextVar}")).isEqualTo("contextVal");
81+
assertThat(eval("${env.does.not.exist:some-default}-")).isEqualTo("some-default-");
82+
83+
assertThatExceptionOfType(EvaluationException.class).isThrownBy(() -> eval("${env.does.not.exist}-"))
84+
.withMessageContaining("Could not resolve placeholder 'env.does.not.exist'");
8185
}
8286

83-
@Test
87+
@Test // GH-2369
8488
void shouldParseLiteral() {
8589

8690
ValueParserConfiguration parserContext = () -> new SpelExpressionParser();
@@ -92,19 +96,22 @@ void shouldParseLiteral() {
9296
assertThat(parser.parse("${key.one}").isLiteral()).isFalse();
9397
}
9498

95-
@Test
99+
@Test // GH-2369
96100
void shouldParseCompoundExpression() {
97101

98102
assertThat(eval("#{'foo'}-${key.one")).isEqualTo("foo-${key.one");
99103
assertThat(eval("#{'foo'}-")).isEqualTo("foo-");
100104
assertThat(eval("#{'fo\"o'}-")).isEqualTo("fo\"o-");
101105
assertThat(eval("#{\"foo\"}-")).isEqualTo("foo-");
102106
assertThat(eval("#{\"fo'o\"}-")).isEqualTo("fo'o-");
103-
assertThat(eval("${key.one}-")).isEqualTo("value-");
104-
assertThat(eval("${key.one}-and-some-#{'foo'}-#{#foo}")).isEqualTo("value-and-some-foo-bar");
107+
assertThat(eval("${env.key.one}-")).isEqualTo("value-");
108+
assertThat(eval("${env.key.one}-and-some-#{'foo'}-#{#contextVar}")).isEqualTo("value-and-some-foo-contextVal");
109+
110+
assertThatExceptionOfType(ParseException.class).isThrownBy(() -> eval("#{#foo - ${numeric.key}}"))
111+
.withMessageContaining("[#foo - ${numeric.key}]");
105112
}
106113

107-
@Test
114+
@Test // GH-2369
108115
void shouldParseQuoted() {
109116

110117
assertThat(eval("#{(1+1) + '-foo'+\"-bar\"}")).isEqualTo("2-foo-bar");

0 commit comments

Comments
 (0)