DATACMNS-1762 - Support Optional wrapping for projection getters.

We now support nullable wrappers for projection interfaces. Getters are inspected whether their return type is a supported nullable wrapper. If so, then the value can be wrapped into that type. Null values default in that case to their corresponding empty wrapper representation.

Original Pull Request: #459
This commit is contained in:
Mark Paluch
2020-07-16 15:17:10 +02:00
committed by Christoph Strobl
parent 04f59f1307
commit bd3992dfc5
4 changed files with 65 additions and 15 deletions

View File

@@ -31,6 +31,8 @@ import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.NullableWrapper;
import org.springframework.data.util.NullableWrapperConverters;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -67,23 +69,43 @@ class ProjectingMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(@SuppressWarnings("null") @Nonnull MethodInvocation invocation) throws Throwable {
TypeInformation<?> type = ClassTypeInformation.fromReturnTypeOf(invocation.getMethod());
TypeInformation<?> resultType = type;
TypeInformation<?> typeToReturn = type;
Object result = delegate.invoke(invocation);
boolean applyWrapper = false;
if (NullableWrapperConverters.supports(type.getType())
&& (result == null || !NullableWrapperConverters.supports(result.getClass()))) {
resultType = NullableWrapperConverters.unwrapActualType(typeToReturn);
applyWrapper = true;
}
result = potentiallyConvertResult(resultType, result);
if (applyWrapper) {
return conversionService.convert(new NullableWrapper(result), typeToReturn.getType());
}
return result;
}
@Nullable
protected Object potentiallyConvertResult(TypeInformation<?> type, @Nullable Object result) {
if (result == null) {
return null;
}
TypeInformation<?> type = ClassTypeInformation.fromReturnTypeOf(invocation.getMethod());
Class<?> rawType = type.getType();
if (type.isCollectionLike() && !ClassUtils.isPrimitiveArray(rawType)) {
if (type.isCollectionLike() && !ClassUtils.isPrimitiveArray(type.getType())) {
return projectCollectionElements(asCollection(result), type);
} else if (type.isMap()) {
return projectMapValues((Map<?, ?>) result, type);
} else if (conversionRequiredAndPossible(result, rawType)) {
return conversionService.convert(result, rawType);
} else if (conversionRequiredAndPossible(result, type.getType())) {
return conversionService.convert(result, type.getType());
} else {
return getProjection(result, rawType);
return getProjection(result, type.getType());
}
}

View File

@@ -26,8 +26,9 @@ import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.util.NullableWrapperConverters;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -47,8 +48,14 @@ import org.springframework.util.ConcurrentReferenceHashMap;
*/
class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware {
final static GenericConversionService CONVERSION_SERVICE = new DefaultConversionService();
static {
NullableWrapperConverters.registerConvertersIn(CONVERSION_SERVICE);
CONVERSION_SERVICE.removeConvertible(Object.class, Object.class);
}
private final List<MethodInterceptorFactory> factories;
private final ConversionService conversionService;
private final Map<Class<?>, ProjectionInformation> projectionInformationCache = new ConcurrentReferenceHashMap<>();
private @Nullable ClassLoader classLoader;
@@ -60,8 +67,6 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
this.factories = new ArrayList<>();
this.factories.add(MapAccessingMethodInterceptorFactory.INSTANCE);
this.factories.add(PropertyAccessingMethodInvokerFactory.INSTANCE);
this.conversionService = DefaultConversionService.getSharedInstance();
}
/*
@@ -174,7 +179,7 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
.createMethodInterceptor(source, projectionType);
return new ProjectingMethodInterceptor(this,
postProcessAccessorInterceptor(propertyInvocationInterceptor, source, projectionType), conversionService);
postProcessAccessorInterceptor(propertyInvocationInterceptor, source, projectionType), CONVERSION_SERVICE);
}
/**