Skip to content

Commit d136577

Browse files
Decouple SerializableLambdaReader from spring-tx.
Since we're using the SerializableLambdaReader from within a GraalVM Feature it needs to operate without references to types potentially not present at build time.
1 parent 2192520 commit d136577

File tree

4 files changed

+71
-33
lines changed

4 files changed

+71
-33
lines changed

src/main/java/org/springframework/data/core/SerializableLambdaReader.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
import org.springframework.asm.Type;
5050
import org.springframework.core.KotlinDetector;
5151
import org.springframework.core.SpringProperties;
52-
import org.springframework.dao.InvalidDataAccessApiUsageException;
5352
import org.springframework.data.core.MemberDescriptor.KPropertyPathDescriptor;
5453
import org.springframework.data.core.MemberDescriptor.KPropertyReferenceDescriptor;
5554
import org.springframework.util.ClassUtils;
@@ -136,7 +135,7 @@ private static boolean isEnabled(String property, boolean defaultValue) {
136135
*
137136
* @param lambdaObject the actual lambda object, must be {@link java.io.Serializable}.
138137
* @return the member reference.
139-
* @throws InvalidDataAccessApiUsageException if the lambda object does not contain a valid property reference or hits
138+
* @throws TypeParsingException if the lambda object does not contain a valid property reference or hits
140139
* any of the mentioned limitations.
141140
*/
142141
public MemberDescriptor read(Object lambdaObject) {
@@ -171,10 +170,10 @@ public MemberDescriptor read(Object lambdaObject) {
171170
return getMemberDescriptor(lambdaObject, lambda);
172171
}
173172
} catch (ReflectiveOperationException | IOException e) {
174-
throw new InvalidDataAccessApiUsageException("Cannot extract method or field", e);
173+
throw new TypeParsingException("Cannot extract method or field", e);
175174
}
176175

177-
throw new InvalidDataAccessApiUsageException("Cannot extract method or field from: " + lambdaObject
176+
throw new TypeParsingException("Cannot extract method or field from: " + lambdaObject
178177
+ ". The given value is not a lambda or method reference.");
179178
}
180179

@@ -183,7 +182,7 @@ private void assertNotConstructor(SerializedLambda lambda) {
183182
if (lambda.getImplMethodKind() == MethodHandleInfo.REF_newInvokeSpecial
184183
|| lambda.getImplMethodKind() == MethodHandleInfo.REF_invokeSpecial) {
185184

186-
InvalidDataAccessApiUsageException e = new InvalidDataAccessApiUsageException(
185+
TypeParsingException e = new TypeParsingException(
187186
"Method reference must not be a constructor call");
188187

189188
if (filterStackTrace) {
@@ -223,7 +222,7 @@ private static SerializedLambda serialize(Object lambda) {
223222
method.setAccessible(true);
224223
return (SerializedLambda) method.invoke(lambda);
225224
} catch (ReflectiveOperationException e) {
226-
throw new InvalidDataAccessApiUsageException(
225+
throw new TypeParsingException(
227226
"Not a lambda: " + (lambda instanceof Enum<?> ? lambda.getClass().getName() + "#" + lambda : lambda), e);
228227
}
229228
}
@@ -297,7 +296,7 @@ public static MemberDescriptor read(Object captured, Object lambda) {
297296
return KPropertyPathDescriptor.create(propRef);
298297
}
299298

300-
throw new InvalidDataAccessApiUsageException("Cannot extract MemberDescriptor from: " + lambda);
299+
throw new TypeParsingException("Cannot extract MemberDescriptor from: " + lambda);
301300
}
302301

303302
}
@@ -467,7 +466,7 @@ public MemberDescriptor resolve(SerializedLambda lambda) {
467466
if (errors.isEmpty()) {
468467

469468
if (memberDescriptors.isEmpty()) {
470-
throw new InvalidDataAccessApiUsageException("There is no method or field access");
469+
throw new TypeParsingException("There is no method or field access");
471470
}
472471

473472
return memberDescriptors.get(memberDescriptors.size() - 1);
@@ -478,7 +477,7 @@ public MemberDescriptor resolve(SerializedLambda lambda) {
478477

479478
String methodName = getDeclaringMethodName(lambda);
480479

481-
InvalidDataAccessApiUsageException e = new InvalidDataAccessApiUsageException(
480+
TypeParsingException e = new TypeParsingException(
482481
"Cannot resolve property path%n%nError%s:%n".formatted(errors.size() > 1 ? "s" : "") + errors.stream()
483482
.map(ReadingError::message).map(LambdaMethodVisitor::formatMessage).collect(Collectors.joining()));
484483

@@ -498,7 +497,7 @@ public MemberDescriptor resolve(SerializedLambda lambda) {
498497
throw e;
499498
}
500499

501-
throw new InvalidDataAccessApiUsageException("Error resolving " + errors);
500+
throw new TypeParsingException("Error resolving " + errors);
502501
}
503502

504503
private static String formatMessage(String args) {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2026-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.core;
17+
18+
import org.jspecify.annotations.Nullable;
19+
import org.springframework.core.NestedRuntimeException;
20+
21+
/**
22+
* {@link RuntimeException} for errors during parsing or introspection of types.
23+
* <p>
24+
* Typically, thrown when a lambda or method reference is not a serializable lambda, or violates supported patterns
25+
* (e.g. constructor references, multi-step access).
26+
*
27+
* @author Christoph Strobl
28+
* @since 4.1
29+
*/
30+
public class TypeParsingException extends NestedRuntimeException { // TypeIntrospectionException ???
31+
32+
public TypeParsingException(@Nullable String msg) {
33+
super(msg);
34+
}
35+
36+
public TypeParsingException(@Nullable String msg, @Nullable Throwable cause) {
37+
super(msg, cause);
38+
}
39+
}

src/test/java/org/springframework/data/core/PropertyReferenceUnitTests.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ void resolvesPrivateMethodLambda() {
101101
@Test // GH-3400
102102
void switchingOwningTypeFails() {
103103

104-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
104+
assertThatExceptionOfType(TypeParsingException.class)
105105
.isThrownBy(() -> PropertyReference.of((PersonQuery person) -> {
106106
return ((SuperClass) person).getTenant();
107107
}));
@@ -110,33 +110,33 @@ void switchingOwningTypeFails() {
110110
@Test // GH-3400
111111
void constructorCallsShouldFail() {
112112

113-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
113+
assertThatExceptionOfType(TypeParsingException.class)
114114
.isThrownBy(() -> PropertyReference.of((PersonQuery person) -> new PersonQuery(person)));
115115
}
116116

117117
@Test // GH-3400
118118
void enumShouldFail() {
119119

120-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
120+
assertThatExceptionOfType(TypeParsingException.class)
121121
.isThrownBy(() -> PropertyReference.of(NotSupported.INSTANCE));
122122
}
123123

124124
@Test // GH-3400
125125
void returningSomethingShouldFail() {
126126

127-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
127+
assertThatExceptionOfType(TypeParsingException.class)
128128
.isThrownBy(() -> PropertyReference.of((PropertyReference<Object, Object>) obj -> null));
129-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
129+
assertThatExceptionOfType(TypeParsingException.class)
130130
.isThrownBy(() -> PropertyReference.of((PropertyReference<Object, Object>) obj -> 1));
131-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
131+
assertThatExceptionOfType(TypeParsingException.class)
132132
.isThrownBy(() -> PropertyReference.of((PropertyReference<Object, Object>) obj -> ""));
133133
}
134134

135135
@Test // GH-3400
136136
@SuppressWarnings("Convert2Lambda")
137137
void classImplementationShouldFail() {
138138

139-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
139+
assertThatExceptionOfType(TypeParsingException.class)
140140
.isThrownBy(() -> PropertyReference.of(new PropertyReference<Object, Object>() {
141141
@Override
142142
public @Nullable Object get(Object obj) {
@@ -148,24 +148,24 @@ void classImplementationShouldFail() {
148148
@Test // GH-3400
149149
void constructorMethodReferenceShouldFail() {
150150

151-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
151+
assertThatExceptionOfType(TypeParsingException.class)
152152
.isThrownBy(() -> PropertyReference.<PersonQuery, PersonQuery> of(PersonQuery::new));
153153
}
154154

155155
@Test // GH-3400
156156
void failsResolutionWith$StrangeStuff() {
157157

158-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
158+
assertThatExceptionOfType(TypeParsingException.class)
159159
.isThrownBy(() -> PropertyReference.of((PersonQuery person) -> {
160160
int a = 1 + 2;
161-
new Integer(a).toString();
161+
Integer.valueOf(a).toString();
162162
return person.getName();
163163
}).getName());
164164
}
165165

166166
@Test // GH-3400
167167
void arithmeticOpsFail() {
168-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() -> {
168+
assertThatExceptionOfType(TypeParsingException.class).isThrownBy(() -> {
169169
PropertyReference.of((PersonQuery person) -> {
170170
int a = 1 + 2;
171171
return person.getName();
@@ -176,7 +176,7 @@ void arithmeticOpsFail() {
176176
@Test // GH-3400
177177
void failsResolvingCallingLocalMethod() {
178178

179-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
179+
assertThatExceptionOfType(TypeParsingException.class)
180180
.isThrownBy(() -> PropertyReference.of((PersonQuery person) -> {
181181
failsResolutionWith$StrangeStuff();
182182
return person.getName();

src/test/java/org/springframework/data/core/TypedPropertyPathUnitTests.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ void resolvesPrivateMethodLambda() {
127127
@Test // GH-3400
128128
void switchingOwningTypeFails() {
129129

130-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
130+
assertThatExceptionOfType(TypeParsingException.class)
131131
.isThrownBy(() -> PropertyPath.of((PersonQuery person) -> {
132132
return ((SuperClass) person).getTenant();
133133
}));
@@ -136,33 +136,33 @@ void switchingOwningTypeFails() {
136136
@Test // GH-3400
137137
void constructorCallsShouldFail() {
138138

139-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
139+
assertThatExceptionOfType(TypeParsingException.class)
140140
.isThrownBy(() -> PropertyPath.of((PersonQuery person) -> new PersonQuery(person)));
141141
}
142142

143143
@Test // GH-3400
144144
void enumShouldFail() {
145145

146-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
146+
assertThatExceptionOfType(TypeParsingException.class)
147147
.isThrownBy(() -> TypedPropertyPath.of(NotSupported.INSTANCE));
148148
}
149149

150150
@Test // GH-3400
151151
void returningSomethingShouldFail() {
152152

153-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
153+
assertThatExceptionOfType(TypeParsingException.class)
154154
.isThrownBy(() -> TypedPropertyPath.of((TypedPropertyPath<Object, Object>) obj -> null));
155-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
155+
assertThatExceptionOfType(TypeParsingException.class)
156156
.isThrownBy(() -> TypedPropertyPath.of((TypedPropertyPath<Object, Object>) obj -> 1));
157-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
157+
assertThatExceptionOfType(TypeParsingException.class)
158158
.isThrownBy(() -> TypedPropertyPath.of((TypedPropertyPath<Object, Object>) obj -> ""));
159159
}
160160

161161
@Test // GH-3400
162162
@SuppressWarnings("Convert2Lambda")
163163
void classImplementationShouldFail() {
164164

165-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
165+
assertThatExceptionOfType(TypeParsingException.class)
166166
.isThrownBy(() -> TypedPropertyPath.of(new TypedPropertyPath<Object, Object>() {
167167
@Override
168168
public @Nullable Object get(Object obj) {
@@ -174,7 +174,7 @@ void classImplementationShouldFail() {
174174
@Test // GH-3400
175175
void constructorMethodReferenceShouldFail() {
176176

177-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
177+
assertThatExceptionOfType(TypeParsingException.class)
178178
.isThrownBy(() -> PropertyPath.<PersonQuery, PersonQuery> of(PersonQuery::new));
179179
}
180180

@@ -190,7 +190,7 @@ void resolvesMRRecordPath() {
190190
@Test // GH-3400
191191
void failsResolutionWith$StrangeStuff() {
192192

193-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
193+
assertThatExceptionOfType(TypeParsingException.class)
194194
.isThrownBy(() -> PropertyPath.of((PersonQuery person) -> {
195195
int a = 1 + 2;
196196
new Integer(a).toString();
@@ -201,7 +201,7 @@ void resolvesMRRecordPath() {
201201
@Test // GH-3400
202202
void arithmeticOpsFail() {
203203

204-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class).isThrownBy(() -> {
204+
assertThatExceptionOfType(TypeParsingException.class).isThrownBy(() -> {
205205
PropertyPath.of((PersonQuery person) -> {
206206
int a = 1 + 2;
207207
return person.getName();
@@ -212,7 +212,7 @@ void arithmeticOpsFail() {
212212
@Test // GH-3400
213213
void failsResolvingCallingLocalMethod() {
214214

215-
assertThatExceptionOfType(InvalidDataAccessApiUsageException.class)
215+
assertThatExceptionOfType(TypeParsingException.class)
216216
.isThrownBy(() -> PropertyPath.of((PersonQuery person) -> {
217217
failsResolutionWith$StrangeStuff();
218218
return person.getName();

0 commit comments

Comments
 (0)