Add custom property converters.

Original Pull Request #1953 
Closes #1945
This commit is contained in:
Peter-Josef Meisch 2021-10-05 21:31:41 +02:00 committed by GitHub
parent 464fc31d87
commit 7ae55b9e75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 358 additions and 125 deletions

View File

@ -71,3 +71,8 @@ Spring Data Elasticsearch now uses `org.springframework.data.elasticsearch.core.
=== Completion classes
The classes from the package `org.springframework.data.elasticsearch.core.completion` have been moved to `org.springframework.data.elasticsearch.core.suggest`.
=== Other renamings
The `org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter` interface has been renamed to `org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter`.
Likewise the implementations classes named _XXPersistentPropertyConverter_ have been renamed to _XXPropertyValueConverter_.

View File

@ -56,6 +56,8 @@ This means, that no mapping entry is written for the property and that Elasticse
** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom analyzers and normalizer.
* `@GeoPoint`: Marks a field as _geo_point_ datatype.
Can be omitted if the field is an instance of the `GeoPoint` class.
* `@ValueConverter` defines a class to be used to convert the given property.
In difference to a registered Spring `Converter` this only converts the annotated property and not every property of the given type.
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
@ -184,6 +186,7 @@ public class Person { <1>
"lastname" : "Connor"
}
----
<1> By default the domain types class name is used for the type hint.
====
@ -211,6 +214,7 @@ public class Person {
"id" : ...
}
----
<1> The configured alias is used when writing the entity.
====
@ -421,6 +425,7 @@ public class Config extends AbstractElasticsearchConfiguration {
"localidad" : { "lat" : 34.118347, "lon" : -118.3026284 }
}
----
<1> Add `Converter` implementations.
<2> Set up the `Converter` used for writing `DomainType` to Elasticsearch.
<3> Set up the `Converter` used for reading `DomainType` from search result.

View File

@ -0,0 +1,48 @@
/*
* 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.annotations;
import java.lang.annotation.Documented;
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.elasticsearch.core.mapping.PropertyValueConverter;
/**
* Annotation to put on a property of an entity to define a value converter which can convert the property to a type
* that Elasticsearch understands and back.
*
* @author Peter-Josef Meisch
* @since 4.3
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Documented
@Inherited
public @interface ValueConverter {
/**
* Defines the class implementing the {@link PropertyValueConverter} interface. If this is a normal class, it must
* provide a default constructor with no arguments. If this is an enum and thus implementing a singleton by enum it
* must only have one enum value.
*
* @return the class to use for conversion
*/
Class<? extends PropertyValueConverter> value();
}

View File

@ -15,7 +15,7 @@
*/
package org.springframework.data.elasticsearch.core.convert;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.util.Assert;
@ -23,11 +23,11 @@ import org.springframework.util.Assert;
* @author Sascha Woo
* @since 4.3
*/
public abstract class AbstractPersistentPropertyConverter implements ElasticsearchPersistentPropertyConverter {
public abstract class AbstractPropertyValueConverter implements PropertyValueConverter {
private final PersistentProperty<?> property;
public AbstractPersistentPropertyConverter(PersistentProperty<?> property) {
public AbstractPropertyValueConverter(PersistentProperty<?> property) {
Assert.notNull(property, "property must not be null.");
this.property = property;

View File

@ -26,14 +26,14 @@ import org.springframework.util.Assert;
* @author Sascha Woo
* @since 4.3
*/
public abstract class AbstractRangePersistentPropertyConverter<T> extends AbstractPersistentPropertyConverter {
public abstract class AbstractRangePropertyValueConverter<T> extends AbstractPropertyValueConverter {
protected static final String LT_FIELD = "lt";
protected static final String LTE_FIELD = "lte";
protected static final String GT_FIELD = "gt";
protected static final String GTE_FIELD = "gte";
public AbstractRangePersistentPropertyConverter(PersistentProperty<?> property) {
public AbstractRangePropertyValueConverter(PersistentProperty<?> property) {
super(property);
}

View File

@ -26,14 +26,13 @@ import org.springframework.data.mapping.PersistentProperty;
* @author Sascha Woo
* @since 4.3
*/
public class DatePersistentPropertyConverter extends AbstractPersistentPropertyConverter {
public class DatePropertyValueConverter extends AbstractPropertyValueConverter {
private static final Logger LOGGER = LoggerFactory.getLogger(DatePersistentPropertyConverter.class);
private static final Logger LOGGER = LoggerFactory.getLogger(DatePropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public DatePersistentPropertyConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) {
public DatePropertyValueConverter(PersistentProperty<?> property, List<ElasticsearchDateConverter> dateConverters) {
super(property);
this.dateConverters = dateConverters;

View File

@ -26,13 +26,13 @@ import org.springframework.data.mapping.PersistentProperty;
* @author Sascha Woo
* @since 4.3
*/
public class DateRangePersistentPropertyConverter extends AbstractRangePersistentPropertyConverter<Date> {
public class DateRangePropertyValueConverter extends AbstractRangePropertyValueConverter<Date> {
private static final Logger LOGGER = LoggerFactory.getLogger(DateRangePersistentPropertyConverter.class);
private static final Logger LOGGER = LoggerFactory.getLogger(DateRangePropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public DateRangePersistentPropertyConverter(PersistentProperty<?> property,
public DateRangePropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) {
super(property);

View File

@ -19,6 +19,7 @@ import org.springframework.data.convert.EntityConverter;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@ -93,8 +94,7 @@ public interface ElasticsearchConverter
/**
* Updates a {@link Query} by renaming the property names in the query to the correct mapped field names and the
* values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If
* domainClass is null it's a noop.
* {@link PropertyValueConverter}. If domainClass is null it's a noop.
*
* @param query the query that is internally updated, must not be {@literal null}
* @param domainClass the class of the object that is searched with the query

View File

@ -39,7 +39,7 @@ import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.document.SearchDocument;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
@ -425,8 +425,9 @@ public class MappingElasticsearchConverter
Class<?> rawType = type.getType();
if (property.hasPropertyConverter()) {
value = propertyConverterRead(property, value);
if (property.hasPropertyValueConverter()) {
// noinspection unchecked
return (R) propertyConverterRead(property, value);
} else if (TemporalAccessor.class.isAssignableFrom(property.getType())
&& !conversions.hasCustomReadTarget(value.getClass(), rawType)) {
@ -466,8 +467,7 @@ public class MappingElasticsearchConverter
}
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
ElasticsearchPersistentPropertyConverter propertyConverter = Objects
.requireNonNull(property.getPropertyConverter());
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
if (source instanceof String[]) {
// convert to a List
@ -475,18 +475,19 @@ public class MappingElasticsearchConverter
}
if (source instanceof List) {
source = ((List<?>) source).stream().map(it -> convertOnRead(propertyConverter, it))
source = ((List<?>) source).stream().map(it -> convertOnRead(propertyValueConverter, it))
.collect(Collectors.toList());
} else if (source instanceof Set) {
source = ((Set<?>) source).stream().map(it -> convertOnRead(propertyConverter, it)).collect(Collectors.toSet());
source = ((Set<?>) source).stream().map(it -> convertOnRead(propertyValueConverter, it))
.collect(Collectors.toSet());
} else {
source = convertOnRead(propertyConverter, source);
source = convertOnRead(propertyValueConverter, source);
}
return source;
}
private Object convertOnRead(ElasticsearchPersistentPropertyConverter propertyConverter, Object source) {
return propertyConverter.read(source);
private Object convertOnRead(PropertyValueConverter propertyValueConverter, Object source) {
return propertyValueConverter.read(source);
}
/**
@ -897,7 +898,7 @@ public class MappingElasticsearchConverter
continue;
}
if (property.hasPropertyConverter()) {
if (property.hasPropertyValueConverter()) {
value = propertyConverterWrite(property, value);
sink.set(property, value);
} else if (TemporalAccessor.class.isAssignableFrom(property.getActualType())
@ -1070,15 +1071,14 @@ public class MappingElasticsearchConverter
}
private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) {
ElasticsearchPersistentPropertyConverter propertyConverter = Objects
.requireNonNull(property.getPropertyConverter());
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
if (value instanceof List) {
value = ((List<?>) value).stream().map(propertyConverter::write).collect(Collectors.toList());
value = ((List<?>) value).stream().map(propertyValueConverter::write).collect(Collectors.toList());
} else if (value instanceof Set) {
value = ((Set<?>) value).stream().map(propertyConverter::write).collect(Collectors.toSet());
value = ((Set<?>) value).stream().map(propertyValueConverter::write).collect(Collectors.toSet());
} else {
value = propertyConverter.write(value);
value = propertyValueConverter.write(value);
}
return value;
}
@ -1252,18 +1252,18 @@ public class MappingElasticsearchConverter
if (persistentProperty != null) {
if (persistentProperty.hasPropertyConverter()) {
ElasticsearchPersistentPropertyConverter propertyConverter = Objects
.requireNonNull(persistentProperty.getPropertyConverter());
if (persistentProperty.hasPropertyValueConverter()) {
PropertyValueConverter propertyValueConverter = Objects
.requireNonNull(persistentProperty.getPropertyValueConverter());
criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> {
Object value = criteriaEntry.getValue();
if (value.getClass().isArray()) {
Object[] objects = (Object[]) value;
for (int i = 0; i < objects.length; i++) {
objects[i] = propertyConverter.write(objects[i]);
objects[i] = propertyValueConverter.write(objects[i]);
}
} else {
criteriaEntry.setValue(propertyConverter.write(value));
criteriaEntry.setValue(propertyValueConverter.write(value));
}
});
}

View File

@ -21,9 +21,9 @@ import org.springframework.data.mapping.PersistentProperty;
* @author Sascha Woo
* @since 4.3
*/
public class NumberRangePersistentPropertyConverter extends AbstractRangePersistentPropertyConverter<Number> {
public class NumberRangePropertyValueConverter extends AbstractRangePropertyValueConverter<Number> {
public NumberRangePersistentPropertyConverter(PersistentProperty<?> property) {
public NumberRangePropertyValueConverter(PersistentProperty<?> property) {
super(property);
}

View File

@ -26,13 +26,13 @@ import org.springframework.data.mapping.PersistentProperty;
* @author Sascha Woo
* @since 4.3
*/
public class TemporalPersistentPropertyConverter extends AbstractPersistentPropertyConverter {
public class TemporalPropertyValueConverter extends AbstractPropertyValueConverter {
private static final Logger LOGGER = LoggerFactory.getLogger(TemporalPersistentPropertyConverter.class);
private static final Logger LOGGER = LoggerFactory.getLogger(TemporalPropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public TemporalPersistentPropertyConverter(PersistentProperty<?> property,
public TemporalPropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) {
super(property);

View File

@ -27,14 +27,13 @@ import org.springframework.util.Assert;
* @author Sascha Woo
* @since 4.3
*/
public class TemporalRangePersistentPropertyConverter
extends AbstractRangePersistentPropertyConverter<TemporalAccessor> {
public class TemporalRangePropertyValueConverter extends AbstractRangePropertyValueConverter<TemporalAccessor> {
private static final Logger LOGGER = LoggerFactory.getLogger(TemporalRangePersistentPropertyConverter.class);
private static final Logger LOGGER = LoggerFactory.getLogger(TemporalRangePropertyValueConverter.class);
private final List<ElasticsearchDateConverter> dateConverters;
public TemporalRangePersistentPropertyConverter(PersistentProperty<?> property,
public TemporalRangePropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) {
super(property);

View File

@ -48,17 +48,17 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas
boolean isSeqNoPrimaryTermProperty();
/**
* @return true if an {@link ElasticsearchPersistentPropertyConverter} is available for this instance.
* @return true if an {@link PropertyValueConverter} is available for this instance.
* @since 4.0
*/
boolean hasPropertyConverter();
boolean hasPropertyValueConverter();
/**
* @return the {@link ElasticsearchPersistentPropertyConverter} for this instance.
* @return the {@link PropertyValueConverter} for this instance.
* @since 4.0
*/
@Nullable
ElasticsearchPersistentPropertyConverter getPropertyConverter();
PropertyValueConverter getPropertyValueConverter();
/**
* Returns true if the property may be read.

View File

@ -16,26 +16,26 @@
package org.springframework.data.elasticsearch.core.mapping;
/**
* Interface defining methods to convert a persistent property value to an elasticsearch property value and back.
* Interface defining methods to convert the value of an entity-property to a value in Elasticsearch and back.
*
* @author Peter-Josef Meisch
* @author Sascha Woo
*/
public interface ElasticsearchPersistentPropertyConverter {
public interface PropertyValueConverter {
/**
* Converts a persistent property value to an elasticsearch property value.
* Converts a property value to an elasticsearch value.
*
* @param value the persistent property value to convert, must not be {@literal null}
* @return The elasticsearch property value.
* @param value the value to convert, must not be {@literal null}
* @return The elasticsearch property value, must not be {@literal null}
*/
Object write(Object value);
/**
* Converts an elasticsearch property value to a persistent property value.
* Converts an elasticsearch property value to a property value.
*
* @param value the elasticsearch property value to convert, must not be {@literal null}
* @return The persistent property value.
* @return The converted value, must not be {@literal null}
*/
Object read(Object value);
}

View File

@ -23,24 +23,26 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.annotations.GeoShapeField;
import org.springframework.data.elasticsearch.annotations.MultiField;
import org.springframework.data.elasticsearch.annotations.ValueConverter;
import org.springframework.data.elasticsearch.core.Range;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.elasticsearch.core.convert.DatePersistentPropertyConverter;
import org.springframework.data.elasticsearch.core.convert.DateRangePersistentPropertyConverter;
import org.springframework.data.elasticsearch.core.convert.DatePropertyValueConverter;
import org.springframework.data.elasticsearch.core.convert.DateRangePropertyValueConverter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchDateConverter;
import org.springframework.data.elasticsearch.core.convert.NumberRangePersistentPropertyConverter;
import org.springframework.data.elasticsearch.core.convert.TemporalPersistentPropertyConverter;
import org.springframework.data.elasticsearch.core.convert.TemporalRangePersistentPropertyConverter;
import org.springframework.data.elasticsearch.core.convert.NumberRangePropertyValueConverter;
import org.springframework.data.elasticsearch.core.convert.TemporalPropertyValueConverter;
import org.springframework.data.elasticsearch.core.convert.TemporalRangePropertyValueConverter;
import org.springframework.data.elasticsearch.core.geo.GeoJson;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.join.JoinField;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
@ -75,7 +77,7 @@ public class SimpleElasticsearchPersistentProperty extends
private final boolean isId;
private final boolean isSeqNoPrimaryTerm;
private final @Nullable String annotatedFieldName;
@Nullable private ElasticsearchPersistentPropertyConverter propertyConverter;
@Nullable private PropertyValueConverter propertyValueConverter;
private final boolean storeNullValue;
public SimpleElasticsearchPersistentProperty(Property property,
@ -98,20 +100,20 @@ public class SimpleElasticsearchPersistentProperty extends
throw new MappingException("@Field annotation must not be used on a @MultiField property.");
}
initPropertyConverter();
initPropertyValueConverter();
storeNullValue = isField && getRequiredAnnotation(Field.class).storeNullValue();
}
@Override
public boolean hasPropertyConverter() {
return propertyConverter != null;
public boolean hasPropertyValueConverter() {
return propertyValueConverter != null;
}
@Nullable
@Override
public ElasticsearchPersistentPropertyConverter getPropertyConverter() {
return propertyConverter;
public PropertyValueConverter getPropertyValueConverter() {
return propertyValueConverter;
}
@Override
@ -136,7 +138,13 @@ public class SimpleElasticsearchPersistentProperty extends
/**
* Initializes the property converter for this {@link PersistentProperty}, if any.
*/
private void initPropertyConverter() {
private void initPropertyValueConverter() {
initPropertyValueConverterFromAnnotation();
if (hasPropertyValueConverter()) {
return;
}
Class<?> actualType = getActualTypeOrNull();
if (actualType == null) {
@ -158,9 +166,9 @@ public class SimpleElasticsearchPersistentProperty extends
}
if (TemporalAccessor.class.isAssignableFrom(actualType)) {
propertyConverter = new TemporalPersistentPropertyConverter(this, dateConverters);
propertyValueConverter = new TemporalPropertyValueConverter(this, dateConverters);
} else if (Date.class.isAssignableFrom(actualType)) {
propertyConverter = new DatePersistentPropertyConverter(this, dateConverters);
propertyValueConverter = new DatePropertyValueConverter(this, dateConverters);
} else {
LOGGER.warn("Unsupported type '{}' for date property '{}'.", actualType, getName());
}
@ -179,9 +187,9 @@ public class SimpleElasticsearchPersistentProperty extends
Class<?> genericType = getTypeInformation().getTypeArguments().get(0).getType();
if (TemporalAccessor.class.isAssignableFrom(genericType)) {
propertyConverter = new TemporalRangePersistentPropertyConverter(this, dateConverters);
propertyValueConverter = new TemporalRangePropertyValueConverter(this, dateConverters);
} else if (Date.class.isAssignableFrom(genericType)) {
propertyConverter = new DateRangePersistentPropertyConverter(this, dateConverters);
propertyValueConverter = new DateRangePropertyValueConverter(this, dateConverters);
} else {
LOGGER.warn("Unsupported generic type '{}' for date range property '{}'.", genericType, getName());
}
@ -205,7 +213,7 @@ public class SimpleElasticsearchPersistentProperty extends
return;
}
propertyConverter = new NumberRangePersistentPropertyConverter(this);
propertyValueConverter = new NumberRangePropertyValueConverter(this);
break;
}
case Ip_Range: {
@ -216,6 +224,26 @@ public class SimpleElasticsearchPersistentProperty extends
}
}
private void initPropertyValueConverterFromAnnotation() {
ValueConverter annotation = findAnnotation(ValueConverter.class);
if (annotation != null) {
Class<? extends PropertyValueConverter> clazz = annotation.value();
if (Enum.class.isAssignableFrom(clazz)) {
PropertyValueConverter[] enumConstants = clazz.getEnumConstants();
if (enumConstants == null || enumConstants.length != 1) {
throw new IllegalArgumentException(clazz + " is an enum with more than 1 constant and cannot be used here");
}
propertyValueConverter = enumConstants[0];
} else {
propertyValueConverter = BeanUtils.instantiateClass(clazz);
}
}
}
private List<ElasticsearchDateConverter> getDateConverters(Field field, Class<?> actualType) {
DateFormat[] dateFormats = field.format();

View File

@ -26,14 +26,11 @@ import org.junit.jupiter.api.Test;
* @author Sascha Woo
* @since 4.3
*/
public class RangeTests {
public class RangeUnitTests {
@Test
public void shouldContainsLocalDate() {
// given
// when
// then
assertThat(Range.open(LocalDate.of(2021, 1, 1), LocalDate.of(2021, 2, 1)).contains(LocalDate.of(2021, 1, 10)))
.isTrue();
}
@ -41,21 +38,17 @@ public class RangeTests {
@Test
public void shouldEqualToSameRange() {
// given
Range<LocalDate> range1 = Range.open(LocalDate.of(2021, 1, 1), LocalDate.of(2021, 2, 1));
Range<LocalDate> range2 = Range.open(LocalDate.of(2021, 1, 1), LocalDate.of(2021, 2, 1));
// when
// then
assertThat(range1).isEqualTo(range2);
}
@Test
public void shouldHaveClosedBoundaries() {
// given
Range<Integer> range = Range.closed(1, 3);
// when
// then
assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isTrue();
@ -64,10 +57,8 @@ public class RangeTests {
@Test
public void shouldHaveJustOneValue() {
// given
Range<Integer> range = Range.just(2);
// when
// then
assertThat(range.contains(1)).isFalse();
assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isFalse();
@ -76,10 +67,8 @@ public class RangeTests {
@Test
public void shouldHaveLeftOpenBoundary() {
// given
Range<Integer> range = Range.leftOpen(1, 3);
// when
// then
assertThat(range.contains(1)).isFalse();
assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isTrue();
@ -88,10 +77,8 @@ public class RangeTests {
@Test
public void shouldHaveLeftUnboundedAndRightExclusive() {
// given
Range<Integer> range = Range.leftUnbounded(Range.Bound.exclusive(3));
// when
// then
assertThat(range.contains(0)).isTrue();
assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue();
@ -101,10 +88,8 @@ public class RangeTests {
@Test
public void shouldHaveLeftUnboundedAndRightInclusive() {
// given
Range<Integer> range = Range.leftUnbounded(Range.Bound.inclusive(3));
// when
// then
assertThat(range.contains(0)).isTrue();
assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue();
@ -114,10 +99,8 @@ public class RangeTests {
@Test
public void shouldHaveOpenBoundaries() {
// given
Range<Integer> range = Range.open(1, 3);
// when
// then
assertThat(range.contains(1)).isFalse();
assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isFalse();
@ -126,10 +109,8 @@ public class RangeTests {
@Test
public void shouldHaveRightOpenBoundary() {
// given
Range<Integer> range = Range.rightOpen(1, 3);
// when
// then
assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isFalse();
@ -138,10 +119,8 @@ public class RangeTests {
@Test
public void shouldHaveRightUnboundedAndLeftExclusive() {
// given
Range<Integer> range = Range.rightUnbounded(Range.Bound.exclusive(1));
// when
// then
assertThat(range.contains(1)).isFalse();
assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isTrue();
@ -151,10 +130,8 @@ public class RangeTests {
@Test
public void shouldHaveRightUnboundedAndLeftInclusive() {
// given
Range<Integer> range = Range.rightUnbounded(Range.Bound.inclusive(1));
// when
// then
assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isTrue();
@ -164,12 +141,7 @@ public class RangeTests {
@Test
public void shouldThrowExceptionIfNotComparable() {
// given
// when
Throwable thrown = catchThrowable(() -> Range.just(Collections.singletonList("test")));
// then
assertThat(thrown).isInstanceOf(IllegalArgumentException.class)
assertThatThrownBy(() -> Range.just(Collections.singletonList("test"))).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("value must implements Comparable!");
}
}

View File

@ -52,6 +52,7 @@ import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.annotations.ValueConverter;
import org.springframework.data.elasticsearch.core.Range;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.geo.GeoJsonEntity;
@ -63,6 +64,7 @@ import org.springframework.data.elasticsearch.core.geo.GeoJsonMultiPolygon;
import org.springframework.data.elasticsearch.core.geo.GeoJsonPoint;
import org.springframework.data.elasticsearch.core.geo.GeoJsonPolygon;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.mapping.PropertyValueConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.geo.Box;
@ -71,6 +73,7 @@ import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Unit tests for {@link MappingElasticsearchConverter}.
@ -1446,6 +1449,53 @@ public class MappingElasticsearchConverterUnitTests {
assertEquals(expected, document.toJson(), true);
}
@Test // #1945
@DisplayName("should write using ValueConverters")
void shouldWriteUsingValueConverters() throws JSONException {
EntityWithCustomValueConverters entity = new EntityWithCustomValueConverters();
entity.setId("42");
entity.setFieldWithClassBasedConverter("classbased");
entity.setFieldWithEnumBasedConverter("enumbased");
entity.setDontConvert("Monty Python's Flying Circus");
String expected = "{\n" + //
" \"id\": \"42\",\n" + //
" \"fieldWithClassBasedConverter\": \"desabssalc\",\n" + //
" \"fieldWithEnumBasedConverter\": \"desabmune\",\n" + //
" \"dontConvert\": \"Monty Python's Flying Circus\"\n" + //
"}\n"; //
Document document = Document.create();
mappingElasticsearchConverter.write(entity, document);
assertEquals(expected, document.toJson(), false);
}
@Test // #1945
@DisplayName("should read using ValueConverters")
void shouldReadUsingValueConverters() throws JSONException {
String json = "{\n" + //
" \"id\": \"42\",\n" + //
" \"fieldWithClassBasedConverter\": \"desabssalc\",\n" + //
" \"fieldWithEnumBasedConverter\": \"desabmune\",\n" + //
" \"dontConvert\": \"Monty Python's Flying Circus\"\n" + //
"}\n"; //
Document source = Document.parse(json);
// when
EntityWithCustomValueConverters entity = mappingElasticsearchConverter.read(EntityWithCustomValueConverters.class,
source);
assertThat(entity.getId()).isEqualTo("42");
assertThat(entity.getFieldWithClassBasedConverter()).isEqualTo("classbased");
assertThat(entity.getFieldWithEnumBasedConverter()).isEqualTo("enumbased");
assertThat(entity.getDontConvert()).isEqualTo("Monty Python's Flying Circus");
}
private Map<String, Object> writeToMap(Object source) {
Document sink = Document.create();
@ -2309,5 +2359,82 @@ public class MappingElasticsearchConverterUnitTests {
this.cars = cars;
}
}
private static class EntityWithCustomValueConverters {
@Nullable @Id private String id;
@Nullable @ValueConverter(ClassBasedValueConverter.class) private String fieldWithClassBasedConverter;
@Nullable @ValueConverter(EnumBasedValueConverter.class) private String fieldWithEnumBasedConverter;
@Nullable private String dontConvert;
@Nullable
public String getId() {
return id;
}
public void setId(@Nullable String id) {
this.id = id;
}
@Nullable
public String getFieldWithClassBasedConverter() {
return fieldWithClassBasedConverter;
}
public void setFieldWithClassBasedConverter(@Nullable String fieldWithClassBasedConverter) {
this.fieldWithClassBasedConverter = fieldWithClassBasedConverter;
}
@Nullable
public String getFieldWithEnumBasedConverter() {
return fieldWithEnumBasedConverter;
}
public void setFieldWithEnumBasedConverter(@Nullable String fieldWithEnumBasedConverter) {
this.fieldWithEnumBasedConverter = fieldWithEnumBasedConverter;
}
@Nullable
public String getDontConvert() {
return dontConvert;
}
public void setDontConvert(@Nullable String dontConvert) {
this.dontConvert = dontConvert;
}
}
private static class ClassBasedValueConverter implements PropertyValueConverter {
@Override
public Object write(Object value) {
return reverse(value);
}
@Override
public Object read(Object value) {
return reverse(value);
}
}
private enum EnumBasedValueConverter implements PropertyValueConverter {
INSTANCE;
@Override
public Object write(Object value) {
return reverse(value);
}
@Override
public Object read(Object value) {
return reverse(value);
}
}
private static String reverse(Object o) {
Assert.notNull(o, "o must not be null");
return new StringBuilder().append(o.toString()).reverse().toString();
}
// endregion
}

View File

@ -27,11 +27,13 @@ import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.MultiField;
import org.springframework.data.elasticsearch.annotations.ValueConverter;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.model.FieldNamingStrategy;
@ -92,20 +94,20 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
SimpleElasticsearchPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DatesProperty.class);
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getRequiredPersistentProperty("localDate");
assertThat(persistentProperty.hasPropertyConverter()).isTrue();
assertThat(persistentProperty.getPropertyConverter()).isNotNull();
assertThat(persistentProperty.hasPropertyValueConverter()).isTrue();
assertThat(persistentProperty.getPropertyValueConverter()).isNotNull();
persistentProperty = persistentEntity.getRequiredPersistentProperty("localDateTime");
assertThat(persistentProperty.hasPropertyConverter()).isTrue();
assertThat(persistentProperty.getPropertyConverter()).isNotNull();
assertThat(persistentProperty.hasPropertyValueConverter()).isTrue();
assertThat(persistentProperty.getPropertyValueConverter()).isNotNull();
persistentProperty = persistentEntity.getRequiredPersistentProperty("legacyDate");
assertThat(persistentProperty.hasPropertyConverter()).isTrue();
assertThat(persistentProperty.getPropertyConverter()).isNotNull();
assertThat(persistentProperty.hasPropertyValueConverter()).isTrue();
assertThat(persistentProperty.getPropertyValueConverter()).isNotNull();
persistentProperty = persistentEntity.getRequiredPersistentProperty("localDateList");
assertThat(persistentProperty.hasPropertyConverter()).isTrue();
assertThat(persistentProperty.getPropertyConverter()).isNotNull();
assertThat(persistentProperty.hasPropertyValueConverter()).isTrue();
assertThat(persistentProperty.getPropertyValueConverter()).isNotNull();
}
@Test // DATAES-716
@ -114,7 +116,7 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getRequiredPersistentProperty("localDate");
LocalDate localDate = LocalDate.of(2019, 12, 27);
String converted = persistentProperty.getPropertyConverter().write(localDate).toString();
String converted = persistentProperty.getPropertyValueConverter().write(localDate).toString();
assertThat(converted).isEqualTo("27.12.2019");
}
@ -124,7 +126,7 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
SimpleElasticsearchPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DatesProperty.class);
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getRequiredPersistentProperty("localDate");
Object converted = persistentProperty.getPropertyConverter().read("27.12.2019");
Object converted = persistentProperty.getPropertyValueConverter().read("27.12.2019");
assertThat(converted).isInstanceOf(LocalDate.class);
assertThat(converted).isEqualTo(LocalDate.of(2019, 12, 27));
@ -138,7 +140,7 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
.from(ZonedDateTime.of(LocalDateTime.of(2020, 4, 19, 19, 44), ZoneId.of("UTC")));
Date legacyDate = calendar.getTime();
String converted = persistentProperty.getPropertyConverter().write(legacyDate).toString();
String converted = persistentProperty.getPropertyValueConverter().write(legacyDate).toString();
assertThat(converted).isEqualTo("20200419T194400.000Z");
}
@ -148,7 +150,7 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
SimpleElasticsearchPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(DatesProperty.class);
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getRequiredPersistentProperty("legacyDate");
Object converted = persistentProperty.getPropertyConverter().read("20200419T194400.000Z");
Object converted = persistentProperty.getPropertyValueConverter().read("20200419T194400.000Z");
assertThat(converted).isInstanceOf(Date.class);
GregorianCalendar calendar = GregorianCalendar
@ -246,6 +248,21 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
assertThat(property.getFieldName()).isEqualTo("CUStomFIEldnAME");
}
@Test // #1945
@DisplayName("should use ValueConverter annotation")
void shouldUseValueConverterAnnotation() {
SimpleElasticsearchPersistentEntity<?> persistentEntity = context
.getRequiredPersistentEntity(EntityWithCustomValueConverters.class);
assertThat(
persistentEntity.getRequiredPersistentProperty("fieldWithClassBasedConverter").getPropertyValueConverter())
.isInstanceOf(ClassBasedValueConverter.class);
assertThat(
persistentEntity.getRequiredPersistentProperty("fieldWithEnumBasedConverter").getPropertyValueConverter())
.isInstanceOf(EnumBasedValueConverter.class);
}
// region entities
static class FieldNameProperty {
@Nullable @Field(name = "by-name") String fieldProperty;
@ -324,5 +341,38 @@ public class SimpleElasticsearchPersistentPropertyUnitTests {
this.withCustomFieldName = withCustomFieldName;
}
}
private static class EntityWithCustomValueConverters {
@Id private String id;
@Nullable @ValueConverter(ClassBasedValueConverter.class) private String fieldWithClassBasedConverter;
@Nullable @ValueConverter(EnumBasedValueConverter.class) private String fieldWithEnumBasedConverter;
}
private static class ClassBasedValueConverter implements PropertyValueConverter {
@Override
public Object write(Object value) {
return value;
}
@Override
public Object read(Object value) {
return value;
}
}
private enum EnumBasedValueConverter implements PropertyValueConverter {
INSTANCE;
@Override
public Object write(Object value) {
return value;
}
@Override
public Object read(Object value) {
return value;
}
}
// endregion
}