From d03510528b9f0c3346042c5ccb43a06759873302 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 7 Sep 2020 22:24:17 +0200 Subject: [PATCH] DATAES-920 - Add parameter to @Field annotation to store null values. Original PR: #516 --- .../data/elasticsearch/annotations/Field.java | 7 +++++ .../MappingElasticsearchConverter.java | 23 ++++++++++----- .../ElasticsearchPersistentProperty.java | 6 ++++ ...SimpleElasticsearchPersistentProperty.java | 12 +++++++- ...appingElasticsearchConverterUnitTests.java | 29 ++++++++++++++++++- 5 files changed, 68 insertions(+), 9 deletions(-) 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 2c328c1be..77046fdda 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java @@ -154,4 +154,11 @@ public @interface Field { * @since 4.0 */ int maxShingleSize() default -1; + + /** + * if true, the field will be stored in Elasticsearch even if it has a null value + * + * @since 4.1 + */ + boolean storeNullValue() default false; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index 5de116b78..8a4d4f43f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -497,6 +497,11 @@ public class MappingElasticsearchConverter Object value = accessor.getProperty(property); if (value == null) { + + if (property.storeNullValue()) { + sink.set(property, null); + } + continue; } @@ -797,7 +802,8 @@ public class MappingElasticsearchConverter }); } - org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = property.findAnnotation(org.springframework.data.elasticsearch.annotations.Field.class); + org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = property + .findAnnotation(org.springframework.data.elasticsearch.annotations.Field.class); if (fieldAnnotation != null) { field.setFieldType(fieldAnnotation.type()); @@ -864,14 +870,17 @@ public class MappingElasticsearchConverter return result; } - public void set(ElasticsearchPersistentProperty property, Object value) { + public void set(ElasticsearchPersistentProperty property, @Nullable Object value) { - if (property.isIdProperty()) { - ((Document) target).setId(value.toString()); - } + if (value != null) { - if (property.isVersionProperty()) { - ((Document) target).setVersion((Long) value); + if (property.isIdProperty()) { + ((Document) target).setId(value.toString()); + } + + if (property.isVersionProperty()) { + ((Document) target).setVersion((Long) value); + } } target.put(property.getFieldName(), value); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java index cd6e93164..1c922e7c8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java @@ -96,6 +96,12 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty { INSTANCE; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java index 7c3218acc..6adf747e7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java @@ -64,6 +64,7 @@ public class SimpleElasticsearchPersistentProperty extends private final boolean isSeqNoPrimaryTerm; private final @Nullable String annotatedFieldName; @Nullable private ElasticsearchPersistentPropertyConverter propertyConverter; + private boolean storeNullValue; public SimpleElasticsearchPersistentProperty(Property property, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { @@ -85,6 +86,8 @@ public class SimpleElasticsearchPersistentProperty extends this.isParent = isAnnotationPresent(Parent.class); this.isSeqNoPrimaryTerm = SeqNoPrimaryTerm.class.isAssignableFrom(getRawType()); + boolean isField = isAnnotationPresent(Field.class); + if (isVersionProperty() && !getType().equals(Long.class)) { throw new MappingException(String.format("Version property %s must be of type Long!", property.getName())); } @@ -98,11 +101,13 @@ public class SimpleElasticsearchPersistentProperty extends throw new MappingException(String.format("Parent property %s must be of type String!", property.getName())); } - if (isAnnotationPresent(Field.class) && isAnnotationPresent(MultiField.class)) { + if (isField && isAnnotationPresent(MultiField.class)) { throw new MappingException("@Field annotation must not be used on a @MultiField property."); } initDateConverter(); + + storeNullValue = isField && getRequiredAnnotation(Field.class).storeNullValue(); } @Override @@ -126,6 +131,11 @@ public class SimpleElasticsearchPersistentProperty extends return !isTransient() && !isSeqNoPrimaryTermProperty(); } + @Override + public boolean storeNullValue() { + return storeNullValue; + } + /** * Initializes an {@link ElasticsearchPersistentPropertyConverter} if this property is annotated as a Field with type * {@link FieldType#Date}, has a {@link DateFormat} set and if the type of the property is one of the Java8 temporal diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java index 56ccb761f..71e710445 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java @@ -39,6 +39,7 @@ import java.util.Map; import org.json.JSONException; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; @@ -616,7 +617,7 @@ public class MappingElasticsearchConverterUnitTests { assertEquals(expected, json, false); } - @Test + @Test // DATAES-716 void shouldReadLocalDate() { Document document = Document.create(); document.put("id", "4711"); @@ -813,6 +814,25 @@ public class MappingElasticsearchConverterUnitTests { assertEquals(expected, document.toJson(), false); } + @Test // DATAES-920 + @DisplayName("should write null value if configured") + void shouldWriteNullValueIfConfigured() throws JSONException { + + EntityWithNullField entity = new EntityWithNullField(); + entity.setId("42"); + + String expected = "{\n" + // + " \"id\": \"42\",\n" + // + " \"saved\": null\n" + // + "}\n"; // + + Document document = Document.create(); + + mappingElasticsearchConverter.write(entity, document); + + assertEquals(expected, document.toJson(), false); + } + private String pointTemplate(String name, Point point) { return String.format(Locale.ENGLISH, "\"%s\":{\"lat\":%.1f,\"lon\":%.1f}", name, point.getX(), point.getY()); } @@ -1046,4 +1066,11 @@ public class MappingElasticsearchConverterUnitTests { @Id private String id; private Object content; } + + @Data + static class EntityWithNullField { + @Id private String id; + @Field(type = FieldType.Text) private String notSaved; + @Field(type = FieldType.Text, storeNullValue = true) private String saved; + } }