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:
@@ -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())) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user