Browse Source

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.
pull/2592/head
Mark Paluch 4 years ago
parent
commit
d86322ee6f
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 11
      src/main/java/org/springframework/data/convert/CustomConversions.java
  2. 18
      src/main/java/org/springframework/data/convert/PropertyValueConversions.java
  3. 46
      src/main/java/org/springframework/data/convert/PropertyValueConverter.java
  4. 47
      src/main/java/org/springframework/data/convert/PropertyValueConverterFactories.java
  5. 20
      src/main/java/org/springframework/data/convert/PropertyValueConverterFactory.java
  6. 39
      src/main/java/org/springframework/data/convert/PropertyValueConverterRegistrar.java
  7. 18
      src/main/java/org/springframework/data/convert/SimplePropertyValueConversions.java
  8. 12
      src/main/java/org/springframework/data/convert/SimplePropertyValueConverterRegistry.java
  9. 14
      src/main/java/org/springframework/data/convert/ValueConversionContext.java
  10. 6
      src/main/java/org/springframework/data/convert/ValueConverter.java
  11. 12
      src/main/java/org/springframework/data/convert/ValueConverterRegistry.java
  12. 20
      src/main/java/org/springframework/data/mapping/PersistentProperty.java
  13. 2
      src/test/java/org/springframework/data/convert/PropertyValueConverterFactoryUnitTests.java
  14. 5
      src/test/java/org/springframework/data/convert/PropertyValueConverterRegistrarUnitTests.java
  15. 2
      src/test/java/org/springframework/data/convert/SimplePropertyValueConversionsUnitTests.java
  16. 3
      src/test/java/org/springframework/data/convert/SimplePropertyValueConverterRegistryUnitTests.java
  17. 264
      src/test/java/org/springframework/data/convert/WhatWeWant.java

11
src/main/java/org/springframework/data/convert/CustomConversions.java

@ -55,7 +55,7 @@ import org.springframework.util.ObjectUtils; @@ -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 { @@ -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 <A> domain specific type
* @param <B> store native type
* @param property must not be {@literal null}.
* @param <DV> domain-specific type
* @param <SV> store-native type
* @param <C> conversion context type
* @return the suitable {@link PropertyValueConverter} or {@literal null} if none available.
* @see PropertyValueConversions#getValueConverter(PersistentProperty)
* @since 2.7
*/
@Nullable
public <A, B, C extends PersistentProperty<C>, D extends ValueConversionContext<C>> PropertyValueConverter<A, B, D> getPropertyValueConverter(
public <DV, SV, C extends PersistentProperty<C>, VCC extends ValueConversionContext<C>> PropertyValueConverter<DV, SV, VCC> getPropertyValueConverter(
C property) {
return propertyValueConversions != null ? propertyValueConversions.getValueConverter(property) : null;
}

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

@ -18,7 +18,6 @@ package org.springframework.data.convert; @@ -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 { @@ -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 <A> domain specific type
* @param <B> store native type
* @param property must not be {@literal null}.
* @param <DV> domain-specific type
* @param <SV> store-native type
* @param <C> conversion context type
* @return the suitable {@link PropertyValueConverter} or {@literal null} if none available.
* @return the suitable {@link PropertyValueConverter}.
*/
@Nullable
<A, B, C extends PersistentProperty<C>, D extends ValueConversionContext<C>> PropertyValueConverter<A, B, D> getValueConverter(
<DV, SV, C extends PersistentProperty<C>, VCC extends ValueConversionContext<C>> PropertyValueConverter<DV, SV, VCC> 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 <P extends PersistentProperty<P>> PropertyValueConversions simple(
Consumer<PropertyValueConverterRegistrar<P>> config) {

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

@ -21,22 +21,22 @@ import org.springframework.data.mapping.PersistentProperty; @@ -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.
* <p>
* 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 <A> domain specific type.
* @param <B> store native type.
* @param <DV> domain-specific type.
* @param <SV> store-native type.
* @param <C> the store specific {@link ValueConversionContext conversion context}.
* @since 2.7
*/
public interface PropertyValueConverter<A, B, C extends ValueConversionContext<? extends PersistentProperty<?>>> {
public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext<? extends PersistentProperty<?>>> {
/**
* 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<A, B, C extends ValueConversionContext<? @@ -44,10 +44,10 @@ public interface PropertyValueConverter<A, B, C extends ValueConversionContext<?
* @return the converted value. Can be {@literal null}.
*/
@Nullable
A read(@Nullable B value, C context);
DV read(@Nullable SV value, C context);
/**
* Convert the given domain specific value into it's native store representation. Typically a {@literal write}
* Convert the given domain-specific value into it's native store representation. Typically, a {@literal write}
* operation.
*
* @param value can be {@literal null}.
@ -55,10 +55,10 @@ public interface PropertyValueConverter<A, B, C extends ValueConversionContext<? @@ -55,10 +55,10 @@ public interface PropertyValueConverter<A, B, C extends ValueConversionContext<?
* @return the converted value. Can be {@literal null}.
*/
@Nullable
B write(@Nullable A value, C context);
SV write(@Nullable DV value, C context);
/**
* NoOp {@link PropertyValueConverter} implementation.
* No-op {@link PropertyValueConverter} implementation.
*
* @author Christoph Strobl
*/
@ -67,13 +67,15 @@ public interface PropertyValueConverter<A, B, C extends ValueConversionContext<? @@ -67,13 +67,15 @@ public interface PropertyValueConverter<A, B, C extends ValueConversionContext<?
INSTANCE;
@Nullable
@Override
public Object read(Object value, ValueConversionContext context) {
public Object read(@Nullable Object value, ValueConversionContext context) {
return value;
}
@Nullable
@Override
public Object write(Object value, ValueConversionContext context) {
public Object write(@Nullable Object value, ValueConversionContext context) {
return value;
}
}
@ -83,26 +85,28 @@ public interface PropertyValueConverter<A, B, C extends ValueConversionContext<? @@ -83,26 +85,28 @@ public interface PropertyValueConverter<A, B, C extends ValueConversionContext<?
*
* @author Oliver Drotbohm
*/
class FunctionPropertyValueConverter<A, B, P extends PersistentProperty<P>>
implements PropertyValueConverter<A, B, ValueConversionContext<P>> {
class FunctionPropertyValueConverter<DV, SV, P extends PersistentProperty<P>>
implements PropertyValueConverter<DV, SV, ValueConversionContext<P>> {
private final BiFunction<A, ValueConversionContext<P>, B> writer;
private final BiFunction<B, ValueConversionContext<P>, A> reader;
private final BiFunction<DV, ValueConversionContext<P>, SV> writer;
private final BiFunction<SV, ValueConversionContext<P>, DV> reader;
public FunctionPropertyValueConverter(BiFunction<A, ValueConversionContext<P>, B> writer,
BiFunction<B, ValueConversionContext<P>, A> reader) {
public FunctionPropertyValueConverter(BiFunction<DV, ValueConversionContext<P>, SV> writer,
BiFunction<SV, ValueConversionContext<P>, DV> reader) {
this.writer = writer;
this.reader = reader;
}
@Nullable
@Override
public B write(A value, ValueConversionContext<P> context) {
public SV write(@Nullable DV value, ValueConversionContext<P> context) {
return writer.apply(value, context);
}
@Nullable
@Override
public A read(B value, ValueConversionContext<P> context) {
public DV read(@Nullable SV value, ValueConversionContext<P> context) {
return reader.apply(value, context);
}
}

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

@ -18,15 +18,14 @@ package org.springframework.data.convert; @@ -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 { @@ -120,29 +119,26 @@ final class PropertyValueConverterFactories {
}
@Override
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
public <DV, SV, C extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, C> getConverter(
Class<? extends PropertyValueConverter<DV, SV, C>> 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<DV, SV, C> converter = beanFactory.getBeanProvider(converterType).getIfAvailable();
if (beanFactory instanceof AutowireCapableBeanFactory) {
return (PropertyValueConverter<S, T, C>) ((AutowireCapableBeanFactory) beanFactory).createBean(converterType,
AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
}
if (converter == null && beanFactory instanceof AutowireCapableBeanFactory) {
return (PropertyValueConverter<DV, SV, C>) ((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 { @@ -158,9 +154,9 @@ final class PropertyValueConverterFactories {
@Nullable
@Override
public <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getConverter(
public <DV, SV, C extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, C> getConverter(
PersistentProperty<?> property) {
return (PropertyValueConverter<A, B, C>) converterRegistry.getConverter(property.getOwner().getType(),
return (PropertyValueConverter<DV, SV, C>) converterRegistry.getConverter(property.getOwner().getType(),
property.getName());
}
@ -174,7 +170,7 @@ final class PropertyValueConverterFactories { @@ -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 { @@ -191,7 +187,7 @@ final class PropertyValueConverterFactories {
@Nullable
@Override
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
public <DV, SV, C extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, C> getConverter(
PersistentProperty<?> property) {
Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> converter = cache.get(property);
@ -201,8 +197,8 @@ final class PropertyValueConverterFactories { @@ -201,8 +197,8 @@ final class PropertyValueConverterFactories {
}
@Override
public <S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> getConverter(
Class<? extends PropertyValueConverter<S, T, C>> converterType) {
public <DV, SV, C extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, C> getConverter(
Class<? extends PropertyValueConverter<DV, SV, C>> converterType) {
Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> converter = cache.get(converterType);
@ -212,8 +208,8 @@ final class PropertyValueConverterFactories { @@ -212,8 +208,8 @@ final class PropertyValueConverterFactories {
static class Cache {
Map<PersistentProperty<?>, Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>>> perPropertyCache = new HashMap<>();
Map<Class<?>, Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>>> typeCache = new HashMap<>();
Map<PersistentProperty<?>, Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>>> perPropertyCache = new ConcurrentHashMap<>();
Map<Class<?>, Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>>> typeCache = new ConcurrentHashMap<>();
Optional<PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>>> get(PersistentProperty<?> property) {
return perPropertyCache.get(property);
@ -226,7 +222,12 @@ final class PropertyValueConverterFactories { @@ -226,7 +222,12 @@ final class PropertyValueConverterFactories {
<S, T, C extends ValueConversionContext<?>> PropertyValueConverter<S, T, C> cache(PersistentProperty<?> property,
@Nullable PropertyValueConverter<S, T, C> converter) {
perPropertyCache.putIfAbsent(property, Optional.ofNullable(converter));
cache(property.getValueConverterType(), converter);
Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<? extends PersistentProperty<?>>>> valueConverterType = property
.getValueConverterType();
if (valueConverterType != null) {
cache(valueConverterType, converter);
}
return converter;
}

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

@ -44,35 +44,35 @@ public interface PropertyValueConverterFactory { @@ -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 <A> domain specific type.
* @param <B> store native type.
* @param <DV> domain-specific type.
* @param <SV> store-native type.
* @param <C> value conversion context to use.
* @return can be {@literal null}.
*/
@Nullable
@SuppressWarnings("unchecked")
default <A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getConverter(
@Nullable
default <DV, SV, C extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, C> getConverter(
PersistentProperty<?> property) {
if (!property.hasValueConverter()) {
return null;
}
return getConverter((Class<PropertyValueConverter<A, B, C>>) property.getValueConverterType());
return getConverter((Class<PropertyValueConverter<DV, SV, C>>) property.getValueConverterType());
}
/**
* Get the converter by its type.
*
* @param converterType must not be {@literal null}.
* @param <A> domain specific type.
* @param <B> store native type.
* @param <DV> domain-specific type.
* @param <SV> store-native type.
* @param <C> value conversion context to use.
* @return
* @return can be {@literal null}.
*/
@Nullable
<A, B, C extends ValueConversionContext<?>> PropertyValueConverter<A, B, C> getConverter(
Class<? extends PropertyValueConverter<A, B, C>> converterType);
<DV, SV, C extends ValueConversionContext<?>> PropertyValueConverter<DV, SV, C> getConverter(
Class<? extends PropertyValueConverter<DV, SV, C>> converterType);
/**
* Obtain a simple {@link PropertyValueConverterFactory} capable of instantiating {@link PropertyValueConverter}

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

@ -29,7 +29,7 @@ import org.springframework.util.Assert; @@ -29,7 +29,7 @@ import org.springframework.util.Assert;
* that can be used with {@link PropertyValueConversions}.
* <p>
* It is possible to register type safe converters via {@link #registerConverter(Class, Function)}
*
*
* <pre class="code">
* registrar.registerConverter(Person.class, Person::getName) //
* .writing(StringConverter::encrypt) //
@ -58,7 +58,7 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -58,7 +58,7 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
String propertyName = MethodInvocationRecorder.forProxyOf(type).record(property).getPropertyPath()
.orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name!"));
return new WritingConverterRegistrationBuilder<T, S, P>(type, propertyName, this);
return new WritingConverterRegistrationBuilder<>(type, propertyName, this);
}
/**
@ -71,8 +71,8 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -71,8 +71,8 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
* @return will never be {@literal null}.
*/
public <T, S> WritingConverterRegistrationBuilder<T, S, P> registerConverter(Class<T> type, String propertyName,
Class<S> propertyType) {
return new WritingConverterRegistrationBuilder<T, S, P>(type, propertyName, this);
@SuppressWarnings("unused") Class<S> propertyType) {
return new WritingConverterRegistrationBuilder<>(type, propertyName, this);
}
/**
@ -83,7 +83,7 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -83,7 +83,7 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
* @param converter the converter to apply.
* @return this.
*/
public PropertyValueConverterRegistrar registerConverter(Class<?> type, String path,
public PropertyValueConverterRegistrar<P> registerConverter(Class<?> type, String path,
PropertyValueConverter<?, ?, ? extends ValueConversionContext<?>> converter) {
registry.registerConverter(type, path,
@ -120,22 +120,22 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -120,22 +120,22 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
*
* @author Oliver Drotbohm
*/
static class WritingConverterRegistrationBuilder<T, S, P extends PersistentProperty<P>> {
public static class WritingConverterRegistrationBuilder<T, S, P extends PersistentProperty<P>> {
private final Consumer<PropertyValueConverter> registration;
private final PropertyValueConverterRegistrar config;
private final Consumer<PropertyValueConverter<T, S, ValueConversionContext<P>>> registration;
private final PropertyValueConverterRegistrar<P> config;
public WritingConverterRegistrationBuilder(Class<T> type, String property, PropertyValueConverterRegistrar config) {
WritingConverterRegistrationBuilder(Class<T> type, String property, PropertyValueConverterRegistrar<P> config) {
this.config = config;
this.registration = (converter) -> config.registerConverter(type, property, converter);
}
<R> ReadingConverterRegistrationBuilder<T, S, S, P> writingAsIs() {
public ReadingConverterRegistrationBuilder<T, S, S, P> writingAsIs() {
return writing((source, context) -> source);
}
<R> ReadingConverterRegistrationBuilder<T, S, R, P> writing(Function<S, R> writer) {
public <R> ReadingConverterRegistrationBuilder<T, S, R, P> writing(Function<S, R> writer) {
return writing((source, context) -> writer.apply(source));
}
@ -146,7 +146,8 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -146,7 +146,8 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
* @param writer the property conversion to extract a value written to the database
* @return will never be {@literal null}.
*/
<R> ReadingConverterRegistrationBuilder<T, S, R, P> writing(BiFunction<S, ValueConversionContext<P>, R> writer) {
public <R> ReadingConverterRegistrationBuilder<T, S, R, P> writing(
BiFunction<S, ValueConversionContext<P>, R> writer) {
return new ReadingConverterRegistrationBuilder<>(this, writer);
}
}
@ -156,22 +157,22 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -156,22 +157,22 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
*
* @author Oliver Drotbohm
*/
static class ReadingConverterRegistrationBuilder<T, S, R, P extends PersistentProperty<P>> {
public static class ReadingConverterRegistrationBuilder<T, S, R, P extends PersistentProperty<P>> {
private WritingConverterRegistrationBuilder<T, S, P> origin;
private BiFunction<S, ValueConversionContext<P>, R> writer;
private final WritingConverterRegistrationBuilder<T, S, P> origin;
private final BiFunction<S, ValueConversionContext<P>, R> writer;
public ReadingConverterRegistrationBuilder(WritingConverterRegistrationBuilder<T, S, P> origin,
ReadingConverterRegistrationBuilder(WritingConverterRegistrationBuilder<T, S, P> origin,
BiFunction<S, ValueConversionContext<P>, R> writer) {
this.origin = origin;
this.writer = writer;
}
PropertyValueConverterRegistrar<P> readingAsIs() {
public PropertyValueConverterRegistrar<P> readingAsIs() {
return reading((source, context) -> (S) source);
}
PropertyValueConverterRegistrar<P> reading(Function<R, S> reader) {
public PropertyValueConverterRegistrar<P> reading(Function<R, S> reader) {
return reading((source, context) -> reader.apply(source));
}
@ -181,7 +182,7 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> { @@ -181,7 +182,7 @@ public class PropertyValueConverterRegistrar<P extends PersistentProperty<P>> {
* @param reader must not be {@literal null}.
* @return
*/
PropertyValueConverterRegistrar<P> reading(BiFunction<R, ValueConversionContext<P>, S> reader) {
public PropertyValueConverterRegistrar<P> reading(BiFunction<R, ValueConversionContext<P>, S> reader) {
origin.registration.accept(new FunctionPropertyValueConverter(writer, reader));

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

@ -40,12 +40,12 @@ public class SimplePropertyValueConversions implements PropertyValueConversions, @@ -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, @@ -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, @@ -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 <A, B, C extends PersistentProperty<C>, D extends ValueConversionContext<C>> PropertyValueConverter<A, B, D> getValueConverter(
public <DV, SV, C extends PersistentProperty<C>, D extends ValueConversionContext<C>> PropertyValueConverter<DV, SV, D> getValueConverter(
C property) {
if (!initialized.get()) {

12
src/main/java/org/springframework/data/convert/SimplePropertyValueConverterRegistry.java

@ -24,11 +24,13 @@ import org.springframework.util.ObjectUtils; @@ -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 <P> persistent property type.
* @author Christoph Strobl
* @since 2.7
*/
public class SimplePropertyValueConverterRegistry<P extends PersistentProperty<P>> implements ValueConverterRegistry<P> {
public class SimplePropertyValueConverterRegistry<P extends PersistentProperty<P>>
implements ValueConverterRegistry<P> {
private final Map<Key, PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>>> converterRegistrationMap = new LinkedHashMap<>();
@ -83,7 +85,7 @@ public class SimplePropertyValueConverterRegistry<P extends PersistentProperty<P @@ -83,7 +85,7 @@ public class SimplePropertyValueConverterRegistry<P extends PersistentProperty<P
/**
* Obtain the underlying (mutable) map of converters.
*
*
* @return never {@literal null}.
*/
Map<Key, PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>>> getConverterRegistrationMap() {
@ -92,8 +94,8 @@ public class SimplePropertyValueConverterRegistry<P extends PersistentProperty<P @@ -92,8 +94,8 @@ public class SimplePropertyValueConverterRegistry<P extends PersistentProperty<P
static class Key {
Class<?> type;
String path;
final Class<?> type;
final String path;
public Key(Class<?> type, String path) {
this.type = type;

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

@ -21,8 +21,8 @@ import org.springframework.data.util.TypeInformation; @@ -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.
* <p>
* Store implementations should provide their own flavor of {@link ValueConversionContext} enhancing the existing API,
* implementing delegates for {@link #read(Object, TypeInformation)}, {@link #write(Object, TypeInformation)}.
@ -71,6 +71,11 @@ public interface ValueConversionContext<P extends PersistentProperty<P>> { @@ -71,6 +71,11 @@ public interface ValueConversionContext<P extends PersistentProperty<P>> {
*/
@Nullable
default <T> T write(@Nullable Object value, 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));
}
@ -107,6 +112,11 @@ public interface ValueConversionContext<P extends PersistentProperty<P>> { @@ -107,6 +112,11 @@ public interface ValueConversionContext<P extends PersistentProperty<P>> {
*/
@Nullable
default <T> T read(@Nullable Object value, 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));
}

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

@ -26,13 +26,13 @@ import org.springframework.data.convert.PropertyValueConverter.ObjectToObjectPro @@ -26,13 +26,13 @@ import org.springframework.data.convert.PropertyValueConverter.ObjectToObjectPro
/**
* Annotation to define usage of a {@link PropertyValueConverter} to read/write the property. <br />
* May be used as meta annotation utilizing {@link org.springframework.core.annotation.AliasFor}.
* Can be used as meta annotation utilizing {@link org.springframework.core.annotation.AliasFor}.
* <p>
* The target {@link PropertyValueConverter} is typically provided via a {@link PropertyValueConverterFactory converter
* factory}.
* <p>
* Consult the store specific documentation for details and support notes.
*
* Consult the store-specific documentation for details and support notes.
*
* @author Christoph Strobl
* @since 2.7
* @see PropertyValueConverter

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

@ -19,8 +19,8 @@ import org.springframework.data.mapping.PersistentProperty; @@ -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<P extends PersistentProperty<P>> { @@ -42,12 +42,12 @@ 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}.
* @param <S>
* @param <T>
* @param <DV>
* @param <SV>
* @return {@literal null} if no converter present for the given type/path combination.
*/
@Nullable
<S, T> PropertyValueConverter<S, T, ? extends ValueConversionContext<P>> getConverter(Class<?> type, String path);
<DV, SV> PropertyValueConverter<DV, SV, ? extends ValueConversionContext<P>> getConverter(Class<?> type, String path);
/**
* Check if a converter is registered for the given type, path combination.
@ -61,7 +61,7 @@ public interface ValueConverterRegistry<P extends PersistentProperty<P>> { @@ -61,7 +61,7 @@ public interface ValueConverterRegistry<P extends PersistentProperty<P>> {
}
/**
* Check if there a converters registered.
* Check if converters are registered.
*/
boolean isEmpty();

20
src/main/java/org/springframework/data/mapping/PersistentProperty.java

@ -439,28 +439,30 @@ public interface PersistentProperty<P extends PersistentProperty<P>> { @@ -439,28 +439,30 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
}
/**
* 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.
* <p>
* 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<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<? extends PersistentProperty<?>>>> getValueConverterType() {
ValueConverter annotation = findAnnotation(ValueConverter.class);
return annotation == null ? null
: (Class<? extends PropertyValueConverter<?, ?, ? extends ValueConversionContext<? extends PersistentProperty<?>>>>) 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.
* <p>
* 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() {

2
src/test/java/org/springframework/data/convert/PropertyValueConverterFactoryUnitTests.java

@ -31,6 +31,8 @@ import org.springframework.data.mapping.context.SamplePersistentProperty; @@ -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 {

5
src/test/java/org/springframework/data/convert/PropertyValueConverterRegistrarUnitTests.java

@ -23,9 +23,12 @@ import org.springframework.data.mapping.context.SamplePersistentProperty; @@ -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() {

2
src/test/java/org/springframework/data/convert/SimplePropertyValueConversionsUnitTests.java

@ -24,6 +24,8 @@ import org.springframework.data.convert.PropertyValueConverterFactories.CachingP @@ -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 {

3
src/test/java/org/springframework/data/convert/SimplePropertyValueConverterRegistryUnitTests.java

@ -22,8 +22,11 @@ import org.junit.jupiter.api.Test; @@ -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

264
src/test/java/org/springframework/data/convert/WhatWeWant.java

@ -1,264 +0,0 @@ @@ -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<SamplePersistentProperty> 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<Object, Object> 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<P extends PersistentProperty<P>> {
ConverterConfig registerConverter(Predicate<P> filter,
PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>> converter) {
return this;
}
ConverterConfig registerConverter(Class type, String property,
PropertyValueConverter<?, ?, ? extends ValueConversionContext<P>> converter) {
PropertyPath.from(property, type);
return this;
}
/**
* Starts a converter registration by pointing to a property of a domain type.
*
* @param <T> the domain type
* @param <S> the property type
* @param type the domain type to obtain the property from
* @param property a function to describe the property to be referenced. Usually a method handle to a getter.
* @return will never be {@literal null}.
*/
<T, S> WritingConverterRegistrationBuilder<T, S, P> registerConverter(
Class<T> type, Function<T, S> property) {
String propertyName = MethodInvocationRecorder.forProxyOf(type)
.record(property)
.getPropertyPath()
.orElseThrow(() -> new IllegalArgumentException("Cannot obtain property name!"));
return new WritingConverterRegistrationBuilder<T, S, P>(type, propertyName, this);
}
<T, S> WritingConverterRegistrationBuilder<T, S, P> registerConverter(Predicate<P> predicate) {
return new WritingConverterRegistrationBuilder<T, S, P>(predicate, this);
}
/**
* Helper to build up a fluent registration API starting on
* {@link ConverterConfig#registerConverter(Class, Function)}.
*
* @author Oliver Drotbohm
*/
static class WritingConverterRegistrationBuilder<T, S, P extends PersistentProperty<P>> {
private final Consumer<PropertyValueConverter> registration;
private final ConverterConfig config;
public WritingConverterRegistrationBuilder(Class<T> type, String property, ConverterConfig config) {
this.config = config;
this.registration = (converter) -> config.registerConverter(type, property, converter);
}
public WritingConverterRegistrationBuilder(Predicate<P> predicate, ConverterConfig config) {
this.config = config;
this.registration = (converter) -> config.registerConverter(predicate, converter);
}
<R> ReadingConverterRegistrationBuilder<T, S, S, P> writingAsIs() {
return writing((source, context) -> source);
}
<R> ReadingConverterRegistrationBuilder<T, S, R, P> writing(Function<S, R> writer) {
return writing((source, context) -> writer.apply(source));
}
/**
* Describes how to convert the domain property value into the database native property.
*
* @param <R> the type to be written to the database
* @param writer the property conversion to extract a value written to the database
* @return will never be {@literal null}.
*/
<R> ReadingConverterRegistrationBuilder<T, S, R, P> writing(BiFunction<S, ValueConversionContext<P>, R> writer) {
return new ReadingConverterRegistrationBuilder<>(this, writer);
}
}
/**
* A helper to build a fluent API to register how to read a database value into a domain object property.
*
* @author Oliver Drotbohm
*/
static class ReadingConverterRegistrationBuilder<T, S, R, P extends PersistentProperty<P>> {
private WritingConverterRegistrationBuilder<T, S, P> origin;
private BiFunction<S, ValueConversionContext<P>, R> writer;
public ReadingConverterRegistrationBuilder(WritingConverterRegistrationBuilder<T, S, P> origin,
BiFunction<S, ValueConversionContext<P>, R> writer) {
this.origin = origin;
this.writer = writer;
}
ConverterConfig<P> readingAsIs() {
return reading((source, context) -> (S) source);
}
ConverterConfig<P> reading(Function<R, S> reader) {
return reading((source, context) -> reader.apply(source));
}
/**
* Describes how to read a database value into a domain object's property value.
*
* @param reader must not be {@literal null}.
* @return
*/
ConverterConfig<P> reading(BiFunction<R, ValueConversionContext<P>, 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<A, B, P extends PersistentProperty<P>>
implements PropertyValueConverter<A, B, ValueConversionContext<P>> {
private final BiFunction<A, ValueConversionContext<P>, B> writer;
private final BiFunction<B, ValueConversionContext<P>, A> reader;
public FunctionPropertyValueConverter(BiFunction<A, ValueConversionContext<P>, B> writer,
BiFunction<B, ValueConversionContext<P>, A> reader) {
this.writer = writer;
this.reader = reader;
}
@Override
public B write(A value, ValueConversionContext<P> context) {
return writer.apply(value, context);
}
@Override
public A read(B value, ValueConversionContext<P> context) {
return reader.apply(value, context);
}
}
}
}
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<P extends PersistentProperty<P>> extends ValueConversionContext<P> {
}
interface SpecificPropertyValueConverter<S, T>
extends PropertyValueConverter<S, T, SpecificValueConversionContext<SamplePersistentProperty>> {}
}
Loading…
Cancel
Save