Skip to content

Commit 06de217

Browse files
authored
Allow to customize the mapped type name for @innerfield and @field annotations.
Original Pull request: #2950 Closes #2942
1 parent eba8eec commit 06de217

File tree

6 files changed

+110
-6
lines changed

6 files changed

+110
-6
lines changed

src/main/java/org/springframework/data/elasticsearch/annotations/Field.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
* @author Morgan Lutz
3939
* @author Sascha Woo
4040
* @author Haibo Liu
41+
* @author Andriy Redko
4142
*/
4243
@Retention(RetentionPolicy.RUNTIME)
4344
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@@ -240,4 +241,11 @@
240241
* @since 5.1
241242
*/
242243
boolean storeEmptyValue() default true;
244+
245+
/**
246+
* overrides the field type in the mapping which otherwise will be taken from corresponding {@link FieldType}
247+
*
248+
* @since 5.4
249+
*/
250+
String mappedTypeName() default "";
243251
}

src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* @author Brian Kimmig
3131
* @author Morgan Lutz
3232
* @author Haibo Liu
33+
* @author Andriy Redko
3334
*/
3435
@Retention(RetentionPolicy.RUNTIME)
3536
@Target(ElementType.ANNOTATION_TYPE)
@@ -171,4 +172,11 @@
171172
* @since 5.4
172173
*/
173174
KnnIndexOptions[] knnIndexOptions() default {};
175+
176+
/**
177+
* overrides the field type in the mapping which otherwise will be taken from corresponding {@link FieldType}
178+
*
179+
* @since 5.4
180+
*/
181+
String mappedTypeName() default "";
174182
}

src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
* @author Peter-Josef Meisch
7070
* @author Xiao Yu
7171
* @author Subhobrata Dey
72+
* @author Andriy Redko
7273
*/
7374
public class MappingBuilder {
7475

@@ -175,7 +176,8 @@ protected String buildPropertyMapping(ElasticsearchPersistentEntity<?> entity,
175176
.findAnnotation(org.springframework.data.elasticsearch.annotations.Document.class);
176177
var dynamicMapping = docAnnotation != null ? docAnnotation.dynamic() : null;
177178

178-
mapEntity(objectNode, entity, true, "", false, FieldType.Auto, null, dynamicMapping, runtimeFields);
179+
final FieldType fieldType = FieldType.Auto;
180+
mapEntity(objectNode, entity, true, "", false, fieldType, fieldType.getMappedName(), null, dynamicMapping, runtimeFields);
179181

180182
if (!excludeFromSource.isEmpty()) {
181183
ObjectNode sourceNode = objectNode.putObject(SOURCE);
@@ -210,7 +212,7 @@ private void writeTypeHintMapping(ObjectNode propertiesNode) throws IOException
210212
}
211213

212214
private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentEntity<?> entity,
213-
boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
215+
boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, String fieldTypeMappedName,
214216
@Nullable Field parentFieldAnnotation, @Nullable Dynamic dynamicMapping, @Nullable Document runtimeFields)
215217
throws IOException {
216218

@@ -244,7 +246,7 @@ private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentE
244246
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
245247
if (writeNestedProperties) {
246248

247-
String type = nestedOrObjectField ? fieldType.getMappedName() : FieldType.Object.getMappedName();
249+
String type = nestedOrObjectField ? fieldTypeMappedName : FieldType.Object.getMappedName();
248250

249251
ObjectNode nestedObjectNode = objectMapper.createObjectNode();
250252
nestedObjectNode.put(FIELD_PARAM_TYPE, type);
@@ -370,7 +372,7 @@ private void buildPropertyMapping(ObjectNode propertiesNode, boolean isRootObjec
370372
nestedPropertyPrefix = nestedPropertyPath;
371373

372374
mapEntity(propertiesNode, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(),
373-
fieldAnnotation, dynamicMapping, null);
375+
getMappedTypeName(fieldAnnotation), fieldAnnotation, dynamicMapping, null);
374376

375377
nestedPropertyPrefix = currentNestedPropertyPrefix;
376378
return;
@@ -473,7 +475,7 @@ private void applyDisabledPropertyMapping(ObjectNode propertiesNode, Elasticsear
473475
}
474476

475477
propertiesNode.set(property.getFieldName(), objectMapper.createObjectNode() //
476-
.put(FIELD_PARAM_TYPE, field.type().getMappedName()) //
478+
.put(FIELD_PARAM_TYPE, getMappedTypeName(field)) //
477479
.put(MAPPING_ENABLED, false) //
478480
);
479481

@@ -482,6 +484,15 @@ private void applyDisabledPropertyMapping(ObjectNode propertiesNode, Elasticsear
482484
}
483485
}
484486

487+
/**
488+
* Return the mapping type name to be used for the {@link Field}
489+
* @param field field to return the mapping type name for
490+
* @return the mapping type name
491+
*/
492+
private String getMappedTypeName(Field field) {
493+
return StringUtils.hasText(field.mappedTypeName()) ? field.mappedTypeName() : field.type().getMappedName();
494+
}
495+
485496
/**
486497
* Add mapping for @Field annotation
487498
*

src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public final class MappingParameters {
116116
private final boolean store;
117117
private final TermVector termVector;
118118
private final FieldType type;
119+
private final String mappedTypeName;
119120

120121
/**
121122
* extracts the mapping parameters from the relevant annotations.
@@ -141,6 +142,7 @@ private MappingParameters(Field field) {
141142
store = field.store();
142143
fielddata = field.fielddata();
143144
type = field.type();
145+
mappedTypeName = StringUtils.hasText(field.mappedTypeName()) ? field.mappedTypeName() : type.getMappedName();
144146
dateFormats = field.format();
145147
dateFormatPatterns = field.pattern();
146148
analyzer = field.analyzer();
@@ -187,6 +189,7 @@ private MappingParameters(InnerField field) {
187189
store = field.store();
188190
fielddata = field.fielddata();
189191
type = field.type();
192+
mappedTypeName = StringUtils.hasText(field.mappedTypeName()) ? field.mappedTypeName() : type.getMappedName();
190193
dateFormats = field.format();
191194
dateFormatPatterns = field.pattern();
192195
analyzer = field.analyzer();
@@ -245,7 +248,7 @@ public void writeTypeAndParametersTo(ObjectNode objectNode) throws IOException {
245248
}
246249

247250
if (type != FieldType.Auto) {
248-
objectNode.put(FIELD_PARAM_TYPE, type.getMappedName());
251+
objectNode.put(FIELD_PARAM_TYPE, mappedTypeName);
249252

250253
if (type == FieldType.Date || type == FieldType.Date_Nanos || type == FieldType.Date_Range) {
251254
List<String> formats = new ArrayList<>();

src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
* @author Brian Kimmig
6060
* @author Morgan Lutz
6161
* @author Haibo Liu
62+
* @author Andriy Redko
6263
*/
6364
@SpringIntegrationTest
6465
public abstract class MappingBuilderIntegrationTests extends MappingContextBaseTests {
@@ -77,6 +78,12 @@ void cleanup() {
7778
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
7879
}
7980

81+
@Test
82+
public void shouldSupportAllTypes() {
83+
IndexOperations indexOperations = operations.indexOps(EntityWithAllTypes.class);
84+
indexOperations.createWithMapping();
85+
}
86+
8087
@Test
8188
public void shouldNotFailOnCircularReference() {
8289

src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
* @author Brian Kimmig
6464
* @author Morgan Lutz
6565
* @author Haibo Liu
66+
* @author Andriy Redko
6667
*/
6768
public class MappingBuilderUnitTests extends MappingContextBaseTests {
6869

@@ -1242,6 +1243,59 @@ void shouldWriteFieldAliasesToTheMapping() throws JSONException {
12421243

12431244
assertEquals(expected, mapping, true);
12441245
}
1246+
1247+
@Test // #2942
1248+
@DisplayName("should use custom mapped name")
1249+
void shouldUseCustomMappedName() throws JSONException {
1250+
1251+
var expected = """
1252+
{
1253+
"properties": {
1254+
"_class": {
1255+
"type": "keyword",
1256+
"index": false,
1257+
"doc_values": false
1258+
},
1259+
"someText": {
1260+
"type": "match_only_text"
1261+
}
1262+
}
1263+
}
1264+
""";
1265+
String mapping = getMappingBuilder().buildPropertyMapping(FieldMappedNameEntity.class);
1266+
1267+
assertEquals(expected, mapping, true);
1268+
}
1269+
1270+
@Test // #2942
1271+
@DisplayName("should use custom mapped name for multifield")
1272+
void shouldUseCustomMappedNameMultiField() throws JSONException {
1273+
1274+
var expected = """
1275+
{
1276+
"properties": {
1277+
"_class": {
1278+
"type": "keyword",
1279+
"index": false,
1280+
"doc_values": false
1281+
},
1282+
"description": {
1283+
"type": "match_only_text",
1284+
"fields": {
1285+
"lower_case": {
1286+
"type": "constant_keyword",
1287+
"normalizer": "lower_case_normalizer"
1288+
}
1289+
}
1290+
}
1291+
}
1292+
}
1293+
""";
1294+
String mapping = getMappingBuilder().buildPropertyMapping(MultiFieldMappedNameEntity.class);
1295+
1296+
assertEquals(expected, mapping, true);
1297+
}
1298+
12451299
// region entities
12461300

12471301
@Document(indexName = "ignore-above-index")
@@ -2503,5 +2557,18 @@ private static class FieldAliasEntity {
25032557
@Nullable
25042558
@Field(type = Text) private String otherText;
25052559
}
2560+
2561+
@SuppressWarnings("unused")
2562+
private static class FieldMappedNameEntity {
2563+
@Nullable
2564+
@Field(type = Text, mappedTypeName = "match_only_text") private String someText;
2565+
}
2566+
2567+
@SuppressWarnings("unused")
2568+
private static class MultiFieldMappedNameEntity {
2569+
@Nullable
2570+
@MultiField(mainField = @Field(type = FieldType.Text, mappedTypeName = "match_only_text"), otherFields = { @InnerField(suffix = "lower_case",
2571+
type = FieldType.Keyword, normalizer = "lower_case_normalizer", mappedTypeName = "constant_keyword") }) private String description;
2572+
}
25062573
// endregion
25072574
}

0 commit comments

Comments
 (0)