Introduce PropertyValueConversionService and specific null-conversion methods.

PropertyValueConverter read and write methods are never called with null values. Instead, PropertyValueConverter now defines readNull and writeNull to encapsulate null conversion. PropertyValueConversionService is a facade that encapsulates these details to simplify converter usage.

Resolves #2577
Closes #2592
This commit is contained in:
Mark Paluch
2022-04-05 11:21:20 +02:00
committed by John Blum
parent d10822e6bd
commit cb5201f9d2
4 changed files with 297 additions and 9 deletions

View File

@@ -0,0 +1,111 @@
/*
* 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 org.springframework.data.mapping.PersistentProperty;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Conversion service based on {@link CustomConversions} to convert domain and store values using
* {@link PropertyValueConverter property-specific converters}.
*
* @author Mark Paluch
* @since 2.7
*/
public class PropertyValueConversionService {
private final CustomConversions conversions;
public PropertyValueConversionService(CustomConversions conversions) {
Assert.notNull(conversions, "CustomConversions must not be null");
this.conversions = conversions;
}
/**
* Return {@literal true} there is a converter registered for {@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.
*
* @param property the underlying property.
* @return {@literal true} there is a converter registered for {@link PersistentProperty}.
*/
public boolean hasConverter(PersistentProperty<?> property) {
return conversions.hasPropertyValueConverter(property);
}
/**
* Convert a value from its store-native representation into its domain-specific type.
*
* @param value the value to convert. Can be {@code null}.
* @param property the underlying property.
* @param context the context object.
* @param <P> property type.
* @param <VCC> value conversion context type.
* @return the value to be used in the domain model. Can be {@code null}.
*/
@Nullable
public <P extends PersistentProperty<P>, VCC extends ValueConversionContext<P>> Object read(@Nullable Object value,
P property, VCC context) {
PropertyValueConverter<Object, Object, ValueConversionContext<P>> converter = getRequiredConverter(property);
if (value == null) {
return converter.readNull(context);
}
return converter.read(value, context);
}
/**
* Convert a value from its domain-specific value into its store-native representation.
*
* @param value the value to convert. Can be {@code null}.
* @param property the underlying property.
* @param context the context object.
* @param <P> property type.
* @param <VCC> value conversion context type.
* @return the value to be written to the data store. Can be {@code null}.
*/
@Nullable
public <P extends PersistentProperty<P>, VCC extends ValueConversionContext<P>> Object write(@Nullable Object value,
P property, VCC context) {
PropertyValueConverter<Object, Object, ValueConversionContext<P>> converter = getRequiredConverter(property);
if (value == null) {
return converter.writeNull(context);
}
return converter.write(value, context);
}
private <P extends PersistentProperty<P>> PropertyValueConverter<Object, Object, ValueConversionContext<P>> getRequiredConverter(
P property) {
PropertyValueConverter<Object, Object, ValueConversionContext<P>> converter = conversions
.getPropertyValueConverter(property);
if (converter == null) {
throw new IllegalArgumentException(String.format("No converter registered for property %s", property));
}
return converter;
}
}

View File

@@ -26,8 +26,13 @@ import org.springframework.lang.Nullable;
* <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.
* <p>
* Converter methods are called with non-null values only and provide specific hooks for {@code null} value handling.
* {@link #readNull(ValueConversionContext)} and {@link #writeNull(ValueConversionContext)} methods are specifically
* designated to either retain {@code null} values or return a different value to indicate {@code null} values.
*
* @author Christoph Strobl
* @author Mark Paluch
* @param <DV> domain-specific type.
* @param <SV> store-native type.
* @param <C> the store specific {@link ValueConversionContext conversion context}.
@@ -39,23 +44,47 @@ 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 can be {@literal null}.
* @param value the value to read.
* @param context never {@literal null}.
* @return the converted value. Can be {@literal null}.
*/
@Nullable
DV read(@Nullable SV value, C context);
DV read(SV value, C context);
/**
* Convert the given {@code null} value from the store into it's domain value representation. Typically, a
* {@literal read} operation. Returns {@code null} by default.
*
* @param context never {@literal null}.
* @return the converted value. Can be {@literal null}.
*/
@Nullable
default DV readNull(C context) {
return null;
}
/**
* Convert the given domain-specific value into it's native store representation. Typically, a {@literal write}
* operation.
*
* @param value can be {@literal null}.
* @param value the value to write.
* @param context never {@literal null}.
* @return the converted value. Can be {@literal null}.
*/
@Nullable
SV write(@Nullable DV value, C context);
SV write(DV value, C context);
/**
* 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}.
* @return the converted value. Can be {@literal null}.
*/
@Nullable
default SV writeNull(C context) {
return null;
}
/**
* No-op {@link PropertyValueConverter} implementation.
@@ -100,14 +129,24 @@ public interface PropertyValueConverter<DV, SV, C extends ValueConversionContext
@Nullable
@Override
public SV write(@Nullable DV value, ValueConversionContext<P> context) {
public SV write(DV value, ValueConversionContext<P> context) {
return writer.apply(value, context);
}
@Override
public SV writeNull(ValueConversionContext<P> context) {
return writer.apply(null, context);
}
@Nullable
@Override
public DV read(@Nullable SV value, ValueConversionContext<P> context) {
return reader.apply(value, context);
}
@Override
public DV readNull(ValueConversionContext<P> context) {
return reader.apply(null, context);
}
}
}