Skip to content

#1945 Custom property converters #1953

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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_.
5 changes: 5 additions & 0 deletions src/main/asciidoc/reference/elasticsearch-object-mapping.adoc
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -15,19 +15,19 @@
*/
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;

/**
* @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;
Original file line number Diff line number Diff line change
@@ -26,14 +26,14 @@
* @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);
}

Original file line number Diff line number Diff line change
@@ -26,14 +26,13 @@
* @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;
Original file line number Diff line number Diff line change
@@ -26,13 +26,13 @@
* @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);
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
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 @@ default Document mapObject(@Nullable Object source) {
/**
* 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
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@
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 @@ protected <R> R readValue(@Nullable Object value, ElasticsearchPersistentPropert

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,27 +467,27 @@ private <T> T readValue(Object value, TypeInformation<?> type) {
}

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
source = Arrays.asList((String[]) source);
}

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 @@ private void writeProperties(ElasticsearchPersistentEntity<?> entity, Persistent
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 @@ private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, @Nulla
}

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 @@ private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity<?>

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));
}
});
}
Original file line number Diff line number Diff line change
@@ -21,9 +21,9 @@
* @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);
}

Original file line number Diff line number Diff line change
@@ -26,13 +26,13 @@
* @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);
Original file line number Diff line number Diff line change
@@ -27,14 +27,13 @@
* @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);
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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);
}
Loading