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 === Completion classes
The classes from the package `org.springframework.data.elasticsearch.core.completion` have been moved to `org.springframework.data.elasticsearch.core.suggest`. 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. ** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom analyzers and normalizer.
* `@GeoPoint`: Marks a field as _geo_point_ datatype. * `@GeoPoint`: Marks a field as _geo_point_ datatype.
Can be omitted if the field is an instance of the `GeoPoint` class. 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. 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" "lastname" : "Connor"
} }
---- ----
<1> By default the domain types class name is used for the type hint. <1> By default the domain types class name is used for the type hint.
==== ====
@ -211,6 +214,7 @@ public class Person {
"id" : ... "id" : ...
} }
---- ----
<1> The configured alias is used when writing the entity. <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 } "localidad" : { "lat" : 34.118347, "lon" : -118.3026284 }
} }
---- ----
<1> Add `Converter` implementations. <1> Add `Converter` implementations.
<2> Set up the `Converter` used for writing `DomainType` to Elasticsearch. <2> Set up the `Converter` used for writing `DomainType` to Elasticsearch.
<3> Set up the `Converter` used for reading `DomainType` from search result. <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; 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.data.mapping.PersistentProperty;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -23,11 +23,11 @@ import org.springframework.util.Assert;
* @author Sascha Woo * @author Sascha Woo
* @since 4.3 * @since 4.3
*/ */
public abstract class AbstractPersistentPropertyConverter implements ElasticsearchPersistentPropertyConverter { public abstract class AbstractPropertyValueConverter implements PropertyValueConverter {
private final PersistentProperty<?> property; private final PersistentProperty<?> property;
public AbstractPersistentPropertyConverter(PersistentProperty<?> property) { public AbstractPropertyValueConverter(PersistentProperty<?> property) {
Assert.notNull(property, "property must not be null."); Assert.notNull(property, "property must not be null.");
this.property = property; this.property = property;

View File

@ -26,14 +26,14 @@ import org.springframework.util.Assert;
* @author Sascha Woo * @author Sascha Woo
* @since 4.3 * @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 LT_FIELD = "lt";
protected static final String LTE_FIELD = "lte"; protected static final String LTE_FIELD = "lte";
protected static final String GT_FIELD = "gt"; protected static final String GT_FIELD = "gt";
protected static final String GTE_FIELD = "gte"; protected static final String GTE_FIELD = "gte";
public AbstractRangePersistentPropertyConverter(PersistentProperty<?> property) { public AbstractRangePropertyValueConverter(PersistentProperty<?> property) {
super(property); super(property);
} }

View File

@ -26,14 +26,13 @@ import org.springframework.data.mapping.PersistentProperty;
* @author Sascha Woo * @author Sascha Woo
* @since 4.3 * @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; private final List<ElasticsearchDateConverter> dateConverters;
public DatePersistentPropertyConverter(PersistentProperty<?> property, public DatePropertyValueConverter(PersistentProperty<?> property, List<ElasticsearchDateConverter> dateConverters) {
List<ElasticsearchDateConverter> dateConverters) {
super(property); super(property);
this.dateConverters = dateConverters; this.dateConverters = dateConverters;

View File

@ -26,13 +26,13 @@ import org.springframework.data.mapping.PersistentProperty;
* @author Sascha Woo * @author Sascha Woo
* @since 4.3 * @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; private final List<ElasticsearchDateConverter> dateConverters;
public DateRangePersistentPropertyConverter(PersistentProperty<?> property, public DateRangePropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) { List<ElasticsearchDateConverter> dateConverters) {
super(property); 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.document.Document;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; 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.elasticsearch.core.query.Query;
import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory; 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 * 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 * values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a
* {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If * {@link PropertyValueConverter}. If domainClass is null it's a noop.
* domainClass is null it's a noop.
* *
* @param query the query that is internally updated, must not be {@literal null} * @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 * @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.document.SearchDocument;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; 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.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
@ -425,8 +425,9 @@ public class MappingElasticsearchConverter
Class<?> rawType = type.getType(); Class<?> rawType = type.getType();
if (property.hasPropertyConverter()) { if (property.hasPropertyValueConverter()) {
value = propertyConverterRead(property, value); // noinspection unchecked
return (R) propertyConverterRead(property, value);
} else if (TemporalAccessor.class.isAssignableFrom(property.getType()) } else if (TemporalAccessor.class.isAssignableFrom(property.getType())
&& !conversions.hasCustomReadTarget(value.getClass(), rawType)) { && !conversions.hasCustomReadTarget(value.getClass(), rawType)) {
@ -466,8 +467,7 @@ public class MappingElasticsearchConverter
} }
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) { private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
ElasticsearchPersistentPropertyConverter propertyConverter = Objects PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
.requireNonNull(property.getPropertyConverter());
if (source instanceof String[]) { if (source instanceof String[]) {
// convert to a List // convert to a List
@ -475,18 +475,19 @@ public class MappingElasticsearchConverter
} }
if (source instanceof List) { 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()); .collect(Collectors.toList());
} else if (source instanceof Set) { } 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 { } else {
source = convertOnRead(propertyConverter, source); source = convertOnRead(propertyValueConverter, source);
} }
return source; return source;
} }
private Object convertOnRead(ElasticsearchPersistentPropertyConverter propertyConverter, Object source) { private Object convertOnRead(PropertyValueConverter propertyValueConverter, Object source) {
return propertyConverter.read(source); return propertyValueConverter.read(source);
} }
/** /**
@ -897,7 +898,7 @@ public class MappingElasticsearchConverter
continue; continue;
} }
if (property.hasPropertyConverter()) { if (property.hasPropertyValueConverter()) {
value = propertyConverterWrite(property, value); value = propertyConverterWrite(property, value);
sink.set(property, value); sink.set(property, value);
} else if (TemporalAccessor.class.isAssignableFrom(property.getActualType()) } else if (TemporalAccessor.class.isAssignableFrom(property.getActualType())
@ -1070,15 +1071,14 @@ public class MappingElasticsearchConverter
} }
private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) { private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) {
ElasticsearchPersistentPropertyConverter propertyConverter = Objects PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
.requireNonNull(property.getPropertyConverter());
if (value instanceof List) { 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) { } 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 { } else {
value = propertyConverter.write(value); value = propertyValueConverter.write(value);
} }
return value; return value;
} }
@ -1252,18 +1252,18 @@ public class MappingElasticsearchConverter
if (persistentProperty != null) { if (persistentProperty != null) {
if (persistentProperty.hasPropertyConverter()) { if (persistentProperty.hasPropertyValueConverter()) {
ElasticsearchPersistentPropertyConverter propertyConverter = Objects PropertyValueConverter propertyValueConverter = Objects
.requireNonNull(persistentProperty.getPropertyConverter()); .requireNonNull(persistentProperty.getPropertyValueConverter());
criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> { criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> {
Object value = criteriaEntry.getValue(); Object value = criteriaEntry.getValue();
if (value.getClass().isArray()) { if (value.getClass().isArray()) {
Object[] objects = (Object[]) value; Object[] objects = (Object[]) value;
for (int i = 0; i < objects.length; i++) { for (int i = 0; i < objects.length; i++) {
objects[i] = propertyConverter.write(objects[i]); objects[i] = propertyValueConverter.write(objects[i]);
} }
} else { } 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 * @author Sascha Woo
* @since 4.3 * @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); super(property);
} }

View File

@ -26,13 +26,13 @@ import org.springframework.data.mapping.PersistentProperty;
* @author Sascha Woo * @author Sascha Woo
* @since 4.3 * @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; private final List<ElasticsearchDateConverter> dateConverters;
public TemporalPersistentPropertyConverter(PersistentProperty<?> property, public TemporalPropertyValueConverter(PersistentProperty<?> property,
List<ElasticsearchDateConverter> dateConverters) { List<ElasticsearchDateConverter> dateConverters) {
super(property); super(property);

View File

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

View File

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

View File

@ -16,26 +16,26 @@
package org.springframework.data.elasticsearch.core.mapping; 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 Peter-Josef Meisch
* @author Sascha Woo * @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} * @param value the value to convert, must not be {@literal null}
* @return The elasticsearch property value. * @return The elasticsearch property value, must not be {@literal null}
*/ */
Object write(Object value); 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} * @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); Object read(Object value);
} }

View File

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

View File

@ -26,14 +26,11 @@ import org.junit.jupiter.api.Test;
* @author Sascha Woo * @author Sascha Woo
* @since 4.3 * @since 4.3
*/ */
public class RangeTests { public class RangeUnitTests {
@Test @Test
public void shouldContainsLocalDate() { 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))) assertThat(Range.open(LocalDate.of(2021, 1, 1), LocalDate.of(2021, 2, 1)).contains(LocalDate.of(2021, 1, 10)))
.isTrue(); .isTrue();
} }
@ -41,21 +38,17 @@ public class RangeTests {
@Test @Test
public void shouldEqualToSameRange() { public void shouldEqualToSameRange() {
// given
Range<LocalDate> range1 = Range.open(LocalDate.of(2021, 1, 1), LocalDate.of(2021, 2, 1)); 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)); Range<LocalDate> range2 = Range.open(LocalDate.of(2021, 1, 1), LocalDate.of(2021, 2, 1));
// when
// then
assertThat(range1).isEqualTo(range2); assertThat(range1).isEqualTo(range2);
} }
@Test @Test
public void shouldHaveClosedBoundaries() { public void shouldHaveClosedBoundaries() {
// given
Range<Integer> range = Range.closed(1, 3); Range<Integer> range = Range.closed(1, 3);
// when
// then
assertThat(range.contains(1)).isTrue(); assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue(); assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isTrue(); assertThat(range.contains(3)).isTrue();
@ -64,10 +57,8 @@ public class RangeTests {
@Test @Test
public void shouldHaveJustOneValue() { public void shouldHaveJustOneValue() {
// given
Range<Integer> range = Range.just(2); Range<Integer> range = Range.just(2);
// when
// then
assertThat(range.contains(1)).isFalse(); assertThat(range.contains(1)).isFalse();
assertThat(range.contains(2)).isTrue(); assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isFalse(); assertThat(range.contains(3)).isFalse();
@ -76,10 +67,8 @@ public class RangeTests {
@Test @Test
public void shouldHaveLeftOpenBoundary() { public void shouldHaveLeftOpenBoundary() {
// given
Range<Integer> range = Range.leftOpen(1, 3); Range<Integer> range = Range.leftOpen(1, 3);
// when
// then
assertThat(range.contains(1)).isFalse(); assertThat(range.contains(1)).isFalse();
assertThat(range.contains(2)).isTrue(); assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isTrue(); assertThat(range.contains(3)).isTrue();
@ -88,10 +77,8 @@ public class RangeTests {
@Test @Test
public void shouldHaveLeftUnboundedAndRightExclusive() { public void shouldHaveLeftUnboundedAndRightExclusive() {
// given
Range<Integer> range = Range.leftUnbounded(Range.Bound.exclusive(3)); Range<Integer> range = Range.leftUnbounded(Range.Bound.exclusive(3));
// when
// then
assertThat(range.contains(0)).isTrue(); assertThat(range.contains(0)).isTrue();
assertThat(range.contains(1)).isTrue(); assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue(); assertThat(range.contains(2)).isTrue();
@ -101,10 +88,8 @@ public class RangeTests {
@Test @Test
public void shouldHaveLeftUnboundedAndRightInclusive() { public void shouldHaveLeftUnboundedAndRightInclusive() {
// given
Range<Integer> range = Range.leftUnbounded(Range.Bound.inclusive(3)); Range<Integer> range = Range.leftUnbounded(Range.Bound.inclusive(3));
// when
// then
assertThat(range.contains(0)).isTrue(); assertThat(range.contains(0)).isTrue();
assertThat(range.contains(1)).isTrue(); assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue(); assertThat(range.contains(2)).isTrue();
@ -114,10 +99,8 @@ public class RangeTests {
@Test @Test
public void shouldHaveOpenBoundaries() { public void shouldHaveOpenBoundaries() {
// given
Range<Integer> range = Range.open(1, 3); Range<Integer> range = Range.open(1, 3);
// when
// then
assertThat(range.contains(1)).isFalse(); assertThat(range.contains(1)).isFalse();
assertThat(range.contains(2)).isTrue(); assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isFalse(); assertThat(range.contains(3)).isFalse();
@ -126,10 +109,8 @@ public class RangeTests {
@Test @Test
public void shouldHaveRightOpenBoundary() { public void shouldHaveRightOpenBoundary() {
// given
Range<Integer> range = Range.rightOpen(1, 3); Range<Integer> range = Range.rightOpen(1, 3);
// when
// then
assertThat(range.contains(1)).isTrue(); assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue(); assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isFalse(); assertThat(range.contains(3)).isFalse();
@ -138,10 +119,8 @@ public class RangeTests {
@Test @Test
public void shouldHaveRightUnboundedAndLeftExclusive() { public void shouldHaveRightUnboundedAndLeftExclusive() {
// given
Range<Integer> range = Range.rightUnbounded(Range.Bound.exclusive(1)); Range<Integer> range = Range.rightUnbounded(Range.Bound.exclusive(1));
// when
// then
assertThat(range.contains(1)).isFalse(); assertThat(range.contains(1)).isFalse();
assertThat(range.contains(2)).isTrue(); assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isTrue(); assertThat(range.contains(3)).isTrue();
@ -151,10 +130,8 @@ public class RangeTests {
@Test @Test
public void shouldHaveRightUnboundedAndLeftInclusive() { public void shouldHaveRightUnboundedAndLeftInclusive() {
// given
Range<Integer> range = Range.rightUnbounded(Range.Bound.inclusive(1)); Range<Integer> range = Range.rightUnbounded(Range.Bound.inclusive(1));
// when
// then
assertThat(range.contains(1)).isTrue(); assertThat(range.contains(1)).isTrue();
assertThat(range.contains(2)).isTrue(); assertThat(range.contains(2)).isTrue();
assertThat(range.contains(3)).isTrue(); assertThat(range.contains(3)).isTrue();
@ -164,12 +141,7 @@ public class RangeTests {
@Test @Test
public void shouldThrowExceptionIfNotComparable() { public void shouldThrowExceptionIfNotComparable() {
// given assertThatThrownBy(() -> Range.just(Collections.singletonList("test"))).isInstanceOf(IllegalArgumentException.class)
// when
Throwable thrown = catchThrowable(() -> Range.just(Collections.singletonList("test")));
// then
assertThat(thrown).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("value must implements Comparable!"); .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.Field;
import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField; 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.Range;
import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.geo.GeoJsonEntity; 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.GeoJsonPoint;
import org.springframework.data.elasticsearch.core.geo.GeoJsonPolygon; import org.springframework.data.elasticsearch.core.geo.GeoJsonPolygon;
import org.springframework.data.elasticsearch.core.geo.GeoPoint; 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.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.geo.Box; 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.geo.Polygon;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/** /**
* Unit tests for {@link MappingElasticsearchConverter}. * Unit tests for {@link MappingElasticsearchConverter}.
@ -1446,6 +1449,53 @@ public class MappingElasticsearchConverterUnitTests {
assertEquals(expected, document.toJson(), true); 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) { private Map<String, Object> writeToMap(Object source) {
Document sink = Document.create(); Document sink = Document.create();
@ -2309,5 +2359,82 @@ public class MappingElasticsearchConverterUnitTests {
this.cars = cars; 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 // endregion
} }

View File

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