Skip to content

Commit

Permalink
Gracefully skip non-assignable lambda callbacks on Java 18.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mp911de committed Mar 23, 2022
1 parent ab27812 commit 2d1b6a7
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public <T> T invokeCallback(EntityCallback<T> callback, T entity,
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())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down Expand Up @@ -95,9 +96,8 @@ void invokeInvalidEvent() {
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
Expand Down Expand Up @@ -126,8 +126,7 @@ void errorsOnNullEntity() {
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
Expand All @@ -144,18 +143,31 @@ void errorsOnNullValueReturnedByCallbackEntity() {

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);

Expand Down Expand Up @@ -288,4 +300,13 @@ public Person onBeforeSave(Person object) {
}
}

static class Java18ClassCastStyle implements BeforeConvertCallback<Person> {

@Override
public Person onBeforeConvert(Person object) {
throw new IllegalArgumentException("argument type mismatch");
}

}

}

0 comments on commit 2d1b6a7

Please sign in to comment.