Browse Source

Additional refinements.

Resolves #2577
Closes #2592
pull/2627/head
John Blum 4 years ago
parent
commit
5ce9669c7a
  1. 46
      src/main/java/org/springframework/data/convert/PropertyValueConversionService.java
  2. 36
      src/main/java/org/springframework/data/convert/PropertyValueConversions.java
  3. 33
      src/main/java/org/springframework/data/convert/PropertyValueConverter.java
  4. 25
      src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java
  5. 6
      src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java
  6. 70
      src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java
  7. 115
      src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java
  8. 65
      src/main/java/org/springframework/data/convert/ValueConversionContext.java
  9. 20
      src/main/java/org/springframework/data/convert/ValueConverterRegistry.java

46
src/main/java/org/springframework/data/convert/PropertyValueConversionService.java

@ -16,36 +16,50 @@ @@ -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}.
* <p>
* 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 { @@ -68,11 +82,8 @@ public class PropertyValueConversionService {
PropertyValueConverter<Object, Object, ValueConversionContext<P>> 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 { @@ -92,11 +103,8 @@ public class PropertyValueConversionService {
PropertyValueConverter<Object, Object, ValueConversionContext<P>> 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 { @@ -111,7 +119,7 @@ public class PropertyValueConversionService {
@Override
public <DV, SV, P extends PersistentProperty<P>, VCC extends ValueConversionContext<P>> PropertyValueConverter<DV, SV, VCC> getValueConverter(
P property) {
throw new UnsupportedOperationException();
throw new UnsupportedOperationException("No PropertyValueConversions was configured");
}
}
}

36
src/main/java/org/springframework/data/convert/PropertyValueConversions.java

@ -21,9 +21,10 @@ import org.springframework.data.mapping.PersistentProperty; @@ -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; @@ -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 <DV> domain-specific type
* @param <SV> store-native type
* @param <P> 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
*/
<DV, SV, P extends PersistentProperty<P>, VCC extends ValueConversionContext<P>> PropertyValueConverter<DV, SV, VCC> 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 <P extends PersistentProperty<P>> PropertyValueConversions simple(

33
src/main/java/org/springframework/data/convert/PropertyValueConverter.java

@ -18,6 +18,7 @@ package org.springframework.data.convert; @@ -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; @@ -36,6 +37,8 @@ import org.springframework.lang.Nullable;
* @param <DV> domain-specific type.
* @param <SV> store-native type.
* @param <C> 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<DV, SV, C extends ValueConversionContext<? extends PersistentProperty<?>>> {
@ -44,18 +47,20 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext @@ -44,18 +47,20 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext
* Convert the given store specific value into it's domain value representation. Typically, a {@literal read}
* operation.
*
* @param value the value to read.
* @param context never {@literal null}.
* @param value value to read.
* @param context {@link ValueConversionContext} containing store-specific metadata
* used in the value conversion; never {@literal null}.
* @return the converted value. Can be {@literal null}.
*/
@Nullable
DV read(SV value, C context);
/**
* Convert the given {@code null} value from the store into it's domain value representation. Typically, a
* Convert the given {@code null} value from the store into its domain value representation. Typically, a
* {@literal read} operation. Returns {@code null} by default.
*
* @param context never {@literal null}.
* @param context {@link ValueConversionContext} containing store-specific metadata
* used in the value conversion; never {@literal null}.
* @return the converted value. Can be {@literal null}.
*/
@Nullable
@ -67,8 +72,9 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext @@ -67,8 +72,9 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext
* Convert the given domain-specific value into it's native store representation. Typically, a {@literal write}
* operation.
*
* @param value the value to write.
* @param context never {@literal null}.
* @param value value to write; can be {@literal null}.
* @param context {@link ValueConversionContext} containing store-specific metadata
* used in the value conversion; never {@literal null}.
* @return the converted value. Can be {@literal null}.
*/
@Nullable
@ -78,7 +84,8 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext @@ -78,7 +84,8 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext
* Convert the given {@code null} value from the domain model into it's native store representation. Typically, a
* {@literal write} operation. Returns {@code null} by default.
*
* @param context never {@literal null}.
* @param context {@link ValueConversionContext} containing store-specific metadata
* used in the value conversion; never {@literal null}.
* @return the converted value. Can be {@literal null}.
*/
@Nullable
@ -120,8 +127,8 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext @@ -120,8 +127,8 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext
private final BiFunction<DV, ValueConversionContext<P>, SV> writer;
private final BiFunction<SV, ValueConversionContext<P>, DV> reader;
public FunctionPropertyValueConverter(BiFunction<DV, ValueConversionContext<P>, SV> writer,
BiFunction<SV, ValueConversionContext<P>, DV> reader) {
public FunctionPropertyValueConverter(@NonNull BiFunction<DV, ValueConversionContext<P>, SV> writer,
@NonNull BiFunction<SV, ValueConversionContext<P>, DV> reader) {
this.writer = writer;
this.reader = reader;
@ -129,23 +136,23 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext @@ -129,23 +136,23 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext
@Nullable
@Override
public SV write(DV value, ValueConversionContext<P> context) {
public SV write(@Nullable DV value, @NonNull ValueConversionContext<P> context) {
return writer.apply(value, context);
}
@Override
public SV writeNull(ValueConversionContext<P> context) {
public SV writeNull(@NonNull ValueConversionContext<P> context) {
return writer.apply(null, context);
}
@Nullable
@Override
public DV read(@Nullable SV value, ValueConversionContext<P> context) {
public DV read(@Nullable SV value, @NonNull ValueConversionContext<P> context) {
return reader.apply(value, context);
}
@Override
public DV readNull(ValueConversionContext<P> context) {
public DV readNull(@NonNull ValueConversionContext<P> context) {
return reader.apply(null, context);
}
}

25
src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java

@ -50,7 +50,7 @@ final class PropertyValueConverterFactories { @@ -50,7 +50,7 @@ final class PropertyValueConverterFactories {
*/
static class ChainedPropertyValueConverterFactory implements PropertyValueConverterFactory {
private List<PropertyValueConverterFactory> delegates;
private final List<PropertyValueConverterFactory> delegates;
ChainedPropertyValueConverterFactory(List<PropertyValueConverterFactory> delegates) {
this.delegates = Collections.unmodifiableList(delegates);
@ -58,8 +58,10 @@ final class PropertyValueConverterFactories { @@ -58,8 +58,10 @@ final class PropertyValueConverterFactories {
@Nullable
@Override
@SuppressWarnings("unchecked")
public <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getConverter(
PersistentProperty<?> property) {
return delegates.stream().map(it -> (PropertyValueConverter<A, B, C>) it.getConverter(property))
.filter(Objects::nonNull).findFirst().orElse(null);
}
@ -67,6 +69,7 @@ final class PropertyValueConverterFactories { @@ -67,6 +69,7 @@ final class PropertyValueConverterFactories {
@Override
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
return delegates.stream().filter(it -> it.getConverter(converterType) != null).findFirst()
.map(it -> it.getConverter(converterType)).orElse(null);
}
@ -82,6 +85,7 @@ final class PropertyValueConverterFactories { @@ -82,6 +85,7 @@ final class PropertyValueConverterFactories {
static class SimplePropertyConverterFactory implements PropertyValueConverterFactory {
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
@ -90,6 +94,7 @@ final class PropertyValueConverterFactories { @@ -90,6 +94,7 @@ final class PropertyValueConverterFactories {
if (converterType.isEnum()) {
return (PropertyValueConverter<S, T, C>) EnumSet.allOf((Class) converterType).iterator().next();
}
return BeanUtils.instantiateClass(converterType);
}
}
@ -110,10 +115,11 @@ final class PropertyValueConverterFactories { @@ -110,10 +115,11 @@ final class PropertyValueConverterFactories {
}
@Override
@SuppressWarnings("unchecked")
public <DV, SV, C extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, C> getConverter(
Class<? extends PropertyValueConverter<DV, SV, C>> converterType) {
Assert.notNull(converterType, "ConverterType must not be null!");
Assert.notNull(converterType, "ConverterType must not be null");
PropertyValueConverter<DV, SV, C> converter = beanFactory.getBeanProvider(converterType).getIfAvailable();
@ -139,14 +145,17 @@ final class PropertyValueConverterFactories { @@ -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 <DV, SV, C extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, C> getConverter(
PersistentProperty<?> property) {
return (PropertyValueConverter<DV, SV, C>) converterRegistry.getConverter(property.getOwner().getType(),
property.getName());
}
@ -154,6 +163,7 @@ final class PropertyValueConverterFactories { @@ -154,6 +163,7 @@ final class PropertyValueConverterFactories {
@Override
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
return null;
}
}
@ -172,12 +182,14 @@ final class PropertyValueConverterFactories { @@ -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 <DV, SV, C extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, C> getConverter(
PersistentProperty<?> property) {
@ -188,6 +200,7 @@ final class PropertyValueConverterFactories { @@ -188,6 +200,7 @@ final class PropertyValueConverterFactories {
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public <DV, SV, C extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, C> getConverter(
Class<? extends PropertyValueConverter<DV, SV, C>> converterType) {
@ -218,15 +231,19 @@ final class PropertyValueConverterFactories { @@ -218,15 +231,19 @@ final class PropertyValueConverterFactories {
AnnotatedPropertyValueConverterAccessor accessor = new AnnotatedPropertyValueConverterAccessor(property);
Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<? extends PersistentProperty<?>>>> valueConverterType = accessor
.getValueConverterType();
if (valueConverterType != null) {
cache(valueConverterType, converter);
}
return converter;
}
<S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> cache(Class<?> type,
@Nullable PropertyValueConverter<S, T, C> converter) {
typeCache.putIfAbsent(type, Optional.ofNullable(converter));
return converter;
}
}

6
src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java

@ -29,9 +29,9 @@ import org.springframework.lang.Nullable; @@ -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}.
* <p>
* 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 { @@ -129,7 +129,7 @@ public interface PropertyValueConverterFactory {
*/
static PropertyValueConverterFactory chained(List<PropertyValueConverterFactory> 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();

70
src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java

@ -22,11 +22,12 @@ import java.util.function.Function; @@ -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}.
* <p>
* It is possible to register type safe converters via {@link #registerConverter(Class, Function)}
*
@ -50,13 +51,14 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -50,13 +51,14 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
* @param <T> the domain type
* @param <S> the property type
* @param type the domain type to obtain the property from
* @param property a function to describe the property to be referenced. Usually a method handle to a getter.
* @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 <T, S> WritingConverterRegistrationBuilder<T, S, P> registerConverter(Class<T> type, Function<T, S> property) {
String propertyName = MethodInvocationRecorder.forProxyOf(type).record(property).getPropertyPath()
.orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name!"));
.orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name"));
return new WritingConverterRegistrationBuilder<>(type, propertyName, this);
}
@ -67,20 +69,23 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -67,20 +69,23 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
* @param <T> the domain type
* @param <S> the property type
* @param type the domain type to obtain the property from
* @param propertyName a function to describe the property to be referenced. Usually a method handle to a getter.
* @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 <T, S> WritingConverterRegistrationBuilder<T, S, P> registerConverter(Class<T> type, String propertyName,
@SuppressWarnings("unused") Class<S> 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<P> registerConverter(Class<?> type, String path,
@ -88,33 +93,39 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -88,33 +93,39 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
registry.registerConverter(type, path,
(PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>>) converter);
return this;
}
/**
* Obtain the {@link SimplePropertyValueConverterRegistry}.
* Register collected {@link PropertyValueConverter converters} within the given
* {@link ValueConverterRegistry registry}.
*
* @return new instance of {@link SimplePropertyValueConverterRegistry}.
* @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 ValueConverterRegistry<P> buildRegistry() {
return new SimplePropertyValueConverterRegistry<>(registry);
public void registerConvertersIn(@NonNull ValueConverterRegistry<P> target) {
Assert.notNull(target, "Target registry must not be null!");
registry.getConverterRegistrationMap().forEach((key, value) ->
target.registerConverter(key.type, key.path, value));
}
/**
* Register collected {@link PropertyValueConverter converters} within the given {@link ValueConverterRegistry
* registry}.
* Obtain the {@link SimplePropertyValueConverterRegistry}.
*
* @return new instance of {@link SimplePropertyValueConverterRegistry}.
*/
public void registerConvertersIn(ValueConverterRegistry<P> target) {
Assert.notNull(target, "Target registry must not be null!");
registry.getConverterRegistrationMap().entrySet().forEach(entry -> {
target.registerConverter(entry.getKey().type, entry.getKey().path, entry.getValue());
});
@NonNull
public ValueConverterRegistry<P> buildRegistry() {
return new SimplePropertyValueConverterRegistry<>(registry);
}
/**
* 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<P extends PersistentProperty<P>> { @@ -123,10 +134,11 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
private final Consumer<PropertyValueConverter<T, S, ValueConversionContext<P>>> registration;
private final PropertyValueConverterRegistrar<P> config;
WritingConverterRegistrationBuilder(Class<T> type, String property, PropertyValueConverterRegistrar<P> config) {
WritingConverterRegistrationBuilder(Class<T> type, String property,
@NonNull PropertyValueConverterRegistrar<P> config) {
this.config = config;
this.registration = (converter) -> config.registerConverter(type, property, converter);
this.registration = converter -> config.registerConverter(type, property, converter);
}
public ReadingConverterRegistrationBuilder<T, S, S, P> writingAsIs() {
@ -146,12 +158,13 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -146,12 +158,13 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
*/
public <R> ReadingConverterRegistrationBuilder<T, S, R, P> writing(
BiFunction<S, ValueConversionContext<P>, R> writer) {
return new ReadingConverterRegistrationBuilder<>(this, writer);
}
}
/**
* A helper to build a fluent API to register how to read a database value into a domain object property.
* 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<P extends PersistentProperty<P>> { @@ -160,12 +173,14 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
private final WritingConverterRegistrationBuilder<T, S, P> origin;
private final BiFunction<S, ValueConversionContext<P>, R> writer;
ReadingConverterRegistrationBuilder(WritingConverterRegistrationBuilder<T, S, P> origin,
BiFunction<S, ValueConversionContext<P>, R> writer) {
ReadingConverterRegistrationBuilder(@NonNull WritingConverterRegistrationBuilder<T, S, P> origin,
@NonNull BiFunction<S, ValueConversionContext<P>, R> writer) {
this.origin = origin;
this.writer = writer;
}
@SuppressWarnings("unchecked")
public PropertyValueConverterRegistrar<P> readingAsIs() {
return reading((source, context) -> (S) source);
}
@ -178,8 +193,9 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -178,8 +193,9 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
* 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<P> reading(BiFunction<R, ValueConversionContext<P>, S> reader) {
origin.registration.accept(new FunctionPropertyValueConverter(writer, reader));

115
src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java

@ -17,73 +17,93 @@ package org.springframework.data.convert; @@ -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.
* <p>
* 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.
* <p>
* 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 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;
private boolean converterCacheEnabled = true;
/**
* 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.
* <p>
* 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, @@ -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 <DV, SV, P extends PersistentProperty<P>, D extends ValueConversionContext<P>> PropertyValueConverter<DV, SV, D> getValueConverter(
P property) {
PropertyValueConverter<DV, SV, D> converter = obtainConverterFactory().getConverter(property);
PropertyValueConverter<DV, SV, D> 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, @@ -125,15 +153,10 @@ public class SimplePropertyValueConversions implements PropertyValueConversions,
List<PropertyValueConverterFactory> 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, @@ -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<PropertyValueConverterFactory> resolveConverterRegistryAsConverterFactory() {
return Optional.ofNullable(getValueConverterRegistry())
.filter(it -> !it.isEmpty())
.map(PropertyValueConverterFactory::configuredInstance);
}
/**
* Initializes this {@link SimplePropertyValueConversions} instance.
*
* @see #init()
*/
@Override
public void afterPropertiesSet() {
init();

65
src/main/java/org/springframework/data/convert/ValueConversionContext.java

@ -18,6 +18,7 @@ package org.springframework.data.convert; @@ -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; @@ -29,6 +30,7 @@ import org.springframework.lang.Nullable;
*
* @author Christoph Strobl
* @author Oliver Drotbohm
* @see org.springframework.data.mapping.PersistentProperty
*/
public interface ValueConversionContext<P extends PersistentProperty<P>> {
@ -36,14 +38,19 @@ public interface ValueConversionContext<P extends PersistentProperty<P>> { @@ -36,14 +38,19 @@ public interface ValueConversionContext<P extends PersistentProperty<P>> {
* 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<P extends PersistentProperty<P>> { @@ -51,26 +58,31 @@ public interface ValueConversionContext<P extends PersistentProperty<P>> {
}
/**
* 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> T write(@Nullable Object value, Class<T> target) {
default <T> T write(@Nullable Object value, @NonNull Class<T> 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> T write(@Nullable Object value, TypeInformation<T> target) {
default <T> T write(@Nullable Object value, @NonNull TypeInformation<T> target) {
if (value == null || target.getType().isInstance(value)) {
return target.getType().cast(value);
@ -81,10 +93,14 @@ public interface ValueConversionContext<P extends PersistentProperty<P>> { @@ -81,10 +93,14 @@ public interface ValueConversionContext<P extends PersistentProperty<P>> {
}
/**
* 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<P extends PersistentProperty<P>> { @@ -92,32 +108,37 @@ public interface ValueConversionContext<P extends PersistentProperty<P>> {
}
/**
* 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> T read(@Nullable Object value, Class<T> target) {
default <T> T read(@Nullable Object value, @NonNull Class<T> 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> T read(@Nullable Object value, TypeInformation<T> target) {
default <T> T read(@Nullable Object value, @NonNull TypeInformation<T> 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));
}
}

20
src/main/java/org/springframework/data/convert/ValueConverterRegistry.java

@ -19,16 +19,17 @@ import org.springframework.data.mapping.PersistentProperty; @@ -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<P extends PersistentProperty<P>> {
/**
* 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<P extends PersistentProperty<P>> { @@ -38,12 +39,13 @@ public interface ValueConverterRegistry<P extends PersistentProperty<P>> {
PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>> 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 <DV>
* @param <SV>
* @param <DV> domain-specific type.
* @param <SV> store-specific type.
* @return {@literal null} if no converter present for the given type/path combination.
*/
@Nullable
@ -54,7 +56,7 @@ public interface ValueConverterRegistry<P extends PersistentProperty<P>> { @@ -54,7 +56,7 @@ public interface ValueConverterRegistry<P extends PersistentProperty<P>> {
*
* @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<P extends PersistentProperty<P>> { @@ -68,10 +70,10 @@ public interface ValueConverterRegistry<P extends PersistentProperty<P>> {
/**
* Obtain a simple {@link ValueConverterRegistry}.
*
* @param <T>
* @param <P> {@link PersistentProperty} type.
* @return new instance of {@link ValueConverterRegistry}.
*/
static <T extends PersistentProperty<T>> ValueConverterRegistry<T> simple() {
static <P extends PersistentProperty<P>> ValueConverterRegistry<P> simple() {
return new SimplePropertyValueConverterRegistry<>();
}
}

Loading…
Cancel
Save