diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java index cd3d9880e..9686f0209 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java @@ -168,4 +168,21 @@ public @interface Field { * @since 4.1 */ boolean positiveScoreImpact() default true; + + /** + * to be used in combination with {@link FieldType#Object} + * + * @since 4.1 + */ + boolean enabled() default true; + + /** + * @since 4.1 + */ + boolean eagerGlobalOrdinals() default false; + + /** + * @since 4.1 + */ + NullValueType nullValueType() default NullValueType.String; } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java b/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java index c4cd4e7cc..101d3b869 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java @@ -130,4 +130,14 @@ public @interface InnerField { * @since 4.1 */ boolean positiveScoreImpact() default true; + + /** + * @since 4.1 + */ + boolean eagerGlobalOrdinals() default false; + + /** + * @since 4.1 + */ + NullValueType nullValueType() default NullValueType.String; } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/NullValueType.java b/src/main/java/org/springframework/data/elasticsearch/annotations/NullValueType.java new file mode 100644 index 000000000..f4cfaee57 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/NullValueType.java @@ -0,0 +1,24 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.annotations; + +/** + * @author Peter-Josef Meisch + * @since 4.1 + */ +public enum NullValueType { + String, Integer, Long, Double +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java index 189f21853..539cfe3ab 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java @@ -26,6 +26,7 @@ import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.IndexOptions; import org.springframework.data.elasticsearch.annotations.IndexPrefixes; import org.springframework.data.elasticsearch.annotations.InnerField; +import org.springframework.data.elasticsearch.annotations.NullValueType; import org.springframework.data.elasticsearch.annotations.Similarity; import org.springframework.data.elasticsearch.annotations.TermVector; import org.springframework.util.Assert; @@ -44,8 +45,10 @@ public final class MappingParameters { static final String FIELD_PARAM_COERCE = "coerce"; static final String FIELD_PARAM_COPY_TO = "copy_to"; - static final String FIELD_PARAM_DOC_VALUES = "doc_values"; static final String FIELD_PARAM_DATA = "fielddata"; + static final String FIELD_PARAM_DOC_VALUES = "doc_values"; + static final String FIELD_PARAM_EAGER_GLOBAL_ORDINALS = "eager_global_ordinals"; + static final String FIELD_PARAM_ENABLED = "enabled"; static final String FIELD_PARAM_FORMAT = "format"; static final String FIELD_PARAM_IGNORE_ABOVE = "ignore_above"; static final String FIELD_PARAM_IGNORE_MALFORMED = "ignore_malformed"; @@ -56,44 +59,47 @@ public final class MappingParameters { static final String FIELD_PARAM_INDEX_PREFIXES_MIN_CHARS = "min_chars"; static final String FIELD_PARAM_INDEX_PREFIXES_MAX_CHARS = "max_chars"; static final String FIELD_PARAM_INDEX_ANALYZER = "analyzer"; + static final String FIELD_PARAM_MAX_SHINGLE_SIZE = "max_shingle_size"; static final String FIELD_PARAM_NORMALIZER = "normalizer"; static final String FIELD_PARAM_NORMS = "norms"; static final String FIELD_PARAM_NULL_VALUE = "null_value"; - static final String FIELD_PARAMETER_NAME_POSITION_INCREMENT_GAP = "position_increment_gap"; + static final String FIELD_PARAM_POSITION_INCREMENT_GAP = "position_increment_gap"; + static final String FIELD_PARAM_POSITIVE_SCORE_IMPACT = "positive_score_impact"; static final String FIELD_PARAM_SCALING_FACTOR = "scaling_factor"; static final String FIELD_PARAM_SEARCH_ANALYZER = "search_analyzer"; static final String FIELD_PARAM_STORE = "store"; static final String FIELD_PARAM_SIMILARITY = "similarity"; static final String FIELD_PARAM_TERM_VECTOR = "term_vector"; static final String FIELD_PARAM_TYPE = "type"; - static final String FIELD_PARAM_MAX_SHINGLE_SIZE = "max_shingle_size"; - static final String FIELD_PARAM_POSITIVE_SCORE_IMPACT = "positive_score_impact"; - private boolean index = true; - private boolean store = false; - private boolean fielddata = false; - private FieldType type = null; - private DateFormat dateFormat = null; - private String datePattern = null; - private String analyzer = null; - private String searchAnalyzer = null; - private String normalizer = null; - private String[] copyTo = null; - private Integer ignoreAbove = null; - private boolean coerce = true; - private boolean docValues = true; - private boolean ignoreMalformed = false; - private IndexOptions indexOptions = null; - boolean indexPhrases = false; - private IndexPrefixes indexPrefixes = null; - private boolean norms = true; - private String nullValue = null; - private Integer positionIncrementGap = null; - private Similarity similarity = Similarity.Default; - private TermVector termVector = TermVector.none; - private double scalingFactor = 1.0; - @Nullable private Integer maxShingleSize; - private boolean positiveScoreImpact = true; + private final String analyzer; + private final boolean coerce; + @Nullable private final String[] copyTo; + private final String datePattern; + private final boolean docValues; + private final boolean eagerGlobalOrdinals; + private final boolean enabled; + private final boolean fielddata; + private final DateFormat format; + @Nullable private final Integer ignoreAbove; + private final boolean ignoreMalformed; + private final boolean index; + private final IndexOptions indexOptions; + private final boolean indexPhrases; + @Nullable private final IndexPrefixes indexPrefixes; + private final String normalizer; + private final boolean norms; + @Nullable private final Integer maxShingleSize; + private final String nullValue; + private final NullValueType nullValueType; + private final Integer positionIncrementGap; + private final boolean positiveScoreImpact; + private final String searchAnalyzer; + private final double scalingFactor; + private final Similarity similarity; + private final boolean store; + private final TermVector termVector; + private final FieldType type; /** * extracts the mapping parameters from the relevant annotations. @@ -119,7 +125,7 @@ public final class MappingParameters { store = field.store(); fielddata = field.fielddata(); type = field.type(); - dateFormat = field.format(); + format = field.format(); datePattern = field.pattern(); analyzer = field.analyzer(); searchAnalyzer = field.searchAnalyzer(); @@ -133,11 +139,10 @@ public final class MappingParameters { ignoreMalformed = field.ignoreMalformed(); indexOptions = field.indexOptions(); indexPhrases = field.indexPhrases(); - if (field.indexPrefixes().length > 0) { - indexPrefixes = field.indexPrefixes()[0]; - } + indexPrefixes = field.indexPrefixes().length > 0 ? field.indexPrefixes()[0] : null; norms = field.norms(); nullValue = field.nullValue(); + nullValueType = field.nullValueType(); positionIncrementGap = field.positionIncrementGap(); similarity = field.similarity(); termVector = field.termVector(); @@ -148,6 +153,9 @@ public final class MappingParameters { || (maxShingleSize >= 2 && maxShingleSize <= 4), // "maxShingleSize must be in inclusive range from 2 to 4 for field type search_as_you_type"); positiveScoreImpact = field.positiveScoreImpact(); + Assert.isTrue(field.enabled() || type == FieldType.Object, "enabled false is only allowed for field type object"); + enabled = field.enabled(); + eagerGlobalOrdinals = field.eagerGlobalOrdinals(); } private MappingParameters(InnerField field) { @@ -155,11 +163,12 @@ public final class MappingParameters { store = field.store(); fielddata = field.fielddata(); type = field.type(); - dateFormat = field.format(); + format = field.format(); datePattern = field.pattern(); analyzer = field.analyzer(); searchAnalyzer = field.searchAnalyzer(); normalizer = field.normalizer(); + copyTo = null; ignoreAbove = field.ignoreAbove() >= 0 ? field.ignoreAbove() : null; coerce = field.coerce(); docValues = field.docValues(); @@ -168,11 +177,10 @@ public final class MappingParameters { ignoreMalformed = field.ignoreMalformed(); indexOptions = field.indexOptions(); indexPhrases = field.indexPhrases(); - if (field.indexPrefixes().length > 0) { - indexPrefixes = field.indexPrefixes()[0]; - } + indexPrefixes = field.indexPrefixes().length > 0 ? field.indexPrefixes()[0] : null; norms = field.norms(); nullValue = field.nullValue(); + nullValueType = field.nullValueType(); positionIncrementGap = field.positionIncrementGap(); similarity = field.similarity(); termVector = field.termVector(); @@ -183,6 +191,8 @@ public final class MappingParameters { || (maxShingleSize >= 2 && maxShingleSize <= 4), // "maxShingleSize must be in inclusive range from 2 to 4 for field type search_as_you_type"); positiveScoreImpact = field.positiveScoreImpact(); + enabled = true; + eagerGlobalOrdinals = field.eagerGlobalOrdinals(); } public boolean isStore() { @@ -204,8 +214,8 @@ public final class MappingParameters { if (type != FieldType.Auto) { builder.field(FIELD_PARAM_TYPE, type.name().toLowerCase()); - if (type == FieldType.Date && dateFormat != DateFormat.none) { - builder.field(FIELD_PARAM_FORMAT, dateFormat == DateFormat.custom ? datePattern : dateFormat.toString()); + if (type == FieldType.Date && format != DateFormat.none) { + builder.field(FIELD_PARAM_FORMAT, format == DateFormat.custom ? datePattern : format.toString()); } } @@ -270,11 +280,27 @@ public final class MappingParameters { } if (!StringUtils.isEmpty(nullValue)) { - builder.field(FIELD_PARAM_NULL_VALUE, nullValue); + Object value; + switch (nullValueType) { + case Integer: + value = Integer.valueOf(nullValue); + break; + case Long: + value = Long.valueOf(nullValue); + break; + case Double: + value = Double.valueOf(nullValue); + break; + case String: + default: + value = nullValue; + break; + } + builder.field(FIELD_PARAM_NULL_VALUE, value); } if (positionIncrementGap != null && positionIncrementGap >= 0) { - builder.field(FIELD_PARAMETER_NAME_POSITION_INCREMENT_GAP, positionIncrementGap); + builder.field(FIELD_PARAM_POSITION_INCREMENT_GAP, positionIncrementGap); } if (similarity != Similarity.Default) { @@ -296,5 +322,13 @@ public final class MappingParameters { if (!positiveScoreImpact) { builder.field(FIELD_PARAM_POSITIVE_SCORE_IMPACT, positiveScoreImpact); } + + if (!enabled) { + builder.field(FIELD_PARAM_ENABLED, enabled); + } + + if (eagerGlobalOrdinals) { + builder.field(FIELD_PARAM_EAGER_GLOBAL_ORDINALS, eagerGlobalOrdinals); + } } } 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 9a7fadc1f..de1a249d2 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 @@ -305,7 +305,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { assertEquals(expected, mapping, false); } - @Test + @Test // DATAES-621, DATAES-943 public void shouldSetFieldMappingProperties() throws JSONException { String expected = "{\n" + // " \"properties\": {\n" + // @@ -364,9 +364,15 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { " \"normsFalse\": {\n" + // " \"norms\": false\n" + // " },\n" + // - " \"nullValueSet\": {\n" + // + " \"nullValueString\": {\n" + // " \"null_value\": \"NULLNULL\"\n" + // " },\n" + // + " \"nullValueInteger\": {\n" + // + " \"null_value\": 42\n" + // + " },\n" + // + " \"nullValueDouble\": {\n" + // + " \"null_value\": 42.0\n" + // + " },\n" + // " \"positionIncrementGap\": {\n" + // " \"position_increment_gap\": 42\n" + // " },\n" + // @@ -379,6 +385,20 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { " \"scaledFloat\": {\n" + // " \"type\": \"scaled_float\",\n" + // " \"scaling_factor\": 100.0\n" + // + " },\n" + // + " \"enabledObject\": {\n" + // + " \"type\": \"object\"\n" + // + " },\n" + // + " \"disabledObject\": {\n" + // + " \"type\": \"object\",\n" + // + " \"enabled\": false\n" + // + " },\n" + // + " \"eagerGlobalOrdinalsTrue\": {\n" + // + " \"type\": \"text\",\n" + // + " \"eager_global_ordinals\": true\n" + // + " },\n" + // + " \"eagerGlobalOrdinalsFalse\": {\n" + // + " \"type\": \"text\"\n" + // " }\n" + // " }\n" + // "}\n"; // @@ -857,7 +877,9 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { @Nullable @Field private String normsTrue; @Nullable @Field(norms = false) private String normsFalse; @Nullable @Field private String nullValueNotSet; - @Nullable @Field(nullValue = "NULLNULL") private String nullValueSet; + @Nullable @Field(nullValue = "NULLNULL") private String nullValueString; + @Nullable @Field(nullValue = "42", nullValueType = NullValueType.Integer) private String nullValueInteger; + @Nullable @Field(nullValue = "42.0", nullValueType = NullValueType.Double) private String nullValueDouble; @Nullable @Field(positionIncrementGap = 42) private String positionIncrementGap; @Nullable @Field private String similarityDefault; @Nullable @Field(similarity = Similarity.Boolean) private String similarityBoolean; @@ -865,6 +887,10 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests { @Nullable @Field(termVector = TermVector.with_offsets) private String termVectorWithOffsets; @Nullable @Field(type = FieldType.Scaled_Float, scalingFactor = 100.0) Double scaledFloat; @Nullable @Field(type = Auto) String autoField; + @Nullable @Field(type = Object, enabled = true) private String enabledObject; + @Nullable @Field(type = Object, enabled = false) private String disabledObject; + @Nullable @Field(type = Text, eagerGlobalOrdinals = true) private String eagerGlobalOrdinalsTrue; + @Nullable @Field(type = Text, eagerGlobalOrdinals = false) private String eagerGlobalOrdinalsFalse; } @Document(indexName = "test-index-configure-dynamic-mapping") diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java index ffce29eec..4ef8e0c07 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java @@ -1,9 +1,11 @@ package org.springframework.data.elasticsearch.core.index; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.elasticsearch.annotations.FieldType.Object; import java.lang.annotation.Annotation; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; @@ -61,6 +63,16 @@ public class MappingParametersTest extends MappingContextBaseTests { assertThatThrownBy(() -> MappingParameters.from(annotation)).isInstanceOf(IllegalArgumentException.class); } + @Test // DATAES-943 + @DisplayName("should allow enabled false only on object fields") + void shouldAllowEnabledFalseOnlyOnObjectFields() { + ElasticsearchPersistentEntity failEntity = elasticsearchConverter.get().getMappingContext() + .getRequiredPersistentEntity(InvalidEnabledFieldClass.class); + Annotation annotation = failEntity.getRequiredPersistentProperty("disabledObject").findAnnotation(Field.class); + + assertThatThrownBy(() -> MappingParameters.from(annotation)).isInstanceOf(IllegalArgumentException.class); + } + static class AnnotatedClass { @Nullable @Field private String field; @Nullable @MultiField(mainField = @Field, @@ -68,5 +80,11 @@ public class MappingParametersTest extends MappingContextBaseTests { @Score private float score; @Nullable @Field(type = FieldType.Text, docValues = false) private String docValuesText; @Nullable @Field(type = FieldType.Nested, docValues = false) private String docValuesNested; + @Nullable @Field(type = Object, enabled = true) private String enabledObject; + @Nullable @Field(type = Object, enabled = false) private String disabledObject; + } + + static class InvalidEnabledFieldClass { + @Nullable @Field(type = FieldType.Text, enabled = false) private String disabledObject; } }