Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
[[new-features.6-0-0]]
== New in Spring Data Elasticsearch 6.0

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


[[new-features.5-5-0]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionException;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
Expand Down Expand Up @@ -298,7 +299,7 @@ public ElasticsearchPersistentProperty getPersistentPropertyWithFieldName(String
Assert.notNull(fieldName, "fieldName must not be null");

return fieldNamePropertyCache.computeIfAbsent(fieldName, key -> {
AtomicReference<ElasticsearchPersistentProperty> propertyRef = new AtomicReference<>();
AtomicReference<@Nullable ElasticsearchPersistentProperty> propertyRef = new AtomicReference<>();
doWithProperties((PropertyHandler<@NonNull ElasticsearchPersistentProperty>) property -> {
if (key.equals(property.getFieldName())) {
propertyRef.set(property);
Expand Down Expand Up @@ -423,9 +424,9 @@ public String resolveRouting(T bean) {

try {
Expression expression = routingExpressions.computeIfAbsent(routing, PARSER::parseExpression);
ExpressionDependencies expressionDependencies = ExpressionDependencies.discover(expression);
ExpressionDependencies expressionDependencies = expression != null ? ExpressionDependencies.discover(expression)
: ExpressionDependencies.none();

// noinspection ConstantConditions
EvaluationContext context = getEvaluationContext(null, expressionDependencies);
context.setVariable("entity", bean);

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

// region index settings
@Override
public String settingPath() {
return settingsParameter.get().settingPath;
public @Nullable String settingPath() {
String settingPathFromParameter = settingsParameter.get().settingPath;
if (settingPathFromParameter == null) {
return null;
}

try {
Expression expression = PARSER.parseExpression(settingPathFromParameter, ParserContext.TEMPLATE_EXPRESSION);
return (expression instanceof LiteralExpression) ? settingPathFromParameter
: expression.getValue(getEvaluationContext(null, ExpressionDependencies.discover(expression)), String.class);
} catch (ExpressionException e) {
throw new InvalidDataAccessApiUsageException(
"Could not resolve expression: " + settingPathFromParameter + " for @Setting.settingPath ", e);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public String evaluate() {

if (expr != null) {
EvaluationContext context = evaluationContextProvider.getEvaluationContext(parameterAccessor.getValues())
.getRequiredEvaluationContext();
.getEvaluationContext();

if (context instanceof StandardEvaluationContext standardEvaluationContext) {
standardEvaluationContext.setTypeConverter(elasticsearchSpELTypeConverter);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.springframework.data.elasticsearch.core;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;

public class IndexSettingsELCIntegrationTests extends IndexSettingsIntegrationTests {
@Configuration
@Import({ ElasticsearchTemplateConfiguration.class })
static class Config {
@Bean
public SpelSettingPath spelSettingPath() {
return new SpelSettingPath();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.springframework.data.elasticsearch.core;

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

import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;

/**
* IndexSettings test that need an regular conext setup for SpEL resolution for example.
*/
@SpringIntegrationTest
public abstract class IndexSettingsIntegrationTests {

@Autowired protected ElasticsearchOperations operations;

@Test // #3187
@DisplayName("should evaluate SpEL expression in settingPath")
void shouldEvaluateSpElExpressionInSettingPath() {

var settingPath = operations.getElasticsearchConverter().getMappingContext()
.getRequiredPersistentEntity(SettingPathWithSpel.class).settingPath();

assertThat(settingPath).isEqualTo(SpelSettingPath.SETTING_PATH);
}

protected static class SpelSettingPath {
public static String SETTING_PATH = "test-setting-path";

public String settingPath() {
return SETTING_PATH;
}
}

@Document(indexName = "foo")
@Setting(settingPath = "#{@spelSettingPath.settingPath}")
private static class SettingPathWithSpel {
@Nullable
@Id String id;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@
import static org.skyscreamer.jsonassert.JSONAssert.*;

import org.json.JSONException;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.elasticsearch.annotations.Document;
Expand All @@ -38,6 +44,7 @@
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.TypeInformation;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.util.ReflectionUtils;

/**
Expand All @@ -60,9 +67,9 @@ class PropertiesTests {
@Test
public void shouldThrowExceptionGivenVersionPropertyIsNotLong() {

TypeInformation<EntityWithWrongVersionType> typeInformation = TypeInformation
TypeInformation<@NonNull EntityWithWrongVersionType> typeInformation = TypeInformation
.of(EntityWithWrongVersionType.class);
SimpleElasticsearchPersistentEntity<EntityWithWrongVersionType> entity = new SimpleElasticsearchPersistentEntity<>(
SimpleElasticsearchPersistentEntity<@NonNull EntityWithWrongVersionType> entity = new SimpleElasticsearchPersistentEntity<>(
typeInformation, contextConfiguration);

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

TypeInformation<EntityWithMultipleVersionField> typeInformation = TypeInformation
TypeInformation<@NonNull EntityWithMultipleVersionField> typeInformation = TypeInformation
.of(EntityWithMultipleVersionField.class);
SimpleElasticsearchPersistentEntity<EntityWithMultipleVersionField> entity = new SimpleElasticsearchPersistentEntity<>(
SimpleElasticsearchPersistentEntity<@NonNull EntityWithMultipleVersionField> entity = new SimpleElasticsearchPersistentEntity<>(
typeInformation, contextConfiguration);
SimpleElasticsearchPersistentProperty persistentProperty1 = createProperty(entity, "version1");
SimpleElasticsearchPersistentProperty persistentProperty2 = createProperty(entity, "version2");
Expand All @@ -100,9 +107,9 @@ void shouldFindPropertiesByMappedName() {
@Test
// DATAES-799
void shouldReportThatThereIsNoSeqNoPrimaryTermPropertyWhenThereIsNoSuchProperty() {
TypeInformation<EntityWithoutSeqNoPrimaryTerm> typeInformation = TypeInformation
TypeInformation<@NonNull EntityWithoutSeqNoPrimaryTerm> typeInformation = TypeInformation
.of(EntityWithoutSeqNoPrimaryTerm.class);
SimpleElasticsearchPersistentEntity<EntityWithoutSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
SimpleElasticsearchPersistentEntity<@NonNull EntityWithoutSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
typeInformation, contextConfiguration);

assertThat(entity.hasSeqNoPrimaryTermProperty()).isFalse();
Expand All @@ -111,9 +118,9 @@ void shouldReportThatThereIsNoSeqNoPrimaryTermPropertyWhenThereIsNoSuchProperty(
@Test
// DATAES-799
void shouldReportThatThereIsSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() {
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
TypeInformation<@NonNull EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
.of(EntityWithSeqNoPrimaryTerm.class);
SimpleElasticsearchPersistentEntity<EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
SimpleElasticsearchPersistentEntity<@NonNull EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
typeInformation, contextConfiguration);

entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm"));
Expand All @@ -125,9 +132,9 @@ void shouldReportThatThereIsSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() {
// DATAES-799
void shouldReturnSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() {

TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
TypeInformation<@NonNull EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
.of(EntityWithSeqNoPrimaryTerm.class);
SimpleElasticsearchPersistentEntity<EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
SimpleElasticsearchPersistentEntity<@NonNull EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
typeInformation, contextConfiguration);
entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm"));
EntityWithSeqNoPrimaryTerm instance = new EntityWithSeqNoPrimaryTerm();
Expand All @@ -144,9 +151,9 @@ void shouldReturnSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() {
@Test
// DATAES-799
void shouldNotAllowMoreThanOneSeqNoPrimaryTermProperties() {
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
TypeInformation<@NonNull EntityWithSeqNoPrimaryTerm> typeInformation = TypeInformation
.of(EntityWithSeqNoPrimaryTerm.class);
SimpleElasticsearchPersistentEntity<EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
SimpleElasticsearchPersistentEntity<@NonNull EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
typeInformation, contextConfiguration);
entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm"));

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

@Nested
@DisplayName("index settings")
@SpringJUnitConfig({ SettingsTests.Config.class })
class SettingsTests {
@Autowired private ApplicationContext applicationContext;

@Configuration
static class Config {
@Bean
public SpelTestBean spelTestBean() {
return new SpelTestBean();
}
}

@BeforeEach
void setUp() {
((SimpleElasticsearchMappingContext) elasticsearchConverter
.get().getMappingContext()).setApplicationContext(applicationContext);

}

@Test // #1719
@DisplayName("should error if index sorting parameters do not have the same number of arguments")
Expand Down Expand Up @@ -205,6 +229,24 @@ void shouldWriteSortParametersToSettingsObject() throws JSONException {
String json = entity.getDefaultSettings().toJson();
assertEquals(expected, json, false);
}

@Test // #3187
@DisplayName("should evaluate SpEL expression in settingPath")
void shouldEvaluateSpElExpressionInSettingPath() {

var settingPath = elasticsearchConverter.get().getMappingContext()
.getRequiredPersistentEntity(SettingPathWithSpel.class).settingPath();

assertThat(settingPath).isEqualTo(SpelTestBean.SETTING_PATH);
}

private static class SpelTestBean {
public static String SETTING_PATH = "test-setting-path";

public String settingPath() {
return SETTING_PATH;
}
}
}

@Nested
Expand Down Expand Up @@ -271,7 +313,7 @@ void shouldWriteTypeHintsWhenConfiguredExplicitlyOnEntityAndGlobalSettingIsFalse
}
}

// region helper functions
// region helper
private static SimpleElasticsearchPersistentProperty createProperty(SimpleElasticsearchPersistentEntity<?> entity,
String fieldName) {

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

}

// endregion

// region entities
Expand All @@ -295,7 +338,7 @@ public String getVersion() {
return version;
}

public void setVersion(String version) {
public void setVersion(@Nullable String version) {
this.version = version;
}
}
Expand All @@ -313,7 +356,7 @@ public Long getVersion1() {
return version1;
}

public void setVersion1(Long version1) {
public void setVersion1(@Nullable Long version1) {
this.version1 = version1;
}

Expand All @@ -322,7 +365,7 @@ public Long getVersion2() {
return version2;
}

public void setVersion2(Long version2) {
public void setVersion2(@Nullable Long version2) {
this.version2 = version2;
}
}
Expand Down Expand Up @@ -397,5 +440,12 @@ private static class EnableTypeHintExplicitSetting {
@Nullable
@Id String id;
}

@Document(indexName = "foo")
@Setting(settingPath = "#{@spelTestBean.settingPath}")
private static class SettingPathWithSpel {
@Nullable
@Id String id;
}
// endregion
}