From d86322ee6f55b454247b76d0c49d1de6d19f2e8a Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 4 Mar 2022 09:13:37 +0100 Subject: [PATCH] Polishing. Increase visibility of converter builders. Refine generics naming towards DV/SV instead of A/B to easier identify store-native values and domains-specific values in the API. Refactor PropertyValueConversions.hasValueConverter into a non-default method. Tweak documentation. See #1484 Original pull request: #2566. --- .../data/convert/CustomConversions.java | 11 +- .../convert/PropertyValueConversions.java | 18 +- .../data/convert/PropertyValueConverter.java | 46 +-- .../PropertyValueConverterFactories.java | 47 ++-- .../PropertyValueConverterFactory.java | 20 +- .../PropertyValueConverterRegistrar.java | 39 +-- .../SimplePropertyValueConversions.java | 18 +- .../SimplePropertyValueConverterRegistry.java | 12 +- .../data/convert/ValueConversionContext.java | 14 +- .../data/convert/ValueConverter.java | 6 +- .../data/convert/ValueConverterRegistry.java | 12 +- .../data/mapping/PersistentProperty.java | 20 +- ...ropertyValueConverterFactoryUnitTests.java | 2 + ...pertyValueConverterRegistrarUnitTests.java | 5 +- ...mplePropertyValueConversionsUnitTests.java | 2 + ...opertyValueConverterRegistryUnitTests.java | 3 + .../data/convert/WhatWeWant.java | 264 ------------------ 17 files changed, 157 insertions(+), 382 deletions(-) delete mode 100644 src/test/java/org/springframework/data/convert/WhatWeWant.java diff --git a/src/main/java/org/springframework/data/convert/CustomConversions.java b/src/main/java/org/springframework/data/convert/CustomConversions.java index 271b1b060..dec8ca27d 100644 --- a/src/main/java/org/springframework/data/convert/CustomConversions.java +++ b/src/main/java/org/springframework/data/convert/CustomConversions.java @@ -55,7 +55,7 @@ import org.springframework.util.ObjectUtils; * Value object to capture custom conversion. That is essentially a {@link List} of converters and some additional logic * around them. The converters build up two sets of types which store-specific basic types can be converted into and * from. These types will be considered simple ones (which means they neither need deeper inspection nor nested - * conversion. Thus the {@link CustomConversions} also act as factory for {@link SimpleTypeHolder} . + * conversion. Thus, the {@link CustomConversions} also act as factory for {@link SimpleTypeHolder} . * * @author Oliver Gierke * @author Thomas Darimont @@ -195,22 +195,23 @@ public class CustomConversions { * @since 2.7 */ public boolean hasPropertyValueConverter(PersistentProperty property) { - return propertyValueConversions != null ? propertyValueConversions.hasValueConverter(property) : false; + return propertyValueConversions != null && propertyValueConversions.hasValueConverter(property); } /** * Delegate to obtain the {@link PropertyValueConverter} for the given {@literal property} from * {@link PropertyValueConversions}. * - * @param property must not be {@literal null}. param domain specific type - * @param store native type + * @param property must not be {@literal null}. + * @param domain-specific type + * @param store-native type * @param conversion context type * @return the suitable {@link PropertyValueConverter} or {@literal null} if none available. * @see PropertyValueConversions#getValueConverter(PersistentProperty) * @since 2.7 */ @Nullable - public , D extends ValueConversionContext> PropertyValueConverter getPropertyValueConverter( + public , VCC extends ValueConversionContext> PropertyValueConverter getPropertyValueConverter( C property) { return propertyValueConversions != null ? propertyValueConversions.getValueConverter(property) : null; } diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConversions.java b/src/main/java/org/springframework/data/convert/PropertyValueConversions.java index 1c1b09fcf..1b80e96e3 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConversions.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConversions.java @@ -18,7 +18,6 @@ package org.springframework.data.convert; 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 @@ -38,26 +37,25 @@ public interface PropertyValueConversions { * @param property must not be {@literal null}. * @return {@literal true} if a specific {@link PropertyValueConverter} is available. */ - default boolean hasValueConverter(PersistentProperty property) { - return getValueConverter((PersistentProperty) property) != null; - } + boolean hasValueConverter(PersistentProperty property); /** - * Get the {@link PropertyValueConverter} for the given {@literal property} if present. + * Get the {@link PropertyValueConverter} for the given {@literal property}. * - * @param property must not be {@literal null}. param domain specific type - * @param store native type + * @param property must not be {@literal null}. + * @param domain-specific type + * @param store-native type * @param conversion context type - * @return the suitable {@link PropertyValueConverter} or {@literal null} if none available. + * @return the suitable {@link PropertyValueConverter}. */ - @Nullable - , D extends ValueConversionContext> PropertyValueConverter getValueConverter( + , VCC extends ValueConversionContext> PropertyValueConverter getValueConverter( C property); /** * Helper that allows to create {@link PropertyValueConversions} instance with the configured * {@link PropertyValueConverter converters} provided via the given callback. */ + @SuppressWarnings({ "rawtypes", "unchecked" }) static

> PropertyValueConversions simple( Consumer> config) { diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConverter.java b/src/main/java/org/springframework/data/convert/PropertyValueConverter.java index 7c0ad8834..f89aba108 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConverter.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConverter.java @@ -21,22 +21,22 @@ import org.springframework.data.mapping.PersistentProperty; import org.springframework.lang.Nullable; /** - * {@link PropertyValueConverter} provides a symmetric way of converting certain properties from domain to store - * specific values. + * {@link PropertyValueConverter} provides a symmetric way of converting certain properties from domain to + * store-specific values. *

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

context) { return writer.apply(value, context); } + @Nullable @Override - public A read(B value, ValueConversionContext

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

context) { return reader.apply(value, context); } } diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java b/src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java index dac34508d..719e3de8c 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java @@ -18,15 +18,14 @@ 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 java.util.concurrent.ConcurrentHashMap; 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.mapping.PersistentProperty; import org.springframework.lang.Nullable; @@ -120,29 +119,26 @@ final class PropertyValueConverterFactories { } @Override - public > PropertyValueConverter getConverter( - Class> converterType) { + public > PropertyValueConverter getConverter( + Class> converterType) { - Assert.state(beanFactory != null, "BeanFactory must not be null. Did you forget to set it!"); Assert.notNull(converterType, "ConverterType must not be null!"); - try { - return beanFactory.getBean(converterType); - } catch (NoSuchBeanDefinitionException exception) { + PropertyValueConverter converter = beanFactory.getBeanProvider(converterType).getIfAvailable(); - if (beanFactory instanceof AutowireCapableBeanFactory) { - return (PropertyValueConverter) ((AutowireCapableBeanFactory) beanFactory).createBean(converterType, - AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); - } + if (converter == null && beanFactory instanceof AutowireCapableBeanFactory) { + return (PropertyValueConverter) ((AutowireCapableBeanFactory) beanFactory).createBean(converterType, + AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false); } - return null; + + return converter; } } /** * {@link PropertyValueConverterFactory} implementation that serves {@link PropertyValueConverter} from a given * {@link ValueConverterRegistry registry}. - * + * * @author Christoph Strobl * @since 2.7 */ @@ -158,9 +154,9 @@ final class PropertyValueConverterFactories { @Nullable @Override - public > PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( PersistentProperty property) { - return (PropertyValueConverter) converterRegistry.getConverter(property.getOwner().getType(), + return (PropertyValueConverter) converterRegistry.getConverter(property.getOwner().getType(), property.getName()); } @@ -174,7 +170,7 @@ final class PropertyValueConverterFactories { /** * {@link PropertyValueConverterFactory} implementation that caches converters provided by an underlying * {@link PropertyValueConverterFactory factory}. - * + * * @author Christoph Strobl * @since 2.7 */ @@ -191,7 +187,7 @@ final class PropertyValueConverterFactories { @Nullable @Override - public > PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( PersistentProperty property) { Optional>> converter = cache.get(property); @@ -201,8 +197,8 @@ final class PropertyValueConverterFactories { } @Override - public > PropertyValueConverter getConverter( - Class> converterType) { + public > PropertyValueConverter getConverter( + Class> converterType) { Optional>> converter = cache.get(converterType); @@ -212,8 +208,8 @@ final class PropertyValueConverterFactories { static class Cache { - Map, Optional>>> perPropertyCache = new HashMap<>(); - Map, Optional>>> typeCache = new HashMap<>(); + Map, Optional>>> perPropertyCache = new ConcurrentHashMap<>(); + Map, Optional>>> typeCache = new ConcurrentHashMap<>(); Optional>> get(PersistentProperty property) { return perPropertyCache.get(property); @@ -226,7 +222,12 @@ final class PropertyValueConverterFactories { > PropertyValueConverter cache(PersistentProperty property, @Nullable PropertyValueConverter converter) { perPropertyCache.putIfAbsent(property, Optional.ofNullable(converter)); - cache(property.getValueConverterType(), converter); + + Class>>> valueConverterType = property + .getValueConverterType(); + if (valueConverterType != null) { + cache(valueConverterType, 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 7a40cf3d5..e969952db 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java @@ -44,35 +44,35 @@ public interface PropertyValueConverterFactory { * Get the {@link PropertyValueConverter} applicable for the given {@link PersistentProperty}. * * @param property must not be {@literal null}. - * @param domain specific type. - * @param store native type. + * @param domain-specific type. + * @param store-native type. * @param value conversion context to use. * @return can be {@literal null}. */ - @Nullable @SuppressWarnings("unchecked") - default > PropertyValueConverter getConverter( + @Nullable + default > PropertyValueConverter getConverter( PersistentProperty property) { if (!property.hasValueConverter()) { return null; } - return getConverter((Class>) property.getValueConverterType()); + return getConverter((Class>) property.getValueConverterType()); } /** * Get the converter by its type. * * @param converterType must not be {@literal null}. - * @param domain specific type. - * @param store native type. + * @param domain-specific type. + * @param store-native type. * @param value conversion context to use. - * @return + * @return can be {@literal null}. */ @Nullable - > PropertyValueConverter getConverter( - Class> converterType); + > PropertyValueConverter getConverter( + Class> converterType); /** * Obtain a simple {@link PropertyValueConverterFactory} capable of instantiating {@link PropertyValueConverter} diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java b/src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java index b7fb46364..d4207d413 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java @@ -29,7 +29,7 @@ import org.springframework.util.Assert; * that can be used with {@link PropertyValueConversions}. *

* It is possible to register type safe converters via {@link #registerConverter(Class, Function)} - * + * *

  * registrar.registerConverter(Person.class, Person::getName) //
  * 		.writing(StringConverter::encrypt) //
@@ -58,7 +58,7 @@ public class PropertyValueConverterRegistrar

> { String propertyName = MethodInvocationRecorder.forProxyOf(type).record(property).getPropertyPath() .orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name!")); - return new WritingConverterRegistrationBuilder(type, propertyName, this); + return new WritingConverterRegistrationBuilder<>(type, propertyName, this); } /** @@ -71,8 +71,8 @@ public class PropertyValueConverterRegistrar

> { * @return will never be {@literal null}. */ public WritingConverterRegistrationBuilder registerConverter(Class type, String propertyName, - Class propertyType) { - return new WritingConverterRegistrationBuilder(type, propertyName, this); + @SuppressWarnings("unused") Class propertyType) { + return new WritingConverterRegistrationBuilder<>(type, propertyName, this); } /** @@ -83,7 +83,7 @@ public class PropertyValueConverterRegistrar

> { * @param converter the converter to apply. * @return this. */ - public PropertyValueConverterRegistrar registerConverter(Class type, String path, + public PropertyValueConverterRegistrar

registerConverter(Class type, String path, PropertyValueConverter> converter) { registry.registerConverter(type, path, @@ -120,22 +120,22 @@ public class PropertyValueConverterRegistrar

> { * * @author Oliver Drotbohm */ - static class WritingConverterRegistrationBuilder> { + public static class WritingConverterRegistrationBuilder> { - private final Consumer registration; - private final PropertyValueConverterRegistrar config; + private final Consumer>> registration; + private final PropertyValueConverterRegistrar

config; - public WritingConverterRegistrationBuilder(Class type, String property, PropertyValueConverterRegistrar config) { + WritingConverterRegistrationBuilder(Class type, String property, PropertyValueConverterRegistrar

config) { this.config = config; this.registration = (converter) -> config.registerConverter(type, property, converter); } - ReadingConverterRegistrationBuilder writingAsIs() { + public ReadingConverterRegistrationBuilder writingAsIs() { return writing((source, context) -> source); } - ReadingConverterRegistrationBuilder writing(Function writer) { + public ReadingConverterRegistrationBuilder writing(Function writer) { return writing((source, context) -> writer.apply(source)); } @@ -146,7 +146,8 @@ public class PropertyValueConverterRegistrar

> { * @param writer the property conversion to extract a value written to the database * @return will never be {@literal null}. */ - ReadingConverterRegistrationBuilder writing(BiFunction, R> writer) { + public ReadingConverterRegistrationBuilder writing( + BiFunction, R> writer) { return new ReadingConverterRegistrationBuilder<>(this, writer); } } @@ -156,22 +157,22 @@ public class PropertyValueConverterRegistrar

> { * * @author Oliver Drotbohm */ - static class ReadingConverterRegistrationBuilder> { + public static class ReadingConverterRegistrationBuilder> { - private WritingConverterRegistrationBuilder origin; - private BiFunction, R> writer; + private final WritingConverterRegistrationBuilder origin; + private final BiFunction, R> writer; - public ReadingConverterRegistrationBuilder(WritingConverterRegistrationBuilder origin, + ReadingConverterRegistrationBuilder(WritingConverterRegistrationBuilder origin, BiFunction, R> writer) { this.origin = origin; this.writer = writer; } - PropertyValueConverterRegistrar

readingAsIs() { + public PropertyValueConverterRegistrar

readingAsIs() { return reading((source, context) -> (S) source); } - PropertyValueConverterRegistrar

reading(Function reader) { + public PropertyValueConverterRegistrar

reading(Function reader) { return reading((source, context) -> reader.apply(source)); } @@ -181,7 +182,7 @@ public class PropertyValueConverterRegistrar

> { * @param reader must not be {@literal null}. * @return */ - PropertyValueConverterRegistrar

reading(BiFunction, S> reader) { + 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 b93f3e660..50274005b 100644 --- a/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java +++ b/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java @@ -40,12 +40,12 @@ public class SimplePropertyValueConversions implements PropertyValueConversions, private @Nullable PropertyValueConverterFactory converterFactory; private @Nullable ValueConverterRegistry valueConverterRegistry; private boolean converterCacheEnabled = true; - private AtomicBoolean initialized = new AtomicBoolean(false); + private final 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) { @@ -62,7 +62,7 @@ public class SimplePropertyValueConversions implements PropertyValueConversions, * 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) { @@ -89,9 +89,19 @@ public class SimplePropertyValueConversions implements PropertyValueConversions, this.converterCacheEnabled = converterCacheEnabled; } + @Override + public boolean hasValueConverter(PersistentProperty property) { + + if (!initialized.get()) { + init(); + } + + return this.converterFactory.getConverter(property) != null; + } + @Nullable @Override - public , D extends ValueConversionContext> PropertyValueConverter getValueConverter( + public , D extends ValueConversionContext> PropertyValueConverter getValueConverter( C property) { if (!initialized.get()) { diff --git a/src/main/java/org/springframework/data/convert/SimplePropertyValueConverterRegistry.java b/src/main/java/org/springframework/data/convert/SimplePropertyValueConverterRegistry.java index 6b336eb45..60dedce6a 100644 --- a/src/main/java/org/springframework/data/convert/SimplePropertyValueConverterRegistry.java +++ b/src/main/java/org/springframework/data/convert/SimplePropertyValueConverterRegistry.java @@ -24,11 +24,13 @@ 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. - * + * + * @param

persistent property type. * @author Christoph Strobl * @since 2.7 */ -public class SimplePropertyValueConverterRegistry

> implements ValueConverterRegistry

{ +public class SimplePropertyValueConverterRegistry

> + implements ValueConverterRegistry

{ private final Map>> converterRegistrationMap = new LinkedHashMap<>(); @@ -83,7 +85,7 @@ public class SimplePropertyValueConverterRegistry

>> getConverterRegistrationMap() { @@ -92,8 +94,8 @@ public class SimplePropertyValueConverterRegistry

type; - String path; + final Class type; + final String path; public Key(Class type, String path) { this.type = type; diff --git a/src/main/java/org/springframework/data/convert/ValueConversionContext.java b/src/main/java/org/springframework/data/convert/ValueConversionContext.java index cf5fec26b..049485af0 100644 --- a/src/main/java/org/springframework/data/convert/ValueConversionContext.java +++ b/src/main/java/org/springframework/data/convert/ValueConversionContext.java @@ -21,8 +21,8 @@ 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. + * The {@link ValueConversionContext} provides access to the store-specific {@link PersistentProperty} and allows to + * call the store-default conversion through the {@literal read}/{@literal write} methods. *

* 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)}. @@ -71,6 +71,11 @@ public interface ValueConversionContext

> { */ @Nullable default T write(@Nullable Object value, 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)); } @@ -107,6 +112,11 @@ public interface ValueConversionContext

> { */ @Nullable default T read(@Nullable Object value, 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)); } diff --git a/src/main/java/org/springframework/data/convert/ValueConverter.java b/src/main/java/org/springframework/data/convert/ValueConverter.java index bdcf7933a..f345ea791 100644 --- a/src/main/java/org/springframework/data/convert/ValueConverter.java +++ b/src/main/java/org/springframework/data/convert/ValueConverter.java @@ -26,13 +26,13 @@ import org.springframework.data.convert.PropertyValueConverter.ObjectToObjectPro /** * Annotation to define usage of a {@link PropertyValueConverter} to read/write the property.
- * May be used as meta annotation utilizing {@link org.springframework.core.annotation.AliasFor}. + * Can be used as meta annotation utilizing {@link org.springframework.core.annotation.AliasFor}. *

* The target {@link PropertyValueConverter} is typically provided via a {@link PropertyValueConverterFactory converter * factory}. *

- * Consult the store specific documentation for details and support notes. - * + * Consult the store-specific documentation for details and support notes. + * * @author Christoph Strobl * @since 2.7 * @see PropertyValueConverter diff --git a/src/main/java/org/springframework/data/convert/ValueConverterRegistry.java b/src/main/java/org/springframework/data/convert/ValueConverterRegistry.java index 2631a57c2..8b516b63a 100644 --- a/src/main/java/org/springframework/data/convert/ValueConverterRegistry.java +++ b/src/main/java/org/springframework/data/convert/ValueConverterRegistry.java @@ -19,8 +19,8 @@ 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. + * A registry of property-specific {@link PropertyValueConverter value converters} to convert only specific + * properties/values of an object. * * @author Christoph Strobl * @since 2.7 @@ -42,12 +42,12 @@ public interface ValueConverterRegistry

> { * * @param type the target type. Must not be {@literal null}. * @param path the property name. Must not be {@literal null}. - * @param - * @param + * @param + * @param * @return {@literal null} if no converter present for the given type/path combination. */ @Nullable - PropertyValueConverter> getConverter(Class type, String path); + PropertyValueConverter> getConverter(Class type, String path); /** * Check if a converter is registered for the given type, path combination. @@ -61,7 +61,7 @@ public interface ValueConverterRegistry

> { } /** - * Check if there a converters registered. + * Check if converters are registered. */ boolean isEmpty(); diff --git a/src/main/java/org/springframework/data/mapping/PersistentProperty.java b/src/main/java/org/springframework/data/mapping/PersistentProperty.java index 5ce731e21..ff2babc20 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/PersistentProperty.java @@ -439,28 +439,30 @@ public interface PersistentProperty

> { } /** - * 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. + * Obtain the {@link PropertyValueConverter converter type} to be used for reading and writing property values. Uses + * the {@link ValueConverter} annotation and extracts its {@link ValueConverter#value() value} attribute. *

- * Store implementations may override the default and provide a more specific type. - * + * Store implementations may override the default and resort to a more specific annotation type. + * * @return {@literal null} if none defined. Check {@link #hasValueConverter()} to check if the annotation is present * at all. * @since 2.7 */ @Nullable + @SuppressWarnings({ "unchecked", "rawtypes" }) default Class>>> getValueConverterType() { ValueConverter annotation = findAnnotation(ValueConverter.class); - return annotation == null ? null - : (Class>>>) annotation - .value(); + return annotation == null ? null : (Class) annotation.value(); } /** - * @return by default return {@literal true} if {@link ValueConverter} annotation is present. + * Return whether a value converter is configured. Uses {@link ValueConverter} as annotation type. + *

+ * Store implementations may override the default and resort to a more specific annotation type. + * + * @return {@literal true} if a value converter is configured. * @since 2.7 */ default boolean hasValueConverter() { diff --git a/src/test/java/org/springframework/data/convert/PropertyValueConverterFactoryUnitTests.java b/src/test/java/org/springframework/data/convert/PropertyValueConverterFactoryUnitTests.java index 7a84aed72..dad7e2273 100644 --- a/src/test/java/org/springframework/data/convert/PropertyValueConverterFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/convert/PropertyValueConverterFactoryUnitTests.java @@ -31,6 +31,8 @@ import org.springframework.data.mapping.context.SamplePersistentProperty; import org.springframework.lang.Nullable; /** + * Unit tests for {@link PropertyValueConverterFactory}. + * * @author Christoph Strobl */ public class PropertyValueConverterFactoryUnitTests { diff --git a/src/test/java/org/springframework/data/convert/PropertyValueConverterRegistrarUnitTests.java b/src/test/java/org/springframework/data/convert/PropertyValueConverterRegistrarUnitTests.java index b15cb5fdb..c86a6a034 100644 --- a/src/test/java/org/springframework/data/convert/PropertyValueConverterRegistrarUnitTests.java +++ b/src/test/java/org/springframework/data/convert/PropertyValueConverterRegistrarUnitTests.java @@ -23,9 +23,12 @@ import org.springframework.data.mapping.context.SamplePersistentProperty; import org.springframework.lang.Nullable; /** + * Unit tests for {@link ValueConverterRegistry}. + * * @author Christoph Strobl */ -public class PropertyValueConverterRegistrarUnitTests { +@SuppressWarnings({ "rawtypes", "unchecked" }) +class PropertyValueConverterRegistrarUnitTests { @Test // GH-1484 void buildsRegistryCorrectly() { diff --git a/src/test/java/org/springframework/data/convert/SimplePropertyValueConversionsUnitTests.java b/src/test/java/org/springframework/data/convert/SimplePropertyValueConversionsUnitTests.java index 047d2dd76..753905f0e 100644 --- a/src/test/java/org/springframework/data/convert/SimplePropertyValueConversionsUnitTests.java +++ b/src/test/java/org/springframework/data/convert/SimplePropertyValueConversionsUnitTests.java @@ -24,6 +24,8 @@ import org.springframework.data.convert.PropertyValueConverterFactories.CachingP import org.springframework.data.convert.PropertyValueConverterFactories.ChainedPropertyValueConverterFactory; /** + * Unit tests for {@link SimplePropertyValueConversions}. + * * @author Christoph Strobl */ class SimplePropertyValueConversionsUnitTests { diff --git a/src/test/java/org/springframework/data/convert/SimplePropertyValueConverterRegistryUnitTests.java b/src/test/java/org/springframework/data/convert/SimplePropertyValueConverterRegistryUnitTests.java index 5eea00e75..5e2c26e51 100644 --- a/src/test/java/org/springframework/data/convert/SimplePropertyValueConverterRegistryUnitTests.java +++ b/src/test/java/org/springframework/data/convert/SimplePropertyValueConverterRegistryUnitTests.java @@ -22,8 +22,11 @@ import org.junit.jupiter.api.Test; import org.springframework.data.mapping.Person; /** + * Unit tests for {@link SimplePropertyValueConverterRegistry}. + * * @author Christoph Strobl */ +@SuppressWarnings({ "rawtypes", "unchecked" }) class SimplePropertyValueConverterRegistryUnitTests { @Test // GH-1484 diff --git a/src/test/java/org/springframework/data/convert/WhatWeWant.java b/src/test/java/org/springframework/data/convert/WhatWeWant.java deleted file mode 100644 index 2f4a14a1e..000000000 --- a/src/test/java/org/springframework/data/convert/WhatWeWant.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * 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.HashMap; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; -import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.mapping.PropertyPath; -import org.springframework.data.mapping.context.SamplePersistentProperty; -import org.springframework.data.util.MethodInvocationRecorder; -import org.springframework.lang.Nullable; - -/** - * @author Christoph Strobl - * @since 2022/01 - */ -public class WhatWeWant { - - @Test - void converterConfig() { - - ConverterConfig converterConfig = new ConverterConfig(); - - // Arbitrary translations - converterConfig.registerConverter(Foo.class, Foo::getValue) - .writing((source, context) -> WhatWeWant.reverse(source)) // store reversed - .reading((source, context) -> WhatWeWant.reverse(source)); // restore order - - converterConfig.registerConverter(it -> it.findAnnotation(Deprecated.class) != null) - .writing((source, context) -> WhatWeWant.reverse(source.toString())) - .reading((source, context) -> { - return context.read(WhatWeWant.reverse(source)); - }); - - // Clean up on read - converterConfig.registerConverter(Foo.class, Foo::getValue) - .writingAsIs() - .reading((source, context) -> source.trim()); - - converterConfig.registerConverter(Foo.class, Foo::getAddress) - .writing((source, context) -> { - - Map map = new HashMap<>(); - map.put("STREET", source.street.toUpperCase()); - map.put("ZIP", context.write(source.zipCode)); - - return map; - }); - - converterConfig.registerConverter(Foo.class, "value", new PropertyValueConverter() { - - @Nullable - @Override - public Object read(@Nullable Object value, ValueConversionContext context) { - return null; - } - - @Nullable - @Override - public Object write(@Nullable Object value, ValueConversionContext context) { - return null; - } - }); - } - - static String reverse(String source) { - return new StringBuilder(source).reverse().toString(); - } - - static class ConverterConfig

> { - - ConverterConfig registerConverter(Predicate

filter, - PropertyValueConverter> converter) { - return this; - } - - ConverterConfig registerConverter(Class type, String property, - PropertyValueConverter> converter) { - PropertyPath.from(property, type); - return this; - } - - /** - * Starts a converter registration by pointing to a property of a domain type. - * - * @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. - * @return will never be {@literal null}. - */ - WritingConverterRegistrationBuilder registerConverter( - Class type, Function property) { - - String propertyName = MethodInvocationRecorder.forProxyOf(type) - .record(property) - .getPropertyPath() - .orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name!")); - - return new WritingConverterRegistrationBuilder(type, propertyName, this); - } - - WritingConverterRegistrationBuilder registerConverter(Predicate

predicate) { - return new WritingConverterRegistrationBuilder(predicate, this); - } - - /** - * Helper to build up a fluent registration API starting on - * {@link ConverterConfig#registerConverter(Class, Function)}. - * - * @author Oliver Drotbohm - */ - static class WritingConverterRegistrationBuilder> { - - private final Consumer registration; - private final ConverterConfig config; - - public WritingConverterRegistrationBuilder(Class type, String property, ConverterConfig config) { - - this.config = config; - this.registration = (converter) -> config.registerConverter(type, property, converter); - } - - public WritingConverterRegistrationBuilder(Predicate

predicate, ConverterConfig config) { - - this.config = config; - this.registration = (converter) -> config.registerConverter(predicate, converter); - } - - ReadingConverterRegistrationBuilder writingAsIs() { - return writing((source, context) -> source); - } - - ReadingConverterRegistrationBuilder writing(Function writer) { - return writing((source, context) -> writer.apply(source)); - } - - /** - * Describes how to convert the domain property value into the database native property. - * - * @param 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}. - */ - 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. - * - * @author Oliver Drotbohm - */ - static class ReadingConverterRegistrationBuilder> { - - private WritingConverterRegistrationBuilder origin; - private BiFunction, R> writer; - - public ReadingConverterRegistrationBuilder(WritingConverterRegistrationBuilder origin, - BiFunction, R> writer) { - this.origin = origin; - this.writer = writer; - } - - ConverterConfig

readingAsIs() { - return reading((source, context) -> (S) source); - } - - ConverterConfig

reading(Function 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 - */ - ConverterConfig

reading(BiFunction, S> reader) { - - origin.registration.accept(new FunctionPropertyValueConverter(writer, reader)); - - return origin.config; - } - - /** - * A {@link PropertyValueConverter} that delegates conversion to the given {@link BiFunction}s. - * - * @author Oliver Drotbohm - */ - static class FunctionPropertyValueConverter> - implements PropertyValueConverter> { - - private final BiFunction, B> writer; - private final BiFunction, A> reader; - - public FunctionPropertyValueConverter(BiFunction, B> writer, - BiFunction, A> reader) { - this.writer = writer; - this.reader = reader; - } - - @Override - public B write(A value, ValueConversionContext

context) { - return writer.apply(value, context); - } - - @Override - public A read(B value, ValueConversionContext

context) { - return reader.apply(value, context); - } - } - } - } - - static class Foo { - String value; - Address address; - - public String getValue() { - return value; - } - - public Address getAddress() { - return address; - } - } - - static class Address { - String street; - ZipCode zipCode; - } - - static class ZipCode { - - } - - interface SpecificValueConversionContext

> extends ValueConversionContext

{ - - } - - interface SpecificPropertyValueConverter - extends PropertyValueConverter> {} -}

* A {@link PropertyValueConverter} is, other than a {@link ReadingConverter} or {@link WritingConverter}, only applied - * to special annotated fields which allows a fine grained conversion of certain values within a specific context. + * to special annotated fields which allows a fine-grained conversion of certain values within a specific context. * * @author Christoph Strobl - * @param domain specific type. - * @param store native type. + * @param domain-specific type. + * @param store-native type. * @param the store specific {@link ValueConversionContext conversion context}. * @since 2.7 */ -public interface PropertyValueConverter>> { +public interface PropertyValueConverter>> { /** - * Convert the given store specific value into it's domain value representation. Typically a {@literal read} + * Convert the given store specific value into it's domain value representation. Typically, a {@literal read} * operation. * * @param value can be {@literal null}. @@ -44,10 +44,10 @@ public interface PropertyValueConverter> - implements PropertyValueConverter> { + class FunctionPropertyValueConverter> + implements PropertyValueConverter> { - private final BiFunction, B> writer; - private final BiFunction, A> reader; + private final BiFunction, SV> writer; + private final BiFunction, DV> reader; - public FunctionPropertyValueConverter(BiFunction, B> writer, - BiFunction, A> reader) { + public FunctionPropertyValueConverter(BiFunction, SV> writer, + BiFunction, DV> reader) { this.writer = writer; this.reader = reader; } + @Nullable @Override - public B write(A value, ValueConversionContext