Browse Source

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
pull/2591/head
Mark Paluch 4 years ago
parent
commit
f5ee277a6e
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 2
      src/main/java/org/springframework/data/mapping/callback/DefaultEntityCallbacks.java
  2. 15
      src/main/java/org/springframework/data/mapping/callback/EntityCallbackInvoker.java
  3. 37
      src/test/java/org/springframework/data/mapping/callback/DefaultEntityCallbacksUnitTests.java

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

@ -109,7 +109,7 @@ class DefaultEntityCallbacks implements EntityCallbacks { @@ -109,7 +109,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) {
var msg = ex.getMessage();
if (msg == null || EntityCallbackInvoker.matchesClassCastMessage(msg, entity.getClass())) {

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

@ -35,21 +35,26 @@ interface EntityCallbackInvoker { @@ -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..."
var moduleSeparatorIndex = classCastMessage.indexOf('/');
if (moduleSeparatorIndex != -1 && classCastMessage.startsWith(eventClass.getName(), moduleSeparatorIndex + 1)) {
var 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;
}

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

@ -28,6 +28,7 @@ import org.springframework.context.annotation.Lazy; @@ -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 { @@ -95,9 +96,8 @@ class DefaultEntityCallbacksUnitTests {
var 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 { @@ -126,8 +126,7 @@ class DefaultEntityCallbacksUnitTests {
var 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 { @@ -144,18 +143,31 @@ class DefaultEntityCallbacksUnitTests {
var 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() {
var ctx = new AnnotationConfigApplicationContext(MultipleCallbacksInOneClassConfig.class);
var ctx = new AnnotationConfigApplicationContext(
MultipleCallbacksInOneClassConfig.class);
var callbacks = new DefaultEntityCallbacks(ctx);
@ -288,4 +300,13 @@ class DefaultEntityCallbacksUnitTests { @@ -288,4 +300,13 @@ class DefaultEntityCallbacksUnitTests {
}
}
static class Java18ClassCastStyle implements BeforeConvertCallback<Person> {
@Override
public Person onBeforeConvert(Person object) {
throw new IllegalArgumentException("argument type mismatch");
}
}
}

Loading…
Cancel
Save