diff --git a/src/main/java/org/springframework/data/convert/CustomConversions.java b/src/main/java/org/springframework/data/convert/CustomConversions.java index de29695fa..2683379b6 100644 --- a/src/main/java/org/springframework/data/convert/CustomConversions.java +++ b/src/main/java/org/springframework/data/convert/CustomConversions.java @@ -33,6 +33,7 @@ import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.convert.ConverterBuilder.ConverterAware; +import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.Predicates; @@ -199,7 +200,7 @@ public class CustomConversions { * @since ? */ @Nullable - public PropertyValueConverter getPropertyValueConverter( + public > PropertyValueConverter getPropertyValueConverter( PersistentProperty property) { return propertyValueConversions != null ? propertyValueConversions.getValueConverter(property) : null; } diff --git a/src/main/java/org/springframework/data/convert/PropertyConverter.java b/src/main/java/org/springframework/data/convert/PropertyConverter.java index 43fc76d35..482e4f318 100644 --- a/src/main/java/org/springframework/data/convert/PropertyConverter.java +++ b/src/main/java/org/springframework/data/convert/PropertyConverter.java @@ -42,6 +42,6 @@ public @interface PropertyConverter { * * @return the configured {@link PropertyValueConverter}. {@link ObjectToObjectPropertyValueConverter} by default. */ - Class> value() default ObjectToObjectPropertyValueConverter.class; + Class value() default ObjectToObjectPropertyValueConverter.class; } diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConversions.java b/src/main/java/org/springframework/data/convert/PropertyValueConversions.java index 8a8a42056..e69b14164 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConversions.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConversions.java @@ -15,6 +15,7 @@ */ package org.springframework.data.convert; +import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; import org.springframework.data.mapping.PersistentProperty; import org.springframework.lang.Nullable; @@ -23,7 +24,7 @@ import org.springframework.lang.Nullable; * applied to a specific property. Other than {@link org.springframework.core.convert.converter.Converter converters} * registered in {@link CustomConversions} the property based variants accept and allow returning {@literal null} values * and provide access to a store specific {@link PropertyValueConverter.ValueConversionContext conversion context}. - * + * * @author Christoph Strobl * @since ? * @currentBook The Desert Prince - Peter V. Brett @@ -49,6 +50,6 @@ public interface PropertyValueConversions { * @return the suitable {@link PropertyValueConverter} or {@literal null} if none available. */ @Nullable - PropertyValueConverter getValueConverter( + > PropertyValueConverter getValueConverter( PersistentProperty property); } diff --git a/src/main/java/org/springframework/data/convert/PropertyValueConverter.java b/src/main/java/org/springframework/data/convert/PropertyValueConverter.java index ce9d8140f..e249931c8 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConverter.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConverter.java @@ -15,7 +15,10 @@ */ package org.springframework.data.convert; +import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; /** @@ -30,7 +33,7 @@ import org.springframework.lang.Nullable; * @param store native type * @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} @@ -56,10 +59,86 @@ public interface PropertyValueConverter> { - PersistentProperty getProperty(); + /** + * Return the {@link PersistentProperty} to be handled. + * + * @return will never be {@literal null}. + */ + P getProperty(); + + /** + * Write to whatever type is considered best for the given source. + * + * @param value + * @return + */ + @Nullable + default Object write(@Nullable Object value) { + return null; + } + + /** + * Write as the given type. + * + * @param value can be {@literal null}. + * @param target must not be {@literal null}. + * @return can be {@literal null}. + */ + @Nullable + default T write(@Nullable Object value, Class 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 write(@Nullable Object value, TypeInformation 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 read(@Nullable Object value, Class 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 read(@Nullable Object value, TypeInformation target) { + return null; + } } /** @@ -67,7 +146,8 @@ public interface PropertyValueConverter { + @SuppressWarnings({ "rawtypes", "null" }) + enum ObjectToObjectPropertyValueConverter implements PropertyValueConverter { INSTANCE; @@ -81,5 +161,4 @@ public interface PropertyValueConverter PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( PersistentProperty property) { return delegates.stream().map(it -> (PropertyValueConverter) it.getConverter(property)) .filter(Objects::nonNull).findFirst().orElse(null); } @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( Class> converterType) { return delegates.stream().filter(it -> it.getConverter(converterType) != null).findFirst() .map(it -> it.getConverter(converterType)).orElse(null); @@ -82,7 +83,7 @@ final class PropertyValueConverterFactories { static class SimplePropertyConverterFactory implements PropertyValueConverterFactory { @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( Class> converterType) { Assert.notNull(converterType, "ConverterType must not be null!"); @@ -110,7 +111,7 @@ final class PropertyValueConverterFactories { } @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( Class> converterType) { Assert.state(beanFactory != null, "BeanFactory must not be null. Did you forget to set it!"); @@ -145,14 +146,14 @@ final class PropertyValueConverterFactories { @Nullable @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( PersistentProperty property) { return (PropertyValueConverter) conversionsRegistrar.getConverter(property.getOwner().getType(), property.getName()); } @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( Class> converterType) { return null; } @@ -170,55 +171,55 @@ final class PropertyValueConverterFactories { private final Cache cache = new Cache(); public CachingPropertyValueConverterFactory(PropertyValueConverterFactory delegate) { - + Assert.notNull(delegate, "Delegate must not be null!"); this.delegate = delegate; } @Nullable @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( PersistentProperty property) { PropertyValueConverter converter = cache.get(property); - if (converter != null) { - return converter; - } - return cache.cache(property, delegate.getConverter(property)); + + return converter != null + ? converter + : cache.cache(property, delegate.getConverter(property)); } @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( Class> converterType) { PropertyValueConverter converter = cache.get(converterType); - if (converter != null) { - return converter; - } - return cache.cache(converterType, delegate.getConverter(converterType)); + + return converter != null + ? converter + : cache.cache(converterType, delegate.getConverter(converterType)); } static class Cache { - Map, PropertyValueConverter> perPropertyCache = new HashMap<>(); - Map, PropertyValueConverter> typeCache = new HashMap<>(); + Map, PropertyValueConverter>> perPropertyCache = new HashMap<>(); + Map, PropertyValueConverter>> typeCache = new HashMap<>(); - PropertyValueConverter get(PersistentProperty property) { + PropertyValueConverter> get(PersistentProperty property) { return perPropertyCache.get(property); } - PropertyValueConverter get(Class type) { + PropertyValueConverter> get(Class type) { return typeCache.get(type); } - PropertyValueConverter cache(PersistentProperty property, + > PropertyValueConverter cache(PersistentProperty property, PropertyValueConverter converter) { perPropertyCache.putIfAbsent(property, converter); cache(property.getValueConverterType(), converter); return converter; } - PropertyValueConverter cache(Class type, + > PropertyValueConverter cache(Class type, PropertyValueConverter converter) { typeCache.putIfAbsent(type, 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 bb5cf47a8..f3df7a182 100644 --- a/src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java +++ b/src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java @@ -19,6 +19,7 @@ import java.util.Arrays; import java.util.List; import org.springframework.beans.factory.BeanFactory; +import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; import org.springframework.data.convert.PropertyValueConverterFactories.BeanFactoryAwarePropertyValueConverterFactory; import org.springframework.data.convert.PropertyValueConverterFactories.CachingPropertyValueConverterFactory; import org.springframework.data.convert.PropertyValueConverterFactories.CompositePropertyValueConverterFactory; @@ -49,7 +50,8 @@ public interface PropertyValueConverterFactory { * @return can be {@literal null}. */ @Nullable - default PropertyValueConverter getConverter( + @SuppressWarnings("unchecked") + default > PropertyValueConverter getConverter( PersistentProperty property) { if (!property.hasValueConverter()) { @@ -59,7 +61,7 @@ public interface PropertyValueConverterFactory { } @Nullable - PropertyValueConverter getConverter( + > PropertyValueConverter getConverter( Class> converterType); /** diff --git a/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java b/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java index 2e13dd7d0..0740fb82f 100644 --- a/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java +++ b/src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; import org.springframework.data.mapping.PersistentProperty; import org.springframework.lang.Nullable; @@ -47,7 +48,7 @@ public class SimplePropertyValueConversions implements PropertyValueConversions, } @Override - public PropertyValueConverter getValueConverter( + public > PropertyValueConverter getValueConverter( PersistentProperty property) { if (!initialized.get()) { @@ -68,7 +69,7 @@ public class SimplePropertyValueConversions implements PropertyValueConversions, factoryList.add(PropertyValueConverterFactory.simple()); } - if (converterRegistrar != null && !converterRegistrar.isEmpty()) { + if ((converterRegistrar != null) && !converterRegistrar.isEmpty()) { factoryList.add(PropertyValueConverterFactory.configuredInstance(converterRegistrar)); } diff --git a/src/main/java/org/springframework/data/mapping/PersistentProperty.java b/src/main/java/org/springframework/data/mapping/PersistentProperty.java index 30fc41e80..ddd863d28 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/PersistentProperty.java @@ -427,9 +427,12 @@ public interface PersistentProperty

> { } @Nullable - default Class> getValueConverterType() { + default Class>> getValueConverterType() { + PropertyConverter annotation = findAnnotation(PropertyConverter.class); - return annotation == null ? null : annotation.value(); + + return annotation == null ? null + : (Class>>) annotation.value(); } default boolean hasValueConverter() { diff --git a/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java b/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java index 318241eb8..921bc8fe4 100644 --- a/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/model/AnnotationBasedPersistentProperty.java @@ -287,10 +287,12 @@ public abstract class AnnotationBasedPersistentProperty

> getValueConverterType() { + @SuppressWarnings("unchecked") + public Class>> getValueConverterType() { return doFindAnnotation(PropertyConverter.class) // .map(PropertyConverter::value) // + .map(Class.class::cast) // .orElse(null); } diff --git a/src/test/java/org/springframework/data/convert/PropertyValueConverterFactoryUnitTests.java b/src/test/java/org/springframework/data/convert/PropertyValueConverterFactoryUnitTests.java index 15426b866..b502145b1 100644 --- a/src/test/java/org/springframework/data/convert/PropertyValueConverterFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/convert/PropertyValueConverterFactoryUnitTests.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.data.convert.PropertyValueConverter.ValueConversionContext; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.SamplePersistentProperty; import org.springframework.lang.Nullable; /** @@ -114,14 +115,14 @@ public class PropertyValueConverterFactoryUnitTests { PropertyValueConverterFactory factory = PropertyValueConverterFactory.chained(new PropertyValueConverterFactory() { @Nullable @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( Class> converterType) { return null; } }, new PropertyValueConverterFactory() { @Nullable @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( Class> converterType) { return expected; } @@ -136,14 +137,14 @@ public class PropertyValueConverterFactoryUnitTests { PropertyValueConverterFactory factory = PropertyValueConverterFactory.chained(new PropertyValueConverterFactory() { @Nullable @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( Class> converterType) { return null; } }, new PropertyValueConverterFactory() { @Nullable @Override - public PropertyValueConverter getConverter( + public > PropertyValueConverter getConverter( Class> converterType) { throw new RuntimeException("can't touch this!"); } @@ -176,22 +177,23 @@ public class PropertyValueConverterFactoryUnitTests { .isSameAs(factory.getConverter(ConverterWithDefaultCtor.class)); // TODO: is this a valid assumption? } - static class ConverterWithDefaultCtor implements PropertyValueConverter { + static class ConverterWithDefaultCtor + implements PropertyValueConverter> { @Nullable @Override - public String nativeToDomain(@Nullable UUID nativeValue, ValueConversionContext context) { + public String nativeToDomain(@Nullable UUID nativeValue, ValueConversionContext context) { return nativeValue.toString(); } @Nullable @Override - public UUID domainToNative(@Nullable String domainValue, ValueConversionContext context) { + public UUID domainToNative(@Nullable String domainValue, ValueConversionContext context) { return UUID.fromString(domainValue); } } - enum ConverterEnum implements PropertyValueConverter { + enum ConverterEnum implements PropertyValueConverter> { INSTANCE; @@ -208,7 +210,8 @@ public class PropertyValueConverterFactoryUnitTests { } } - static class ConverterWithDependency implements PropertyValueConverter { + static class ConverterWithDependency + implements PropertyValueConverter> { private final SomeDependency someDependency; @@ -218,7 +221,7 @@ public class PropertyValueConverterFactoryUnitTests { @Nullable @Override - public String nativeToDomain(@Nullable UUID nativeValue, ValueConversionContext context) { + public String nativeToDomain(@Nullable UUID nativeValue, ValueConversionContext context) { assertThat(someDependency).isNotNull(); return nativeValue.toString(); @@ -226,7 +229,7 @@ public class PropertyValueConverterFactoryUnitTests { @Nullable @Override - public UUID domainToNative(@Nullable String domainValue, ValueConversionContext context) { + public UUID domainToNative(@Nullable String domainValue, ValueConversionContext context) { assertThat(someDependency).isNotNull(); return UUID.fromString(domainValue); diff --git a/src/test/java/org/springframework/data/convert/WhatWeWant.java b/src/test/java/org/springframework/data/convert/WhatWeWant.java index 9092b40e1..7ca616af4 100644 --- a/src/test/java/org/springframework/data/convert/WhatWeWant.java +++ b/src/test/java/org/springframework/data/convert/WhatWeWant.java @@ -31,11 +31,19 @@ */ 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.convert.PropertyValueConverter.ValueConversionContext; 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; /** @@ -47,7 +55,33 @@ public class WhatWeWant { @Test void converterConfig() { - ConverterConfig converterConfig = null; + 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() { @@ -63,28 +97,185 @@ public class WhatWeWant { return null; } }); + } + static String reverse(String source) { + return new StringBuilder(source).reverse().toString(); } - static class ConverterConfig { + static class ConverterConfig

> { - ConverterConfig registerConverter(Predicate> filter, PropertyValueConverter converter) { + ConverterConfig registerConverter(Predicate

filter, + PropertyValueConverter> converter) { return this; } - ConverterConfig registerConverter(Class type, String property, PropertyValueConverter converter) { + 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 domainToNative(A domainValue, ValueConversionContext

context) { + return writer.apply(domainValue, context); + } + + @Override + public A nativeToDomain(B nativeValue, ValueConversionContext

context) { + return reader.apply(nativeValue, 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 PropertyValueConverter.ValueConversionContext { + interface SpecificValueConversionContext

> extends ValueConversionContext

{ } - interface SpecificPropertyValueConverter extends PropertyValueConverter {} + interface SpecificPropertyValueConverter + extends PropertyValueConverter> {} } diff --git a/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java index 9ac5b96e3..e9f2c5cd0 100755 --- a/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/AnnotationBasedPersistentPropertyUnitTests.java @@ -543,7 +543,8 @@ public class AnnotationBasedPersistentPropertyUnitTests

{ + static class MyPropertyConverter + implements PropertyValueConverter> { @Override public Object nativeToDomain(Object value, ValueConversionContext context) { @@ -556,7 +557,8 @@ public class AnnotationBasedPersistentPropertyUnitTests

{ + static class MyPropertyConverterThatRequiresComponents + implements PropertyValueConverter> { private final SomeDependency someDependency;