DATACMNS-836 - Polishing.
RepositoryMetadata now exposes an ….isReactiveRepository() that's implemented by inspecting the backing repository interface for any reactive wrapper type appearing in method signatures. That allows us to move down the use of a ConversionService down to ReactiveRepositoryInformation.WrapperConversionMatch. Made a couple of methods static that could be. Simplified some logic using streams. A bit better wording in assertions. Some formatting when using streams.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2015 the original author or authors.
|
||||
* Copyright 2011-2016 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.
|
||||
@@ -83,4 +83,12 @@ public interface RepositoryMetadata {
|
||||
* @since 1.11
|
||||
*/
|
||||
Set<Class<?>> getAlternativeDomainTypes();
|
||||
|
||||
/**
|
||||
* Returns whether the repository is a reactive one, i.e. if it uses reactive types in one of its methods.
|
||||
*
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
boolean isReactiveRepository();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2015 the original author or authors.
|
||||
* Copyright 2011-2016 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.
|
||||
@@ -25,6 +25,7 @@ import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.CrudMethods;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.util.QueryExecutionConverters;
|
||||
import org.springframework.data.repository.util.ReactiveWrappers;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
@@ -126,6 +127,15 @@ public abstract class AbstractRepositoryMetadata implements RepositoryMetadata {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.RepositoryMetadata#isReactiveRepository()
|
||||
*/
|
||||
@Override
|
||||
public boolean isReactiveRepository() {
|
||||
return ReactiveWrappers.usesReactiveType(repositoryInterface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively unwraps well known wrapper types from the given {@link TypeInformation}.
|
||||
*
|
||||
|
||||
@@ -47,11 +47,12 @@ import org.springframework.util.ClassUtils;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class DefaultRepositoryInformation implements RepositoryInformation {
|
||||
|
||||
@SuppressWarnings("rawtypes") private static final TypeVariable<Class<Repository>>[] PARAMETERS = Repository.class
|
||||
.getTypeParameters();
|
||||
@SuppressWarnings("rawtypes") //
|
||||
private static final TypeVariable<Class<Repository>>[] PARAMETERS = Repository.class.getTypeParameters();
|
||||
private static final String DOMAIN_TYPE_NAME = PARAMETERS[0].getName();
|
||||
private static final String ID_TYPE_NAME = PARAMETERS[1].getName();
|
||||
|
||||
@@ -334,42 +335,13 @@ class DefaultRepositoryInformation implements RepositoryInformation {
|
||||
return metadata.getAlternativeDomainTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given method's parameters to match the ones of the given base class method. Matches generic arguments
|
||||
* against the ones bound in the given repository interface.
|
||||
*
|
||||
* @param method
|
||||
* @param baseClassMethod
|
||||
* @return
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.RepositoryMetadata#isReactiveRepository()
|
||||
*/
|
||||
private boolean parametersMatch(Method method, Method baseClassMethod) {
|
||||
|
||||
Class<?>[] methodParameterTypes = method.getParameterTypes();
|
||||
Type[] genericTypes = baseClassMethod.getGenericParameterTypes();
|
||||
Class<?>[] types = baseClassMethod.getParameterTypes();
|
||||
|
||||
for (int i = 0; i < genericTypes.length; i++) {
|
||||
|
||||
Type genericType = genericTypes[i];
|
||||
Class<?> type = types[i];
|
||||
MethodParameter parameter = new MethodParameter(method, i);
|
||||
Class<?> parameterType = resolveParameterType(parameter, metadata.getRepositoryInterface());
|
||||
|
||||
if (genericType instanceof TypeVariable<?>) {
|
||||
|
||||
if (!matchesGenericType((TypeVariable<?>) genericType, ResolvableType.forMethodParameter(parameter))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!type.isAssignableFrom(parameterType) || !type.equals(methodParameterTypes[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@Override
|
||||
public boolean isReactiveRepository() {
|
||||
return metadata.isReactiveRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -408,4 +380,42 @@ class DefaultRepositoryInformation implements RepositoryInformation {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given method's parameters to match the ones of the given base class method. Matches generic arguments
|
||||
* against the ones bound in the given repository interface.
|
||||
*
|
||||
* @param method
|
||||
* @param baseClassMethod
|
||||
* @return
|
||||
*/
|
||||
private boolean parametersMatch(Method method, Method baseClassMethod) {
|
||||
|
||||
Class<?>[] methodParameterTypes = method.getParameterTypes();
|
||||
Type[] genericTypes = baseClassMethod.getGenericParameterTypes();
|
||||
Class<?>[] types = baseClassMethod.getParameterTypes();
|
||||
|
||||
for (int i = 0; i < genericTypes.length; i++) {
|
||||
|
||||
Type genericType = genericTypes[i];
|
||||
Class<?> type = types[i];
|
||||
MethodParameter parameter = new MethodParameter(method, i);
|
||||
Class<?> parameterType = resolveParameterType(parameter, metadata.getRepositoryInterface());
|
||||
|
||||
if (genericType instanceof TypeVariable<?>) {
|
||||
|
||||
if (!matchesGenericType((TypeVariable<?>) genericType, ResolvableType.forMethodParameter(parameter))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!type.isAssignableFrom(parameterType) || !type.equals(methodParameterTypes[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2016 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.
|
||||
@@ -72,14 +72,15 @@ class QueryExecutionResultHandler {
|
||||
|
||||
TypeDescriptor targetType = TypeDescriptor.valueOf(expectedReturnType);
|
||||
|
||||
if(conversionService.canConvert(WRAPPER_TYPE, returnTypeDescriptor)
|
||||
&& !conversionService.canBypassConvert(WRAPPER_TYPE, targetType)) {
|
||||
if (conversionService.canConvert(WRAPPER_TYPE, returnTypeDescriptor)
|
||||
&& !conversionService.canBypassConvert(WRAPPER_TYPE, targetType)) {
|
||||
|
||||
return conversionService.convert(new NullableWrapper(result), expectedReturnType);
|
||||
}
|
||||
|
||||
if(result != null && conversionService.canConvert(TypeDescriptor.valueOf(result.getClass()), returnTypeDescriptor)
|
||||
&& !conversionService.canBypassConvert(TypeDescriptor.valueOf(result.getClass()), targetType)) {
|
||||
if (result != null
|
||||
&& conversionService.canConvert(TypeDescriptor.valueOf(result.getClass()), returnTypeDescriptor)
|
||||
&& !conversionService.canBypassConvert(TypeDescriptor.valueOf(result.getClass()), targetType)) {
|
||||
|
||||
return conversionService.convert(result, expectedReturnType);
|
||||
}
|
||||
|
||||
@@ -17,15 +17,21 @@ package org.springframework.data.repository.core.support;
|
||||
|
||||
import static org.springframework.core.GenericTypeResolver.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.util.QueryExecutionConverters;
|
||||
import org.springframework.data.repository.util.ReactiveWrapperConverters;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -33,29 +39,22 @@ import org.springframework.util.Assert;
|
||||
* converted for invocation of implementation methods.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Gierke
|
||||
* @since 2.0
|
||||
*/
|
||||
public class ReactiveRepositoryInformation extends DefaultRepositoryInformation {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReactiveRepositoryInformation} for the given repository interface and repository base class
|
||||
* using a {@link ConversionService}.
|
||||
* Creates a new {@link ReactiveRepositoryInformation} for the given {@link RepositoryMetadata}, repository base
|
||||
* class, custom implementation and {@link ConversionService}.
|
||||
*
|
||||
* @param metadata must not be {@literal null}.
|
||||
* @param repositoryBaseClass must not be {@literal null}.
|
||||
* @param customImplementationClass
|
||||
* @param conversionService must not be {@literal null}.
|
||||
* @param customImplementationClass can be {@literal null}.
|
||||
*/
|
||||
public ReactiveRepositoryInformation(RepositoryMetadata metadata, Class<?> repositoryBaseClass,
|
||||
Class<?> customImplementationClass, ConversionService conversionService) {
|
||||
|
||||
Class<?> customImplementationClass) {
|
||||
super(metadata, repositoryBaseClass, customImplementationClass);
|
||||
|
||||
Assert.notNull(conversionService, "Conversion service must not be null!");
|
||||
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,8 +62,8 @@ public class ReactiveRepositoryInformation extends DefaultRepositoryInformation
|
||||
* at the target class. Returns the given method if the given base class does not declare the method given. Takes
|
||||
* generics into account.
|
||||
*
|
||||
* @param method must not be {@literal null}
|
||||
* @param baseClass
|
||||
* @param method must not be {@literal null}.
|
||||
* @param baseClass can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@@ -76,13 +75,13 @@ public class ReactiveRepositoryInformation extends DefaultRepositoryInformation
|
||||
|
||||
if (usesParametersWithReactiveWrappers(method)) {
|
||||
|
||||
Method candidate = getMethodCandidate(method, baseClass, new AssignableWrapperMatch(method));
|
||||
Method candidate = getMethodCandidate(method, baseClass, new AssignableWrapperMatch(method.getParameterTypes()));
|
||||
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
candidate = getMethodCandidate(method, baseClass, new WrapperConversionMatch(method, conversionService));
|
||||
candidate = getMethodCandidate(method, baseClass, WrapperConversionMatch.of(method.getParameterTypes()));
|
||||
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
@@ -90,31 +89,50 @@ public class ReactiveRepositoryInformation extends DefaultRepositoryInformation
|
||||
}
|
||||
|
||||
Method candidate = getMethodCandidate(method, baseClass,
|
||||
new MatchParameterOrComponentType(method, getRepositoryInterface()));
|
||||
MatchParameterOrComponentType.of(method, getRepositoryInterface()));
|
||||
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
return method;
|
||||
return candidate != null ? candidate : method;
|
||||
}
|
||||
|
||||
private boolean usesParametersWithReactiveWrappers(Method method) {
|
||||
/**
|
||||
* Checks whether the type is a wrapper without unwrapping support. Reactive wrappers don't like to be unwrapped.
|
||||
*
|
||||
* @param parameterType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
static boolean isNonUnwrappingWrapper(Class<?> parameterType) {
|
||||
|
||||
boolean wantsWrappers = false;
|
||||
Assert.notNull(parameterType, "Parameter type must not be null!");
|
||||
|
||||
for (Class<?> parameterType : method.getParameterTypes()) {
|
||||
|
||||
if (isNonUnwrappingWrapper(parameterType)) {
|
||||
wantsWrappers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return wantsWrappers;
|
||||
return QueryExecutionConverters.supports(parameterType)
|
||||
&& !QueryExecutionConverters.supportsUnwrapping(parameterType);
|
||||
}
|
||||
|
||||
private Method getMethodCandidate(Method method, Class<?> baseClass, BiPredicate<Class<?>, Integer> predicate) {
|
||||
/**
|
||||
* Returns whether the given {@link Method} uses a reactive wrapper type as parameter.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static boolean usesParametersWithReactiveWrappers(Method method) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
|
||||
return Arrays.stream(method.getParameterTypes())//
|
||||
.anyMatch(ReactiveRepositoryInformation::isNonUnwrappingWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a candidate method from the base class for the given one or the method given in the first place if none one
|
||||
* the base class matches.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param baseClass must not be {@literal null}.
|
||||
* @param predicate must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static Method getMethodCandidate(Method method, Class<?> baseClass,
|
||||
BiPredicate<Class<?>, Integer> predicate) {
|
||||
|
||||
for (Method baseClassMethod : baseClass.getMethods()) {
|
||||
|
||||
@@ -143,12 +161,13 @@ public class ReactiveRepositoryInformation extends DefaultRepositoryInformation
|
||||
* Checks the given method's parameters to match the ones of the given base class method. Matches generic arguments
|
||||
* against the ones bound in the given repository interface.
|
||||
*
|
||||
* @param method
|
||||
* @param baseClassMethod
|
||||
* @param predicate
|
||||
* @param method must not be {@literal null}.
|
||||
* @param baseClassMethod must not be {@literal null}.
|
||||
* @param predicate must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private boolean parametersMatch(Method method, Method baseClassMethod, BiPredicate<Class<?>, Integer> predicate) {
|
||||
private static boolean parametersMatch(Method method, Method baseClassMethod,
|
||||
BiPredicate<Class<?>, Integer> predicate) {
|
||||
|
||||
Type[] genericTypes = baseClassMethod.getGenericParameterTypes();
|
||||
Class<?>[] types = baseClassMethod.getParameterTypes();
|
||||
@@ -162,46 +181,34 @@ public class ReactiveRepositoryInformation extends DefaultRepositoryInformation
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the type is a wrapper without unwrapping support. Reactive wrappers don't like to be unwrapped.
|
||||
*
|
||||
* @param parameterType
|
||||
* @return
|
||||
*/
|
||||
static boolean isNonUnwrappingWrapper(Class<?> parameterType) {
|
||||
return QueryExecutionConverters.supports(parameterType)
|
||||
&& !QueryExecutionConverters.supportsUnwrapping(parameterType);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BiPredicate} to check whether a method parameter is a {@link #isNonUnwrappingWrapper(Class)} and can be
|
||||
* converted into a different wrapper. Usually {@link rx.Observable} to {@link org.reactivestreams.Publisher}
|
||||
* conversion.
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
static class WrapperConversionMatch implements BiPredicate<Class<?>, Integer> {
|
||||
|
||||
final Method declaredMethod;
|
||||
final Class<?>[] declaredParameterTypes;
|
||||
final ConversionService conversionService;
|
||||
|
||||
public WrapperConversionMatch(Method declaredMethod, ConversionService conversionService) {
|
||||
|
||||
this.declaredMethod = declaredMethod;
|
||||
this.declaredParameterTypes = declaredMethod.getParameterTypes();
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
private static final ConversionService CONVERSION_SERVICE = ReactiveWrapperConverters
|
||||
.registerConvertersIn(new DefaultConversionService());
|
||||
private final Class<?>[] declaredParameterTypes;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.function.BiPredicate#test(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean test(Class<?> candidateParameterType, Integer index) {
|
||||
|
||||
if (isNonUnwrappingWrapper(candidateParameterType) && isNonUnwrappingWrapper(declaredParameterTypes[index])) {
|
||||
|
||||
if (conversionService.canConvert(declaredParameterTypes[index], candidateParameterType)) {
|
||||
return true;
|
||||
}
|
||||
if (!isNonUnwrappingWrapper(candidateParameterType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (!isNonUnwrappingWrapper(declaredParameterTypes[index])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CONVERSION_SERVICE.canConvert(declaredParameterTypes[index], candidateParameterType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,28 +217,27 @@ public class ReactiveRepositoryInformation extends DefaultRepositoryInformation
|
||||
* a declared parameter. Usually {@link reactor.core.publisher.Flux} vs. {@link org.reactivestreams.Publisher}
|
||||
* conversion.
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
static class AssignableWrapperMatch implements BiPredicate<Class<?>, Integer> {
|
||||
|
||||
final Method declaredMethod;
|
||||
final Class<?>[] declaredParameterTypes;
|
||||
|
||||
public AssignableWrapperMatch(Method declaredMethod) {
|
||||
|
||||
this.declaredMethod = declaredMethod;
|
||||
this.declaredParameterTypes = declaredMethod.getParameterTypes();
|
||||
}
|
||||
private final Class<?>[] declaredParameterTypes;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.function.BiPredicate#test(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean test(Class<?> candidateParameterType, Integer index) {
|
||||
|
||||
if (isNonUnwrappingWrapper(candidateParameterType) && isNonUnwrappingWrapper(declaredParameterTypes[index])) {
|
||||
|
||||
if (declaredParameterTypes[index].isAssignableFrom(candidateParameterType)) {
|
||||
return true;
|
||||
}
|
||||
if (!isNonUnwrappingWrapper(candidateParameterType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (!isNonUnwrappingWrapper(declaredParameterTypes[index])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return declaredParameterTypes[index].isAssignableFrom(candidateParameterType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,31 +247,29 @@ public class ReactiveRepositoryInformation extends DefaultRepositoryInformation
|
||||
*
|
||||
* @see QueryExecutionConverters
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
static class MatchParameterOrComponentType implements BiPredicate<Class<?>, Integer> {
|
||||
|
||||
final Method declaredMethod;
|
||||
final Class<?>[] declaredParameterTypes;
|
||||
final Class<?> repositoryInterface;
|
||||
private final Method declaredMethod;
|
||||
private final Class<?>[] declaredParameterTypes;
|
||||
private final Class<?> repositoryInterface;
|
||||
|
||||
public MatchParameterOrComponentType(Method declaredMethod, Class<?> repositoryInterface) {
|
||||
|
||||
this.declaredMethod = declaredMethod;
|
||||
this.declaredParameterTypes = declaredMethod.getParameterTypes();
|
||||
this.repositoryInterface = repositoryInterface;
|
||||
public static MatchParameterOrComponentType of(Method declaredMethod, Class<?> repositoryInterface) {
|
||||
return new MatchParameterOrComponentType(declaredMethod, declaredMethod.getParameterTypes(), repositoryInterface);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.function.BiPredicate#test(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean test(Class<?> candidateParameterType, Integer index) {
|
||||
|
||||
MethodParameter parameter = new MethodParameter(declaredMethod, index);
|
||||
Class<?> parameterType = resolveParameterType(parameter, repositoryInterface);
|
||||
|
||||
if (!candidateParameterType.isAssignableFrom(parameterType)
|
||||
|| !candidateParameterType.equals(declaredParameterTypes[index])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return candidateParameterType.isAssignableFrom(parameterType)
|
||||
&& candidateParameterType.equals(declaredParameterTypes[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
@@ -51,6 +52,7 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.util.ClassUtils;
|
||||
import org.springframework.data.repository.util.ReactiveWrapperConverters;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -79,7 +81,6 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
|
||||
private ClassLoader classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
|
||||
private EvaluationContextProvider evaluationContextProvider = DefaultEvaluationContextProvider.INSTANCE;
|
||||
private BeanFactory beanFactory;
|
||||
private ConversionService conversionService;
|
||||
|
||||
private QueryCollectingQueryCreationListener collectingListener = new QueryCollectingQueryCreationListener();
|
||||
|
||||
@@ -105,16 +106,6 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
|
||||
this.namedQueries = namedQueries == null ? PropertiesBasedNamedQueries.EMPTY : namedQueries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a {@link ConversionService} instance to convert method parameters when calling implementation methods on
|
||||
* the base class or a custom implementation.
|
||||
*
|
||||
* @param conversionService the conversionService to set.
|
||||
*/
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
|
||||
@@ -232,12 +223,9 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
|
||||
|
||||
result.addAdvice(new QueryExecutorMethodInterceptor(information));
|
||||
|
||||
if (conversionService == null) {
|
||||
result.addAdvice(new ImplementationMethodExecutionInterceptor(information, customImplementation, target));
|
||||
} else {
|
||||
result.addAdvice(new ConvertingImplementationMethodExecutionInterceptor(information, customImplementation, target,
|
||||
conversionService));
|
||||
}
|
||||
result.addAdvice(information.isReactiveRepository()
|
||||
? new ConvertingImplementationMethodExecutionInterceptor(information, customImplementation, target)
|
||||
: new ImplementationMethodExecutionInterceptor(information, customImplementation, target));
|
||||
|
||||
return (T) result.getProxy(classLoader);
|
||||
}
|
||||
@@ -272,16 +260,9 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
|
||||
Class<?> repositoryBaseClass = this.repositoryBaseClass == null ? getRepositoryBaseClass(metadata)
|
||||
: this.repositoryBaseClass;
|
||||
|
||||
if (conversionService == null) {
|
||||
repositoryInformation = new DefaultRepositoryInformation(metadata, repositoryBaseClass,
|
||||
customImplementationClass);
|
||||
} else {
|
||||
// TODO: Not sure this is the best idea but at some point we need to distinguish between
|
||||
// methods that want to get unwrapped data from wrapped parameters and those which are
|
||||
// just fine with wrappers because at some point a converting interceptor kicks in
|
||||
repositoryInformation = new ReactiveRepositoryInformation(metadata, repositoryBaseClass,
|
||||
customImplementationClass, conversionService);
|
||||
}
|
||||
repositoryInformation = metadata.isReactiveRepository()
|
||||
? new ReactiveRepositoryInformation(metadata, repositoryBaseClass, customImplementationClass)
|
||||
: new DefaultRepositoryInformation(metadata, repositoryBaseClass, customImplementationClass);
|
||||
|
||||
repositoryInformationCache.put(cacheKey, repositoryInformation);
|
||||
return repositoryInformation;
|
||||
@@ -602,18 +583,18 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
|
||||
*/
|
||||
public class ConvertingImplementationMethodExecutionInterceptor extends ImplementationMethodExecutionInterceptor {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
private final ConversionService conversionService = ReactiveWrapperConverters
|
||||
.registerConvertersIn(new DefaultConversionService());
|
||||
|
||||
/**
|
||||
* @param repositoryInformation
|
||||
* @param customImplementation
|
||||
* @param target
|
||||
* @param conversionService
|
||||
*/
|
||||
public ConvertingImplementationMethodExecutionInterceptor(RepositoryInformation repositoryInformation,
|
||||
Object customImplementation, Object target, ConversionService conversionService) {
|
||||
Object customImplementation, Object target) {
|
||||
|
||||
super(repositoryInformation, customImplementation, target);
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
|
||||
@@ -158,7 +158,7 @@ public class ResultProcessor {
|
||||
return (T) new StreamQueryResultHandler(type, converter).handle(source);
|
||||
}
|
||||
|
||||
if(ReactiveWrapperConverters.supports(source.getClass())){
|
||||
if (ReactiveWrapperConverters.supports(source.getClass())) {
|
||||
return (T) ReactiveWrapperConverters.map(source, converter::convert);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.repository.util;
|
||||
|
||||
import scala.Function0;
|
||||
import scala.Option;
|
||||
import scala.runtime.AbstractFunction0;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -33,10 +37,6 @@ import org.springframework.util.concurrent.ListenableFuture;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
import scala.Function0;
|
||||
import scala.Option;
|
||||
import scala.runtime.AbstractFunction0;
|
||||
|
||||
/**
|
||||
* Converters to potentially wrap the execution of a repository method into a variety of wrapper types potentially being
|
||||
* available on the classpath. Currently supported:
|
||||
@@ -105,7 +105,7 @@ public abstract class QueryExecutionConverters {
|
||||
UNWRAPPERS.add(ScalOptionUnwrapper.INSTANCE);
|
||||
}
|
||||
|
||||
if(ReactiveWrappers.isAvailable()) {
|
||||
if (ReactiveWrappers.isAvailable()) {
|
||||
WRAPPER_TYPES.addAll(ReactiveWrappers.getNoValueTypes());
|
||||
WRAPPER_TYPES.addAll(ReactiveWrappers.getSingleValueTypes());
|
||||
WRAPPER_TYPES.addAll(ReactiveWrappers.getMultiValueTypes());
|
||||
|
||||
@@ -17,19 +17,6 @@ package org.springframework.data.repository.util;
|
||||
|
||||
import static org.springframework.data.repository.util.ReactiveWrapperConverters.RegistryHolder.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.repository.util.ReactiveWrappers.ReactiveLibrary;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import io.reactivex.BackpressureStrategy;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Maybe;
|
||||
@@ -40,6 +27,20 @@ import rx.Completable;
|
||||
import rx.Observable;
|
||||
import rx.Single;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.repository.util.ReactiveWrappers.ReactiveLibrary;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Conversion support for reactive wrapper types. This class is a reactive extension to
|
||||
* {@link QueryExecutionConverters}.
|
||||
@@ -89,7 +90,7 @@ public class ReactiveWrapperConverters {
|
||||
*
|
||||
* @param conversionService must not be {@literal null}.
|
||||
*/
|
||||
public static void registerConvertersIn(ConfigurableConversionService conversionService) {
|
||||
public static ConversionService registerConvertersIn(ConfigurableConversionService conversionService) {
|
||||
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
|
||||
@@ -151,6 +152,8 @@ public class ReactiveWrapperConverters {
|
||||
conversionService.addConverter(RxJava2ObservableToMaybeConverter.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,14 +203,11 @@ public class ReactiveWrapperConverters {
|
||||
Assert.notNull(stream, "Stream must not be null!");
|
||||
Assert.notNull(converter, "Converter must not be null!");
|
||||
|
||||
for (ReactiveTypeWrapper<?> reactiveWrapper : REACTIVE_WRAPPERS) {
|
||||
|
||||
if (ClassUtils.isAssignable(reactiveWrapper.getWrapperClass(), stream.getClass())) {
|
||||
return (T) reactiveWrapper.map(stream, converter);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException(String.format("Cannot apply converter to %s", stream));
|
||||
return REACTIVE_WRAPPERS.stream()//
|
||||
.filter(it -> ClassUtils.isAssignable(it.getWrapperClass(), stream.getClass()))//
|
||||
.findFirst()//
|
||||
.map(it -> (T) it.map(stream, converter))//
|
||||
.orElseThrow(() -> new IllegalStateException(String.format("Cannot apply converter to %s", stream)));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.repository.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import rx.Completable;
|
||||
import rx.Observable;
|
||||
import rx.Single;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -25,16 +33,10 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.core.ReactiveAdapter.Descriptor;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import rx.Completable;
|
||||
import rx.Observable;
|
||||
import rx.Single;
|
||||
|
||||
/**
|
||||
* Utility class to expose details about reactive wrapper types. This class exposes whether a reactive wrapper is
|
||||
* supported in general and whether a particular type is suitable for no-value/single-value/multi-value usage.
|
||||
@@ -44,6 +46,7 @@ import rx.Single;
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
* @since 2.0
|
||||
* @see org.reactivestreams.Publisher
|
||||
* @see rx.Single
|
||||
@@ -71,6 +74,15 @@ public class ReactiveWrappers {
|
||||
|
||||
private static final Map<Class<?>, Descriptor> REACTIVE_WRAPPERS;
|
||||
|
||||
/**
|
||||
* Enumeration of supported reactive libraries.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
static enum ReactiveLibrary {
|
||||
PROJECT_REACTOR, RXJAVA1, RXJAVA2;
|
||||
}
|
||||
|
||||
static {
|
||||
|
||||
Map<Class<?>, Descriptor> reactiveWrappers = new LinkedHashMap<>(5);
|
||||
@@ -102,14 +114,13 @@ public class ReactiveWrappers {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@literal true} if reactive support is available. More specifically, whether RxJava1/2 or Project Reactor
|
||||
* libraries are on the class path.
|
||||
* Returns {@literal true} if reactive support is available. More specifically, whether any of the libraries defined
|
||||
* in {@link ReactiveLibrary} are on the class path.
|
||||
*
|
||||
* @return {@literal true} if reactive support is available.
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
return isAvailable(ReactiveLibrary.PROJECT_REACTOR) || isAvailable(ReactiveLibrary.RXJAVA1)
|
||||
|| isAvailable(ReactiveLibrary.RXJAVA2);
|
||||
return Arrays.stream(ReactiveLibrary.values()).anyMatch(ReactiveWrappers::isAvailable);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +131,7 @@ public class ReactiveWrappers {
|
||||
*/
|
||||
public static boolean isAvailable(ReactiveLibrary reactiveLibrary) {
|
||||
|
||||
Assert.notNull(reactiveLibrary, "ReactiveLibrary must not be null!");
|
||||
Assert.notNull(reactiveLibrary, "Reactive library must not be null!");
|
||||
|
||||
switch (reactiveLibrary) {
|
||||
case PROJECT_REACTOR:
|
||||
@@ -129,9 +140,9 @@ public class ReactiveWrappers {
|
||||
return RXJAVA1_PRESENT;
|
||||
case RXJAVA2:
|
||||
return RXJAVA2_PRESENT;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Reactive library %s not supported", reactiveLibrary));
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("ReactiveLibrary %s not supported", reactiveLibrary));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,8 +155,19 @@ public class ReactiveWrappers {
|
||||
return isWrapper(ClassUtils.getUserClass(type));
|
||||
}
|
||||
|
||||
private static boolean isWrapper(Class<?> type) {
|
||||
return isNoValueType(type) || isSingleValueType(type) || isMultiValueType(type);
|
||||
/**
|
||||
* Returns whether the given type uses any reactive wrapper type in its method signatures.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static boolean usesReactiveType(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return Arrays.stream(type.getMethods())//
|
||||
.flatMap(ReflectionUtils::returnTypeAndParameters)//
|
||||
.anyMatch(ReactiveWrappers::supports);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,7 +178,7 @@ public class ReactiveWrappers {
|
||||
*/
|
||||
public static boolean isNoValueType(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Class must not be null!");
|
||||
Assert.notNull(type, "Candidate type must not be null!");
|
||||
|
||||
return findDescriptor(type).map(Descriptor::isNoValue).orElse(false);
|
||||
}
|
||||
@@ -169,10 +191,9 @@ public class ReactiveWrappers {
|
||||
*/
|
||||
public static boolean isSingleValueType(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Class must not be null!");
|
||||
Assert.notNull(type, "Candidate type must not be null!");
|
||||
|
||||
return findDescriptor(type).map((descriptor) -> !descriptor.isMultiValue() && !descriptor.isNoValue())
|
||||
.orElse(false);
|
||||
return findDescriptor(type).map(it -> !it.isMultiValue() && !it.isNoValue()).orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,63 +206,75 @@ public class ReactiveWrappers {
|
||||
*/
|
||||
public static boolean isMultiValueType(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Class must not be null!");
|
||||
Assert.notNull(type, "Candidate type must not be null!");
|
||||
|
||||
// Prevent single-types with a multi-hierarchy supertype to be reported as multi type
|
||||
// See Mono implements Publisher
|
||||
if (isSingleValueType(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return findDescriptor(type).map(Descriptor::isMultiValue).orElse(false);
|
||||
return isSingleValueType(type) ? false : findDescriptor(type).map(Descriptor::isMultiValue).orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of No-Value wrapper types.
|
||||
* Returns a collection of no-value wrapper types.
|
||||
*
|
||||
* @return a collection of No-Value wrapper types.
|
||||
* @return a collection of no-value wrapper types.
|
||||
*/
|
||||
public static Collection<Class<?>> getNoValueTypes() {
|
||||
return REACTIVE_WRAPPERS.entrySet().stream().filter(entry -> entry.getValue().isNoValue()).map(Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return REACTIVE_WRAPPERS.entrySet().stream()//
|
||||
.filter(entry -> entry.getValue().isNoValue())//
|
||||
.map(Entry::getKey).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of Single-Value wrapper types.
|
||||
* Returns a collection of single-value wrapper types.
|
||||
*
|
||||
* @return a collection of Single-Value wrapper types.
|
||||
* @return a collection of single-value wrapper types.
|
||||
*/
|
||||
public static Collection<Class<?>> getSingleValueTypes() {
|
||||
return REACTIVE_WRAPPERS.entrySet().stream().filter(entry -> !entry.getValue().isMultiValue()).map(Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return REACTIVE_WRAPPERS.entrySet().stream()//
|
||||
.filter(entry -> !entry.getValue().isMultiValue())//
|
||||
.map(Entry::getKey).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of Multi-Value wrapper types.
|
||||
* Returns a collection of multi-value wrapper types.
|
||||
*
|
||||
* @return a collection of Multi-Value wrapper types.
|
||||
* @return a collection of multi-value wrapper types.
|
||||
*/
|
||||
public static Collection<Class<?>> getMultiValueTypes() {
|
||||
return REACTIVE_WRAPPERS.entrySet().stream().filter(entry -> entry.getValue().isMultiValue()).map(Entry::getKey)
|
||||
|
||||
return REACTIVE_WRAPPERS.entrySet().stream()//
|
||||
.filter(entry -> entry.getValue().isMultiValue())//
|
||||
.map(Entry::getKey)//
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static Optional<Descriptor> findDescriptor(Class<?> rhsType) {
|
||||
/**
|
||||
* Returns whether the given type is a reactive wrapper type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static boolean isWrapper(Class<?> type) {
|
||||
|
||||
for (Class<?> type : REACTIVE_WRAPPERS.keySet()) {
|
||||
if (org.springframework.util.ClassUtils.isAssignable(type, rhsType)) {
|
||||
return Optional.ofNullable(REACTIVE_WRAPPERS.get(type));
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
Assert.notNull(type, "Candidate type must not be null!");
|
||||
|
||||
return isNoValueType(type) || isSingleValueType(type) || isMultiValueType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of supported reactive libraries.
|
||||
* Looks up a {@link Descriptor} for the given wrapper type.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
enum ReactiveLibrary {
|
||||
PROJECT_REACTOR, RXJAVA1, RXJAVA2;
|
||||
private static Optional<Descriptor> findDescriptor(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Wrapper type must not be null!");
|
||||
|
||||
return REACTIVE_WRAPPERS.entrySet().stream()//
|
||||
.filter(it -> ClassUtils.isAssignable(it.getKey(), type))//
|
||||
.findFirst().map(it -> it.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -264,6 +265,23 @@ public abstract class ReflectionUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Stream} of the return and parameters types of the given {@link Method}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return
|
||||
* @since 2.0
|
||||
*/
|
||||
public static Stream<Class<?>> returnTypeAndParameters(Method method) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
|
||||
Stream<Class<?>> returnType = Stream.of(method.getReturnType());
|
||||
Stream<Class<?>> parameterTypes = Arrays.stream(method.getParameterTypes());
|
||||
|
||||
return Stream.concat(returnType, parameterTypes);
|
||||
}
|
||||
|
||||
private static final boolean argumentsMatch(Class<?>[] parameterTypes, Object[] arguments) {
|
||||
|
||||
if (parameterTypes.length != arguments.length) {
|
||||
|
||||
@@ -51,8 +51,8 @@ public class StreamUtils {
|
||||
Spliterator<T> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL);
|
||||
Stream<T> stream = StreamSupport.stream(spliterator, false);
|
||||
|
||||
return iterator instanceof CloseableIterator ? stream.onClose(new CloseableIteratorDisposingRunnable(
|
||||
(CloseableIterator<T>) iterator)) : stream;
|
||||
return iterator instanceof CloseableIterator
|
||||
? stream.onClose(new CloseableIteratorDisposingRunnable((CloseableIterator<T>) iterator)) : stream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user