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
This commit is contained in:
Mark Paluch
2022-03-23 10:43:53 +01:00
parent dc609f0751
commit 7312904ae4
3 changed files with 40 additions and 14 deletions

View File

@@ -117,7 +117,7 @@ class DefaultEntityCallbacks implements EntityCallbacks {
throw new IllegalArgumentException(
String.format("Callback invocation on %s returned null value for %s", callback.getClass(), entity));
} catch (ClassCastException ex) {
} catch (IllegalArgumentException | ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || EntityCallbackInvoker.matchesClassCastMessage(msg, entity.getClass())) {

View File

@@ -35,21 +35,26 @@ interface EntityCallbackInvoker {
<T> Object invokeCallback(EntityCallback<T> callback, T entity,
BiFunction<EntityCallback<T>, T, Object> callbackInvokerFunction);
static boolean matchesClassCastMessage(String classCastMessage, Class<?> eventClass) {
static boolean matchesClassCastMessage(String exceptionMessage, Class<?> eventClass) {
// On Java 8, the message starts with the class name: "java.lang.String cannot be cast..."
if (classCastMessage.startsWith(eventClass.getName())) {
if (exceptionMessage.startsWith(eventClass.getName())) {
return true;
}
// On Java 11, the message starts with "class ..." a.k.a. Class.toString()
if (classCastMessage.startsWith(eventClass.toString())) {
if (exceptionMessage.startsWith(eventClass.toString())) {
return true;
}
// On Java 9, the message used to contain the module name: "java.base/java.lang.String cannot be cast..."
int moduleSeparatorIndex = classCastMessage.indexOf('/');
if (moduleSeparatorIndex != -1 && classCastMessage.startsWith(eventClass.getName(), moduleSeparatorIndex + 1)) {
int moduleSeparatorIndex = exceptionMessage.indexOf('/');
if (moduleSeparatorIndex != -1 && exceptionMessage.startsWith(eventClass.getName(), moduleSeparatorIndex + 1)) {
return true;
}
// On Java 18, the message is "IllegalArgumentException: argument type mismatch"
if (exceptionMessage.equals("argument type mismatch")) {
return true;
}

View File

@@ -28,6 +28,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.data.mapping.Person;
import org.springframework.data.mapping.PersonDocument;
import org.springframework.data.mapping.PersonNoId;
import org.springframework.data.mapping.callback.CapturingEntityCallback.FirstCallback;
import org.springframework.data.mapping.callback.CapturingEntityCallback.SecondCallback;
import org.springframework.data.mapping.callback.CapturingEntityCallback.ThirdCallback;
@@ -95,9 +96,8 @@ class DefaultEntityCallbacksUnitTests {
DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
callbacks.addEntityCallback(new InvalidEntityCallback() {});
assertThatIllegalStateException()
.isThrownBy(() -> callbacks.callback(InvalidEntityCallback.class, new PersonDocument(null, "Walter", null),
"agr0", Float.POSITIVE_INFINITY));
assertThatIllegalStateException().isThrownBy(() -> callbacks.callback(InvalidEntityCallback.class,
new PersonDocument(null, "Walter", null), "agr0", Float.POSITIVE_INFINITY));
}
@Test // DATACMNS-1467
@@ -126,8 +126,7 @@ class DefaultEntityCallbacksUnitTests {
DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
callbacks.addEntityCallback(new CapturingEntityCallback());
assertThatIllegalArgumentException()
.isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, null));
assertThatIllegalArgumentException().isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, null));
}
@Test // DATACMNS-1467
@@ -144,18 +143,31 @@ class DefaultEntityCallbacksUnitTests {
PersonDocument initial = new PersonDocument(null, "Walter", null);
assertThatIllegalArgumentException()
.isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, initial));
assertThatIllegalArgumentException().isThrownBy(() -> callbacks.callback(CapturingEntityCallback.class, initial));
assertThat(first.capturedValue()).isSameAs(initial);
assertThat(second.capturedValue()).isNotNull().isNotSameAs(initial);
assertThat(third.capturedValues()).isEmpty();
}
@Test // GH-2583
void skipsInvocationUsingJava18ReflectiveTypeRejection() {
DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks();
callbacks.addEntityCallback(new Java18ClassCastStyle());
Person person = new PersonNoId(42, "Walter", "White");
Person afterCallback = callbacks.callback(BeforeConvertCallback.class, person);
assertThat(afterCallback).isSameAs(person);
}
@Test // DATACMNS-1467
void detectsMultipleCallbacksWithinOneClass() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MultipleCallbacksInOneClassConfig.class);
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
MultipleCallbacksInOneClassConfig.class);
DefaultEntityCallbacks callbacks = new DefaultEntityCallbacks(ctx);
@@ -288,4 +300,13 @@ class DefaultEntityCallbacksUnitTests {
}
}
static class Java18ClassCastStyle implements BeforeConvertCallback<Person> {
@Override
public Person onBeforeConvert(Person object) {
throw new IllegalArgumentException("argument type mismatch");
}
}
}