Skip to content

Commit 2894ff3

Browse files
Move and reuse existing nullability validator.
Original Pull Request: #3244
1 parent c6d2758 commit 2894ff3

11 files changed

+335
-221
lines changed

Diff for: src/main/java/org/springframework/data/projection/ProjectingMethodInterceptor.java

-11
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,10 @@
2424
import java.util.Map;
2525
import java.util.Map.Entry;
2626

27-
import kotlin.reflect.KFunction;
2827
import org.aopalliance.intercept.MethodInterceptor;
2928
import org.aopalliance.intercept.MethodInvocation;
3029
import org.springframework.core.CollectionFactory;
31-
import org.springframework.core.KotlinDetector;
3230
import org.springframework.core.convert.ConversionService;
33-
import org.springframework.data.util.KotlinReflectionUtils;
3431
import org.springframework.data.util.NullableWrapper;
3532
import org.springframework.data.util.NullableWrapperConverters;
3633
import org.springframework.data.util.TypeInformation;
@@ -90,14 +87,6 @@ public Object invoke(@SuppressWarnings("null") @NonNull MethodInvocation invocat
9087
return conversionService.convert(new NullableWrapper(result), typeToReturn.getType());
9188
}
9289

93-
if (result == null) {
94-
KFunction<?> function = KotlinDetector.isKotlinType(method.getDeclaringClass()) ?
95-
KotlinReflectionUtils.findKotlinFunction(method) : null;
96-
if (function != null && !function.getReturnType().isMarkedNullable()) {
97-
throw new IllegalArgumentException("Kotlin function '%s' requires non-null return value".formatted(method.toString()));
98-
}
99-
}
100-
10190
return result;
10291
}
10392

Diff for: src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java

+9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.core.convert.support.GenericConversionService;
3232
import org.springframework.data.convert.Jsr310Converters;
3333
import org.springframework.data.util.Lazy;
34+
import org.springframework.data.util.NullabilityMethodInvocationValidator;
3435
import org.springframework.data.util.NullableWrapperConverters;
3536
import org.springframework.lang.Nullable;
3637
import org.springframework.util.Assert;
@@ -66,6 +67,9 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
6667
private final Lazy<DefaultMethodInvokingMethodInterceptor> defaultMethodInvokingMethodInterceptor = Lazy
6768
.of(DefaultMethodInvokingMethodInterceptor::new);
6869

70+
private final Lazy<NullabilityMethodInvocationValidator> nullabilityValidator = Lazy
71+
.of(NullabilityMethodInvocationValidator::new);
72+
6973
/**
7074
* Creates a new {@link ProxyProjectionFactory}.
7175
*/
@@ -119,6 +123,11 @@ public <T> T createProjection(Class<T> projectionType, Object source) {
119123
}
120124

121125
factory.addAdvice(new TargetAwareMethodInterceptor(source.getClass()));
126+
127+
if(NullabilityMethodInvocationValidator.supports(projectionType)) {
128+
factory.addAdvice(nullabilityValidator.get());
129+
}
130+
122131
factory.addAdvice(getMethodInterceptor(source, projectionType));
123132

124133
return (T) factory.getProxy(classLoader == null ? ClassUtils.getDefaultClassLoader() : classLoader);

Diff for: src/main/java/org/springframework/data/repository/core/support/MethodInvocationValidator.java

+10-180
Original file line numberDiff line numberDiff line change
@@ -15,196 +15,26 @@
1515
*/
1616
package org.springframework.data.repository.core.support;
1717

18-
import java.lang.annotation.ElementType;
19-
import java.lang.reflect.Method;
20-
import java.util.Map;
21-
import java.util.concurrent.ConcurrentHashMap;
22-
23-
import org.aopalliance.intercept.MethodInterceptor;
24-
import org.aopalliance.intercept.MethodInvocation;
25-
import org.springframework.core.DefaultParameterNameDiscoverer;
26-
import org.springframework.core.KotlinDetector;
27-
import org.springframework.core.MethodParameter;
28-
import org.springframework.core.ParameterNameDiscoverer;
2918
import org.springframework.dao.EmptyResultDataAccessException;
30-
import org.springframework.data.util.KotlinReflectionUtils;
31-
import org.springframework.data.util.NullableUtils;
32-
import org.springframework.data.util.ReflectionUtils;
33-
import org.springframework.lang.Nullable;
34-
import org.springframework.util.ClassUtils;
35-
import org.springframework.util.ConcurrentReferenceHashMap;
36-
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
37-
import org.springframework.util.ObjectUtils;
19+
import org.springframework.data.util.NullabilityMethodInvocationValidator;
3820

3921
/**
4022
* Interceptor enforcing required return value and method parameter constraints declared on repository query methods.
4123
* Supports Kotlin nullability markers and JSR-305 Non-null annotations.
4224
*
4325
* @author Mark Paluch
4426
* @author Johannes Englmeier
27+
* @author Christoph Strobl
4528
* @since 2.0
4629
* @see org.springframework.lang.NonNull
47-
* @see ReflectionUtils#isNullable(MethodParameter)
48-
* @see NullableUtils
30+
* @see org.springframework.data.util.ReflectionUtils#isNullable(org.springframework.core.MethodParameter)
31+
* @see org.springframework.data.util.NullableUtils
32+
* @deprecated use {@link NullabilityMethodInvocationValidator} instead.
4933
*/
50-
public class MethodInvocationValidator implements MethodInterceptor {
51-
52-
private final ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
53-
private final Map<Method, Nullability> nullabilityCache = new ConcurrentHashMap<>(16);
54-
55-
/**
56-
* Returns {@literal true} if the {@code repositoryInterface} is supported by this interceptor.
57-
*
58-
* @param repositoryInterface the interface class.
59-
* @return {@literal true} if the {@code repositoryInterface} is supported by this interceptor.
60-
*/
61-
public static boolean supports(Class<?> repositoryInterface) {
62-
63-
return KotlinDetector.isKotlinPresent() && KotlinReflectionUtils.isSupportedKotlinClass(repositoryInterface)
64-
|| NullableUtils.isNonNull(repositoryInterface, ElementType.METHOD)
65-
|| NullableUtils.isNonNull(repositoryInterface, ElementType.PARAMETER);
66-
}
67-
68-
@Nullable
69-
@Override
70-
public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
71-
72-
Method method = invocation.getMethod();
73-
Nullability nullability = nullabilityCache.get(method);
74-
75-
if (nullability == null) {
76-
77-
nullability = Nullability.of(method, discoverer);
78-
nullabilityCache.put(method, nullability);
79-
}
80-
81-
Object[] arguments = invocation.getArguments();
82-
83-
for (int i = 0; i < method.getParameterCount(); i++) {
84-
85-
if (nullability.isNullableParameter(i)) {
86-
continue;
87-
}
88-
89-
if ((arguments.length < i) || (arguments[i] == null)) {
90-
throw new IllegalArgumentException(
91-
String.format("Parameter %s in %s.%s must not be null", nullability.getMethodParameterName(i),
92-
ClassUtils.getShortName(method.getDeclaringClass()), method.getName()));
93-
}
94-
}
95-
96-
Object result = invocation.proceed();
97-
98-
if ((result == null) && !nullability.isNullableReturn()) {
99-
throw new EmptyResultDataAccessException("Result must not be null", 1);
100-
}
101-
102-
return result;
103-
}
104-
105-
static final class Nullability {
106-
107-
private final boolean nullableReturn;
108-
private final boolean[] nullableParameters;
109-
private final MethodParameter[] methodParameters;
110-
111-
private Nullability(boolean nullableReturn, boolean[] nullableParameters, MethodParameter[] methodParameters) {
112-
this.nullableReturn = nullableReturn;
113-
this.nullableParameters = nullableParameters;
114-
this.methodParameters = methodParameters;
115-
}
116-
117-
static Nullability of(Method method, ParameterNameDiscoverer discoverer) {
118-
119-
boolean nullableReturn = isNullableParameter(new MethodParameter(method, -1));
120-
boolean[] nullableParameters = new boolean[method.getParameterCount()];
121-
MethodParameter[] methodParameters = new MethodParameter[method.getParameterCount()];
122-
123-
for (int i = 0; i < method.getParameterCount(); i++) {
124-
125-
MethodParameter parameter = new MethodParameter(method, i);
126-
parameter.initParameterNameDiscovery(discoverer);
127-
nullableParameters[i] = isNullableParameter(parameter);
128-
methodParameters[i] = parameter;
129-
}
130-
131-
return new Nullability(nullableReturn, nullableParameters, methodParameters);
132-
}
133-
134-
String getMethodParameterName(int index) {
135-
136-
String parameterName = methodParameters[index].getParameterName();
137-
138-
if (parameterName == null) {
139-
parameterName = String.format("of type %s at index %d",
140-
ClassUtils.getShortName(methodParameters[index].getParameterType()), index);
141-
}
142-
143-
return parameterName;
144-
}
145-
146-
boolean isNullableReturn() {
147-
return nullableReturn;
148-
}
149-
150-
boolean isNullableParameter(int index) {
151-
return nullableParameters[index];
152-
}
153-
154-
private static boolean isNullableParameter(MethodParameter parameter) {
155-
156-
return requiresNoValue(parameter) || NullableUtils.isExplicitNullable(parameter)
157-
|| (KotlinReflectionUtils.isSupportedKotlinClass(parameter.getDeclaringClass())
158-
&& ReflectionUtils.isNullable(parameter));
159-
}
160-
161-
private static boolean requiresNoValue(MethodParameter parameter) {
162-
return ReflectionUtils.isVoid(parameter.getParameterType());
163-
}
164-
165-
public boolean[] getNullableParameters() {
166-
return this.nullableParameters;
167-
}
168-
169-
public MethodParameter[] getMethodParameters() {
170-
return this.methodParameters;
171-
}
172-
173-
@Override
174-
public boolean equals(@Nullable Object o) {
175-
176-
if (this == o) {
177-
return true;
178-
}
179-
180-
if (!(o instanceof Nullability that)) {
181-
return false;
182-
}
183-
184-
if (nullableReturn != that.nullableReturn) {
185-
return false;
186-
}
187-
188-
if (!ObjectUtils.nullSafeEquals(nullableParameters, that.nullableParameters)) {
189-
return false;
190-
}
191-
192-
return ObjectUtils.nullSafeEquals(methodParameters, that.methodParameters);
193-
}
194-
195-
@Override
196-
public int hashCode() {
197-
int result = (nullableReturn ? 1 : 0);
198-
result = (31 * result) + ObjectUtils.nullSafeHashCode(nullableParameters);
199-
result = (31 * result) + ObjectUtils.nullSafeHashCode(methodParameters);
200-
return result;
201-
}
34+
@Deprecated // TODO: do we want to remove this with next major
35+
public class MethodInvocationValidator extends NullabilityMethodInvocationValidator {
20236

203-
@Override
204-
public String toString() {
205-
return "MethodInvocationValidator.Nullability(nullableReturn=" + this.isNullableReturn() + ", nullableParameters="
206-
+ java.util.Arrays.toString(this.getNullableParameters()) + ", methodParameters="
207-
+ java.util.Arrays.deepToString(this.getMethodParameters()) + ")";
208-
}
209-
}
37+
public MethodInvocationValidator() {
38+
super((invocation) -> new EmptyResultDataAccessException("Result must not be null", 1));
39+
}
21040
}

Diff for: src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import org.springframework.data.repository.util.QueryExecutionConverters;
7474
import org.springframework.data.spel.EvaluationContextProvider;
7575
import org.springframework.data.util.Lazy;
76+
import org.springframework.data.util.NullabilityMethodInvocationValidator;
7677
import org.springframework.data.util.ReflectionUtils;
7778
import org.springframework.expression.ExpressionParser;
7879
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -334,7 +335,7 @@ public <T> T getRepository(Class<T> repositoryInterface, Object customImplementa
334335
* @return the implemented repository interface.
335336
* @since 2.0
336337
*/
337-
@SuppressWarnings({ "unchecked" })
338+
@SuppressWarnings({ "unchecked", "deprecation" })
338339
public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {
339340

340341
if (logger.isDebugEnabled()) {
@@ -399,7 +400,7 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
399400
result.setTarget(target);
400401
result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
401402

402-
if (MethodInvocationValidator.supports(repositoryInterface)) {
403+
if (NullabilityMethodInvocationValidator.supports(repositoryInterface)) {
403404
if (logger.isTraceEnabled()) {
404405
logger.trace(LogMessage.format("Register MethodInvocationValidator for %s…", repositoryInterface.getName()));
405406
}

0 commit comments

Comments
 (0)