GH-3474: LambdaMPP: rethrow runtime ex as is

Fixes https://github.com/spring-projects/spring-integration/issues/3474

The `AbstractTransformer` implementation just wraps a thrown exception into
a `MessageTransformationException`.
It is expected with any method transformer impl (POJO, Lambda direct etc.)
the exception is thrown as is without extra wrapping into an `IllegalStateException`
for consistency

* Fix `LambdaMessageProcessor` to rethrow `RuntimeException` as is
and only wrap into an `IllegalStateException` for all other exceptions
* Fix tests for missed extra stack trace
This commit is contained in:
Artem Bilan
2021-01-25 16:34:51 -05:00
committed by Gary Russell
parent 707b653d97
commit 31863f06e3
5 changed files with 43 additions and 69 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2019 the original author or authors.
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -97,15 +97,19 @@ public class LambdaMessageProcessor implements MessageProcessor<Object>, BeanFac
return this.method.invoke(this.target, args);
}
catch (InvocationTargetException e) {
final Throwable cause = e.getCause();
if (e.getTargetException() instanceof ClassCastException) {
LOGGER.error("Could not invoke the method '" + this.method + "' due to a class cast exception, " +
"if using a lambda in the DSL, consider using an overloaded EIP method " +
"that takes a Class<?> argument to explicitly specify the type. " +
"An example of when this often occurs is if the lambda is configured to " +
"receive a Message<?> argument.", e.getCause());
"receive a Message<?> argument.", cause);
}
if (cause instanceof RuntimeException) { // NOSONAR
throw (RuntimeException) cause;
}
throw new IllegalStateException(// NOSONAR lost stack trace
"Could not invoke the method '" + this.method + "'", e.getCause());
"Could not invoke the method '" + this.method + "'", cause);
}
catch (Exception e) {
throw new IllegalStateException(

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2019 the original author or authors.
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,13 +18,11 @@ package org.springframework.integration.dsl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import java.util.Objects;
import java.util.function.Function;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -38,7 +36,7 @@ import org.springframework.integration.handler.LambdaMessageProcessor;
import org.springframework.integration.transformer.GenericTransformer;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
@@ -47,7 +45,7 @@ import org.springframework.test.context.junit4.SpringRunner;
*
* @since 5.0
*/
@RunWith(SpringRunner.class)
@SpringJUnitConfig
public class LambdaMessageProcessorTests {
@Autowired
@@ -56,13 +54,8 @@ public class LambdaMessageProcessorTests {
@Test
@SuppressWarnings("divzero")
public void testException() {
try {
handle((m, h) -> 1 / 0);
fail("Expected exception");
}
catch (Exception e) {
assertThat(e.getCause()).isInstanceOf(ArithmeticException.class);
}
assertThatExceptionOfType(ArithmeticException.class)
.isThrownBy(() -> handle((m, h) -> 1 / 0));
}
@Test
@@ -88,9 +81,8 @@ public class LambdaMessageProcessorTests {
(GenericTransformer<Message<?>, Message<?>>) this::messageTransformer, null);
lmp.setBeanFactory(this.beanFactory);
GenericMessage<String> testMessage = new GenericMessage<>("foo");
assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> lmp.processMessage(testMessage))
.withCauseInstanceOf(ClassCastException.class);
assertThatExceptionOfType(ClassCastException.class)
.isThrownBy(() -> lmp.processMessage(testMessage));
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2020 the original author or authors.
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,8 @@ package org.springframework.integration.dsl.manualflow;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import java.lang.reflect.Method;
import java.util.Arrays;
@@ -220,14 +221,9 @@ public class ManualFlowTests {
assertThat(messagingTemplate.convertSendAndReceive("bar", String.class)).isEqualTo("Hello, BAR");
try {
messagingTemplate.receive();
fail("UnsupportedOperationException expected");
}
catch (Exception e) {
assertThat(e).isInstanceOf(UnsupportedOperationException.class);
assertThat(e.getMessage()).contains("The 'receive()/receiveAndConvert()' isn't supported");
}
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(messagingTemplate::receive)
.withMessageContaining("The 'receive()/receiveAndConvert()' isn't supported");
assertThat(this.beanFactory.getBeanNamesForType(MessageTransformingHandler.class)[0]).startsWith(flowId + ".");
@@ -251,15 +247,9 @@ public class ManualFlowTests {
@Test
public void testWrongLifecycle() {
try {
this.integrationFlowContext.remove("foo");
fail("IllegalStateException expected");
}
catch (Exception e) {
assertThat(e).isInstanceOf(IllegalStateException.class);
assertThat(e.getMessage())
.contains("An IntegrationFlow with the id [" + "foo" + "] doesn't exist in the registry.");
}
assertThatIllegalStateException()
.isThrownBy(() -> this.integrationFlowContext.remove("foo"))
.withMessageContaining("An IntegrationFlow with the id [" + "foo" + "] doesn't exist in the registry.");
}
@Test
@@ -305,14 +295,10 @@ public class ManualFlowTests {
@Test
public void testWrongIntegrationFlowScope() {
try {
new AnnotationConfigApplicationContext(InvalidIntegrationFlowScopeConfiguration.class).close();
fail("BeanCreationNotAllowedException expected");
}
catch (Exception e) {
assertThat(e).isInstanceOf(BeanCreationNotAllowedException.class);
assertThat(e.getMessage()).contains("IntegrationFlows can not be scoped beans.");
}
assertThatExceptionOfType(BeanCreationNotAllowedException.class)
.isThrownBy(() ->
new AnnotationConfigApplicationContext(InvalidIntegrationFlowScopeConfiguration.class))
.withMessageContaining("IntegrationFlows can not be scoped beans.");
}
@Test
@@ -382,13 +368,9 @@ public class ManualFlowTests {
this.roleController.stopLifecyclesInRole(testRole);
try {
messagingTemplate.send(new GenericMessage<>("test2"));
}
catch (Exception e) {
assertThat(e).isInstanceOf(MessageDeliveryException.class);
assertThat(e.getMessage()).contains("Dispatcher has no subscribers for channel");
}
assertThatExceptionOfType(MessageDeliveryException.class)
.isThrownBy(() -> messagingTemplate.send(new GenericMessage<>("test2")))
.withMessageContaining("Dispatcher has no subscribers for channel");
this.roleController.startLifecyclesInRole(testRole);
@@ -453,16 +435,13 @@ public class ManualFlowTests {
.id(testId)
.register();
try {
this.integrationFlowContext
.registration(testFlow)
.id(testId)
.register();
}
catch (Exception e) {
assertThat(e).isInstanceOf(IllegalArgumentException.class);
assertThat(e.getMessage()).contains("with flowId '" + testId + "' is already registered.");
}
assertThatIllegalArgumentException()
.isThrownBy(() ->
this.integrationFlowContext
.registration(testFlow)
.id(testId)
.register())
.withMessageContaining("with flowId '" + testId + "' is already registered.");
flowRegistration.destroy();
}
@@ -527,8 +506,7 @@ public class ManualFlowTests {
.register();
assertThatExceptionOfType(MessageTransformationException.class)
.isThrownBy(() -> flowRegistration.getInputChannel().send(new GenericMessage<>(new Date())))
.withCauseExactlyInstanceOf(IllegalStateException.class)
.withRootCauseInstanceOf(ClassCastException.class)
.withCauseExactlyInstanceOf(ClassCastException.class)
.withMessageContaining("from source: '" + source + "'")
.withStackTraceContaining("java.util.Date cannot be cast to");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2020 the original author or authors.
* Copyright 2016-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -913,7 +913,7 @@ public class RouterTests {
@ServiceActivator(inputChannel = "scatterGatherErrorChannel")
public Message<?> processAsyncScatterError(MessagingException payload) {
return MessageBuilder.withPayload(payload.getCause().getCause())
return MessageBuilder.withPayload(payload.getCause())
.copyHeaders(payload.getFailedMessage().getHeaders())
.build();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2020 the original author or authors.
* Copyright 2017-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -89,7 +89,7 @@ public class ExpressionEvaluatingRequestHandlerAdviceTests {
advice.setOnSuccessExpressionString("payload + ' was successful'");
advice.setFailureChannelName("failure.input");
advice.setOnFailureExpressionString(
"payload + ' was bad, with reason: ' + #exception.cause.message");
"payload + ' was bad, with reason: ' + #exception.message");
advice.setTrapException(true);
return advice;
}