Refine API and property-converter documentation.

See #1484
Original pull request: #2566.
This commit is contained in:
Christoph Strobl
2022-02-24 14:59:17 +01:00
committed by Mark Paluch
parent e7292279bf
commit 228431534f
20 changed files with 1067 additions and 316 deletions

View File

@@ -16,7 +16,16 @@
package org.springframework.data.convert;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -33,7 +42,6 @@ import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.ConverterBuilder.ConverterAware;
import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.Predicates;
@@ -98,7 +106,7 @@ public class CustomConversions {
private final Function<ConvertiblePair, Class<?>> getRawWriteTarget = convertiblePair -> getCustomTarget(
convertiblePair.getSourceType(), null, writingPairs);
private PropertyValueConversions propertyValueConversions;
private @Nullable PropertyValueConversions propertyValueConversions;
/**
* @param converterConfiguration the {@link ConverterConfiguration} to apply.
@@ -182,7 +190,7 @@ public class CustomConversions {
* @param property must not be {@literal null}.
* @return {@literal true} if a specific {@link PropertyValueConverter} is available.
* @see PropertyValueConversions#hasValueConverter(PersistentProperty)
* @since ?
* @since 2.7
*/
public boolean hasPropertyValueConverter(PersistentProperty<?> property) {
return propertyValueConversions != null ? propertyValueConversions.hasValueConverter(property) : false;
@@ -197,11 +205,11 @@ public class CustomConversions {
* @param <C> conversion context type
* @return the suitable {@link PropertyValueConverter} or {@literal null} if none available.
* @see PropertyValueConversions#getValueConverter(PersistentProperty)
* @since ?
* @since 2.7
*/
@Nullable
public <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getPropertyValueConverter(
PersistentProperty<?> property) {
public <A, B, C extends PersistentProperty<C>, D extends ValueConversionContext<C>> PropertyValueConverter<A, B, D> getPropertyValueConverter(
C property) {
return propertyValueConversions != null ? propertyValueConversions.getValueConverter(property) : null;
}
@@ -326,8 +334,8 @@ public class CustomConversions {
registrationIntent.getSourceType(), registrationIntent.getTargetType(),
registrationIntent.isReading() ? "reading" : "writing"));
} else {
logger.debug(String.format(SKIP_CONVERTER, registrationIntent.getSourceType(), registrationIntent.getTargetType(),
registrationIntent.isReading() ? "reading" : "writing",
logger.debug(String.format(SKIP_CONVERTER, registrationIntent.getSourceType(),
registrationIntent.getTargetType(), registrationIntent.isReading() ? "reading" : "writing",
registrationIntent.isReading() ? registrationIntent.getSourceType() : registrationIntent.getTargetType()));
}
}
@@ -926,8 +934,22 @@ public class CustomConversions {
this(storeConversions, userConverters, converterRegistrationFilter, new SimplePropertyValueConversions());
}
/**
* Create a new ConverterConfiguration holding the given {@link StoreConversions} and user defined converters as
* well as a {@link Collection} of {@link ConvertiblePair} for which to skip the registration of default converters.
* <br />
* This allows store implementations to modify default converter registration based on specific needs and
* configurations. User defined converters will are never subject of filtering.
*
* @param storeConversions must not be {@literal null}.
* @param userConverters must not be {@literal null} use {@link Collections#emptyList()} instead.
* @param converterRegistrationFilter must not be {@literal null}.
* @param propertyValueConversions can be {@literal null}.
* @since 2.7
*/
public ConverterConfiguration(StoreConversions storeConversions, List<?> userConverters,
Predicate<ConvertiblePair> converterRegistrationFilter, @Nullable PropertyValueConversions propertyValueConversions) {
Predicate<ConvertiblePair> converterRegistrationFilter,
@Nullable PropertyValueConversions propertyValueConversions) {
this.storeConversions = storeConversions;
this.userConverters = new ArrayList<>(userConverters);
@@ -958,7 +980,7 @@ public class CustomConversions {
/**
* @return the configured {@link PropertyValueConversions} if set, {@literal null} otherwise.
* @since ?
* @since 2.7
*/
@Nullable
public PropertyValueConversions getPropertyValueConversions() {

View File

@@ -15,18 +15,19 @@
*/
package org.springframework.data.convert;
import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext;
import java.util.function.Consumer;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.lang.Nullable;
/**
* {@link PropertyValueConversions} provides access to {@link PropertyValueConverter converters} that may only be
* applied to a specific property. Other than {@link org.springframework.core.convert.converter.Converter converters}
* registered in {@link CustomConversions} the property based variants accept and allow returning {@literal null} values
* and provide access to a store specific {@link PropertyValueConverter.ValueConversionContext conversion context}.
* registered in {@link CustomConversions}, the property based variants accept and allow returning {@literal null}
* values and provide access to a store specific {@link ValueConversionContext conversion context}.
*
* @author Christoph Strobl
* @since ?
* @since 2.7
* @currentBook The Desert Prince - Peter V. Brett
*/
public interface PropertyValueConversions {
@@ -38,7 +39,7 @@ public interface PropertyValueConversions {
* @return {@literal true} if a specific {@link PropertyValueConverter} is available.
*/
default boolean hasValueConverter(PersistentProperty<?> property) {
return getValueConverter(property) != null;
return getValueConverter((PersistentProperty) property) != null;
}
/**
@@ -50,6 +51,20 @@ public interface PropertyValueConversions {
* @return the suitable {@link PropertyValueConverter} or {@literal null} if none available.
*/
@Nullable
<A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getValueConverter(
PersistentProperty<?> property);
<A, B, C extends PersistentProperty<C>, D extends ValueConversionContext<C>> PropertyValueConverter<A, B, D> getValueConverter(
C property);
/**
* Helper that allows to create {@link PropertyValueConversions} instance with the configured
* {@link PropertyValueConverter converters} provided via the given callback.
*/
static <P extends PersistentProperty<P>> PropertyValueConversions simple(
Consumer<PropertyValueConverterRegistrar<P>> config) {
SimplePropertyValueConversions conversions = new SimplePropertyValueConversions();
PropertyValueConverterRegistrar registrar = new PropertyValueConverterRegistrar();
config.accept(registrar);
conversions.setValueConverterRegistry(registrar.buildRegistry());
return conversions;
}
}

View File

@@ -15,10 +15,9 @@
*/
package org.springframework.data.convert;
import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext;
import java.util.function.BiFunction;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
/**
@@ -29,8 +28,9 @@ import org.springframework.lang.Nullable;
* to special annotated fields which allows a fine grained conversion of certain values within a specific context.
*
* @author Christoph Strobl
* @param <A> domain specific type
* @param <B> store native type
* @param <A> domain specific type.
* @param <B> store native type.
* @param <C> the store specific {@link ValueConversionContext conversion context}.
* @since 2.7
*/
public interface PropertyValueConverter<A, B, C extends ValueConversionContext<? extends PersistentProperty<?>>> {
@@ -39,107 +39,23 @@ public interface PropertyValueConverter<A, B, C extends ValueConversionContext<?
* Convert the given store specific value into it's domain value representation. Typically a {@literal read}
* operation.
*
* @param nativeValue can be {@literal null}.
* @param value can be {@literal null}.
* @param context never {@literal null}.
* @return the converted value. Can be {@literal null}.
*/
@Nullable
A /*read*/ nativeToDomain(@Nullable B nativeValue, C context);
A read(@Nullable B value, C context);
/**
* Convert the given domain specific value into it's native store representation. Typically a {@literal write}
* operation.
*
* @param domainValue can be {@literal null}.
* @param value can be {@literal null}.
* @param context never {@literal null}.
* @return the converted value. Can be {@literal null}.
*/
@Nullable
B /*write*/ domainToNative(@Nullable A domainValue, C context);
/**
* @author Christoph Strobl
* @author Oliver Drotbohm
*/
interface ValueConversionContext<P extends PersistentProperty<P>> {
/**
* Return the {@link PersistentProperty} to be handled.
*
* @return will never be {@literal null}.
*/
P getProperty();
/**
* Write to whatever type is considered best for the given source.
*
* @param value
* @return
*/
@Nullable
default Object write(@Nullable Object value) {
return null;
}
/**
* Write as the given type.
*
* @param value can be {@literal null}.
* @param target must not be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
default <T> T write(@Nullable Object value, Class<T> target) {
return write(value, ClassTypeInformation.from(target));
}
/**
* Write as the given type.
*
* @param value can be {@literal null}.
* @param target must not be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
default <T> T write(@Nullable Object value, TypeInformation<T> target) {
return null;
}
/**
* Reads the value into the type of the current property.
*
* @param value can be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
default Object read(@Nullable Object value) {
return read(value, getProperty().getTypeInformation());
}
/**
* Reads the value as the given type.
*
* @param value can be {@literal null}.
* @param target must not be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
default <T> T read(@Nullable Object value, Class<T> target) {
return null;
}
/**
* Reads the value as the given type.
*
* @param value can be {@literal null}.
* @param target must not be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
default <T> T read(@Nullable Object value, TypeInformation<T> target) {
return null;
}
}
B write(@Nullable A value, C context);
/**
* NoOp {@link PropertyValueConverter} implementation.
@@ -152,13 +68,42 @@ public interface PropertyValueConverter<A, B, C extends ValueConversionContext<?
INSTANCE;
@Override
public Object nativeToDomain(Object value, ValueConversionContext context) {
public Object read(Object value, ValueConversionContext context) {
return value;
}
@Override
public Object domainToNative(Object value, ValueConversionContext context) {
public Object write(Object value, ValueConversionContext context) {
return value;
}
}
/**
* A {@link PropertyValueConverter} that delegates conversion to the given {@link BiFunction}s.
*
* @author Oliver Drotbohm
*/
class FunctionPropertyValueConverter<A, B, P extends PersistentProperty<P>>
implements PropertyValueConverter<A, B, ValueConversionContext<P>> {
private final BiFunction<A, ValueConversionContext<P>, B> writer;
private final BiFunction<B, ValueConversionContext<P>, A> reader;
public FunctionPropertyValueConverter(BiFunction<A, ValueConversionContext<P>, B> writer,
BiFunction<B, ValueConversionContext<P>, A> reader) {
this.writer = writer;
this.reader = reader;
}
@Override
public B write(A value, ValueConversionContext<P> context) {
return writer.apply(value, context);
}
@Override
public A read(B value, ValueConversionContext<P> context) {
return reader.apply(value, context);
}
}
}

View File

@@ -16,17 +16,18 @@
package org.springframework.data.convert;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -34,28 +35,31 @@ import org.springframework.util.Assert;
/**
* {@link PropertyValueConverterFactories} provides a collection of predefined {@link PropertyValueConverterFactory}
* implementations. Depending on the applications need {@link PropertyValueConverterFactory factories} can be
* {@link CompositePropertyValueConverterFactory chained} and the created {@link PropertyValueConverter converter}
* {@link ChainedPropertyValueConverterFactory chained} and the created {@link PropertyValueConverter converter}
* {@link CachingPropertyValueConverterFactory cached}.
*
* @author Christoph Strobl
* @since ?
* @since 2.7
*/
final class PropertyValueConverterFactories {
/**
* {@link PropertyValueConverterFactory} implementation that returns the first no null {@link PropertyValueConverter}
* by asking given {@link PropertyValueConverterFactory factories} one by one.
*
* @author Christoph Strobl
* @since ?
* @since 2.7
*/
static class CompositePropertyValueConverterFactory implements PropertyValueConverterFactory {
static class ChainedPropertyValueConverterFactory implements PropertyValueConverterFactory {
private List<PropertyValueConverterFactory> delegates;
CompositePropertyValueConverterFactory(PropertyValueConverterFactory... delegates) {
ChainedPropertyValueConverterFactory(PropertyValueConverterFactory... delegates) {
this(Arrays.asList(delegates));
}
CompositePropertyValueConverterFactory(List<PropertyValueConverterFactory> delegates) {
this.delegates = delegates;
ChainedPropertyValueConverterFactory(List<PropertyValueConverterFactory> delegates) {
this.delegates = Collections.unmodifiableList(delegates);
}
@Nullable
@@ -72,13 +76,18 @@ final class PropertyValueConverterFactories {
return delegates.stream().filter(it -> it.getConverter(converterType) != null).findFirst()
.map(it -> it.getConverter(converterType)).orElse(null);
}
public List<PropertyValueConverterFactory> converterFactories() {
return delegates;
}
}
/**
* Trivial implementation of {@link PropertyValueConverter}.
* Trivial implementation of {@link PropertyValueConverterFactory} capable of instantiating a
* {@link PropertyValueConverter} via default constructor or in case of {@link Enum} returning the first value.
*
* @author Christoph Strobl
* @since ?
* @since 2.7
*/
static class SimplePropertyConverterFactory implements PropertyValueConverterFactory {
@@ -100,13 +109,13 @@ final class PropertyValueConverterFactories {
* {@link PropertyValueConverter}. This allows the {@link PropertyValueConverter} to make use of DI.
*
* @author Christoph Strobl
* @since ?
* @since 2.7
*/
static class BeanFactoryAwarePropertyValueConverterFactory implements PropertyValueConverterFactory {
private final BeanFactory beanFactory;
public BeanFactoryAwarePropertyValueConverterFactory(BeanFactory beanFactory) {
BeanFactoryAwarePropertyValueConverterFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@@ -131,24 +140,27 @@ final class PropertyValueConverterFactories {
}
/**
* {@link PropertyValueConverterFactory} implementation that serves {@link PropertyValueConverter} from a given
* {@link ValueConverterRegistry registry}.
*
* @author Christoph Strobl
* @since ?
* @since 2.7
*/
static class ConfiguredInstanceServingValueConverterFactory implements PropertyValueConverterFactory {
private final PropertyValueConverterRegistrar conversionsRegistrar;
private final ValueConverterRegistry<?> converterRegistry;
public ConfiguredInstanceServingValueConverterFactory(PropertyValueConverterRegistrar conversionsRegistrar) {
ConfiguredInstanceServingValueConverterFactory(ValueConverterRegistry<?> converterRegistry) {
Assert.notNull(conversionsRegistrar, "ConversionsRegistrar must not be null!");
this.conversionsRegistrar = conversionsRegistrar;
Assert.notNull(converterRegistry, "ConversionsRegistrar must not be null!");
this.converterRegistry = converterRegistry;
}
@Nullable
@Override
public <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getConverter(
PersistentProperty<?> property) {
return (PropertyValueConverter<A, B, C>) conversionsRegistrar.getConverter(property.getOwner().getType(),
return (PropertyValueConverter<A, B, C>) converterRegistry.getConverter(property.getOwner().getType(),
property.getName());
}
@@ -160,17 +172,18 @@ final class PropertyValueConverterFactories {
}
/**
* TODO: This would be easier if we'd get rid of {@link PropertyValueConverterFactory#getConverter(Class)}.
*
* {@link PropertyValueConverterFactory} implementation that caches converters provided by an underlying
* {@link PropertyValueConverterFactory factory}.
*
* @author Christoph Strobl
* @since ?
* @since 2.7
*/
static class CachingPropertyValueConverterFactory implements PropertyValueConverterFactory {
private final PropertyValueConverterFactory delegate;
private final Cache cache = new Cache();
public CachingPropertyValueConverterFactory(PropertyValueConverterFactory delegate) {
CachingPropertyValueConverterFactory(PropertyValueConverterFactory delegate) {
Assert.notNull(delegate, "Delegate must not be null!");
this.delegate = delegate;
@@ -181,10 +194,9 @@ final class PropertyValueConverterFactories {
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
PersistentProperty<?> property) {
PropertyValueConverter converter = cache.get(property);
Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> converter = cache.get(property);
return converter != null
? converter
return converter != null ? (PropertyValueConverter) converter.orElse(null)
: cache.cache(property, delegate.getConverter(property));
}
@@ -192,36 +204,35 @@ final class PropertyValueConverterFactories {
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
PropertyValueConverter converter = cache.get(converterType);
Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> converter = cache.get(converterType);
return converter != null
? converter
return converter != null ? (PropertyValueConverter) converter.orElse(null)
: cache.cache(converterType, delegate.getConverter(converterType));
}
static class Cache {
Map<PersistentProperty<?>, PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> perPropertyCache = new HashMap<>();
Map<Class<?>, PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> typeCache = new HashMap<>();
Map<PersistentProperty<?>, Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>>> perPropertyCache = new HashMap<>();
Map<Class<?>, Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>>> typeCache = new HashMap<>();
PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>> get(PersistentProperty<?> property) {
Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> get(PersistentProperty<?> property) {
return perPropertyCache.get(property);
}
PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>> get(Class<?> type) {
Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> get(Class<?> type) {
return typeCache.get(type);
}
<S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> cache(PersistentProperty<?> property,
PropertyValueConverter<S, T, C> converter) {
perPropertyCache.putIfAbsent(property, converter);
@Nullable PropertyValueConverter<S, T, C> converter) {
perPropertyCache.putIfAbsent(property, Optional.ofNullable(converter));
cache(property.getValueConverterType(), converter);
return converter;
}
<S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> cache(Class<?> type,
PropertyValueConverter<S, T, C> converter) {
typeCache.putIfAbsent(type, converter);
@Nullable PropertyValueConverter<S, T, C> converter) {
typeCache.putIfAbsent(type, Optional.ofNullable(converter));
return converter;
}
}

View File

@@ -19,10 +19,9 @@ import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext;
import org.springframework.data.convert.PropertyValueConverterFactories.BeanFactoryAwarePropertyValueConverterFactory;
import org.springframework.data.convert.PropertyValueConverterFactories.CachingPropertyValueConverterFactory;
import org.springframework.data.convert.PropertyValueConverterFactories.CompositePropertyValueConverterFactory;
import org.springframework.data.convert.PropertyValueConverterFactories.ChainedPropertyValueConverterFactory;
import org.springframework.data.convert.PropertyValueConverterFactories.ConfiguredInstanceServingValueConverterFactory;
import org.springframework.data.convert.PropertyValueConverterFactories.SimplePropertyConverterFactory;
import org.springframework.data.mapping.PersistentProperty;
@@ -37,7 +36,7 @@ import org.springframework.util.Assert;
* {@link #caching(PropertyValueConverterFactory) cached}.
*
* @author Christoph Strobl
* @since ?
* @since 2.7
*/
public interface PropertyValueConverterFactory {
@@ -45,8 +44,9 @@ public interface PropertyValueConverterFactory {
* Get the {@link PropertyValueConverter} applicable for the given {@link PersistentProperty}.
*
* @param property must not be {@literal null}.
* @param <A> domain specific type
* @param <B> store native type
* @param <A> domain specific type.
* @param <B> store native type.
* @param <C> value conversion context to use.
* @return can be {@literal null}.
*/
@Nullable
@@ -57,12 +57,22 @@ public interface PropertyValueConverterFactory {
if (!property.hasValueConverter()) {
return null;
}
return getConverter((Class<PropertyValueConverter<A, B, C>>) property.getValueConverterType());
}
/**
* Get the converter by its type.
*
* @param converterType must not be {@literal null}.
* @param <A> domain specific type.
* @param <B> store native type.
* @param <C> value conversion context to use.
* @return
*/
@Nullable
<S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
Class<? extends PropertyValueConverter<S, T, C>> converterType);
<A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getConverter(
Class<? extends PropertyValueConverter<A, B, C>> converterType);
/**
* Obtain a simple {@link PropertyValueConverterFactory} capable of instantiating {@link PropertyValueConverter}
@@ -93,7 +103,7 @@ public interface PropertyValueConverterFactory {
* @param registrar must not be {@literal null}.
* @return new instance of {@link PropertyValueConverterFactory}.
*/
static PropertyValueConverterFactory configuredInstance(PropertyValueConverterRegistrar registrar) {
static PropertyValueConverterFactory configuredInstance(ValueConverterRegistry<?> registrar) {
return new ConfiguredInstanceServingValueConverterFactory(registrar);
}
@@ -123,15 +133,15 @@ public interface PropertyValueConverterFactory {
return factoryList.iterator().next();
}
return new CompositePropertyValueConverterFactory(factoryList);
return new ChainedPropertyValueConverterFactory(factoryList);
}
/**
* Obtain a {@link PropertyValueConverterFactory} that will cache {@link PropertyValueConverter} instances per
* {@link PersistentProperty}.
*
* @param factory
* @return
* @param factory must not be {@literal null}.
* @return new instance of {@link PropertyValueConverterFactory}.
*/
static PropertyValueConverterFactory caching(PropertyValueConverterFactory factory) {
return new CachingPropertyValueConverterFactory(factory);

View File

@@ -15,85 +15,177 @@
*/
package org.springframework.data.convert;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.data.convert.PropertyValueConverter.FunctionPropertyValueConverter;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.util.MethodInvocationRecorder;
import org.springframework.util.Assert;
/**
* Configuration class to register a {@link PropertyValueConverter} with a {@link SimplePropertyValueConverterRegistry}
* that can be used with {@link PropertyValueConversions}.
* <p>
* It is possible to register type safe converters via {@link #registerConverter(Class, Function)}
*
* <pre class="code">
* registrar.registerConverter(Person.class, Person::getName) //
* .writing(StringConverter::encrypt) //
* .reading(StringConverter::decrypt);
* </pre>
*
* @author Christoph Strobl
* @since ?
* @author Oliver Drotbohm
* @since 2.7
*/
public class PropertyValueConverterRegistrar {
public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
private final Map<Key, PropertyValueConverter<?, ?, ? extends PropertyValueConverter.ValueConversionContext>> converterRegistrationMap = new LinkedHashMap<>();
private final SimplePropertyValueConverterRegistry<P> registry = new SimplePropertyValueConverterRegistry<>();
boolean hasConverterFor(Class<?> type, String path) {
return converterRegistrationMap.containsKey(new Key(type, path));
/**
* Starts a converter registration by pointing to a property of a domain type.
*
* @param <T> the domain type
* @param <S> the property type
* @param type the domain type to obtain the property from
* @param property a function to describe the property to be referenced. Usually a method handle to a getter.
* @return will never be {@literal null}.
*/
public <T, S> WritingConverterRegistrationBuilder<T, S, P> registerConverter(Class<T> type, Function<T, S> property) {
String propertyName = MethodInvocationRecorder.forProxyOf(type).record(property).getPropertyPath()
.orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name!"));
return new WritingConverterRegistrationBuilder<T, S, P>(type, propertyName, this);
}
@Nullable
public PropertyValueConverter<?, ?, ? extends PropertyValueConverter.ValueConversionContext> getConverter(Class<?> type, String path) {
return converterRegistrationMap.get(new Key(type, path));
/**
* Starts a converter registration by pointing to a property of a domain type.
*
* @param <T> the domain type
* @param <S> the property type
* @param type the domain type to obtain the property from
* @param propertyName a function to describe the property to be referenced. Usually a method handle to a getter.
* @return will never be {@literal null}.
*/
public <T, S> WritingConverterRegistrationBuilder<T, S, P> registerConverter(Class<T> type, String propertyName,
Class<S> propertyType) {
return new WritingConverterRegistrationBuilder<T, S, P>(type, propertyName, this);
}
public int size() {
return converterRegistrationMap.size();
}
/**
* Register the given converter for the types property identified via its name.
*
* @param type the domain type to obtain the property from
* @param path the property name.
* @param converter the converter to apply.
* @return this.
*/
public PropertyValueConverterRegistrar registerConverter(Class<?> type, String path,
PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>> converter) {
public boolean isEmpty() {
return converterRegistrationMap.isEmpty();
}
public PropertyValueConverterRegistrar register(Class<?> type, String path,
PropertyValueConverter<?, ?, ? extends PropertyValueConverter.ValueConversionContext> converter) {
converterRegistrationMap.put(new Key(type, path), converter);
registry.registerConverter(type, path,
(PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>>) converter);
return this;
}
public Collection<PropertyValueConverter<?, ?, ? extends PropertyValueConverter.ValueConversionContext>> converters() {
return converterRegistrationMap.values();
/**
* Obtain the {@link SimplePropertyValueConverterRegistry}.
*
* @return new instance of {@link SimplePropertyValueConverterRegistry}.
*/
public ValueConverterRegistry<P> buildRegistry() {
return new SimplePropertyValueConverterRegistry<>(registry);
}
public PropertyValueConverterRegistrar registerIfAbsent(Class<?> type, String path,
PropertyValueConverter<?, ?, ? extends PropertyValueConverter.ValueConversionContext> converter) {
converterRegistrationMap.putIfAbsent(new Key(type, path), converter);
return this;
/**
* Register collected {@link PropertyValueConverter converters} within the given {@link ValueConverterRegistry
* registry}.
*
* @return new instance of {@link SimplePropertyValueConverterRegistry}.
*/
public void registerConvertersIn(ValueConverterRegistry<P> target) {
Assert.notNull(target, "Target registry must not be null!");
registry.getConverterRegistrationMap().entrySet().forEach(entry -> {
target.registerConverter(entry.getKey().type, entry.getKey().path, entry.getValue());
});
}
static class Key {
/**
* Helper to build up a fluent registration API starting on
*
* @author Oliver Drotbohm
*/
static class WritingConverterRegistrationBuilder<T, S, P extends PersistentProperty<P>> {
Class<?> type;
String path;
private final Consumer<PropertyValueConverter> registration;
private final PropertyValueConverterRegistrar config;
public Key(Class<?> type, String path) {
this.type = type;
this.path = path;
public WritingConverterRegistrationBuilder(Class<T> type, String property, PropertyValueConverterRegistrar config) {
this.config = config;
this.registration = (converter) -> config.registerConverter(type, property, converter);
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Key key = (Key) o;
if (!ObjectUtils.nullSafeEquals(type, key.type)) {
return false;
}
return ObjectUtils.nullSafeEquals(path, key.path);
<R> ReadingConverterRegistrationBuilder<T, S, S, P> writingAsIs() {
return writing((source, context) -> source);
}
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(type);
result = 31 * result + ObjectUtils.nullSafeHashCode(path);
return result;
<R> ReadingConverterRegistrationBuilder<T, S, R, P> writing(Function<S, R> writer) {
return writing((source, context) -> writer.apply(source));
}
/**
* Describes how to convert the domain property value into the database native property.
*
* @param <R> the type to be written to the database
* @param writer the property conversion to extract a value written to the database
* @return will never be {@literal null}.
*/
<R> ReadingConverterRegistrationBuilder<T, S, R, P> writing(BiFunction<S, ValueConversionContext<P>, R> writer) {
return new ReadingConverterRegistrationBuilder<>(this, writer);
}
}
/**
* A helper to build a fluent API to register how to read a database value into a domain object property.
*
* @author Oliver Drotbohm
*/
static class ReadingConverterRegistrationBuilder<T, S, R, P extends PersistentProperty<P>> {
private WritingConverterRegistrationBuilder<T, S, P> origin;
private BiFunction<S, ValueConversionContext<P>, R> writer;
public ReadingConverterRegistrationBuilder(WritingConverterRegistrationBuilder<T, S, P> origin,
BiFunction<S, ValueConversionContext<P>, R> writer) {
this.origin = origin;
this.writer = writer;
}
PropertyValueConverterRegistrar<P> readingAsIs() {
return reading((source, context) -> (S) source);
}
PropertyValueConverterRegistrar<P> reading(Function<R, S> reader) {
return reading((source, context) -> reader.apply(source));
}
/**
* Describes how to read a database value into a domain object's property value.
*
* @param reader must not be {@literal null}.
* @return
*/
PropertyValueConverterRegistrar<P> reading(BiFunction<R, ValueConversionContext<P>, S> reader) {
origin.registration.accept(new FunctionPropertyValueConverter(writer, reader));
return origin.config;
}
}
}

View File

@@ -20,36 +20,79 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext;
import org.springframework.data.convert.PropertyValueConverterFactories.ChainedPropertyValueConverterFactory;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.lang.Nullable;
/**
* {@link PropertyValueConversions} implementation that allows to pick a {@link PropertyValueConverterFactory} serving
* {@link PropertyValueConverter converters}. Activating {@link #setConverterCacheEnabled(boolean) cahing} allows to
* reuse converters. Providing a {@link SimplePropertyValueConverterRegistry} adds path configured converter instances.
* <p>
* Should be {@link #afterPropertiesSet() initialized}. If not, {@link #init()} will be called of fist attempt of
* {@link PropertyValueConverter converter} retrieval.
*
* @author Christoph Strobl
* @since ?
* @since 2.7
*/
public class SimplePropertyValueConversions implements PropertyValueConversions, InitializingBean {
private @Nullable PropertyValueConverterFactory converterFactory;
private @Nullable PropertyValueConverterRegistrar converterRegistrar;
private @Nullable ValueConverterRegistry<?> valueConverterRegistry;
private boolean converterCacheEnabled = true;
private AtomicBoolean initialized = new AtomicBoolean(false);
/**
* Set the {@link PropertyValueConverterFactory factory} responsible for creating the actual
* {@link PropertyValueConverter converter}.
*
* @param converterFactory must not be {@literal null}.
*/
public void setConverterFactory(PropertyValueConverterFactory converterFactory) {
this.converterFactory = converterFactory;
}
public void setConverterRegistrar(PropertyValueConverterRegistrar converterRegistrar) {
this.converterRegistrar = converterRegistrar;
@Nullable
public PropertyValueConverterFactory getConverterFactory() {
return converterFactory;
}
/**
* Set the {@link ValueConverterRegistry converter registry} for path configured converters. This is short for adding
* a
* {@link org.springframework.data.convert.PropertyValueConverterFactories.ConfiguredInstanceServingValueConverterFactory}
* at the end of a {@link ChainedPropertyValueConverterFactory}.
*
* @param valueConverterRegistry must not be {@literal null}.
*/
public void setValueConverterRegistry(ValueConverterRegistry<?> valueConverterRegistry) {
this.valueConverterRegistry = valueConverterRegistry;
}
/**
* Get the {@link ValueConverterRegistry} used for path configured converters.
*
* @return can be {@literal null}.
*/
@Nullable
public ValueConverterRegistry<?> getValueConverterRegistry() {
return valueConverterRegistry;
}
/**
* Dis-/Enable caching. Enabled by default.
*
* @param converterCacheEnabled set to {@literal true} to enable caching of {@link PropertyValueConverter converter}
* instances.
*/
public void setConverterCacheEnabled(boolean converterCacheEnabled) {
this.converterCacheEnabled = converterCacheEnabled;
}
@Nullable
@Override
public <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getValueConverter(
PersistentProperty<?> property) {
public <A, B, C extends PersistentProperty<C>, D extends ValueConversionContext<C>> PropertyValueConverter<A, B, D> getValueConverter(
C property) {
if (!initialized.get()) {
init();
@@ -58,9 +101,13 @@ public class SimplePropertyValueConversions implements PropertyValueConversions,
return this.converterFactory.getConverter(property);
}
/**
* May be called just once to initialize the underlying factory with its values.
*/
public void init() {
if (initialized.compareAndSet(false, true)) {
List<PropertyValueConverterFactory> factoryList = new ArrayList<>(3);
if (converterFactory != null) {
@@ -69,8 +116,8 @@ public class SimplePropertyValueConversions implements PropertyValueConversions,
factoryList.add(PropertyValueConverterFactory.simple());
}
if ((converterRegistrar != null) && !converterRegistrar.isEmpty()) {
factoryList.add(PropertyValueConverterFactory.configuredInstance(converterRegistrar));
if ((valueConverterRegistry != null) && !valueConverterRegistry.isEmpty()) {
factoryList.add(PropertyValueConverterFactory.configuredInstance(valueConverterRegistry));
}
PropertyValueConverterFactory targetFactory = factoryList.size() > 1
@@ -84,7 +131,6 @@ public class SimplePropertyValueConversions implements PropertyValueConversions,
@Override
public void afterPropertiesSet() throws Exception {
init();
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2022 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.convert;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.util.ObjectUtils;
/**
* A registry of property specific {@link PropertyValueConverter value convertes} that may be used to convert only
* specific properties/values of an object.
*
* @author Christoph Strobl
* @since 2.7
*/
public class SimplePropertyValueConverterRegistry<P extends PersistentProperty<P>> implements ValueConverterRegistry<P> {
private final Map<Key, PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>>> converterRegistrationMap = new LinkedHashMap<>();
public SimplePropertyValueConverterRegistry() {}
SimplePropertyValueConverterRegistry(SimplePropertyValueConverterRegistry<P> source) {
this.converterRegistrationMap.putAll(source.converterRegistrationMap);
}
@Override
public void registerConverter(Class<?> type, String path,
PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>> converter) {
converterRegistrationMap.put(new Key(type, path), converter);
}
/**
* Register the {@link PropertyValueConverter} for the property of the given type if none had been registered before.
*
* @param type the target type.
* @param path the property name.
* @param converter the converter to register.
*/
public void registerConverterIfAbsent(Class<?> type, String path,
PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>> converter) {
converterRegistrationMap.putIfAbsent(new Key(type, path), converter);
}
@Override
public boolean containsConverterFor(Class<?> type, String path) {
return converterRegistrationMap.containsKey(new Key(type, path));
}
@Override
public <S, T> PropertyValueConverter<S, T, ? extends ValueConversionContext<P>> getConverter(Class<?> type,
String path) {
return (PropertyValueConverter<S, T, ? extends ValueConversionContext<P>>) converterRegistrationMap
.get(new Key(type, path));
}
/**
* @return the number of registered converters.
*/
public int size() {
return converterRegistrationMap.size();
}
public boolean isEmpty() {
return converterRegistrationMap.isEmpty();
}
/**
* Obtain the underlying (mutable) map of converters.
*
* @return never {@literal null}.
*/
Map<Key, PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>>> getConverterRegistrationMap() {
return converterRegistrationMap;
}
static class Key {
Class<?> type;
String path;
public Key(Class<?> type, String path) {
this.type = type;
this.path = path;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Key key = (Key) o;
if (!ObjectUtils.nullSafeEquals(type, key.type)) {
return false;
}
return ObjectUtils.nullSafeEquals(path, key.path);
}
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(type);
result = 31 * result + ObjectUtils.nullSafeHashCode(path);
return result;
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2022 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.convert;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
/**
* The {@link ValueConversionContext} provides access to the store specific {@link PersistentProperty} and allows to
* call the store default conversion via the {@literal read}/{@literal write} methods.
* <p>
* Store implementations should provide their own flavor of {@link ValueConversionContext} enhancing the existing API,
* implementing delegates for {@link #read(Object, TypeInformation)}, {@link #write(Object, TypeInformation)}.
*
* @author Christoph Strobl
* @author Oliver Drotbohm
*/
public interface ValueConversionContext<P extends PersistentProperty<P>> {
/**
* Return the {@link PersistentProperty} to be handled.
*
* @return will never be {@literal null}.
*/
P getProperty();
/**
* Write to whatever type is considered best for the given source.
*
* @param value
* @return
*/
@Nullable
default Object write(@Nullable Object value) {
return write(value, getProperty().getTypeInformation());
}
/**
* Write as the given type.
*
* @param value can be {@literal null}.
* @param target must not be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
default <T> T write(@Nullable Object value, Class<T> target) {
return write(value, ClassTypeInformation.from(target));
}
/**
* Write as the given type.
*
* @param value can be {@literal null}.
* @param target must not be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
default <T> T write(@Nullable Object value, TypeInformation<T> target) {
throw new IllegalStateException(String.format(
"%s does not provide write function that allows value conversion to target type (%s).", getClass(), target));
}
/**
* Reads the value into the type of the current property.
*
* @param value can be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
default Object read(@Nullable Object value) {
return read(value, getProperty().getTypeInformation());
}
/**
* Reads the value as the given type.
*
* @param value can be {@literal null}.
* @param target must not be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
default <T> T read(@Nullable Object value, Class<T> target) {
return read(value, ClassTypeInformation.from(target));
}
/**
* Reads the value as the given type.
*
* @param value can be {@literal null}.
* @param target must not be {@literal null}.
* @return can be {@literal null}.
*/
@Nullable
default <T> T read(@Nullable Object value, TypeInformation<T> target) {
throw new IllegalStateException(String.format(
"%s does not provide write function that allows value conversion to target type (%s).", getClass(), target));
}
}

View File

@@ -25,17 +25,23 @@ import java.lang.annotation.Target;
import org.springframework.data.convert.PropertyValueConverter.ObjectToObjectPropertyValueConverter;
/**
* Annotation to define usage of a {@link PropertyValueConverter} to read/write the property.
* Annotation to define usage of a {@link PropertyValueConverter} to read/write the property. <br />
* May be used as meta annotation utilizing {@link org.springframework.core.annotation.AliasFor}.
* <p>
* The target {@link PropertyValueConverter} is typically provided via a {@link PropertyValueConverterFactory converter
* factory}.
* <p>
* Consult the store specific documentation for details and support notes.
*
*
* @author Christoph Strobl
* @since ?
* @since 2.7
* @see PropertyValueConverter
* @see PropertyValueConverterFactory
*/
@Target(FIELD)
@Target({ FIELD, ANNOTATION_TYPE })
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PropertyConverter {
public @interface ValueConverter {
/**
* The {@link PropertyValueConverter} type handling the value conversion of the annotated property.

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2022 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.convert;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.lang.Nullable;
/**
* A registry of property specific {@link PropertyValueConverter value convertes} that may be used to convert only
* specific properties/values of an object.
*
* @author Christoph Strobl
* @since 2.7
*/
public interface ValueConverterRegistry<P extends PersistentProperty<P>> {
/**
* Register the {@link PropertyValueConverter} for the property of the given type.
*
* @param type the target type. Must not be {@literal null}.
* @param path the property name. Must not be {@literal null}.
* @param converter the converter to register. Must not be {@literal null}.
*/
void registerConverter(Class<?> type, String path,
PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>> converter);
/**
* Obtain the converter registered for the given type, path combination or {@literal null} if none defined.
*
* @param type the target type. Must not be {@literal null}.
* @param path the property name. Must not be {@literal null}.
* @param <S>
* @param <T>
* @return {@literal null} if no converter present for the given type/path combination.
*/
@Nullable
<S, T> PropertyValueConverter<S, T, ? extends ValueConversionContext<P>> getConverter(Class<?> type, String path);
/**
* Check if a converter is registered for the given type, path combination.
*
* @param type the target type. Must not be {@literal null}.
* @param path the property name. Must not be {@literal null}.
* @return {@literal false} if no converter present for the given type/path combination.
*/
default boolean containsConverterFor(Class<?> type, String path) {
return getConverter(type, path) != null;
}
/**
* Check if there a converters registered.
*/
boolean isEmpty();
/**
* Obtain a simple {@link ValueConverterRegistry}.
*
* @param <T>
* @return new instance of {@link ValueConverterRegistry}.
*/
static <T extends PersistentProperty<T>> ValueConverterRegistry<T> simple() {
return new SimplePropertyValueConverterRegistry<>();
}
}

View File

@@ -23,9 +23,9 @@ import java.util.Map;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.convert.PropertyConverter;
import org.springframework.data.convert.PropertyValueConverter;
import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext;
import org.springframework.data.convert.ValueConversionContext;
import org.springframework.data.convert.ValueConverter;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -36,6 +36,7 @@ import org.springframework.util.Assert;
* @author Oliver Gierke
* @author Mark Paluch
* @author Jens Schauder
* @author Christoph Strobl
*/
public interface PersistentProperty<P extends PersistentProperty<P>> {
@@ -68,9 +69,9 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
TypeInformation<?> getTypeInformation();
/**
* Returns the detected {@link TypeInformation TypeInformations} if the property references a {@link PersistentEntity}.
* Will return an {@literal empty} {@link Iterable} in case it refers to a simple type. Will return the {@link Collection}'s
* component types or the {@link Map}'s value type transparently.
* Returns the detected {@link TypeInformation TypeInformations} if the property references a
* {@link PersistentEntity}. Will return an {@literal empty} {@link Iterable} in case it refers to a simple type. Will
* return the {@link Collection}'s component types or the {@link Map}'s value type transparently.
*
* @return never {@literal null}.
* @since 2.6
@@ -426,16 +427,32 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
return getOwner().getPropertyAccessor(owner);
}
/**
* Obtain the the {@link PropertyValueConverter converter type} to be used for read-/writing the properties value. By
* default looks for the {@link ValueConverter} annotation and extracts its {@link ValueConverter#value() value}
* attribute.
* <p>
* Store implementations may override the default and provide a more specific type.
*
* @return {@literal null} if none defined. Check {@link #hasValueConverter()} to check if the annotation is present
* at all.
* @since 2.7
*/
@Nullable
default Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> getValueConverterType() {
default Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<? extends PersistentProperty<?>>>> getValueConverterType() {
PropertyConverter annotation = findAnnotation(PropertyConverter.class);
ValueConverter annotation = findAnnotation(ValueConverter.class);
return annotation == null ? null
: (Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>>) annotation.value();
: (Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<? extends PersistentProperty<?>>>>) annotation
.value();
}
/**
* @return by default return {@literal true} if {@link ValueConverter} annotation is present.
* @since 2.7
*/
default boolean hasValueConverter() {
return isAnnotationPresent(PropertyConverter.class);
return isAnnotationPresent(ValueConverter.class);
}
}

View File

@@ -33,9 +33,9 @@ import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Reference;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.Version;
import org.springframework.data.convert.PropertyConverter;
import org.springframework.data.convert.ValueConverter;
import org.springframework.data.convert.PropertyValueConverter;
import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext;
import org.springframework.data.convert.ValueConversionContext;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
@@ -288,10 +288,10 @@ public abstract class AnnotationBasedPersistentProperty<P extends PersistentProp
@Nullable
@Override
@SuppressWarnings("unchecked")
public Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> getValueConverterType() {
public Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<? extends PersistentProperty<?>>>> getValueConverterType() {
return doFindAnnotation(PropertyConverter.class) //
.map(PropertyConverter::value) //
return doFindAnnotation(ValueConverter.class) //
.map(ValueConverter::value) //
.map(Class.class::cast) //
.orElse(null);
}