From aba14c5e11a839beb1879485f184d10d155e4050 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 17 Jan 2021 23:54:35 +0100 Subject: [PATCH] Add enabled mapping parameter. Original PullRequest #1652 Closes #1370 --- .../elasticsearch/annotations/Mapping.java | 15 ++- .../core/index/MappingBuilder.java | 65 ++++++++--- .../index/MappingBuilderIntegrationTests.java | 81 ++++++------- .../core/index/MappingBuilderUnitTests.java | 110 +++++++++++------- 4 files changed, 167 insertions(+), 104 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java index e9d0a07ee..b334b46f1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java @@ -15,7 +15,12 @@ */ package org.springframework.data.elasticsearch.annotations; -import java.lang.annotation.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + import org.springframework.data.annotation.Persistent; /** @@ -26,9 +31,15 @@ import org.springframework.data.annotation.Persistent; @Persistent @Inherited @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE, ElementType.FIELD}) +@Target({ ElementType.TYPE, ElementType.FIELD }) public @interface Mapping { String mappingPath() default ""; + /** + * whether mappings are enabled + * + * @since 4.2 + */ + boolean enabled() default true; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index 6a9674309..f2e2b02e1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -31,7 +31,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.data.annotation.Transient; -import org.springframework.data.elasticsearch.ElasticsearchException; import org.springframework.data.elasticsearch.annotations.*; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.ResourceUtil; @@ -66,6 +65,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; */ public class MappingBuilder { + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchRestTemplate.class); + private static final String FIELD_INDEX = "index"; private static final String FIELD_PROPERTIES = "properties"; @Deprecated private static final String FIELD_PARENT = "_parent"; @@ -88,7 +89,7 @@ public class MappingBuilder { private static final String JOIN_TYPE_RELATIONS = "relations"; - private static final Logger logger = LoggerFactory.getLogger(ElasticsearchRestTemplate.class); + private static final String MAPPING_ENABLED = "enabled"; private final ElasticsearchConverter elasticsearchConverter; @@ -100,9 +101,9 @@ public class MappingBuilder { * builds the Elasticsearch mapping for the given clazz. * * @return JSON string - * @throws ElasticsearchException on errors while building the mapping + * @throws MappingException on errors while building the mapping */ - public String buildPropertyMapping(Class clazz) throws ElasticsearchException { + public String buildPropertyMapping(Class clazz) throws MappingException { try { ElasticsearchPersistentEntity entity = elasticsearchConverter.getMappingContext() @@ -125,8 +126,8 @@ public class MappingBuilder { .close(); return builder.getOutputStream().toString(); - } catch (MappingException | IOException e) { - throw new ElasticsearchException("could not build mapping", e); + } catch (IOException e) { + throw new MappingException("could not build mapping", e); } } @@ -134,6 +135,14 @@ public class MappingBuilder { boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, @Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping) throws IOException { + if (entity != null && entity.isAnnotationPresent(Mapping.class)) { + + if (!entity.getRequiredAnnotation(Mapping.class).enabled()) { + builder.field(MAPPING_ENABLED, false); + return; + } + } + boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField); if (writeNestedProperties) { @@ -189,14 +198,22 @@ public class MappingBuilder { if (property.isAnnotationPresent(Mapping.class)) { - String mappingPath = property.getRequiredAnnotation(Mapping.class).mappingPath(); - if (!StringUtils.isEmpty(mappingPath)) { + Mapping mapping = property.getRequiredAnnotation(Mapping.class); - ClassPathResource mappings = new ClassPathResource(mappingPath); - if (mappings.exists()) { - builder.rawField(property.getFieldName(), mappings.getInputStream(), XContentType.JSON); - return; + if (mapping.enabled()) { + String mappingPath = mapping.mappingPath(); + + if (StringUtils.hasText(mappingPath)) { + + ClassPathResource mappings = new ClassPathResource(mappingPath); + if (mappings.exists()) { + builder.rawField(property.getFieldName(), mappings.getInputStream(), XContentType.JSON); + return; + } } + } else { + applyDisabledPropertyMapping(builder, property); + return; } } @@ -317,9 +334,29 @@ public class MappingBuilder { private void applyDefaultIdFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property) throws IOException { + builder.startObject(property.getFieldName()) // + .field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // + .field(FIELD_INDEX, true) // + .endObject(); // + } - builder.startObject(property.getFieldName()).field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD).field(FIELD_INDEX, true) - .endObject(); + private void applyDisabledPropertyMapping(XContentBuilder builder, ElasticsearchPersistentProperty property) + throws IOException { + + try { + Field field = property.getRequiredAnnotation(Field.class); + + if (field.type() != FieldType.Object) { + throw new IllegalArgumentException("Field type must be 'object"); + } + + builder.startObject(property.getFieldName()) // + .field(FIELD_PARAM_TYPE, field.type().name().toLowerCase()) // + .field(MAPPING_ENABLED, false) // + .endObject(); // + } catch (Exception e) { + throw new MappingException("Could not write enabled: false mapping for " + property.getFieldName(), e); + } } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java index ed4f9f1c7..06e1b5a72 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java @@ -30,6 +30,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import java.lang.Integer; +import java.lang.Object; import java.math.BigDecimal; import java.util.Collection; import java.util.Collections; @@ -268,9 +269,26 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { indexOps.putMapping(); } - /** - * @author Xiao Yu - */ + @Test // #1370 + @DisplayName("should write mapping for disabled entity") + void shouldWriteMappingForDisabledEntity() { + + IndexOperations indexOps = operations.indexOps(DisabledMappingEntity.class); + indexOps.create(); + indexOps.putMapping(); + indexOps.delete(); + } + + @Test // #1370 + @DisplayName("should write mapping for disabled property") + void shouldWriteMappingForDisabledProperty() { + + IndexOperations indexOps = operations.indexOps(DisabledMappingProperty.class); + indexOps.create(); + indexOps.putMapping(); + indexOps.delete(); + } + @Setter @Getter @NoArgsConstructor @@ -284,9 +302,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { @Field(type = FieldType.Keyword, ignoreAbove = 10) private String message; } - /** - * @author Peter-Josef Meisch - */ static class FieldNameEntity { @Document(indexName = "fieldname-index") @@ -351,11 +366,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Nordine Bittich - */ @Setter @Getter @NoArgsConstructor @@ -373,10 +383,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { searchAnalyzer = "standard") }) private String description; } - /** - * @author Stuart Stevenson - * @author Mohsin Husen - */ @Data @Document(indexName = "test-index-simple-recursive-mapping-builder", replicas = 0, refreshInterval = "-1") static class SimpleRecursiveEntity { @@ -386,9 +392,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { ignoreFields = { "circularObject" }) private SimpleRecursiveEntity circularObject; } - /** - * @author Sascha Woo - */ @Setter @Getter @NoArgsConstructor @@ -406,9 +409,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { @Field(type = FieldType.Keyword) private String name; } - /** - * @author Sascha Woo - */ @Setter @Getter @NoArgsConstructor @@ -426,10 +426,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { type = FieldType.Keyword, normalizer = "lower_case_normalizer") }) private String description; } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - */ static class Author { @Nullable private String id; @@ -454,9 +450,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { } } - /** - * @author Kevin Leturc - */ @Document(indexName = "test-index-sample-inherited-mapping-builder", replicas = 0, refreshInterval = "-1") static class SampleInheritedEntity extends AbstractInheritedEntity { @@ -472,9 +465,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { } } - /** - * @author Kevin Leturc - */ static class SampleInheritedEntityBuilder { private final SampleInheritedEntity result; @@ -506,10 +496,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { } } - /** - * @author Artur Konczak - * @author Mohsin Husen - */ @Setter @Getter @NoArgsConstructor @@ -525,9 +511,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { @Field(type = FieldType.Double) private BigDecimal price; } - /** - * @author Kevin Letur - */ static class AbstractInheritedEntity { @Nullable @Id private String id; @@ -580,9 +563,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { orientation = GeoShapeField.Orientation.clockwise) private String shape2; } - /** - * Created by akonczak on 21/08/2016. - */ @Document(indexName = "test-index-user-mapping-builder") static class User { @Nullable @Id private String id; @@ -590,9 +570,6 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { @Field(type = FieldType.Nested, ignoreFields = { "users" }) private Set groups = new HashSet<>(); } - /** - * Created by akonczak on 21/08/2016. - */ @Document(indexName = "test-index-group-mapping-builder") static class Group { @@ -662,4 +639,20 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { @Nullable @Field(type = Wildcard) private String wildcardWithoutParams; @Nullable @Field(type = Wildcard, nullValue = "WILD", ignoreAbove = 42) private String wildcardWithParams; } + + @Data + @Document(indexName = "disabled-entity-mapping") + @Mapping(enabled = false) + static class DisabledMappingEntity { + @Id private String id; + @Field(type = Text) private String text; + } + + @Data + @Document(indexName = "disabled-property-mapping") + static class DisabledMappingProperty { + @Id private String id; + @Field(type = Text) private String text; + @Mapping(enabled = false) @Field(type = Object) private Object object; + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index de423f957..99d8a097a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -54,6 +54,7 @@ import org.springframework.data.geo.Box; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; +import org.springframework.data.mapping.MappingException; import org.springframework.lang.Nullable; /** @@ -504,9 +505,49 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { assertEquals(expected, mapping, false); } - /** - * @author Xiao Yu - */ + @Test // #1370 + @DisplayName("should not write mapping when enabled is false on entity") + void shouldNotWriteMappingWhenEnabledIsFalseOnEntity() throws JSONException { + + String expected = "{\n" + // + " \"enabled\": false" + // + "}"; + + String mapping = getMappingBuilder().buildPropertyMapping(DisabledMappingEntity.class); + + assertEquals(expected, mapping, false); + } + + @Test // #1370 + @DisplayName("should write disabled property mapping") + void shouldWriteDisabledPropertyMapping() throws JSONException { + + String expected = "{\n" + // + " \"properties\":{\n" + // + " \"text\": {\n" + // + " \"type\": \"text\"\n" + // + " },\n" + // + " \"object\": {\n" + // + " \"type\": \"object\",\n" + // + " \"enabled\": false\n" + // + " }\n" + // + " }\n" + // + "}\n"; // + + String mapping = getMappingBuilder().buildPropertyMapping(DisabledMappingProperty.class); + + assertEquals(expected, mapping, false); + } + + @Test // #1370 + @DisplayName("should only allow disabled properties on type object") + void shouldOnlyAllowDisabledPropertiesOnTypeObject() { + + assertThatThrownBy(() -> + getMappingBuilder().buildPropertyMapping(InvalidDisabledMappingProperty.class) + ).isInstanceOf(MappingException.class); + } + @Setter @Getter @NoArgsConstructor @@ -520,9 +561,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { @Field(type = FieldType.Keyword, ignoreAbove = 10) private String message; } - /** - * @author Peter-Josef Meisch - */ static class FieldNameEntity { @Document(indexName = "fieldname-index") @@ -587,11 +625,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Nordine Bittich - */ @Setter @Getter @NoArgsConstructor @@ -609,10 +642,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { searchAnalyzer = "standard") }) private String description; } - /** - * @author Stuart Stevenson - * @author Mohsin Husen - */ @Data @Document(indexName = "test-index-simple-recursive-mapping-builder", replicas = 0, refreshInterval = "-1") static class SimpleRecursiveEntity { @@ -622,9 +651,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { ignoreFields = { "circularObject" }) private SimpleRecursiveEntity circularObject; } - /** - * @author Sascha Woo - */ @Setter @Getter @NoArgsConstructor @@ -642,9 +668,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { @Field(type = FieldType.Keyword) private String name; } - /** - * @author Sascha Woo - */ @Setter @Getter @NoArgsConstructor @@ -662,10 +685,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { type = FieldType.Keyword, normalizer = "lower_case_normalizer") }) private String description; } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - */ static class Author { @Nullable private String id; @@ -690,9 +709,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { } } - /** - * @author Kevin Leturc - */ @Document(indexName = "test-index-sample-inherited-mapping-builder", replicas = 0, refreshInterval = "-1") static class SampleInheritedEntity extends AbstractInheritedEntity { @@ -708,10 +724,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { } } - /** - * @author Artur Konczak - * @author Mohsin Husen - */ @Setter @Getter @NoArgsConstructor @@ -727,9 +739,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { @Field(type = FieldType.Double) private BigDecimal price; } - /** - * @author Kevin Letur - */ static class AbstractInheritedEntity { @Nullable @Id private String id; @@ -755,9 +764,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { } } - /** - * @author Jakub Vavrik - */ @Document(indexName = "test-index-recursive-mapping-mapping-builder", replicas = 0, refreshInterval = "-1") static class SampleTransientEntity { @@ -836,9 +842,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { orientation = GeoShapeField.Orientation.clockwise) private String shape2; } - /** - * Created by akonczak on 21/08/2016. - */ @Document(indexName = "test-index-user-mapping-builder") static class User { @Nullable @Id private String id; @@ -846,9 +849,6 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { @Field(type = FieldType.Nested, ignoreFields = { "users" }) private Set groups = new HashSet<>(); } - /** - * Created by akonczak on 21/08/2016. - */ @Document(indexName = "test-index-group-mapping-builder") static class Group { @@ -961,4 +961,26 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { @Field(type = FieldType.Rank_Feature, positiveScoreImpact = false) private Integer urlLength; @Field(type = FieldType.Rank_Features) private Map topics; } + + @Data + @Mapping(enabled = false) + static class DisabledMappingEntity { + @Id private String id; + @Field(type = Text) private String text; + } + + @Data + static class InvalidDisabledMappingProperty { + @Id private String id; + @Mapping(enabled = false) + @Field(type = Text) private String text; + } + + @Data + static class DisabledMappingProperty { + @Id private String id; + @Field(type = Text) private String text; + @Mapping(enabled = false) + @Field(type = Object) private Object object; + } }