diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/handler/AbstractMessageProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/handler/AbstractMessageProcessor.java index 4c3a847730..0c90e19702 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/handler/AbstractMessageProcessor.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/handler/AbstractMessageProcessor.java @@ -47,6 +47,10 @@ public abstract class AbstractMessageProcessor implements MessageProcessor { try { return expression.getValue(this.evaluationContext, message); } + catch (EvaluationException e) { + Throwable cause = e.getCause(); + throw new MessageHandlingException(message, "Expression evaluation failed.", cause==null ? e : cause); + } catch (Exception e) { throw new MessageHandlingException(message, "Expression evaluation failed.", e); } diff --git a/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java index dcacff0373..d196e0aa4d 100644 --- a/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java +++ b/org.springframework.integration/src/main/java/org/springframework/integration/handler/MethodInvokingMessageProcessor.java @@ -31,14 +31,12 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.aop.support.AopUtils; import org.springframework.context.expression.MapAccessor; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -156,7 +154,7 @@ public class MethodInvokingMessageProcessor extends AbstractMessageProcessor { } public Object processMessage(Message message) { - EvaluationException evaluationException = null; + Throwable evaluationException = null; List candidates = this.findHandlerMethodsForMessage(message); for (HandlerMethod candidate : candidates) { try { @@ -169,17 +167,13 @@ public class MethodInvokingMessageProcessor extends AbstractMessageProcessor { } return result; } - catch (EvaluationException e) { + catch (MessageHandlingException e) { if (evaluationException == null) { // keep the first exception - evaluationException = e; + evaluationException = e.getCause(); } } } - if (evaluationException == null) { - throw new MessageHandlingException(message, "Failed to find a suitable Message-handling " + - "method on target of type [" + targetObject.getClass() + "]."); - } throw new MessageHandlingException(message, "Failed to process Message.", evaluationException); } diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java new file mode 100644 index 0000000000..31d3a52dcd --- /dev/null +++ b/org.springframework.integration/src/test/java/org/springframework/integration/handler/ExpressionEvaluatingMessageProcessorTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2010 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. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package org.springframework.integration.handler; + +import static org.junit.Assert.assertEquals; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hamcrest.Description; +import org.junit.Rule; +import org.junit.Test; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.junit.rules.ExpectedException; +import org.springframework.expression.EvaluationException; +import org.springframework.integration.message.GenericMessage; +import org.springframework.integration.message.StringMessage; + +/** + * @author Dave Syer + * @since 2.0 + * + */ +public class ExpressionEvaluatingMessageProcessorTests { + + private static final Log logger = LogFactory.getLog(ExpressionEvaluatingMessageProcessorTests.class); + + @Rule + public ExpectedException expected = ExpectedException.none(); + + @Test + public void testProcessMessage() { + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("payload"); + assertEquals("foo", processor.processMessage(new StringMessage("foo"))); + } + + @Test + public void testProcessMessageBadExpression() { + expected.expect(new TypeSafeMatcher(Exception.class) { + private Throwable cause; + @Override + public boolean matchesSafely(Exception item) { + logger.debug(item); + cause = item.getCause(); + return cause instanceof EvaluationException; + } + public void describeTo(Description description) { + description.appendText("cause to be EvaluationException but was ").appendValue(cause); + } + }); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("payload.fixMe()"); + assertEquals("foo", processor.processMessage(new StringMessage("foo"))); + } + + @Test + public void testProcessMessageExpressionThrowsRuntimeException() { + expected.expect(new TypeSafeMatcher(Exception.class) { + private Throwable cause; + @Override + public boolean matchesSafely(Exception item) { + logger.debug(item); + cause = item.getCause(); + return cause instanceof UnsupportedOperationException; + } + public void describeTo(Description description) { + description.appendText("cause to be UnsupportedOperationException but was ").appendValue(cause); + } + }); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("payload.error()"); + assertEquals("foo", processor.processMessage(new GenericMessage(this))); + } + + @Test + public void testProcessMessageExpressionThrowsCheckedException() { + expected.expect(new TypeSafeMatcher(Exception.class) { + private Throwable cause; + @Override + public boolean matchesSafely(Exception item) { + logger.debug(item); + cause = item.getCause(); + return cause instanceof CheckedException; + } + public void describeTo(Description description) { + description.appendText("cause to be CheckedException but was ").appendValue(cause); + } + }); + ExpressionEvaluatingMessageProcessor processor = new ExpressionEvaluatingMessageProcessor("payload.check()"); + assertEquals("foo", processor.processMessage(new GenericMessage(this))); + } + + public String error() { + throw new UnsupportedOperationException("Expected test exception"); + } + + public String check() throws Exception { + throw new CheckedException("Expected test exception"); + } + + private static final class CheckedException extends Exception { + public CheckedException(String string) { + super(string); + } + } + +} diff --git a/org.springframework.integration/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java b/org.springframework.integration/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java index 7f3513bef0..82fe5986bd 100644 --- a/org.springframework.integration/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java +++ b/org.springframework.integration/src/test/java/org/springframework/integration/handler/MethodInvokingMessageProcessorTests.java @@ -25,8 +25,14 @@ import java.lang.reflect.Method; import java.util.Map; import java.util.Properties; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hamcrest.Description; +import org.junit.Rule; import org.junit.Test; - +import org.junit.internal.matchers.TypeSafeMatcher; +import org.junit.rules.ExpectedException; +import org.springframework.core.convert.ConversionFailedException; import org.springframework.integration.annotation.Header; import org.springframework.integration.annotation.ServiceActivator; import org.springframework.integration.core.Message; @@ -39,9 +45,15 @@ import org.springframework.integration.message.StringMessage; * @author Mark Fisher * @author Marius Bogoevici * @author Oleg Zhurakousky + * @author Dave Syer */ public class MethodInvokingMessageProcessorTests { + private static final Log logger = LogFactory.getLog(MethodInvokingMessageProcessorTests.class); + + @Rule + public ExpectedException expected = ExpectedException.none(); + @Test public void testHandlerInheritanceMethodImplInSuper() { class A { @@ -246,6 +258,33 @@ public class MethodInvokingMessageProcessorTests { processor.processMessage(new StringMessage("foo")); } + @Test + public void testProcessMessageBadExpression() throws Exception { + expected.expect(new ExceptionCauseMatcher(ConversionFailedException.class)); + AnnotatedTestService service = new AnnotatedTestService(); + Method method = service.getClass().getMethod("integerMethod", Integer.class); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method); + assertEquals("foo", processor.processMessage(new StringMessage("foo"))); + } + + @Test + public void testProcessMessageRuntimeException() throws Exception { + expected.expect(new ExceptionCauseMatcher(UnsupportedOperationException.class)); + TestErrorService service = new TestErrorService(); + Method method = service.getClass().getMethod("error", String.class); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method); + assertEquals("foo", processor.processMessage(new StringMessage("foo"))); + } + + @Test + public void testProcessMessageCheckedException() throws Exception { + expected.expect(new ExceptionCauseMatcher(CheckedException.class)); + TestErrorService service = new TestErrorService(); + Method method = service.getClass().getMethod("checked", String.class); + MethodInvokingMessageProcessor processor = new MethodInvokingMessageProcessor(service, method); + assertEquals("foo", processor.processMessage(new StringMessage("foo"))); + } + @Test public void messageAndHeaderWithAnnotatedMethod() throws Exception { AnnotatedTestService service = new AnnotatedTestService(); @@ -289,7 +328,40 @@ public class MethodInvokingMessageProcessorTests { assertEquals("true", bean.lastArg); } + + private static class ExceptionCauseMatcher extends TypeSafeMatcher { + private Throwable cause; + private Class type; + public ExceptionCauseMatcher(Class type) { + this.type = type; + } + @Override + public boolean matchesSafely(Exception item) { + logger.debug(item); + cause = item.getCause(); + assertNotNull("There is no cause for "+item, cause); + return type.isAssignableFrom(cause.getClass()); + } + public void describeTo(Description description) { + description.appendText("cause to be ").appendValue(type).appendText("but was ").appendValue(cause); + } + } + @SuppressWarnings("unused") + private static class TestErrorService { + public String error(String input) { + throw new UnsupportedOperationException("Expected test exception"); + } + public String checked(String input) throws Exception { + throw new CheckedException("Expected test exception"); + } + } + + public static final class CheckedException extends Exception { + public CheckedException(String string) { + super(string); + } + } @SuppressWarnings("unused") private static class TestBean {