diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractRangePropertyValueConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractRangePropertyValueConverter.java index 09a89db09..4558d820d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractRangePropertyValueConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractRangePropertyValueConverter.java @@ -76,7 +76,10 @@ public Object read(Object value) { public Object write(Object value) { Assert.notNull(value, "value must not be null."); - Assert.isInstanceOf(Range.class, value, "value must be instance of Range."); + + if (!Range.class.isAssignableFrom(value.getClass())) { + return value.toString(); + } try { Range range = (Range) value; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/DatePropertyValueConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/DatePropertyValueConverter.java index 1bc8b3685..2542376b4 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/DatePropertyValueConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/DatePropertyValueConverter.java @@ -58,6 +58,10 @@ public Object read(Object value) { @Override public Object write(Object value) { + if (!Date.class.isAssignableFrom(value.getClass())) { + return value.toString(); + } + try { return dateConverters.get(0).format((Date) value); } catch (Exception e) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalPropertyValueConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalPropertyValueConverter.java index 357b49dde..bcac2e22e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalPropertyValueConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalPropertyValueConverter.java @@ -61,6 +61,10 @@ public Object read(Object value) { @Override public Object write(Object value) { + if (!TemporalAccessor.class.isAssignableFrom(value.getClass())) { + return value.toString(); + } + try { return dateConverters.get(0).format((TemporalAccessor) value); } catch (Exception e) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/PropertyValueConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/PropertyValueConverter.java index 97fa4fbd6..85c98e365 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/PropertyValueConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/PropertyValueConverter.java @@ -24,7 +24,8 @@ public interface PropertyValueConverter { /** - * Converts a property value to an elasticsearch value. + * Converts a property value to an elasticsearch value. If the converter cannot convert the value, it must return a + * String representation. * * @param value the value to convert, must not be {@literal null} * @return The elasticsearch property value, must not be {@literal null} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/PropertyValueConvertersUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/PropertyValueConvertersUnitTests.java new file mode 100644 index 000000000..546e86ae5 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/PropertyValueConvertersUnitTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2021 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.core.convert; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.params.provider.Arguments.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.data.elasticsearch.annotations.DateFormat; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; +import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter; +import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; +import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchPersistentEntity; +import org.springframework.lang.Nullable; + +/** + * @author Peter-Josef Meisch + */ +public class PropertyValueConvertersUnitTests { + + @ParameterizedTest(name = "{0}") // #2018 + @MethodSource("propertyValueConverters") + @DisplayName("should return original object on write if it cannot be converted") + void shouldReturnOriginalObjectOnWriteIfItCannotBeConverted(PropertyValueConverter converter) { + + NoConverterForThisClass value = new NoConverterForThisClass(); + + Object written = converter.write(value); + + assertThat(written).isEqualTo(value.toString()); + } + + static Stream propertyValueConverters() { + + SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); + SimpleElasticsearchPersistentEntity persistentEntity = context + .getRequiredPersistentEntity(NoConverterForThisClass.class); + ElasticsearchPersistentProperty persistentProperty = persistentEntity.getRequiredPersistentProperty("property"); + + List converters = new ArrayList<>(); + + converters.add(new DatePropertyValueConverter(persistentProperty, + Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date)))); + converters.add(new DateRangePropertyValueConverter(persistentProperty, + Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date)))); + converters.add(new NumberRangePropertyValueConverter(persistentProperty)); + converters.add(new TemporalPropertyValueConverter(persistentProperty, + Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date)))); + converters.add(new TemporalRangePropertyValueConverter(persistentProperty, + Collections.singletonList(ElasticsearchDateConverter.of(DateFormat.basic_date)))); + + return converters.stream().map(propertyValueConverter -> arguments( + Named.of(propertyValueConverter.getClass().getSimpleName(), propertyValueConverter))); + } + + static class NoConverterForThisClass { + @SuppressWarnings("unused") + @Nullable Long property; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java index 35c5a04e6..0bc140506 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java @@ -265,23 +265,30 @@ void shouldUseValueConverterAnnotation() { // region entities static class FieldNameProperty { - @Nullable @Field(name = "by-name") String fieldProperty; + @Nullable + @Field(name = "by-name") String fieldProperty; } static class FieldValueProperty { - @Nullable @Field(value = "by-value") String fieldProperty; + @Nullable + @Field(value = "by-value") String fieldProperty; } static class MultiFieldProperty { - @Nullable @MultiField(mainField = @Field("mainfield"), + @Nullable + @MultiField(mainField = @Field("mainfield"), otherFields = { @InnerField(suffix = "suff", type = FieldType.Keyword) }) String mainfieldProperty; } static class DatesProperty { - @Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate localDate; - @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) LocalDateTime localDateTime; - @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) Date legacyDate; - @Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") List localDateList; + @Nullable + @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate localDate; + @Nullable + @Field(type = FieldType.Date, format = DateFormat.basic_date_time) LocalDateTime localDateTime; + @Nullable + @Field(type = FieldType.Date, format = DateFormat.basic_date_time) Date legacyDate; + @Nullable + @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") List localDateList; } static class SeqNoPrimaryTermProperty { @@ -344,8 +351,10 @@ public void setWithCustomFieldName(String withCustomFieldName) { private static class EntityWithCustomValueConverters { @Id private String id; - @Nullable @ValueConverter(ClassBasedValueConverter.class) private String fieldWithClassBasedConverter; - @Nullable @ValueConverter(EnumBasedValueConverter.class) private String fieldWithEnumBasedConverter; + @Nullable + @ValueConverter(ClassBasedValueConverter.class) private String fieldWithClassBasedConverter; + @Nullable + @ValueConverter(EnumBasedValueConverter.class) private String fieldWithEnumBasedConverter; } private static class ClassBasedValueConverter implements PropertyValueConverter {