Skip to content

Commit 2d1b6a7

Browse files
committed
Gracefully skip non-assignable lambda callbacks on Java 18.
We now consider IllegalArgumentException as marker for incompatible lambda payload that was introduced with Java 18's reflection rewrite that uses method handles internally. Closes #2583
1 parent ab27812 commit 2d1b6a7

File tree

3 files changed

+40
-14
lines changed

3 files changed

+40
-14
lines changed

src/main/java/org/springframework/data/mapping/callback/DefaultEntityCallbacks.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public <T> T invokeCallback(EntityCallback<T> callback, T entity,
117117
throw new IllegalArgumentException(
118118
String.format("Callback invocation on %s returned null value for %s", callback.getClass(), entity));
119119

120-
} catch (ClassCastException ex) {
120+
} catch (IllegalArgumentException | ClassCastException ex) {
121121

122122
String msg = ex.getMessage();
123123
if (msg == null || EntityCallbackInvoker.matchesClassCastMessage(msg, entity.getClass())) {

src/main/java/org/springframework/data/mapping/callback/EntityCallbackInvoker.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,26 @@ interface EntityCallbackInvoker {
3535
<T> Object invokeCallback(EntityCallback<T> callback, T entity,
3636
BiFunction<EntityCallback<T>, T, Object> callbackInvokerFunction);
3737

38-
static boolean matchesClassCastMessage(String classCastMessage, Class<?> eventClass) {
38+
static boolean matchesClassCastMessage(String exceptionMessage, Class<?> eventClass) {
3939

4040
// On Java 8, the message starts with the class name: "java.lang.String cannot be cast..."
41-
if (classCastMessage.startsWith(eventClass.getName())) {
41+
if (exceptionMessage.startsWith(eventClass.getName())) {
4242
return true;
4343
}
4444

4545
// On Java 11, the message starts with "class ..." a.k.a. Class.toString()
46-
if (classCastMessage.startsWith(eventClass.toString())) {
46+
if (exceptionMessage.startsWith(eventClass.toString())) {
4747
return true;
4848
}
4949

5050
// On Java 9, the message used to contain the module name: "java.base/java.lang.String cannot be cast..."
51-
int moduleSeparatorIndex = classCastMessage.indexOf('/');
52-
if (moduleSeparatorIndex != -1 && classCastMessage.startsWith(eventClass.getName(), moduleSeparatorIndex + 1)) {
51+
int moduleSeparatorIndex = exceptionMessage.indexOf('/');
52+
if (moduleSeparatorIndex != -1 && exceptionMessage.startsWith(eventClass.getName(), moduleSeparatorIndex + 1)) {
53+
return true;
54+
}
55+
56+
// On Java 18, the message is "IllegalArgumentException: argument type mismatch"
57+
if (exceptionMessage.equals("argument type mismatch")) {
5358
return true;
5459
}
5560

src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java

+29-8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.core.Ordered;
2929
import org.springframework.data.mapping.Person;
3030
import org.springframework.data.mapping.PersonDocument;
31+
import org.springframework.data.mapping.PersonNoId;
3132
import org.springframework.data.mapping.callback.CapturingEntityCallback.FirstCallback;
3233
import org.springframework.data.mapping.callback.CapturingEntityCallback.SecondCallback;
3334
import org.springframework.data.mapping.callback.CapturingEntityCallback.ThirdCallback;
@@ -95,9 +96,8 @@ void invokeInvalidEvent() {
9596
DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
9697
callbacks.addEntityCallback(new InvalidEntityCallback() {});
9798

98-
assertThatIllegalStateException()
99-
.isThrownBy(() -> callbacks.callback(InvalidEntityCallback.class, new PersonDocument(null, "Walter", null),
100-
"agr0", Float.POSITIVE_INFINITY));
99+
assertThatIllegalStateException().isThrownBy(() -> callbacks.callback(InvalidEntityCallback.class,
100+
new PersonDocument(null, "Walter", null), "agr0", Float.POSITIVE_INFINITY));
101101
}
102102

103103
@Test // DATACMNS-1467
@@ -126,8 +126,7 @@ void errorsOnNullEntity() {
126126
DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
127127
callbacks.addEntityCallback(new CapturingEntityCallback());
128128

129-
assertThatIllegalArgumentException()
130-
.isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, null));
129+
assertThatIllegalArgumentException().isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, null));
131130
}
132131

133132
@Test // DATACMNS-1467
@@ -144,18 +143,31 @@ void errorsOnNullValueReturnedByCallbackEntity() {
144143

145144
PersonDocument initial = new PersonDocument(null, "Walter", null);
146145

147-
assertThatIllegalArgumentException()
148-
.isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, initial));
146+
assertThatIllegalArgumentException().isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, initial));
149147

150148
assertThat(first.capturedValue()).isSameAs(initial);
151149
assertThat(second.capturedValue()).isNotNull().isNotSameAs(initial);
152150
assertThat(third.capturedValues()).isEmpty();
153151
}
154152

153+
@Test // GH-2583
154+
void skipsInvocationUsingJava18ReflectiveTypeRejection() {
155+
156+
DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
157+
callbacks.addEntityCallback(new Java18ClassCastStyle());
158+
159+
Person person = new PersonNoId(42, "Walter", "White");
160+
161+
Person afterCallback = callbacks.callback(BeforeConvertCallback.class, person);
162+
163+
assertThat(afterCallback).isSameAs(person);
164+
}
165+
155166
@Test // DATACMNS-1467
156167
void detectsMultipleCallbacksWithinOneClass() {
157168

158-
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MultipleCallbacksInOneClassConfig.class);
169+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
170+
MultipleCallbacksInOneClassConfig.class);
159171

160172
DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks(ctx);
161173

@@ -288,4 +300,13 @@ public Person onBeforeSave(Person object) {
288300
}
289301
}
290302

303+
static class Java18ClassCastStyle implements BeforeConvertCallback<Person> {
304+
305+
@Override
306+
public Person onBeforeConvert(Person object) {
307+
throw new IllegalArgumentException("argument type mismatch");
308+
}
309+
310+
}
311+
291312
}

0 commit comments

Comments
 (0)