mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-05-30 16:52:11 +00:00
Add the type hint _class attribute to the index mapping.
Original Pull Request #1717 Closes #1711
This commit is contained in:
parent
6634d0075a
commit
e4c7b968e1
@ -23,7 +23,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@ -550,13 +549,13 @@ public class MappingElasticsearchConverter
|
||||
}
|
||||
|
||||
Class<?> entityType = ClassUtils.getUserClass(source.getClass());
|
||||
TypeInformation<? extends Object> type = ClassTypeInformation.from(entityType);
|
||||
TypeInformation<? extends Object> typeInformation = ClassTypeInformation.from(entityType);
|
||||
|
||||
if (requiresTypeHint(entityType)) {
|
||||
typeMapper.writeType(type, sink);
|
||||
typeMapper.writeType(typeInformation, sink);
|
||||
}
|
||||
|
||||
writeInternal(source, sink, type);
|
||||
writeInternal(source, sink, typeInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -564,11 +563,11 @@ public class MappingElasticsearchConverter
|
||||
*
|
||||
* @param source
|
||||
* @param sink
|
||||
* @param typeHint
|
||||
* @param typeInformation
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeInternal(@Nullable Object source, Map<String, Object> sink,
|
||||
@Nullable TypeInformation<?> typeHint) {
|
||||
@Nullable TypeInformation<?> typeInformation) {
|
||||
|
||||
if (null == source) {
|
||||
return;
|
||||
@ -594,7 +593,7 @@ public class MappingElasticsearchConverter
|
||||
}
|
||||
|
||||
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(entityType);
|
||||
addCustomTypeKeyIfNecessary(typeHint, source, sink);
|
||||
addCustomTypeKeyIfNecessary(source, sink, typeInformation);
|
||||
writeInternal(source, sink, entity);
|
||||
}
|
||||
|
||||
@ -603,7 +602,7 @@ public class MappingElasticsearchConverter
|
||||
*
|
||||
* @param source
|
||||
* @param sink
|
||||
* @param typeHint
|
||||
* @param entity
|
||||
*/
|
||||
protected void writeInternal(@Nullable Object source, Map<String, Object> sink,
|
||||
@Nullable ElasticsearchPersistentEntity<?> entity) {
|
||||
@ -725,7 +724,7 @@ public class MappingElasticsearchConverter
|
||||
Map<String, Object> document = existingValue instanceof Map ? (Map<String, Object>) existingValue
|
||||
: Document.create();
|
||||
|
||||
addCustomTypeKeyIfNecessary(ClassTypeInformation.from(property.getRawType()), value, document);
|
||||
addCustomTypeKeyIfNecessary(value, document, ClassTypeInformation.from(property.getRawType()));
|
||||
writeInternal(value, document, entity);
|
||||
sink.set(property, document);
|
||||
}
|
||||
@ -923,18 +922,18 @@ public class MappingElasticsearchConverter
|
||||
// region helper methods
|
||||
|
||||
/**
|
||||
* Adds custom type information to the given {@link Map} if necessary. That is if the value is not the same as the one
|
||||
* given. This is usually the case if you store a subtype of the actual declared type of the property.
|
||||
* Adds custom typeInformation information to the given {@link Map} if necessary. That is if the value is not the same
|
||||
* as the one given. This is usually the case if you store a subtype of the actual declared typeInformation of the
|
||||
* property.
|
||||
*
|
||||
* @param type
|
||||
* @param value must not be {@literal null}.
|
||||
* @param source must not be {@literal null}.
|
||||
* @param sink must not be {@literal null}.
|
||||
* @param type
|
||||
*/
|
||||
protected void addCustomTypeKeyIfNecessary(@Nullable TypeInformation<?> type, Object value,
|
||||
Map<String, Object> sink) {
|
||||
protected void addCustomTypeKeyIfNecessary(Object source, Map<String, Object> sink, @Nullable TypeInformation<?> type) {
|
||||
|
||||
Class<?> reference = type != null ? type.getActualType().getType() : Object.class;
|
||||
Class<?> valueType = ClassUtils.getUserClass(value.getClass());
|
||||
Class<?> valueType = ClassUtils.getUserClass(source.getClass());
|
||||
|
||||
boolean notTheSameClass = !valueType.equals(reference);
|
||||
if (notTheSameClass) {
|
||||
@ -948,7 +947,7 @@ public class MappingElasticsearchConverter
|
||||
* @param type must not be {@literal null}.
|
||||
* @return {@literal true} if not a simple type, {@link Collection} or type with custom write target.
|
||||
*/
|
||||
private boolean requiresTypeHint(Class<?> type) {
|
||||
public boolean requiresTypeHint(Class<?> type) {
|
||||
|
||||
return !isSimpleType(type) && !ClassUtils.isAssignable(Collection.class, type)
|
||||
&& !conversions.hasCustomWriteTarget(type, Document.class);
|
||||
|
@ -81,6 +81,8 @@ public class MappingBuilder {
|
||||
private static final String COMPLETION_MAX_INPUT_LENGTH = "max_input_length";
|
||||
private static final String COMPLETION_CONTEXTS = "contexts";
|
||||
|
||||
private static final String TYPEHINT_PROPERTY = "_class";
|
||||
|
||||
private static final String TYPE_DYNAMIC = "dynamic";
|
||||
private static final String TYPE_VALUE_KEYWORD = "keyword";
|
||||
private static final String TYPE_VALUE_GEO_POINT = "geo_point";
|
||||
@ -131,6 +133,14 @@ public class MappingBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTypeHintMapping(XContentBuilder builder) throws IOException {
|
||||
builder.startObject(TYPEHINT_PROPERTY) //
|
||||
.field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) //
|
||||
.field(FIELD_PARAM_INDEX, false) //
|
||||
.field(FIELD_PARAM_DOC_VALUES, false) //
|
||||
.endObject();
|
||||
}
|
||||
|
||||
private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity<?> entity,
|
||||
boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
|
||||
@Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping) throws IOException {
|
||||
@ -162,6 +172,8 @@ public class MappingBuilder {
|
||||
|
||||
builder.startObject(FIELD_PROPERTIES);
|
||||
|
||||
writeTypeHintMapping(builder);
|
||||
|
||||
if (entity != null) {
|
||||
entity.doWithProperties((PropertyHandler<ElasticsearchPersistentProperty>) property -> {
|
||||
try {
|
||||
|
@ -31,7 +31,6 @@ import org.json.JSONException;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
@ -257,7 +256,7 @@ public class ReactiveIndexOperationsTest {
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(document -> {
|
||||
try {
|
||||
assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE);
|
||||
assertEquals(expected, document.toJson(), false);
|
||||
} catch (JSONException e) {
|
||||
fail("", e);
|
||||
}
|
||||
@ -282,7 +281,7 @@ public class ReactiveIndexOperationsTest {
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(document -> {
|
||||
try {
|
||||
assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE);
|
||||
assertEquals(expected, document.toJson(), false);
|
||||
} catch (JSONException e) {
|
||||
fail("", e);
|
||||
}
|
||||
@ -310,7 +309,7 @@ public class ReactiveIndexOperationsTest {
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(document -> {
|
||||
try {
|
||||
assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE);
|
||||
assertEquals(expected, document.toJson(), false);
|
||||
} catch (JSONException e) {
|
||||
fail("", e);
|
||||
}
|
||||
@ -340,7 +339,7 @@ public class ReactiveIndexOperationsTest {
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(document -> {
|
||||
try {
|
||||
assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE);
|
||||
assertEquals(expected, document.toJson(), false);
|
||||
} catch (JSONException e) {
|
||||
fail("", e);
|
||||
}
|
||||
|
@ -37,9 +37,7 @@ import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.elasticsearch.search.suggest.completion.context.ContextMapping;
|
||||
import org.json.JSONException;
|
||||
@ -420,7 +418,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(FieldMappingParameters.class);
|
||||
|
||||
// then
|
||||
assertEquals(expected, mapping, true);
|
||||
assertEquals(expected, mapping, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -439,7 +437,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(ConfigureDynamicMappingEntity.class);
|
||||
|
||||
assertEquals(expected, mapping, true);
|
||||
assertEquals(expected, mapping, false);
|
||||
}
|
||||
|
||||
@Test // DATAES-784
|
||||
@ -454,7 +452,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(ValueDoc.class);
|
||||
|
||||
assertEquals(expected, mapping, true);
|
||||
assertEquals(expected, mapping, false);
|
||||
}
|
||||
|
||||
@Test // DATAES-788
|
||||
@ -568,6 +566,54 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
.isInstanceOf(MappingException.class);
|
||||
}
|
||||
|
||||
@Test // #1711
|
||||
@DisplayName("should write typeHint entries")
|
||||
void shouldWriteTypeHintEntries() throws JSONException {
|
||||
|
||||
String expected = "{\n" + //
|
||||
" \"properties\": {\n" + //
|
||||
" \"_class\": {\n" + //
|
||||
" \"type\": \"keyword\",\n" + //
|
||||
" \"index\": false,\n" + //
|
||||
" \"doc_values\": false\n" + //
|
||||
" },\n" + //
|
||||
" \"id\": {\n" + //
|
||||
" \"type\": \"keyword\"\n" + //
|
||||
" },\n" + //
|
||||
" \"nestedEntity\": {\n" + //
|
||||
" \"type\": \"nested\",\n" + //
|
||||
" \"properties\": {\n" + //
|
||||
" \"_class\": {\n" + //
|
||||
" \"type\": \"keyword\",\n" + //
|
||||
" \"index\": false,\n" + //
|
||||
" \"doc_values\": false\n" + //
|
||||
" },\n" + //
|
||||
" \"nestedField\": {\n" + //
|
||||
" \"type\": \"text\"\n" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
" },\n" + //
|
||||
" \"objectEntity\": {\n" + //
|
||||
" \"type\": \"object\",\n" + //
|
||||
" \"properties\": {\n" + //
|
||||
" \"_class\": {\n" + //
|
||||
" \"type\": \"keyword\",\n" + //
|
||||
" \"index\": false,\n" + //
|
||||
" \"doc_values\": false\n" + //
|
||||
" },\n" + //
|
||||
" \"objectField\": {\n" + //
|
||||
" \"type\": \"text\"\n" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
"}\n"; //
|
||||
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(TypeHintEntity.class);
|
||||
|
||||
assertEquals(expected, mapping, false);
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@ -862,21 +908,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
orientation = GeoShapeField.Orientation.clockwise) private String shape2;
|
||||
}
|
||||
|
||||
@Document(indexName = "test-index-user-mapping-builder")
|
||||
static class User {
|
||||
@Nullable @Id private String id;
|
||||
|
||||
@Field(type = FieldType.Nested, ignoreFields = { "users" }) private Set<Group> groups = new HashSet<>();
|
||||
}
|
||||
|
||||
@Document(indexName = "test-index-group-mapping-builder")
|
||||
static class Group {
|
||||
|
||||
@Nullable @Id String id;
|
||||
|
||||
@Field(type = FieldType.Nested, ignoreFields = { "groups" }) private Set<User> users = new HashSet<>();
|
||||
}
|
||||
|
||||
@Document(indexName = "test-index-field-mapping-parameters")
|
||||
static class FieldMappingParameters {
|
||||
@Nullable @Field private String indexTrue;
|
||||
@ -1008,4 +1039,25 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
@Field(type = Text) private String text;
|
||||
@Mapping(enabled = false) @Field(type = Object) private Object object;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
static class TypeHintEntity {
|
||||
@Id @Field(type = Keyword) private String id;
|
||||
|
||||
@Field(type = Nested) private NestedEntity nestedEntity;
|
||||
|
||||
@Field(type = Object) private ObjectEntity objectEntity;
|
||||
|
||||
@Data
|
||||
static class NestedEntity {
|
||||
@Field(type = Text) private String nestedField;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class ObjectEntity {
|
||||
@Field(type = Text) private String objectField;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.index;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.skyscreamer.jsonassert.JSONAssert.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
@ -38,7 +39,7 @@ import org.springframework.lang.Nullable;
|
||||
public class SimpleDynamicTemplatesMappingTests extends MappingContextBaseTests {
|
||||
|
||||
@Test // DATAES-568
|
||||
public void testCorrectDynamicTemplatesMappings() {
|
||||
public void testCorrectDynamicTemplatesMappings() throws JSONException {
|
||||
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(SampleDynamicTemplatesEntity.class);
|
||||
|
||||
@ -46,11 +47,11 @@ public class SimpleDynamicTemplatesMappingTests extends MappingContextBaseTests
|
||||
+ "\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"},"
|
||||
+ "\"path_match\":\"names.*\"}}]," + "\"properties\":{\"names\":{\"type\":\"object\"}}}";
|
||||
|
||||
assertThat(mapping).isEqualTo(EXPECTED_MAPPING_ONE);
|
||||
assertEquals(EXPECTED_MAPPING_ONE, mapping, false);
|
||||
}
|
||||
|
||||
@Test // DATAES-568
|
||||
public void testCorrectDynamicTemplatesMappingsTwo() {
|
||||
public void testCorrectDynamicTemplatesMappingsTwo() throws JSONException {
|
||||
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(SampleDynamicTemplatesEntityTwo.class);
|
||||
String EXPECTED_MAPPING_TWO = "{\"dynamic_templates\":" + "[{\"with_custom_analyzer\":{"
|
||||
@ -59,7 +60,7 @@ public class SimpleDynamicTemplatesMappingTests extends MappingContextBaseTests
|
||||
+ "\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"},"
|
||||
+ "\"path_match\":\"participantA1.*\"}}]," + "\"properties\":{\"names\":{\"type\":\"object\"}}}";
|
||||
|
||||
assertThat(mapping).isEqualTo(EXPECTED_MAPPING_TWO);
|
||||
assertEquals(EXPECTED_MAPPING_TWO, mapping, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -15,13 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core.index;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.skyscreamer.jsonassert.JSONAssert.*;
|
||||
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.DateFormat;
|
||||
@ -43,11 +44,11 @@ public class SimpleElasticsearchDateMappingTests extends MappingContextBaseTests
|
||||
+ "\"basicFormatDate\":{\"" + "type\":\"date\",\"format\":\"basic_date\"}}}";
|
||||
|
||||
@Test // DATAES-568, DATAES-828
|
||||
public void testCorrectDateMappings() {
|
||||
public void testCorrectDateMappings() throws JSONException {
|
||||
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(SampleDateMappingEntity.class);
|
||||
|
||||
assertThat(mapping).isEqualTo(EXPECTED_MAPPING);
|
||||
assertEquals(EXPECTED_MAPPING, mapping, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user