From 5ce9669c7a4b6fcece5306667a83493af0e59cb5 Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 29 Apr 2022 14:34:55 -0700 Subject: [PATCH] Additional refinements. Resolves #2577 Closes #2592 --- .../PropertyValueConversionService.java | 46 ++++--- .../convert/PropertyValueConversions.java | 36 ++++-- .../data/convert/PropertyValueConverter.java | 33 +++-- .../PropertyValueConverterFactories.java | 25 +++- .../PropertyValueConverterFactory.java | 6 +- .../PropertyValueConverterRegistrar.java | 70 +++++++---- .../SimplePropertyValueConversions.java | 117 ++++++++++++------ .../data/convert/ValueConversionContext.java | 65 ++++++---- .../data/convert/ValueConverterRegistry.java | 20 +-- 9 files changed, 272 insertions(+), 146 deletions(-) diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConversionService.java b/src/main/java/org/springframework/data/convert/PropertyValueConversionService.java index 1c2854557..2b7a430a1 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConversionService.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConversionService.java @@ -16,36 +16,50 @@ package org.springframework.data.convert; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * Conversion service based on {@link CustomConversions} to convert domain and store values using + * Conversion service based on {@link CustomConversions} used to convert domain and store values using * {@link PropertyValueConverter property-specific converters}. * * @author Mark Paluch + * @see PropertyValueConverter * @since 2.7 */ public class PropertyValueConversionService { private final PropertyValueConversions conversions; - public PropertyValueConversionService(CustomConversions conversions) { + /** + * Constructs a new instance of the {@link PropertyValueConversionService} initialized with the given, + * required {@link CustomConversions} for resolving the {@link PropertyValueConversions} used to + * convert {@link PersistentProperty} values during data access operations. + * + * @param conversions {@link CustomConversions} used to handle domain and store type conversions; + * must not be {@literal null}. + * @throws IllegalArgumentException if {@link CustomConversions} is {@literal null}. + * @see CustomConversions + */ + public PropertyValueConversionService(@NonNull CustomConversions conversions) { Assert.notNull(conversions, "CustomConversions must not be null"); - PropertyValueConversions pvc = conversions.getPropertyValueConversions(); - this.conversions = pvc == null ? NoOpPropertyValueConversions.INSTANCE : pvc; + PropertyValueConversions valueConversions = conversions.getPropertyValueConversions(); + + this.conversions = valueConversions != null ? valueConversions : NoOpPropertyValueConversions.INSTANCE; } /** - * Return {@literal true} there is a converter registered for {@link PersistentProperty}. + * Return {@literal true} if a {@link PropertyValueConverter} is registered for the {@link PersistentProperty}. *

* If this method returns {@literal true}, it means {@link #read(Object, PersistentProperty, ValueConversionContext)} - * and {@link #write(Object, PersistentProperty, ValueConversionContext)} are capable to invoke conversion. + * and {@link #write(Object, PersistentProperty, ValueConversionContext)} are capable of handling conversions. * - * @param property the underlying property. - * @return {@literal true} there is a converter registered for {@link PersistentProperty}. + * @param property {@link PersistentProperty property} to evaluate for registration. + * @return {@literal true} if a {@link PropertyValueConverter} is registered for the {@link PersistentProperty}. + * @see PersistentProperty */ public boolean hasConverter(PersistentProperty property) { return conversions.hasValueConverter(property); @@ -68,11 +82,8 @@ public class PropertyValueConversionService { PropertyValueConverter> converter = conversions .getValueConverter(property); - if (value == null) { - return converter.readNull(context); - } - - return converter.read(value, context); + return value != null ? converter.read(value, context) + : converter.readNull(context); } /** @@ -92,11 +103,8 @@ public class PropertyValueConversionService { PropertyValueConverter> converter = conversions .getValueConverter(property); - if (value == null) { - return converter.writeNull(context); - } - - return converter.write(value, context); + return value != null ? converter.write(value, context) + : converter.writeNull(context); } enum NoOpPropertyValueConversions implements PropertyValueConversions { @@ -111,7 +119,7 @@ public class PropertyValueConversionService { @Override public , VCC extends ValueConversionContext

> PropertyValueConverter getValueConverter( P property) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("No PropertyValueConversions was configured"); } } } diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConversions.java b/src/main/java/org/springframework/data/convert/PropertyValueConversions.java index e3034da49..41244b775 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConversions.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConversions.java @@ -21,9 +21,10 @@ import org.springframework.data.mapping.PersistentProperty; /** * {@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 ValueConversionContext conversion context}. + * applied to a specific {@link PersistentProperty property}. Other than + * {@link org.springframework.core.convert.converter.Converter converters} registered in {@link CustomConversions}, + * the {@link PersistentProperty 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 2.7 @@ -32,30 +33,41 @@ import org.springframework.data.mapping.PersistentProperty; public interface PropertyValueConversions { /** - * Check if a {@link PropertyValueConverter} is present for the given {@literal property}. + * Check if a {@link PropertyValueConverter converter} is registered for + * the given {@link PersistentProperty property}. * - * @param property must not be {@literal null}. - * @return {@literal true} if a specific {@link PropertyValueConverter} is available. + * @param property {@link PersistentProperty} to evaluate; must not be {@literal null}. + * @return {@literal true} if a specific {@link PropertyValueConverter converter} is registered for + * the given {@link PersistentProperty}. + * @see PersistentProperty */ boolean hasValueConverter(PersistentProperty property); /** - * Get the {@link PropertyValueConverter} for the given {@literal property}. + * Get the {@link PropertyValueConverter converter} registered for the given {@link PersistentProperty property}. * - * @param property must not be {@literal null}. + * @param property {@link PersistentProperty} used to look up the registered {@link PropertyValueConverter}; + * must not be {@literal null}. * @param domain-specific type * @param store-native type * @param

conversion context type - * @return the suitable {@link PropertyValueConverter}. - * @throws IllegalArgumentException if there is no converter available for {@code property}. + * @return the {@link PropertyValueConverter} registered for the given {@link PersistentProperty}; + * never {@literal null}. + * @throws IllegalArgumentException if no {@link PropertyValueConverter} was registered for + * the given {@link PersistentProperty}. * @see #hasValueConverter(PersistentProperty) + * @see PropertyValueConverter + * @see PersistentProperty */ , VCC extends ValueConversionContext

> PropertyValueConverter getValueConverter( P property); /** - * Helper that allows to create {@link PropertyValueConversions} instance with the configured - * {@link PropertyValueConverter converters} provided via the given callback. + * Helper method used to create a {@link PropertyValueConversions} instance with the configured + * {@link PropertyValueConverter converters} provided by the {@link Consumer callback}. + * + * @see PropertyValueConverterRegistrar + * @see PropertyValueConversions */ @SuppressWarnings({ "rawtypes", "unchecked" }) static

> PropertyValueConversions simple( diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConverter.java b/src/main/java/org/springframework/data/convert/PropertyValueConverter.java index b58532d7b..42d1224ea 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConverter.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConverter.java @@ -18,6 +18,7 @@ package org.springframework.data.convert; import java.util.function.BiFunction; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; /** @@ -36,6 +37,8 @@ import org.springframework.lang.Nullable; * @param domain-specific type. * @param store-native type. * @param the store specific {@link ValueConversionContext conversion context}. + * @see org.springframework.data.convert.ValueConversionContext + * @see org.springframework.data.mapping.PersistentProperty * @since 2.7 */ public interface PropertyValueConverter>> { @@ -44,18 +47,20 @@ public interface PropertyValueConverter, SV> writer; private final BiFunction, DV> reader; - public FunctionPropertyValueConverter(BiFunction, SV> writer, - BiFunction, DV> reader) { + public FunctionPropertyValueConverter(@NonNull BiFunction, SV> writer, + @NonNull BiFunction, DV> reader) { this.writer = writer; this.reader = reader; @@ -129,23 +136,23 @@ public interface PropertyValueConverter context) { + public SV write(@Nullable DV value, @NonNull ValueConversionContext

context) { return writer.apply(value, context); } @Override - public SV writeNull(ValueConversionContext

context) { + public SV writeNull(@NonNull ValueConversionContext

context) { return writer.apply(null, context); } @Nullable @Override - public DV read(@Nullable SV value, ValueConversionContext

context) { + public DV read(@Nullable SV value, @NonNull ValueConversionContext

context) { return reader.apply(value, context); } @Override - public DV readNull(ValueConversionContext

context) { + public DV readNull(@NonNull ValueConversionContext

context) { return reader.apply(null, context); } } diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java b/src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java index 91f5686c9..9f94871c6 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java @@ -50,7 +50,7 @@ final class PropertyValueConverterFactories { */ static class ChainedPropertyValueConverterFactory implements PropertyValueConverterFactory { - private List delegates; + private final List delegates; ChainedPropertyValueConverterFactory(List delegates) { this.delegates = Collections.unmodifiableList(delegates); @@ -58,8 +58,10 @@ final class PropertyValueConverterFactories { @Nullable @Override + @SuppressWarnings("unchecked") public > PropertyValueConverter getConverter( PersistentProperty property) { + return delegates.stream().map(it -> (PropertyValueConverter) it.getConverter(property)) .filter(Objects::nonNull).findFirst().orElse(null); } @@ -67,6 +69,7 @@ final class PropertyValueConverterFactories { @Override public > PropertyValueConverter getConverter( Class> converterType) { + return delegates.stream().filter(it -> it.getConverter(converterType) != null).findFirst() .map(it -> it.getConverter(converterType)).orElse(null); } @@ -82,6 +85,7 @@ final class PropertyValueConverterFactories { static class SimplePropertyConverterFactory implements PropertyValueConverterFactory { @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public > PropertyValueConverter getConverter( Class> converterType) { @@ -90,6 +94,7 @@ final class PropertyValueConverterFactories { if (converterType.isEnum()) { return (PropertyValueConverter) EnumSet.allOf((Class) converterType).iterator().next(); } + return BeanUtils.instantiateClass(converterType); } } @@ -110,10 +115,11 @@ final class PropertyValueConverterFactories { } @Override + @SuppressWarnings("unchecked") public > PropertyValueConverter getConverter( Class> converterType) { - Assert.notNull(converterType, "ConverterType must not be null!"); + Assert.notNull(converterType, "ConverterType must not be null"); PropertyValueConverter converter = beanFactory.getBeanProvider(converterType).getIfAvailable(); @@ -139,14 +145,17 @@ final class PropertyValueConverterFactories { ConfiguredInstanceServingValueConverterFactory(ValueConverterRegistry converterRegistry) { - Assert.notNull(converterRegistry, "ConversionsRegistrar must not be null!"); + Assert.notNull(converterRegistry, "ConversionsRegistrar must not be null"); + this.converterRegistry = converterRegistry; } @Nullable @Override + @SuppressWarnings("unchecked") public > PropertyValueConverter getConverter( PersistentProperty property) { + return (PropertyValueConverter) converterRegistry.getConverter(property.getOwner().getType(), property.getName()); } @@ -154,6 +163,7 @@ final class PropertyValueConverterFactories { @Override public > PropertyValueConverter getConverter( Class> converterType) { + return null; } } @@ -172,12 +182,14 @@ final class PropertyValueConverterFactories { CachingPropertyValueConverterFactory(PropertyValueConverterFactory delegate) { - Assert.notNull(delegate, "Delegate must not be null!"); + Assert.notNull(delegate, "Delegate must not be null"); + this.delegate = delegate; } @Nullable @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public > PropertyValueConverter getConverter( PersistentProperty property) { @@ -188,6 +200,7 @@ final class PropertyValueConverterFactories { } @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public > PropertyValueConverter getConverter( Class> converterType) { @@ -218,15 +231,19 @@ final class PropertyValueConverterFactories { AnnotatedPropertyValueConverterAccessor accessor = new AnnotatedPropertyValueConverterAccessor(property); Class>>> valueConverterType = accessor .getValueConverterType(); + if (valueConverterType != null) { cache(valueConverterType, converter); } + return converter; } > PropertyValueConverter cache(Class type, @Nullable PropertyValueConverter converter) { + typeCache.putIfAbsent(type, Optional.ofNullable(converter)); + return converter; } } diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java b/src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java index 93f0cd3f8..8481fdcfe 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java @@ -29,9 +29,9 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * A factory that provides {@link PropertyValueConverter value converters}. + * A factory providing {@link PropertyValueConverter value converters}. *

- * Depending on the applications need {@link PropertyValueConverterFactory factories} can be {@link #chained(List) + * Depending on the applications' need {@link PropertyValueConverterFactory factories} can be {@link #chained(List) * chained} and the resulting {@link PropertyValueConverter converter} may be * {@link #caching(PropertyValueConverterFactory) cached}. * @@ -129,7 +129,7 @@ public interface PropertyValueConverterFactory { */ static PropertyValueConverterFactory chained(List factoryList) { - Assert.noNullElements(factoryList, "FactoryList must not contain null elements."); + Assert.noNullElements(factoryList, "FactoryList must not contain null elements"); if (factoryList.size() == 1) { return factoryList.iterator().next(); diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java b/src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java index 7e4246329..753d89beb 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java @@ -22,11 +22,12 @@ import java.util.function.Function; import org.springframework.data.convert.PropertyValueConverter.FunctionPropertyValueConverter; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.util.MethodInvocationRecorder; +import org.springframework.lang.NonNull; import org.springframework.util.Assert; /** - * Configuration class to register a {@link PropertyValueConverter} with a {@link SimplePropertyValueConverterRegistry} - * that can be used with {@link PropertyValueConversions}. + * Configuration class used to register a {@link PropertyValueConverter} with + * a {@link SimplePropertyValueConverterRegistry} that can be used in {@link PropertyValueConversions}. *

* It is possible to register type safe converters via {@link #registerConverter(Class, Function)} * @@ -50,13 +51,14 @@ public class PropertyValueConverterRegistrar

> { * @param the domain type * @param 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. + * @param property a {@link Function} to describe the property to be referenced. + * Usually a method handle to a getter. * @return will never be {@literal null}. */ public WritingConverterRegistrationBuilder registerConverter(Class type, Function property) { String propertyName = MethodInvocationRecorder.forProxyOf(type).record(property).getPropertyPath() - .orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name!")); + .orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name")); return new WritingConverterRegistrationBuilder<>(type, propertyName, this); } @@ -67,20 +69,23 @@ public class PropertyValueConverterRegistrar

> { * @param the domain type * @param 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. + * @param propertyName a {@link Function} to describe the property to be referenced. + * Usually a method handle to a getter. * @return will never be {@literal null}. */ public WritingConverterRegistrationBuilder registerConverter(Class type, String propertyName, @SuppressWarnings("unused") Class propertyType) { + return new WritingConverterRegistrationBuilder<>(type, propertyName, this); } /** - * Register the given converter for the types property identified via its name. + * Register the given {@link PropertyValueConverter converter} for the given type and property identified by + * its name. * * @param type the domain type to obtain the property from * @param path the property name. - * @param converter the converter to apply. + * @param converter the {@link PropertyValueConverter converter} to apply. * @return this. */ public PropertyValueConverterRegistrar

registerConverter(Class type, String path, @@ -88,33 +93,39 @@ public class PropertyValueConverterRegistrar

> { registry.registerConverter(type, path, (PropertyValueConverter>) converter); + return this; } + /** + * Register collected {@link PropertyValueConverter converters} within the given + * {@link ValueConverterRegistry registry}. + * + * @param target {@link ValueConverterRegistry} from which to register {@link PropertyValueConverter converters}; + * must not be {@literal null}. + * @throws IllegalArgumentException if the {@link ValueConverterRegistry} is {@literal null}. + * @see ValueConverterRegistry + */ + public void registerConvertersIn(@NonNull ValueConverterRegistry

target) { + + Assert.notNull(target, "Target registry must not be null!"); + + registry.getConverterRegistrationMap().forEach((key, value) -> + target.registerConverter(key.type, key.path, value)); + } + /** * Obtain the {@link SimplePropertyValueConverterRegistry}. * * @return new instance of {@link SimplePropertyValueConverterRegistry}. */ + @NonNull public ValueConverterRegistry

buildRegistry() { return new SimplePropertyValueConverterRegistry<>(registry); } /** - * Register collected {@link PropertyValueConverter converters} within the given {@link ValueConverterRegistry - * registry}. - */ - public void registerConvertersIn(ValueConverterRegistry

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()); - }); - } - - /** - * Helper to build up a fluent registration API starting on + * Helper class used to build up a fluent registration API starting with writing. * * @author Oliver Drotbohm */ @@ -123,10 +134,11 @@ public class PropertyValueConverterRegistrar

> { private final Consumer>> registration; private final PropertyValueConverterRegistrar

config; - WritingConverterRegistrationBuilder(Class type, String property, PropertyValueConverterRegistrar

config) { + WritingConverterRegistrationBuilder(Class type, String property, + @NonNull PropertyValueConverterRegistrar

config) { this.config = config; - this.registration = (converter) -> config.registerConverter(type, property, converter); + this.registration = converter -> config.registerConverter(type, property, converter); } public ReadingConverterRegistrationBuilder writingAsIs() { @@ -146,12 +158,13 @@ public class PropertyValueConverterRegistrar

> { */ public ReadingConverterRegistrationBuilder writing( BiFunction, 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. + * Helper class used to build a fluent API to register how to read a database value into a domain object property. * * @author Oliver Drotbohm */ @@ -160,12 +173,14 @@ public class PropertyValueConverterRegistrar

> { private final WritingConverterRegistrationBuilder origin; private final BiFunction, R> writer; - ReadingConverterRegistrationBuilder(WritingConverterRegistrationBuilder origin, - BiFunction, R> writer) { + ReadingConverterRegistrationBuilder(@NonNull WritingConverterRegistrationBuilder origin, + @NonNull BiFunction, R> writer) { + this.origin = origin; this.writer = writer; } + @SuppressWarnings("unchecked") public PropertyValueConverterRegistrar

readingAsIs() { return reading((source, context) -> (S) source); } @@ -178,8 +193,9 @@ public class PropertyValueConverterRegistrar

> { * Describes how to read a database value into a domain object's property value. * * @param reader must not be {@literal null}. - * @return + * @return the confiured {@link PropertyValueConverterRegistrar}. */ + @SuppressWarnings({ "rawtypes", "unchecked" }) public PropertyValueConverterRegistrar

reading(BiFunction, S> reader) { origin.registration.accept(new FunctionPropertyValueConverter(writer, reader)); diff --git a/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java b/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java index bb58214be..70f61ac6f 100644 --- a/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java +++ b/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java @@ -17,73 +17,93 @@ package org.springframework.data.convert; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.convert.PropertyValueConverterFactories.ChainedPropertyValueConverterFactory; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** - * {@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. + * {@link PropertyValueConversions} implementation allowing a {@link PropertyValueConverterFactory} creating + * {@link PropertyValueConverter converters} to be chosen. Activating {@link #setConverterCacheEnabled(boolean) cahing} + * allows converters to be reused. *

- * Should be {@link #afterPropertiesSet() initialized}. If not, {@link #init()} will be called of fist attempt of - * {@link PropertyValueConverter converter} retrieval. + * Providing a {@link SimplePropertyValueConverterRegistry} adds path configured converter instances. + *

+ * This class should be {@link #afterPropertiesSet() initialized}. If not, {@link #init()} will be called on the first + * attempt of {@link PropertyValueConverter converter} retrieval. * * @author Christoph Strobl * @author Mark Paluch + * @see PropertyValueConversions + * @see InitializingBean * @since 2.7 */ public class SimplePropertyValueConversions implements PropertyValueConversions, InitializingBean { - private @Nullable PropertyValueConverterFactory converterFactory; - private @Nullable ValueConverterRegistry valueConverterRegistry; + private static final String NO_CONVERTER_FACTORY_ERROR_MESSAGE = + "PropertyValueConverterFactory is not set. Make sure to either set the converter factory or call afterPropertiesSet() to initialize the object."; + private boolean converterCacheEnabled = true; + private @Nullable PropertyValueConverterFactory converterFactory; + + private @Nullable ValueConverterRegistry valueConverterRegistry; + /** - * Set the {@link PropertyValueConverterFactory factory} responsible for creating the actual - * {@link PropertyValueConverter converter}. + * Set the {@link PropertyValueConverterFactory} responsible for creating the actual {@link PropertyValueConverter}. * - * @param converterFactory must not be {@literal null}. + * @param converterFactory {@link PropertyValueConverterFactory} used to create the actual + * {@link PropertyValueConverter}. + * @see PropertyValueConverterFactory */ - public void setConverterFactory(PropertyValueConverterFactory converterFactory) { + public void setConverterFactory(@Nullable PropertyValueConverterFactory converterFactory) { this.converterFactory = converterFactory; } + /** + * Returns the configured {@link PropertyValueConverterFactory} responsible for creating the actual + * {@link PropertyValueConverter}. + * + * @return the configured {@link PropertyValueConverterFactory}; can be {@literal null}. + * @see PropertyValueConverterFactory + */ @Nullable public PropertyValueConverterFactory getConverterFactory() { return converterFactory; } - private PropertyValueConverterFactory obtainConverterFactory() { + private @NonNull PropertyValueConverterFactory requireConverterFactory() { PropertyValueConverterFactory factory = getConverterFactory(); - if (factory == null) { - throw new IllegalStateException( - "PropertyValueConverterFactory is not set. Make sure to either set the converter factory or call afterPropertiesSet() to initialize the object."); - } + Assert.state(factory != null, NO_CONVERTER_FACTORY_ERROR_MESSAGE); return factory; } /** - * Set the {@link ValueConverterRegistry converter registry} for path configured converters. This is short for adding - * a + * Set the {@link ValueConverterRegistry converter registry} used 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}. + * @param valueConverterRegistry registry of {@link PropertyValueConverter PropertyValueConverters}. + * @see ValueConverterRegistry */ - public void setValueConverterRegistry(ValueConverterRegistry valueConverterRegistry) { + public void setValueConverterRegistry(@Nullable ValueConverterRegistry valueConverterRegistry) { this.valueConverterRegistry = valueConverterRegistry; } /** * Get the {@link ValueConverterRegistry} used for path configured converters. * - * @return can be {@literal null}. + * @return the configured {@link ValueConverterRegistry}; can be {@literal null}. + * @see ValueConverterRegistry */ @Nullable public ValueConverterRegistry getValueConverterRegistry() { @@ -91,29 +111,37 @@ public class SimplePropertyValueConversions implements PropertyValueConversions, } /** - * Configure whether to use converter cache. Enabled by default. + * Configure whether to use converter the cache. Enabled by default. * - * @param converterCacheEnabled set to {@literal true} to enable caching of {@link PropertyValueConverter converter} - * instances. + * @param converterCacheEnabled set to {@literal true} to enable caching of + * {@link PropertyValueConverter converter} instances. */ public void setConverterCacheEnabled(boolean converterCacheEnabled) { this.converterCacheEnabled = converterCacheEnabled; } + /** + * Determines whether a {@link PropertyValueConverter} has been registered for + * the given {@link PersistentProperty property}. + * + * @param property {@link PersistentProperty} to evaluate. + * @return {@literal true} if a {@link PropertyValueConverter} has been registered for + * the given {@link PersistentProperty property}. + * @see PersistentProperty + */ @Override public boolean hasValueConverter(PersistentProperty property) { - return obtainConverterFactory().getConverter(property) != null; + return requireConverterFactory().getConverter(property) != null; } + @NonNull @Override public , D extends ValueConversionContext

> PropertyValueConverter getValueConverter( P property) { - PropertyValueConverter converter = obtainConverterFactory().getConverter(property); + PropertyValueConverter converter = requireConverterFactory().getConverter(property); - if (converter == null) { - throw new IllegalArgumentException(String.format("No PropertyValueConverter registered for %s", property)); - } + Assert.notNull(converter, String.format("No PropertyValueConverter registered for %s", property)); return converter; } @@ -125,15 +153,10 @@ public class SimplePropertyValueConversions implements PropertyValueConversions, List factoryList = new ArrayList<>(3); - if (converterFactory != null) { - factoryList.add(converterFactory); - } else { - factoryList.add(PropertyValueConverterFactory.simple()); - } + factoryList.add(resolveConverterFactory()); - if ((valueConverterRegistry != null) && !valueConverterRegistry.isEmpty()) { - factoryList.add(PropertyValueConverterFactory.configuredInstance(valueConverterRegistry)); - } + resolveConverterRegistryAsConverterFactory() + .ifPresent(factoryList::add); PropertyValueConverterFactory targetFactory = factoryList.size() > 1 ? PropertyValueConverterFactory.chained(factoryList) @@ -143,6 +166,26 @@ public class SimplePropertyValueConversions implements PropertyValueConversions, : targetFactory; } + private @NonNull PropertyValueConverterFactory resolveConverterFactory() { + + PropertyValueConverterFactory converterFactory = getConverterFactory(); + + return converterFactory != null ? converterFactory + : PropertyValueConverterFactory.simple(); + } + + private Optional resolveConverterRegistryAsConverterFactory() { + + return Optional.ofNullable(getValueConverterRegistry()) + .filter(it -> !it.isEmpty()) + .map(PropertyValueConverterFactory::configuredInstance); + } + + /** + * Initializes this {@link SimplePropertyValueConversions} instance. + * + * @see #init() + */ @Override public void afterPropertiesSet() { init(); diff --git a/src/main/java/org/springframework/data/convert/ValueConversionContext.java b/src/main/java/org/springframework/data/convert/ValueConversionContext.java index 049485af0..ece29d117 100644 --- a/src/main/java/org/springframework/data/convert/ValueConversionContext.java +++ b/src/main/java/org/springframework/data/convert/ValueConversionContext.java @@ -18,6 +18,7 @@ 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.NonNull; import org.springframework.lang.Nullable; /** @@ -29,6 +30,7 @@ import org.springframework.lang.Nullable; * * @author Christoph Strobl * @author Oliver Drotbohm + * @see org.springframework.data.mapping.PersistentProperty */ public interface ValueConversionContext

> { @@ -36,14 +38,19 @@ public interface ValueConversionContext

> { * Return the {@link PersistentProperty} to be handled. * * @return will never be {@literal null}. + * @see org.springframework.data.mapping.PersistentProperty */ P getProperty(); /** - * Write to whatever type is considered best for the given source. + * Write the value as an instance of the {@link PersistentProperty#getTypeInformation() property type}. * - * @param value - * @return + * @param value {@link Object value} to write; can be {@literal null}. + * @return can be {@literal null}. + * @throws IllegalStateException if value cannot be written as an instance of the + * {@link PersistentProperty#getTypeInformation() property type}. + * @see PersistentProperty#getTypeInformation() + * @see #write(Object, TypeInformation) */ @Nullable default Object write(@Nullable Object value) { @@ -51,26 +58,31 @@ public interface ValueConversionContext

> { } /** - * Write as the given type. + * Write the value as an instance of {@link Class type}. * - * @param value can be {@literal null}. - * @param target must not be {@literal null}. + * @param value {@link Object value} to write; can be {@literal null}. + * @param target {@link Class type} of value to be written; must not be {@literal null}. * @return can be {@literal null}. + * @throws IllegalStateException if value cannot be written as an instance of {@link Class type}. + * @see #write(Object, TypeInformation) + * @see ClassTypeInformation */ @Nullable - default T write(@Nullable Object value, Class target) { + default T write(@Nullable Object value, @NonNull Class target) { return write(value, ClassTypeInformation.from(target)); } /** - * Write as the given type. + * Write the value as an instance of {@link TypeInformation type}. * - * @param value can be {@literal null}. - * @param target must not be {@literal null}. + * @param value {@link Object value} to write; can be {@literal null}. + * @param target {@link TypeInformation type} of value to be written; must not be {@literal null}. * @return can be {@literal null}. + * @throws IllegalStateException if value cannot be written as an instance of {@link TypeInformation type}. + * @see TypeInformation */ @Nullable - default T write(@Nullable Object value, TypeInformation target) { + default T write(@Nullable Object value, @NonNull TypeInformation target) { if (value == null || target.getType().isInstance(value)) { return target.getType().cast(value); @@ -81,10 +93,14 @@ public interface ValueConversionContext

> { } /** - * Reads the value into the type of the current property. + * Reads the value as an instance of the {@link PersistentProperty#getTypeInformation() property type}. * - * @param value can be {@literal null}. + * @param value {@link Object value} to be read; can be {@literal null}. * @return can be {@literal null}. + * @throws IllegalStateException if value cannot be read as an instance of the + * {@link PersistentProperty#getTypeInformation() property type}. + * @see PersistentProperty#getTypeInformation() + * @see #read(Object, TypeInformation) */ @Nullable default Object read(@Nullable Object value) { @@ -92,32 +108,37 @@ public interface ValueConversionContext

> { } /** - * Reads the value as the given type. + * Reads the value as an instance of {@link Class type}. * - * @param value can be {@literal null}. - * @param target must not be {@literal null}. + * @param value {@link Object value} to be read; can be {@literal null}. + * @param target {@link Class type} of value to be read; must not be {@literal null}. * @return can be {@literal null}. + * @throws IllegalStateException if value cannot be read as an instance of {@link Class type}. + * @see #read(Object, TypeInformation) + * @see ClassTypeInformation */ @Nullable - default T read(@Nullable Object value, Class target) { + default T read(@Nullable Object value, @NonNull Class target) { return read(value, ClassTypeInformation.from(target)); } /** - * Reads the value as the given type. + * Reads the value as an instance of {@link TypeInformation type}. * - * @param value can be {@literal null}. - * @param target must not be {@literal null}. + * @param value {@link Object value} to be read; can be {@literal null}. + * @param target {@link TypeInformation type} of value to be read; must not be {@literal null}. * @return can be {@literal null}. + * @throws IllegalStateException if value cannot be read as an instance of {@link TypeInformation type}. + * @see TypeInformation */ @Nullable - default T read(@Nullable Object value, TypeInformation target) { + default T read(@Nullable Object value, @NonNull TypeInformation target) { if (value == null || target.getType().isInstance(value)) { return target.getType().cast(value); } throw new IllegalStateException(String.format( - "%s does not provide write function that allows value conversion to target type (%s).", getClass(), target)); + "%s does not provide read function that allows value conversion to target type (%s).", getClass(), target)); } } diff --git a/src/main/java/org/springframework/data/convert/ValueConverterRegistry.java b/src/main/java/org/springframework/data/convert/ValueConverterRegistry.java index 8b516b63a..960fe8fbb 100644 --- a/src/main/java/org/springframework/data/convert/ValueConverterRegistry.java +++ b/src/main/java/org/springframework/data/convert/ValueConverterRegistry.java @@ -19,16 +19,17 @@ import org.springframework.data.mapping.PersistentProperty; import org.springframework.lang.Nullable; /** - * A registry of property-specific {@link PropertyValueConverter value converters} to convert only specific - * properties/values of an object. + * A registry of {@link PersistentProperty property-specific} {@link PropertyValueConverter value converters} + * to convert only specific properties/values of an object. * * @author Christoph Strobl + * @see PropertyValueConverter * @since 2.7 */ public interface ValueConverterRegistry

> { /** - * Register the {@link PropertyValueConverter} for the property of the given type. + * Register the {@link PropertyValueConverter} for the {@link PersistentProperty 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}. @@ -38,12 +39,13 @@ public interface ValueConverterRegistry

> { PropertyValueConverter> converter); /** - * Obtain the converter registered for the given type, path combination or {@literal null} if none defined. + * Obtain the {@link PropertyValueConverter} 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 - * @param + * @param domain-specific type. + * @param store-specific type. * @return {@literal null} if no converter present for the given type/path combination. */ @Nullable @@ -54,7 +56,7 @@ public interface ValueConverterRegistry

> { * * @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. + * @return {@literal false} if no converter is present for the given type/path combination. */ default boolean containsConverterFor(Class type, String path) { return getConverter(type, path) != null; @@ -68,10 +70,10 @@ public interface ValueConverterRegistry

> { /** * Obtain a simple {@link ValueConverterRegistry}. * - * @param + * @param

{@link PersistentProperty} type. * @return new instance of {@link ValueConverterRegistry}. */ - static > ValueConverterRegistry simple() { + static

> ValueConverterRegistry

simple() { return new SimplePropertyValueConverterRegistry<>(); } }