Skip to content

Commit b814da6

Browse files
committed
Add SpEL support for settingPath in @Settings annotation.
Signed-off-by: Peter-Josef Meisch <[email protected]>
1 parent b552128 commit b814da6

File tree

6 files changed

+151
-23
lines changed

6 files changed

+151
-23
lines changed

src/main/antora/modules/ROOT/pages/elasticsearch/elasticsearch-new.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
[[new-features.6-0-0]]
55
== New in Spring Data Elasticsearch 6.0
66

7-
* Upgarde to Spring 7
7+
* Upgrade to Spring 7
88
* Switch to jspecify nullability annotations
99
* Upgrade to Elasticsearch 9.1.5
1010
* Use the new Elasticsearch Rest5Client as default
11+
* Add support for SpEL expressions in the `settingPath` parameter of the `@Setting` annotation
1112

1213

1314
[[new-features.5-5-0]]

src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.expression.EvaluationContext;
4848
import org.springframework.expression.EvaluationException;
4949
import org.springframework.expression.Expression;
50+
import org.springframework.expression.ExpressionException;
5051
import org.springframework.expression.ParserContext;
5152
import org.springframework.expression.common.LiteralExpression;
5253
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -298,7 +299,7 @@ public ElasticsearchPersistentProperty getPersistentPropertyWithFieldName(String
298299
Assert.notNull(fieldName, "fieldName must not be null");
299300

300301
return fieldNamePropertyCache.computeIfAbsent(fieldName, key -> {
301-
AtomicReference<ElasticsearchPersistentProperty> propertyRef = new AtomicReference<>();
302+
AtomicReference<@Nullable ElasticsearchPersistentProperty> propertyRef = new AtomicReference<>();
302303
doWithProperties((PropertyHandler<@NonNull ElasticsearchPersistentProperty>) property -> {
303304
if (key.equals(property.getFieldName())) {
304305
propertyRef.set(property);
@@ -423,9 +424,9 @@ public String resolveRouting(T bean) {
423424

424425
try {
425426
Expression expression = routingExpressions.computeIfAbsent(routing, PARSER::parseExpression);
426-
ExpressionDependencies expressionDependencies = ExpressionDependencies.discover(expression);
427+
ExpressionDependencies expressionDependencies = expression != null ? ExpressionDependencies.discover(expression)
428+
: ExpressionDependencies.none();
427429

428-
// noinspection ConstantConditions
429430
EvaluationContext context = getEvaluationContext(null, expressionDependencies);
430431
context.setVariable("entity", bean);
431432

@@ -440,8 +441,20 @@ public String resolveRouting(T bean) {
440441

441442
// region index settings
442443
@Override
443-
public String settingPath() {
444-
return settingsParameter.get().settingPath;
444+
public @Nullable String settingPath() {
445+
String settingPathFromParameter = settingsParameter.get().settingPath;
446+
if (settingPathFromParameter == null) {
447+
return null;
448+
}
449+
450+
try {
451+
Expression expression = PARSER.parseExpression(settingPathFromParameter, ParserContext.TEMPLATE_EXPRESSION);
452+
return (expression instanceof LiteralExpression) ? settingPathFromParameter
453+
: expression.getValue(getEvaluationContext(null, ExpressionDependencies.discover(expression)), String.class);
454+
} catch (ExpressionException e) {
455+
throw new InvalidDataAccessApiUsageException(
456+
"Could not resolve expression: " + settingPathFromParameter + " for @Setting.settingPath ", e);
457+
}
445458
}
446459

447460
@Override

src/main/java/org/springframework/data/elasticsearch/repository/support/spel/QueryStringSpELEvaluator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public String evaluate() {
8484

8585
if (expr != null) {
8686
EvaluationContext context = evaluationContextProvider.getEvaluationContext(parameterAccessor.getValues())
87-
.getRequiredEvaluationContext();
87+
.getEvaluationContext();
8888

8989
if (context instanceof StandardEvaluationContext standardEvaluationContext) {
9090
standardEvaluationContext.setTypeConverter(elasticsearchSpELTypeConverter);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.springframework.data.elasticsearch.core;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.context.annotation.Import;
6+
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
7+
8+
public class IndexSettingsELCIntegrationTests extends IndexSettingsIntegrationTests {
9+
@Configuration
10+
@Import({ ElasticsearchTemplateConfiguration.class })
11+
static class Config {
12+
@Bean
13+
public SpelSettingPath spelSettingPath() {
14+
return new SpelSettingPath();
15+
}
16+
}
17+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.springframework.data.elasticsearch.core;
2+
3+
import static org.assertj.core.api.Assertions.*;
4+
5+
import org.jspecify.annotations.Nullable;
6+
import org.junit.jupiter.api.DisplayName;
7+
import org.junit.jupiter.api.Test;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
import org.springframework.data.annotation.Id;
10+
import org.springframework.data.elasticsearch.annotations.Document;
11+
import org.springframework.data.elasticsearch.annotations.Setting;
12+
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
13+
14+
/**
15+
* IndexSettings test that need an regular conext setup for SpEL resolution for example.
16+
*/
17+
@SpringIntegrationTest
18+
public abstract class IndexSettingsIntegrationTests {
19+
20+
@Autowired protected ElasticsearchOperations operations;
21+
22+
@Test // #3187
23+
@DisplayName("should evaluate SpEL expression in settingPath")
24+
void shouldEvaluateSpElExpressionInSettingPath() {
25+
26+
var settingPath = operations.getElasticsearchConverter().getMappingContext()
27+
.getRequiredPersistentEntity(SettingPathWithSpel.class).settingPath();
28+
29+
assertThat(settingPath).isEqualTo(SpelSettingPath.SETTING_PATH);
30+
}
31+
32+
protected static class SpelSettingPath {
33+
public static String SETTING_PATH = "test-setting-path";
34+
35+
public String settingPath() {
36+
return SETTING_PATH;
37+
}
38+
}
39+
40+
@Document(indexName = "foo")
41+
@Setting(settingPath = "#{@spelSettingPath.settingPath}")
42+
private static class SettingPathWithSpel {
43+
@Nullable
44+
@Id String id;
45+
}
46+
47+
}

src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@
1919
import static org.skyscreamer.jsonassert.JSONAssert.*;
2020

2121
import org.json.JSONException;
22+
import org.jspecify.annotations.NonNull;
2223
import org.jspecify.annotations.Nullable;
24+
import org.junit.jupiter.api.BeforeEach;
2325
import org.junit.jupiter.api.DisplayName;
2426
import org.junit.jupiter.api.Nested;
2527
import org.junit.jupiter.api.Test;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.context.ApplicationContext;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
2632
import org.springframework.data.annotation.Id;
2733
import org.springframework.data.annotation.Version;
2834
import org.springframework.data.elasticsearch.annotations.Document;
@@ -38,6 +44,7 @@
3844
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
3945
import org.springframework.data.mapping.model.SimpleTypeHolder;
4046
import org.springframework.data.util.TypeInformation;
47+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
4148
import org.springframework.util.ReflectionUtils;
4249

4350
/**
@@ -60,9 +67,9 @@ class PropertiesTests {
6067
@Test
6168
public void shouldThrowExceptionGivenVersionPropertyIsNotLong() {
6269

63-
TypeInformation<EntityWithWrongVersionType> typeInformation = TypeInformation
70+
TypeInformation<@NonNull EntityWithWrongVersionType> typeInformation = TypeInformation
6471
.of(EntityWithWrongVersionType.class);
65-
SimpleElasticsearchPersistentEntity<EntityWithWrongVersionType> entity = new SimpleElasticsearchPersistentEntity<>(
72+
SimpleElasticsearchPersistentEntity<@NonNull EntityWithWrongVersionType> entity = new SimpleElasticsearchPersistentEntity<>(
6673
typeInformation, contextConfiguration);
6774

6875
assertThatThrownBy(() -> createProperty(entity, "version")).isInstanceOf(MappingException.class);
@@ -71,9 +78,9 @@ public void shouldThrowExceptionGivenVersionPropertyIsNotLong() {
7178
@Test
7279
public void shouldThrowExceptionGivenMultipleVersionPropertiesArePresent() {
7380

74-
TypeInformation<EntityWithMultipleVersionField> typeInformation = TypeInformation
81+
TypeInformation<@NonNull EntityWithMultipleVersionField> typeInformation = TypeInformation
7582
.of(EntityWithMultipleVersionField.class);
76-
SimpleElasticsearchPersistentEntity<EntityWithMultipleVersionField> entity = new SimpleElasticsearchPersistentEntity<>(
83+
SimpleElasticsearchPersistentEntity<@NonNull EntityWithMultipleVersionField> entity = new SimpleElasticsearchPersistentEntity<>(
7784
typeInformation, contextConfiguration);
7885
SimpleElasticsearchPersistentProperty persistentProperty1 = createProperty(entity, "version1");
7986
SimpleElasticsearchPersistentProperty persistentProperty2 = createProperty(entity, "version2");
@@ -100,9 +107,9 @@ void shouldFindPropertiesByMappedName() {
100107
@Test
101108
// DATAES-799
102109
void shouldReportThatThereIsNoSeqNoPrimaryTermPropertyWhenThereIsNoSuchProperty() {
103-
TypeInformation<EntityWithoutSeqNoPrimaryTerm> typeInformation = TypeInformation
110+
TypeInformation<@NonNull EntityWithoutSeqNoPrimaryTerm> typeInformation = TypeInformation
104111
.of(EntityWithoutSeqNoPrimaryTerm.class);
105-
SimpleElasticsearchPersistentEntity<EntityWithoutSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
112+
SimpleElasticsearchPersistentEntity<@NonNull EntityWithoutSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
106113
typeInformation, contextConfiguration);
107114

108115
assertThat(entity.hasSeqNoPrimaryTermProperty()).isFalse();
@@ -111,9 +118,9 @@ void shouldReportThatThereIsNoSeqNoPrimaryTermPropertyWhenThereIsNoSuchProperty(
111118
@Test
112119
// DATAES-799
113120
void shouldReportThatThereIsSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() {
114-
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
121+
TypeInformation<@NonNull EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
115122
.of(EntityWithSeqNoPrimaryTerm.class);
116-
SimpleElasticsearchPersistentEntity<EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
123+
SimpleElasticsearchPersistentEntity<@NonNull EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
117124
typeInformation, contextConfiguration);
118125

119126
entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm"));
@@ -125,9 +132,9 @@ void shouldReportThatThereIsSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() {
125132
// DATAES-799
126133
void shouldReturnSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() {
127134

128-
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
135+
TypeInformation<@NonNull EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
129136
.of(EntityWithSeqNoPrimaryTerm.class);
130-
SimpleElasticsearchPersistentEntity<EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
137+
SimpleElasticsearchPersistentEntity<@NonNull EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
131138
typeInformation, contextConfiguration);
132139
entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm"));
133140
EntityWithSeqNoPrimaryTerm instance = new EntityWithSeqNoPrimaryTerm();
@@ -144,9 +151,9 @@ void shouldReturnSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() {
144151
@Test
145152
// DATAES-799
146153
void shouldNotAllowMoreThanOneSeqNoPrimaryTermProperties() {
147-
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
154+
TypeInformation<@NonNull EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
148155
.of(EntityWithSeqNoPrimaryTerm.class);
149-
SimpleElasticsearchPersistentEntity<EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
156+
SimpleElasticsearchPersistentEntity<@NonNull EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
150157
typeInformation, contextConfiguration);
151158
entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm"));
152159

@@ -164,7 +171,24 @@ void shouldAllowFieldsWithIdPropertyNames() {
164171

165172
@Nested
166173
@DisplayName("index settings")
174+
@SpringJUnitConfig({ SettingsTests.Config.class })
167175
class SettingsTests {
176+
@Autowired private ApplicationContext applicationContext;
177+
178+
@Configuration
179+
static class Config {
180+
@Bean
181+
public SpelTestBean spelTestBean() {
182+
return new SpelTestBean();
183+
}
184+
}
185+
186+
@BeforeEach
187+
void setUp() {
188+
((SimpleElasticsearchMappingContext) elasticsearchConverter
189+
.get().getMappingContext()).setApplicationContext(applicationContext);
190+
191+
}
168192

169193
@Test // #1719
170194
@DisplayName("should error if index sorting parameters do not have the same number of arguments")
@@ -205,6 +229,24 @@ void shouldWriteSortParametersToSettingsObject() throws JSONException {
205229
String json = entity.getDefaultSettings().toJson();
206230
assertEquals(expected, json, false);
207231
}
232+
233+
@Test // #3187
234+
@DisplayName("should evaluate SpEL expression in settingPath")
235+
void shouldEvaluateSpElExpressionInSettingPath() {
236+
237+
var settingPath = elasticsearchConverter.get().getMappingContext()
238+
.getRequiredPersistentEntity(SettingPathWithSpel.class).settingPath();
239+
240+
assertThat(settingPath).isEqualTo(SpelTestBean.SETTING_PATH);
241+
}
242+
243+
private static class SpelTestBean {
244+
public static String SETTING_PATH = "test-setting-path";
245+
246+
public String settingPath() {
247+
return SETTING_PATH;
248+
}
249+
}
208250
}
209251

210252
@Nested
@@ -271,7 +313,7 @@ void shouldWriteTypeHintsWhenConfiguredExplicitlyOnEntityAndGlobalSettingIsFalse
271313
}
272314
}
273315

274-
// region helper functions
316+
// region helper
275317
private static SimpleElasticsearchPersistentProperty createProperty(SimpleElasticsearchPersistentEntity<?> entity,
276318
String fieldName) {
277319

@@ -282,6 +324,7 @@ private static SimpleElasticsearchPersistentProperty createProperty(SimpleElasti
282324
return new SimpleElasticsearchPersistentProperty(property, entity, SimpleTypeHolder.DEFAULT);
283325

284326
}
327+
285328
// endregion
286329

287330
// region entities
@@ -295,7 +338,7 @@ public String getVersion() {
295338
return version;
296339
}
297340

298-
public void setVersion(String version) {
341+
public void setVersion(@Nullable String version) {
299342
this.version = version;
300343
}
301344
}
@@ -313,7 +356,7 @@ public Long getVersion1() {
313356
return version1;
314357
}
315358

316-
public void setVersion1(Long version1) {
359+
public void setVersion1(@Nullable Long version1) {
317360
this.version1 = version1;
318361
}
319362

@@ -322,7 +365,7 @@ public Long getVersion2() {
322365
return version2;
323366
}
324367

325-
public void setVersion2(Long version2) {
368+
public void setVersion2(@Nullable Long version2) {
326369
this.version2 = version2;
327370
}
328371
}
@@ -397,5 +440,12 @@ private static class EnableTypeHintExplicitSetting {
397440
@Nullable
398441
@Id String id;
399442
}
443+
444+
@Document(indexName = "foo")
445+
@Setting(settingPath = "#{@spelTestBean.settingPath}")
446+
private static class SettingPathWithSpel {
447+
@Nullable
448+
@Id String id;
449+
}
400450
// endregion
401451
}

0 commit comments

Comments
 (0)