Skip to content

Commit 29de360

Browse files
committed
Use FieldNamingStrategy for property name matching.
1 parent ee30ef6 commit 29de360

11 files changed

+347
-7
lines changed

Diff for: src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java

+13
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
3434
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
3535
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
36+
import org.springframework.data.mapping.model.FieldNamingStrategy;
37+
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
3638
import org.springframework.lang.Nullable;
3739
import org.springframework.util.ClassUtils;
3840
import org.springframework.util.StringUtils;
@@ -69,6 +71,7 @@ public SimpleElasticsearchMappingContext elasticsearchMappingContext(
6971
SimpleElasticsearchMappingContext mappingContext = new SimpleElasticsearchMappingContext();
7072
mappingContext.setInitialEntitySet(getInitialEntitySet());
7173
mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions.getSimpleTypeHolder());
74+
mappingContext.setFieldNamingStrategy(fieldNamingStrategy());
7275

7376
return mappingContext;
7477
}
@@ -160,4 +163,14 @@ protected Set<Class<?>> scanForEntities(String basePackage) {
160163
protected RefreshPolicy refreshPolicy() {
161164
return null;
162165
}
166+
167+
/**
168+
* Configures a {@link FieldNamingStrategy} on the {@link SimpleElasticsearchMappingContext} instance created.
169+
*
170+
* @return the {@link FieldNamingStrategy} to use
171+
* @since 4.2
172+
*/
173+
protected FieldNamingStrategy fieldNamingStrategy() {
174+
return PropertyNameFieldNamingStrategy.INSTANCE;
175+
}
163176
}

Diff for: src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchMappingContext.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
package org.springframework.data.elasticsearch.core.mapping;
1717

1818
import org.springframework.data.mapping.context.AbstractMappingContext;
19+
import org.springframework.data.mapping.model.FieldNamingStrategy;
1920
import org.springframework.data.mapping.model.Property;
21+
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
2022
import org.springframework.data.mapping.model.SimpleTypeHolder;
2123
import org.springframework.data.util.TypeInformation;
24+
import org.springframework.lang.Nullable;
2225

2326
/**
2427
* SimpleElasticsearchMappingContext
@@ -31,6 +34,22 @@
3134
public class SimpleElasticsearchMappingContext
3235
extends AbstractMappingContext<SimpleElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> {
3336

37+
private static final FieldNamingStrategy DEFAULT_NAMING_STRATEGY = PropertyNameFieldNamingStrategy.INSTANCE;
38+
39+
private FieldNamingStrategy fieldNamingStrategy = DEFAULT_NAMING_STRATEGY;
40+
41+
/**
42+
* Configures the {@link FieldNamingStrategy} to be used to determine the field name if no manual mapping is applied.
43+
* Defaults to a strategy using the plain property name.
44+
*
45+
* @param fieldNamingStrategy the {@link FieldNamingStrategy} to be used to determine the field name if no manual
46+
* mapping is applied.
47+
* @since 4.2
48+
*/
49+
public void setFieldNamingStrategy(@Nullable FieldNamingStrategy fieldNamingStrategy) {
50+
this.fieldNamingStrategy = fieldNamingStrategy == null ? DEFAULT_NAMING_STRATEGY : fieldNamingStrategy;
51+
}
52+
3453
@Override
3554
protected <T> SimpleElasticsearchPersistentEntity<?> createPersistentEntity(TypeInformation<T> typeInformation) {
3655
return new SimpleElasticsearchPersistentEntity<>(typeInformation);
@@ -39,6 +58,6 @@ protected <T> SimpleElasticsearchPersistentEntity<?> createPersistentEntity(Type
3958
@Override
4059
protected ElasticsearchPersistentProperty createPersistentProperty(Property property,
4160
SimpleElasticsearchPersistentEntity<?> owner, SimpleTypeHolder simpleTypeHolder) {
42-
return new SimpleElasticsearchPersistentProperty(property, owner, simpleTypeHolder);
61+
return new SimpleElasticsearchPersistentProperty(property, owner, simpleTypeHolder, fieldNamingStrategy);
4362
}
4463
}

Diff for: src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@
4242
import org.springframework.data.mapping.MappingException;
4343
import org.springframework.data.mapping.PersistentEntity;
4444
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
45+
import org.springframework.data.mapping.model.FieldNamingStrategy;
4546
import org.springframework.data.mapping.model.Property;
47+
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
4648
import org.springframework.data.mapping.model.SimpleTypeHolder;
4749
import org.springframework.lang.Nullable;
4850
import org.springframework.util.StringUtils;
@@ -72,13 +74,17 @@ public class SimpleElasticsearchPersistentProperty extends
7274
private final @Nullable String annotatedFieldName;
7375
@Nullable private ElasticsearchPersistentPropertyConverter propertyConverter;
7476
private final boolean storeNullValue;
77+
private final FieldNamingStrategy fieldNamingStrategy;
7578

7679
public SimpleElasticsearchPersistentProperty(Property property,
77-
PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
80+
PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder,
81+
@Nullable FieldNamingStrategy fieldNamingStrategy) {
7882

7983
super(property, owner, simpleTypeHolder);
8084

8185
this.annotatedFieldName = getAnnotatedFieldName();
86+
this.fieldNamingStrategy = fieldNamingStrategy == null ? PropertyNameFieldNamingStrategy.INSTANCE
87+
: fieldNamingStrategy;
8288
this.isId = super.isIdProperty() || SUPPORTED_ID_PROPERTY_NAMES.contains(getFieldName());
8389

8490
// deprecated since 4.1
@@ -233,7 +239,19 @@ private String getAnnotatedFieldName() {
233239

234240
@Override
235241
public String getFieldName() {
236-
return annotatedFieldName == null ? getProperty().getName() : annotatedFieldName;
242+
243+
if (annotatedFieldName == null) {
244+
String fieldName = fieldNamingStrategy.getFieldName(this);
245+
246+
if (!StringUtils.hasText(fieldName)) {
247+
throw new MappingException(String.format("Invalid (null or empty) field name returned for property %s by %s!",
248+
this, fieldNamingStrategy.getClass()));
249+
}
250+
251+
return fieldName;
252+
}
253+
254+
return annotatedFieldName;
237255
}
238256

239257
@Override

Diff for: src/test/java/org/springframework/data/elasticsearch/core/geo/GeoPointUnitTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import org.springframework.data.geo.Point;
88

99
/**
10-
* @author P.J. Meisch ([email protected])
10+
* @author Peter-Josef Meisch
1111
*/
1212
class GeoPointUnitTests {
1313

@@ -36,7 +36,7 @@ void shouldConvertToAPoint() {
3636
@DisplayName("should not be equal to a Point")
3737
void shouldNotBeEqualToAPoint() {
3838

39-
//noinspection AssertBetweenInconvertibleTypes
39+
// noinspection AssertBetweenInconvertibleTypes
4040
assertThat(new GeoPoint(48, 8)).isNotEqualTo(new Point(8, 48));
4141
}
4242

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core.mapping;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.elasticsearch.index.query.QueryBuilders.*;
20+
21+
import lombok.Builder;
22+
import lombok.Data;
23+
24+
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.DisplayName;
26+
import org.junit.jupiter.api.Test;
27+
import org.springframework.beans.factory.annotation.Autowired;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.data.annotation.Id;
30+
import org.springframework.data.elasticsearch.annotations.Document;
31+
import org.springframework.data.elasticsearch.annotations.Field;
32+
import org.springframework.data.elasticsearch.annotations.FieldType;
33+
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
34+
import org.springframework.data.elasticsearch.core.ReactiveIndexOperations;
35+
import org.springframework.data.elasticsearch.core.SearchHits;
36+
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
37+
import org.springframework.data.elasticsearch.core.query.Query;
38+
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
39+
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
40+
import org.springframework.data.mapping.model.FieldNamingStrategy;
41+
import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy;
42+
import org.springframework.test.context.ContextConfiguration;
43+
import reactor.test.StepVerifier;
44+
45+
/**
46+
* @author Peter-Josef Meisch
47+
*/
48+
@SpringIntegrationTest
49+
@ContextConfiguration(classes = { FieldNamingStrategyIntegrationReactiveTest.Config.class })
50+
public class FieldNamingStrategyIntegrationReactiveTest {
51+
52+
@Autowired private ReactiveElasticsearchOperations operations;
53+
54+
@Configuration
55+
static class Config extends ReactiveElasticsearchRestTemplateConfiguration {
56+
57+
@Override
58+
protected FieldNamingStrategy fieldNamingStrategy() {
59+
return new SnakeCaseFieldNamingStrategy();
60+
}
61+
}
62+
63+
@BeforeEach
64+
void setUp() {
65+
ReactiveIndexOperations indexOps = this.operations.indexOps(Entity.class);
66+
indexOps.delete() //
67+
.then(indexOps.create()) //
68+
.then(indexOps.putMapping()) //
69+
.block();
70+
}
71+
72+
@Test // #1565
73+
@DisplayName("should use configured FieldNameStrategy")
74+
void shouldUseConfiguredFieldNameStrategy() {
75+
76+
Entity entity = new Entity.EntityBuilder().id("42").someText("the text to be searched").build();
77+
operations.save(entity).block();
78+
79+
// use a native query here to prevent automatic property name matching
80+
Query query = new NativeSearchQueryBuilder().withQuery(matchQuery("some_text", "searched")).build();
81+
operations.search(query, Entity.class) //
82+
.as(StepVerifier::create) //
83+
.expectNextCount(1) //
84+
.verifyComplete();
85+
}
86+
87+
@Data
88+
@Builder
89+
@Document(indexName = "field-naming-strategy-test")
90+
static class Entity {
91+
@Id private String id;
92+
@Field(type = FieldType.Text) private String someText;
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* (c) Copyright 2021 sothawo
3+
*/
4+
package org.springframework.data.elasticsearch.core.mapping;
5+
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
8+
import org.springframework.data.mapping.model.FieldNamingStrategy;
9+
import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy;
10+
import org.springframework.test.context.ContextConfiguration;
11+
12+
/**
13+
* @author P.J. Meisch ([email protected])
14+
*/
15+
@ContextConfiguration(classes = { FieldNamingStrategyIntegrationTemplateTest.Config.class })
16+
public class FieldNamingStrategyIntegrationTemplateTest extends FieldNamingStrategyIntegrationTest {
17+
18+
@Configuration
19+
static class Config extends ElasticsearchTemplateConfiguration {
20+
21+
@Override
22+
protected FieldNamingStrategy fieldNamingStrategy() {
23+
return new SnakeCaseFieldNamingStrategy();
24+
}
25+
}
26+
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core.mapping;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.elasticsearch.index.query.QueryBuilders.*;
20+
21+
import lombok.Builder;
22+
import lombok.Data;
23+
24+
import org.elasticsearch.client.RestHighLevelClient;
25+
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.api.DisplayName;
27+
import org.junit.jupiter.api.Test;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.data.annotation.Id;
32+
import org.springframework.data.elasticsearch.annotations.Document;
33+
import org.springframework.data.elasticsearch.annotations.Field;
34+
import org.springframework.data.elasticsearch.annotations.FieldType;
35+
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
36+
import org.springframework.data.elasticsearch.core.IndexOperations;
37+
import org.springframework.data.elasticsearch.core.SearchHits;
38+
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
39+
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
40+
import org.springframework.data.elasticsearch.core.query.Query;
41+
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
42+
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
43+
import org.springframework.data.mapping.model.FieldNamingStrategy;
44+
import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy;
45+
import org.springframework.test.context.ContextConfiguration;
46+
47+
/**
48+
* @author Peter-Josef Meisch
49+
*/
50+
@SpringIntegrationTest
51+
@ContextConfiguration(classes = { FieldNamingStrategyIntegrationTest.Config.class })
52+
public class FieldNamingStrategyIntegrationTest {
53+
54+
@Autowired private ElasticsearchOperations operations;
55+
56+
@Configuration
57+
static class Config extends ElasticsearchRestTemplateConfiguration {
58+
59+
@Override
60+
@Bean
61+
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
62+
RestHighLevelClient elasticsearchClient) {
63+
return super.elasticsearchOperations(elasticsearchConverter, elasticsearchClient);
64+
}
65+
66+
@Override
67+
protected FieldNamingStrategy fieldNamingStrategy() {
68+
return new SnakeCaseFieldNamingStrategy();
69+
}
70+
}
71+
72+
@BeforeEach
73+
void setUp() {
74+
IndexOperations indexOps = this.operations.indexOps(Entity.class);
75+
indexOps.delete();
76+
indexOps.create();
77+
indexOps.putMapping();
78+
}
79+
80+
@Test // #1565
81+
@DisplayName("should use configured FieldNameStrategy")
82+
void shouldUseConfiguredFieldNameStrategy() {
83+
84+
Entity entity = new Entity.EntityBuilder().id("42").someText("the text to be searched").build();
85+
operations.save(entity);
86+
87+
// use a native query here to prevent automatic property name matching
88+
Query query = new NativeSearchQueryBuilder().withQuery(matchQuery("some_text", "searched")).build();
89+
SearchHits<Entity> searchHits = operations.search(query, Entity.class);
90+
91+
assertThat(searchHits.getTotalHits()).isEqualTo(1);
92+
}
93+
94+
@Data
95+
@Builder
96+
@Document(indexName = "field-naming-strategy-test")
97+
static class Entity {
98+
@Id private String id;
99+
@Field(type = FieldType.Text) private String someText;
100+
}
101+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ private static SimpleElasticsearchPersistentProperty createProperty(SimpleElasti
152152

153153
TypeInformation<?> type = entity.getTypeInformation();
154154
Property property = Property.of(type, ReflectionUtils.findField(entity.getType(), field));
155-
return new SimpleElasticsearchPersistentProperty(property, entity, SimpleTypeHolder.DEFAULT);
155+
return new SimpleElasticsearchPersistentProperty(property, entity, SimpleTypeHolder.DEFAULT, null);
156156

157157
}
158158

0 commit comments

Comments
 (0)