Refine API and property-converter documentation.
See #1484 Original pull request: #2566.
This commit is contained in:
committed by
Mark Paluch
parent
e7292279bf
commit
228431534f
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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<>();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user