Skip to content

Commit e4c7b96

Browse files
authored
Add the type hint _class attribute to the index mapping.
Original Pull Request #1717 Closes #1711
1 parent 6634d00 commit e4c7b96

File tree

6 files changed

+114
-50
lines changed

6 files changed

+114
-50
lines changed

Diff for: src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java

+16-17
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
import org.slf4j.Logger;
2525
import org.slf4j.LoggerFactory;
26-
2726
import org.springframework.beans.BeansException;
2827
import org.springframework.beans.factory.InitializingBean;
2928
import org.springframework.context.ApplicationContext;
@@ -550,25 +549,25 @@ public void write(Object source, Document sink) {
550549
}
551550

552551
Class<?> entityType = ClassUtils.getUserClass(source.getClass());
553-
TypeInformation<? extends Object> type = ClassTypeInformation.from(entityType);
552+
TypeInformation<? extends Object> typeInformation = ClassTypeInformation.from(entityType);
554553

555554
if (requiresTypeHint(entityType)) {
556-
typeMapper.writeType(type, sink);
555+
typeMapper.writeType(typeInformation, sink);
557556
}
558557

559-
writeInternal(source, sink, type);
558+
writeInternal(source, sink, typeInformation);
560559
}
561560

562561
/**
563562
* Internal write conversion method which should be used for nested invocations.
564563
*
565564
* @param source
566565
* @param sink
567-
* @param typeHint
566+
* @param typeInformation
568567
*/
569568
@SuppressWarnings("unchecked")
570569
protected void writeInternal(@Nullable Object source, Map<String, Object> sink,
571-
@Nullable TypeInformation<?> typeHint) {
570+
@Nullable TypeInformation<?> typeInformation) {
572571

573572
if (null == source) {
574573
return;
@@ -594,7 +593,7 @@ protected void writeInternal(@Nullable Object source, Map<String, Object> sink,
594593
}
595594

596595
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(entityType);
597-
addCustomTypeKeyIfNecessary(typeHint, source, sink);
596+
addCustomTypeKeyIfNecessary(source, sink, typeInformation);
598597
writeInternal(source, sink, entity);
599598
}
600599

@@ -603,7 +602,7 @@ protected void writeInternal(@Nullable Object source, Map<String, Object> sink,
603602
*
604603
* @param source
605604
* @param sink
606-
* @param typeHint
605+
* @param entity
607606
*/
608607
protected void writeInternal(@Nullable Object source, Map<String, Object> sink,
609608
@Nullable ElasticsearchPersistentEntity<?> entity) {
@@ -725,7 +724,7 @@ protected void writeProperty(ElasticsearchPersistentProperty property, Object va
725724
Map<String, Object> document = existingValue instanceof Map ? (Map<String, Object>) existingValue
726725
: Document.create();
727726

728-
addCustomTypeKeyIfNecessary(ClassTypeInformation.from(property.getRawType()), value, document);
727+
addCustomTypeKeyIfNecessary(value, document, ClassTypeInformation.from(property.getRawType()));
729728
writeInternal(value, document, entity);
730729
sink.set(property, document);
731730
}
@@ -923,18 +922,18 @@ protected Object getWriteComplexValue(ElasticsearchPersistentProperty property,
923922
// region helper methods
924923

925924
/**
926-
* Adds custom type information to the given {@link Map} if necessary. That is if the value is not the same as the one
927-
* given. This is usually the case if you store a subtype of the actual declared type of the property.
925+
* Adds custom typeInformation information to the given {@link Map} if necessary. That is if the value is not the same
926+
* as the one given. This is usually the case if you store a subtype of the actual declared typeInformation of the
927+
* property.
928928
*
929-
* @param type
930-
* @param value must not be {@literal null}.
929+
* @param source must not be {@literal null}.
931930
* @param sink must not be {@literal null}.
931+
* @param type
932932
*/
933-
protected void addCustomTypeKeyIfNecessary(@Nullable TypeInformation<?> type, Object value,
934-
Map<String, Object> sink) {
933+
protected void addCustomTypeKeyIfNecessary(Object source, Map<String, Object> sink, @Nullable TypeInformation<?> type) {
935934

936935
Class<?> reference = type != null ? type.getActualType().getType() : Object.class;
937-
Class<?> valueType = ClassUtils.getUserClass(value.getClass());
936+
Class<?> valueType = ClassUtils.getUserClass(source.getClass());
938937

939938
boolean notTheSameClass = !valueType.equals(reference);
940939
if (notTheSameClass) {
@@ -948,7 +947,7 @@ protected void addCustomTypeKeyIfNecessary(@Nullable TypeInformation<?> type, Ob
948947
* @param type must not be {@literal null}.
949948
* @return {@literal true} if not a simple type, {@link Collection} or type with custom write target.
950949
*/
951-
private boolean requiresTypeHint(Class<?> type) {
950+
public boolean requiresTypeHint(Class<?> type) {
952951

953952
return !isSimpleType(type) && !ClassUtils.isAssignable(Collection.class, type)
954953
&& !conversions.hasCustomWriteTarget(type, Document.class);

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

+12
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public class MappingBuilder {
8181
private static final String COMPLETION_MAX_INPUT_LENGTH = "max_input_length";
8282
private static final String COMPLETION_CONTEXTS = "contexts";
8383

84+
private static final String TYPEHINT_PROPERTY = "_class";
85+
8486
private static final String TYPE_DYNAMIC = "dynamic";
8587
private static final String TYPE_VALUE_KEYWORD = "keyword";
8688
private static final String TYPE_VALUE_GEO_POINT = "geo_point";
@@ -131,6 +133,14 @@ public String buildPropertyMapping(Class<?> clazz) throws MappingException {
131133
}
132134
}
133135

136+
private void writeTypeHintMapping(XContentBuilder builder) throws IOException {
137+
builder.startObject(TYPEHINT_PROPERTY) //
138+
.field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) //
139+
.field(FIELD_PARAM_INDEX, false) //
140+
.field(FIELD_PARAM_DOC_VALUES, false) //
141+
.endObject();
142+
}
143+
134144
private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity<?> entity,
135145
boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
136146
@Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping) throws IOException {
@@ -162,6 +172,8 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten
162172

163173
builder.startObject(FIELD_PROPERTIES);
164174

175+
writeTypeHintMapping(builder);
176+
165177
if (entity != null) {
166178
entity.doWithProperties((PropertyHandler<ElasticsearchPersistentProperty>) property -> {
167179
try {

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

+4-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import org.junit.jupiter.api.AfterEach;
3232
import org.junit.jupiter.api.BeforeEach;
3333
import org.junit.jupiter.api.Test;
34-
import org.skyscreamer.jsonassert.JSONCompareMode;
3534
import org.springframework.beans.factory.annotation.Autowired;
3635
import org.springframework.context.annotation.Configuration;
3736
import org.springframework.context.annotation.Import;
@@ -257,7 +256,7 @@ void shouldCreateMappingForEntityFromProperties() {
257256
.as(StepVerifier::create) //
258257
.assertNext(document -> {
259258
try {
260-
assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE);
259+
assertEquals(expected, document.toJson(), false);
261260
} catch (JSONException e) {
262261
fail("", e);
263262
}
@@ -282,7 +281,7 @@ void shouldCreateMappingForEntityFromMappingAnnotation() {
282281
.as(StepVerifier::create) //
283282
.assertNext(document -> {
284283
try {
285-
assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE);
284+
assertEquals(expected, document.toJson(), false);
286285
} catch (JSONException e) {
287286
fail("", e);
288287
}
@@ -310,7 +309,7 @@ void shouldCreateMappingBoundEntity() {
310309
.as(StepVerifier::create) //
311310
.assertNext(document -> {
312311
try {
313-
assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE);
312+
assertEquals(expected, document.toJson(), false);
314313
} catch (JSONException e) {
315314
fail("", e);
316315
}
@@ -340,7 +339,7 @@ void shouldPutAndGetMapping() {
340339
.as(StepVerifier::create) //
341340
.assertNext(document -> {
342341
try {
343-
assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE);
342+
assertEquals(expected, document.toJson(), false);
344343
} catch (JSONException e) {
345344
fail("", e);
346345
}

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

+72-20
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@
3737
import java.util.Collection;
3838
import java.util.Date;
3939
import java.util.HashMap;
40-
import java.util.HashSet;
4140
import java.util.Map;
42-
import java.util.Set;
4341

4442
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
4543
import org.json.JSONException;
@@ -420,7 +418,7 @@ public void shouldSetFieldMappingProperties() throws JSONException {
420418
String mapping = getMappingBuilder().buildPropertyMapping(FieldMappingParameters.class);
421419

422420
// then
423-
assertEquals(expected, mapping, true);
421+
assertEquals(expected, mapping, false);
424422
}
425423

426424
@Test
@@ -439,7 +437,7 @@ void shouldWriteDynamicMappingSettings() throws JSONException {
439437

440438
String mapping = getMappingBuilder().buildPropertyMapping(ConfigureDynamicMappingEntity.class);
441439

442-
assertEquals(expected, mapping, true);
440+
assertEquals(expected, mapping, false);
443441
}
444442

445443
@Test // DATAES-784
@@ -454,7 +452,7 @@ void shouldMapPropertyObjectsToFieldDefinition() throws JSONException {
454452

455453
String mapping = getMappingBuilder().buildPropertyMapping(ValueDoc.class);
456454

457-
assertEquals(expected, mapping, true);
455+
assertEquals(expected, mapping, false);
458456
}
459457

460458
@Test // DATAES-788
@@ -568,6 +566,54 @@ void shouldOnlyAllowDisabledPropertiesOnTypeObject() {
568566
.isInstanceOf(MappingException.class);
569567
}
570568

569+
@Test // #1711
570+
@DisplayName("should write typeHint entries")
571+
void shouldWriteTypeHintEntries() throws JSONException {
572+
573+
String expected = "{\n" + //
574+
" \"properties\": {\n" + //
575+
" \"_class\": {\n" + //
576+
" \"type\": \"keyword\",\n" + //
577+
" \"index\": false,\n" + //
578+
" \"doc_values\": false\n" + //
579+
" },\n" + //
580+
" \"id\": {\n" + //
581+
" \"type\": \"keyword\"\n" + //
582+
" },\n" + //
583+
" \"nestedEntity\": {\n" + //
584+
" \"type\": \"nested\",\n" + //
585+
" \"properties\": {\n" + //
586+
" \"_class\": {\n" + //
587+
" \"type\": \"keyword\",\n" + //
588+
" \"index\": false,\n" + //
589+
" \"doc_values\": false\n" + //
590+
" },\n" + //
591+
" \"nestedField\": {\n" + //
592+
" \"type\": \"text\"\n" + //
593+
" }\n" + //
594+
" }\n" + //
595+
" },\n" + //
596+
" \"objectEntity\": {\n" + //
597+
" \"type\": \"object\",\n" + //
598+
" \"properties\": {\n" + //
599+
" \"_class\": {\n" + //
600+
" \"type\": \"keyword\",\n" + //
601+
" \"index\": false,\n" + //
602+
" \"doc_values\": false\n" + //
603+
" },\n" + //
604+
" \"objectField\": {\n" + //
605+
" \"type\": \"text\"\n" + //
606+
" }\n" + //
607+
" }\n" + //
608+
" }\n" + //
609+
" }\n" + //
610+
"}\n"; //
611+
612+
String mapping = getMappingBuilder().buildPropertyMapping(TypeHintEntity.class);
613+
614+
assertEquals(expected, mapping, false);
615+
}
616+
571617
@Setter
572618
@Getter
573619
@NoArgsConstructor
@@ -862,21 +908,6 @@ static class GeoEntity {
862908
orientation = GeoShapeField.Orientation.clockwise) private String shape2;
863909
}
864910

865-
@Document(indexName = "test-index-user-mapping-builder")
866-
static class User {
867-
@Nullable @Id private String id;
868-
869-
@Field(type = FieldType.Nested, ignoreFields = { "users" }) private Set<Group> groups = new HashSet<>();
870-
}
871-
872-
@Document(indexName = "test-index-group-mapping-builder")
873-
static class Group {
874-
875-
@Nullable @Id String id;
876-
877-
@Field(type = FieldType.Nested, ignoreFields = { "groups" }) private Set<User> users = new HashSet<>();
878-
}
879-
880911
@Document(indexName = "test-index-field-mapping-parameters")
881912
static class FieldMappingParameters {
882913
@Nullable @Field private String indexTrue;
@@ -1008,4 +1039,25 @@ static class DisabledMappingProperty {
10081039
@Field(type = Text) private String text;
10091040
@Mapping(enabled = false) @Field(type = Object) private Object object;
10101041
}
1042+
1043+
@Data
1044+
@AllArgsConstructor
1045+
@NoArgsConstructor
1046+
static class TypeHintEntity {
1047+
@Id @Field(type = Keyword) private String id;
1048+
1049+
@Field(type = Nested) private NestedEntity nestedEntity;
1050+
1051+
@Field(type = Object) private ObjectEntity objectEntity;
1052+
1053+
@Data
1054+
static class NestedEntity {
1055+
@Field(type = Text) private String nestedField;
1056+
}
1057+
1058+
@Data
1059+
static class ObjectEntity {
1060+
@Field(type = Text) private String objectField;
1061+
}
1062+
}
10111063
}

Diff for: src/test/java/org/springframework/data/elasticsearch/core/index/SimpleDynamicTemplatesMappingTests.java

+6-5
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
*/
1616
package org.springframework.data.elasticsearch.core.index;
1717

18-
import static org.assertj.core.api.Assertions.*;
18+
import static org.skyscreamer.jsonassert.JSONAssert.*;
1919

2020
import java.util.HashMap;
2121
import java.util.Map;
2222

23+
import org.json.JSONException;
2324
import org.junit.jupiter.api.Test;
2425
import org.springframework.data.annotation.Id;
2526
import org.springframework.data.elasticsearch.annotations.Document;
@@ -38,19 +39,19 @@
3839
public class SimpleDynamicTemplatesMappingTests extends MappingContextBaseTests {
3940

4041
@Test // DATAES-568
41-
public void testCorrectDynamicTemplatesMappings() {
42+
public void testCorrectDynamicTemplatesMappings() throws JSONException {
4243

4344
String mapping = getMappingBuilder().buildPropertyMapping(SampleDynamicTemplatesEntity.class);
4445

4546
String EXPECTED_MAPPING_ONE = "{\"dynamic_templates\":" + "[{\"with_custom_analyzer\":{"
4647
+ "\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"},"
4748
+ "\"path_match\":\"names.*\"}}]," + "\"properties\":{\"names\":{\"type\":\"object\"}}}";
4849

49-
assertThat(mapping).isEqualTo(EXPECTED_MAPPING_ONE);
50+
assertEquals(EXPECTED_MAPPING_ONE, mapping, false);
5051
}
5152

5253
@Test // DATAES-568
53-
public void testCorrectDynamicTemplatesMappingsTwo() {
54+
public void testCorrectDynamicTemplatesMappingsTwo() throws JSONException {
5455

5556
String mapping = getMappingBuilder().buildPropertyMapping(SampleDynamicTemplatesEntityTwo.class);
5657
String EXPECTED_MAPPING_TWO = "{\"dynamic_templates\":" + "[{\"with_custom_analyzer\":{"
@@ -59,7 +60,7 @@ public void testCorrectDynamicTemplatesMappingsTwo() {
5960
+ "\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"},"
6061
+ "\"path_match\":\"participantA1.*\"}}]," + "\"properties\":{\"names\":{\"type\":\"object\"}}}";
6162

62-
assertThat(mapping).isEqualTo(EXPECTED_MAPPING_TWO);
63+
assertEquals(EXPECTED_MAPPING_TWO, mapping, false);
6364
}
6465

6566
/**

Diff for: src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
*/
1616
package org.springframework.data.elasticsearch.core.index;
1717

18-
import static org.assertj.core.api.Assertions.*;
18+
import static org.skyscreamer.jsonassert.JSONAssert.*;
1919
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
2020

2121
import lombok.Data;
2222

2323
import java.time.LocalDateTime;
2424

25+
import org.json.JSONException;
2526
import org.junit.jupiter.api.Test;
2627
import org.springframework.data.annotation.Id;
2728
import org.springframework.data.elasticsearch.annotations.DateFormat;
@@ -43,11 +44,11 @@ public class SimpleElasticsearchDateMappingTests extends MappingContextBaseTests
4344
+ "\"basicFormatDate\":{\"" + "type\":\"date\",\"format\":\"basic_date\"}}}";
4445

4546
@Test // DATAES-568, DATAES-828
46-
public void testCorrectDateMappings() {
47+
public void testCorrectDateMappings() throws JSONException {
4748

4849
String mapping = getMappingBuilder().buildPropertyMapping(SampleDateMappingEntity.class);
4950

50-
assertThat(mapping).isEqualTo(EXPECTED_MAPPING);
51+
assertEquals(EXPECTED_MAPPING, mapping, false);
5152
}
5253

5354
/**

0 commit comments

Comments
 (0)