Fluent registration API for PropertyValueConverters.
Introduce a builder API to register PropertyValueConverters using simple (Bi)Functions. Additional methods on ValueConversionContext to enable advanced use cases in converter implementation. Tweak generics of VCC to be able to expose store-specific PersistentProperty implementations via the context. See #1484 Original pull request: #2566.
This commit is contained in:
committed by
Mark Paluch
parent
caf49ad739
commit
e7292279bf
@@ -33,6 +33,7 @@ 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;
|
||||
@@ -199,7 +200,7 @@ public class CustomConversions {
|
||||
* @since ?
|
||||
*/
|
||||
@Nullable
|
||||
public <A, B, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<A, B, C> getPropertyValueConverter(
|
||||
public <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getPropertyValueConverter(
|
||||
PersistentProperty<?> property) {
|
||||
return propertyValueConversions != null ? propertyValueConversions.getValueConverter(property) : null;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,6 @@ public @interface PropertyConverter {
|
||||
*
|
||||
* @return the configured {@link PropertyValueConverter}. {@link ObjectToObjectPropertyValueConverter} by default.
|
||||
*/
|
||||
Class<? extends PropertyValueConverter<?, ?, ? extends PropertyValueConverter.ValueConversionContext>> value() default ObjectToObjectPropertyValueConverter.class;
|
||||
Class<? extends PropertyValueConverter> value() default ObjectToObjectPropertyValueConverter.class;
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.convert;
|
||||
|
||||
import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -23,7 +24,7 @@ import org.springframework.lang.Nullable;
|
||||
* 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}.
|
||||
*
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since ?
|
||||
* @currentBook The Desert Prince - Peter V. Brett
|
||||
@@ -49,6 +50,6 @@ public interface PropertyValueConversions {
|
||||
* @return the suitable {@link PropertyValueConverter} or {@literal null} if none available.
|
||||
*/
|
||||
@Nullable
|
||||
<A, B, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<A, B, C> getValueConverter(
|
||||
<A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getValueConverter(
|
||||
PersistentProperty<?> property);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.convert;
|
||||
|
||||
import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -30,7 +33,7 @@ import org.springframework.lang.Nullable;
|
||||
* @param <B> store native type
|
||||
* @since 2.7
|
||||
*/
|
||||
public interface PropertyValueConverter<A, B, C extends PropertyValueConverter.ValueConversionContext> {
|
||||
public interface PropertyValueConverter<A, B, C extends ValueConversionContext<? extends PersistentProperty<?>>> {
|
||||
|
||||
/**
|
||||
* Convert the given store specific value into it's domain value representation. Typically a {@literal read}
|
||||
@@ -56,10 +59,86 @@ public interface PropertyValueConverter<A, B, C extends PropertyValueConverter.V
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
interface ValueConversionContext {
|
||||
interface ValueConversionContext<P extends PersistentProperty<P>> {
|
||||
|
||||
PersistentProperty<?> getProperty();
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +146,8 @@ public interface PropertyValueConverter<A, B, C extends PropertyValueConverter.V
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
enum ObjectToObjectPropertyValueConverter implements PropertyValueConverter<Object, Object, ValueConversionContext> {
|
||||
@SuppressWarnings({ "rawtypes", "null" })
|
||||
enum ObjectToObjectPropertyValueConverter implements PropertyValueConverter {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@@ -81,5 +161,4 @@ public interface PropertyValueConverter<A, B, C extends PropertyValueConverter.V
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ 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;
|
||||
@@ -59,14 +60,14 @@ final class PropertyValueConverterFactories {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <A, B, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<A, B, C> getConverter(
|
||||
public <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getConverter(
|
||||
PersistentProperty<?> property) {
|
||||
return delegates.stream().map(it -> (PropertyValueConverter<A, B, C>) it.getConverter(property))
|
||||
.filter(Objects::nonNull).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S, T, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<S, T, C> getConverter(
|
||||
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
|
||||
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
|
||||
return delegates.stream().filter(it -> it.getConverter(converterType) != null).findFirst()
|
||||
.map(it -> it.getConverter(converterType)).orElse(null);
|
||||
@@ -82,7 +83,7 @@ final class PropertyValueConverterFactories {
|
||||
static class SimplePropertyConverterFactory implements PropertyValueConverterFactory {
|
||||
|
||||
@Override
|
||||
public <S, T, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<S, T, C> getConverter(
|
||||
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
|
||||
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
|
||||
|
||||
Assert.notNull(converterType, "ConverterType must not be null!");
|
||||
@@ -110,7 +111,7 @@ final class PropertyValueConverterFactories {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S, T, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<S, T, C> getConverter(
|
||||
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
|
||||
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
|
||||
|
||||
Assert.state(beanFactory != null, "BeanFactory must not be null. Did you forget to set it!");
|
||||
@@ -145,14 +146,14 @@ final class PropertyValueConverterFactories {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <A, B, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<A, B, C> getConverter(
|
||||
public <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getConverter(
|
||||
PersistentProperty<?> property) {
|
||||
return (PropertyValueConverter<A, B, C>) conversionsRegistrar.getConverter(property.getOwner().getType(),
|
||||
property.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S, T, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<S, T, C> getConverter(
|
||||
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
|
||||
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
|
||||
return null;
|
||||
}
|
||||
@@ -170,55 +171,55 @@ final class PropertyValueConverterFactories {
|
||||
private final Cache cache = new Cache();
|
||||
|
||||
public CachingPropertyValueConverterFactory(PropertyValueConverterFactory delegate) {
|
||||
|
||||
|
||||
Assert.notNull(delegate, "Delegate must not be null!");
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <S, T, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<S, T, C> getConverter(
|
||||
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
|
||||
PersistentProperty<?> property) {
|
||||
|
||||
PropertyValueConverter converter = cache.get(property);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
return cache.cache(property, delegate.getConverter(property));
|
||||
|
||||
return converter != null
|
||||
? converter
|
||||
: cache.cache(property, delegate.getConverter(property));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S, T, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<S, T, C> getConverter(
|
||||
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
|
||||
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
|
||||
|
||||
PropertyValueConverter converter = cache.get(converterType);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
return cache.cache(converterType, delegate.getConverter(converterType));
|
||||
|
||||
return converter != null
|
||||
? converter
|
||||
: cache.cache(converterType, delegate.getConverter(converterType));
|
||||
}
|
||||
|
||||
static class Cache {
|
||||
|
||||
Map<PersistentProperty<?>, PropertyValueConverter<?, ?, ? extends PropertyValueConverter.ValueConversionContext>> perPropertyCache = new HashMap<>();
|
||||
Map<Class<?>, PropertyValueConverter<?, ?, ? extends PropertyValueConverter.ValueConversionContext>> typeCache = new HashMap<>();
|
||||
Map<PersistentProperty<?>, PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> perPropertyCache = new HashMap<>();
|
||||
Map<Class<?>, PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> typeCache = new HashMap<>();
|
||||
|
||||
PropertyValueConverter<?, ?, ? extends PropertyValueConverter.ValueConversionContext> get(PersistentProperty<?> property) {
|
||||
PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>> get(PersistentProperty<?> property) {
|
||||
return perPropertyCache.get(property);
|
||||
}
|
||||
|
||||
PropertyValueConverter<?, ?, ? extends PropertyValueConverter.ValueConversionContext> get(Class<?> type) {
|
||||
PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>> get(Class<?> type) {
|
||||
return typeCache.get(type);
|
||||
}
|
||||
|
||||
<S, T, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<S, T, C> cache(PersistentProperty<?> property,
|
||||
<S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> cache(PersistentProperty<?> property,
|
||||
PropertyValueConverter<S, T, C> converter) {
|
||||
perPropertyCache.putIfAbsent(property, converter);
|
||||
cache(property.getValueConverterType(), converter);
|
||||
return converter;
|
||||
}
|
||||
|
||||
<S, T, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<S, T, C> cache(Class<?> type,
|
||||
<S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> cache(Class<?> type,
|
||||
PropertyValueConverter<S, T, C> converter) {
|
||||
typeCache.putIfAbsent(type, converter);
|
||||
return converter;
|
||||
|
||||
@@ -19,6 +19,7 @@ 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;
|
||||
@@ -49,7 +50,8 @@ public interface PropertyValueConverterFactory {
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
default <A, B, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<A, B, C> getConverter(
|
||||
@SuppressWarnings("unchecked")
|
||||
default <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getConverter(
|
||||
PersistentProperty<?> property) {
|
||||
|
||||
if (!property.hasValueConverter()) {
|
||||
@@ -59,7 +61,7 @@ public interface PropertyValueConverterFactory {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
<S, T, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<S, T, C> getConverter(
|
||||
<S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
|
||||
Class<? extends PropertyValueConverter<S, T, C>> converterType);
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ 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.mapping.PersistentProperty;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -47,7 +48,7 @@ public class SimplePropertyValueConversions implements PropertyValueConversions,
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A, B, C extends PropertyValueConverter.ValueConversionContext> PropertyValueConverter<A, B, C> getValueConverter(
|
||||
public <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getValueConverter(
|
||||
PersistentProperty<?> property) {
|
||||
|
||||
if (!initialized.get()) {
|
||||
@@ -68,7 +69,7 @@ public class SimplePropertyValueConversions implements PropertyValueConversions,
|
||||
factoryList.add(PropertyValueConverterFactory.simple());
|
||||
}
|
||||
|
||||
if (converterRegistrar != null && !converterRegistrar.isEmpty()) {
|
||||
if ((converterRegistrar != null) && !converterRegistrar.isEmpty()) {
|
||||
factoryList.add(PropertyValueConverterFactory.configuredInstance(converterRegistrar));
|
||||
}
|
||||
|
||||
|
||||
@@ -427,9 +427,12 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default Class<? extends PropertyValueConverter<?,?, ? extends ValueConversionContext>> getValueConverterType() {
|
||||
default Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> getValueConverterType() {
|
||||
|
||||
PropertyConverter annotation = findAnnotation(PropertyConverter.class);
|
||||
return annotation == null ? null : annotation.value();
|
||||
|
||||
return annotation == null ? null
|
||||
: (Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>>) annotation.value();
|
||||
}
|
||||
|
||||
default boolean hasValueConverter() {
|
||||
|
||||
@@ -287,10 +287,12 @@ public abstract class AnnotationBasedPersistentProperty<P extends PersistentProp
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext>> getValueConverterType() {
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> getValueConverterType() {
|
||||
|
||||
return doFindAnnotation(PropertyConverter.class) //
|
||||
.map(PropertyConverter::value) //
|
||||
.map(Class.class::cast) //
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user