/* * Copyright 2002-2017 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.advice; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.hamcrest.Matchers; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.AopUtils; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.endpoint.PollingConsumer; import org.springframework.integration.filter.MessageFilter; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice.MessageHandlingExpressionEvaluatingAdviceException; import org.springframework.integration.message.AdviceMessage; import org.springframework.integration.test.util.TestUtils; import org.springframework.integration.util.ErrorHandlingTaskExecutor; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandlingException; import org.springframework.messaging.MessagingException; import org.springframework.messaging.PollableChannel; import org.springframework.messaging.support.ErrorMessage; import org.springframework.messaging.support.GenericMessage; import org.springframework.retry.RetryContext; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.DefaultRetryState; import org.springframework.retry.support.RetryTemplate; import org.springframework.scheduling.TaskScheduler; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @author Gary Russell * @author Artem Bilan * @since 2.2 */ @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) @DirtiesContext public class AdvisedMessageHandlerTests { @Autowired private MessageChannel input; @Test public void circuitBreakerExceptionText() { GenericMessage<String> message = new GenericMessage<String>("foo"); try { input.send(message); fail("expected exception"); } catch (MessageHandlingException e) { assertThat(e.getCause(), Matchers.instanceOf(ArithmeticException.class)); } try { input.send(message); fail("expected exception"); } catch (RuntimeException e) { assertThat(e.getMessage(), containsString("(myService)]")); } } @Test public void successFailureAdvice() { final AtomicBoolean doFail = new AtomicBoolean(); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { if (doFail.get()) { throw new RuntimeException("qux"); } return "baz"; } }; String componentName = "testComponentName"; handler.setComponentName(componentName); QueueChannel replies = new QueueChannel(); handler.setOutputChannel(replies); Message<String> message = new GenericMessage<String>("Hello, world!"); // no advice handler.handleMessage(message); Message<?> reply = replies.receive(1000); assertNotNull(reply); assertEquals("baz", reply.getPayload()); PollableChannel successChannel = new QueueChannel(); PollableChannel failureChannel = new QueueChannel(); ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice(); advice.setBeanFactory(mock(BeanFactory.class)); advice.setSuccessChannel(successChannel); advice.setFailureChannel(failureChannel); advice.setOnSuccessExpressionString("'foo'"); advice.setOnFailureExpressionString("'bar:' + #exception.message"); List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(advice); final AtomicReference<String> compName = new AtomicReference<String>(); adviceChain.add(new AbstractRequestHandlerAdvice() { @Override protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception { compName.set(((AbstractReplyProducingMessageHandler.RequestHandler) target).getAdvisedHandler() .getComponentName()); return callback.execute(); } }); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); // advice with success handler.handleMessage(message); reply = replies.receive(1000); assertNotNull(reply); assertEquals("baz", reply.getPayload()); assertEquals(componentName, compName.get()); Message<?> success = successChannel.receive(1000); assertNotNull(success); assertEquals("Hello, world!", ((AdviceMessage<?>) success).getInputMessage().getPayload()); assertEquals("foo", success.getPayload()); // advice with failure, not trapped doFail.set(true); try { handler.handleMessage(message); fail("Expected exception"); } catch (Exception e) { assertEquals("qux", e.getCause().getMessage()); } Message<?> failure = failureChannel.receive(1000); assertNotNull(failure); assertEquals("Hello, world!", ((MessagingException) failure.getPayload()).getFailedMessage().getPayload()); assertEquals("bar:qux", ((MessageHandlingExpressionEvaluatingAdviceException) failure.getPayload()).getEvaluationResult()); // advice with failure, trapped advice.setTrapException(true); handler.handleMessage(message); failure = failureChannel.receive(1000); assertNotNull(failure); assertEquals("Hello, world!", ((MessagingException) failure.getPayload()).getFailedMessage().getPayload()); assertEquals("bar:qux", ((MessageHandlingExpressionEvaluatingAdviceException) failure.getPayload()).getEvaluationResult()); assertNull(replies.receive(1)); // advice with failure, eval is result advice.setReturnFailureExpressionResult(true); handler.handleMessage(message); failure = failureChannel.receive(1000); assertNotNull(failure); assertEquals("Hello, world!", ((MessagingException) failure.getPayload()).getFailedMessage().getPayload()); assertEquals("bar:qux", ((MessageHandlingExpressionEvaluatingAdviceException) failure.getPayload()).getEvaluationResult()); reply = replies.receive(1000); assertNotNull(reply); assertEquals("bar:qux", reply.getPayload()); } @Test public void propagateOnSuccessExpressionFailures() { final AtomicBoolean doFail = new AtomicBoolean(); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { if (doFail.get()) { throw new RuntimeException("qux"); } return "baz"; } }; QueueChannel replies = new QueueChannel(); handler.setOutputChannel(replies); Message<String> message = new GenericMessage<String>("Hello, world!"); PollableChannel successChannel = new QueueChannel(); PollableChannel failureChannel = new QueueChannel(); ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice(); advice.setBeanFactory(mock(BeanFactory.class)); advice.setSuccessChannel(successChannel); advice.setFailureChannel(failureChannel); advice.setOnSuccessExpressionString("1/0"); advice.setOnFailureExpressionString("1/0"); List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(advice); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); // failing advice with success handler.handleMessage(message); Message<?> reply = replies.receive(1000); assertNotNull(reply); assertEquals("baz", reply.getPayload()); Message<?> success = successChannel.receive(1000); assertNotNull(success); assertEquals("Hello, world!", ((AdviceMessage<?>) success).getInputMessage().getPayload()); assertEquals(ArithmeticException.class, success.getPayload().getClass()); assertEquals("/ by zero", ((Exception) success.getPayload()).getMessage()); // propagate failing advice with success advice.setPropagateEvaluationFailures(true); try { handler.handleMessage(message); fail("Expected Exception"); } catch (MessageHandlingException e) { assertEquals("/ by zero", e.getCause().getMessage()); } reply = replies.receive(1); assertNull(reply); success = successChannel.receive(1000); assertNotNull(success); assertEquals("Hello, world!", ((AdviceMessage<?>) success).getInputMessage().getPayload()); assertEquals(ArithmeticException.class, success.getPayload().getClass()); assertEquals("/ by zero", ((Exception) success.getPayload()).getMessage()); } @Test public void propagateOnFailureExpressionFailures() { final AtomicBoolean doFail = new AtomicBoolean(true); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { if (doFail.get()) { throw new RuntimeException("qux"); } return "baz"; } }; QueueChannel replies = new QueueChannel(); handler.setOutputChannel(replies); Message<String> message = new GenericMessage<String>("Hello, world!"); PollableChannel successChannel = new QueueChannel(); PollableChannel failureChannel = new QueueChannel(); ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice(); advice.setBeanFactory(mock(BeanFactory.class)); advice.setSuccessChannel(successChannel); advice.setFailureChannel(failureChannel); advice.setOnSuccessExpressionString("1/0"); advice.setOnFailureExpressionString("1/0"); List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(advice); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); // failing advice with failure try { handler.handleMessage(message); fail("Expected exception"); } catch (Exception e) { assertEquals("qux", e.getCause().getMessage()); } Message<?> reply = replies.receive(1); assertNull(reply); Message<?> failure = failureChannel.receive(10000); assertNotNull(failure); assertEquals("Hello, world!", ((MessagingException) failure.getPayload()).getFailedMessage().getPayload()); assertEquals(MessageHandlingExpressionEvaluatingAdviceException.class, failure.getPayload().getClass()); assertEquals("qux", ((Exception) failure.getPayload()).getCause().getMessage()); // propagate failing advice with failure; expect original exception advice.setPropagateEvaluationFailures(true); try { handler.handleMessage(message); fail("Expected Exception"); } catch (MessageHandlingException e) { assertEquals("qux", e.getCause().getMessage()); } reply = replies.receive(1); assertNull(reply); failure = failureChannel.receive(10000); assertNotNull(failure); assertEquals("Hello, world!", ((MessagingException) failure.getPayload()).getFailedMessage().getPayload()); assertEquals(MessageHandlingExpressionEvaluatingAdviceException.class, failure.getPayload().getClass()); assertEquals("qux", ((Exception) failure.getPayload()).getCause().getMessage()); } @Test @SuppressWarnings("rawtypes") public void circuitBreakerTests() throws Exception { final AtomicBoolean doFail = new AtomicBoolean(); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { if (doFail.get()) { throw new RuntimeException("foo"); } return "bar"; } }; handler.setBeanName("baz"); handler.setOutputChannel(new QueueChannel()); RequestHandlerCircuitBreakerAdvice advice = new RequestHandlerCircuitBreakerAdvice(); /* * Circuit breaker opens after 2 failures; allows a new attempt after 100ms and * immediately opens again if that attempt fails. After a successful attempt, * we reset the failure counter. */ advice.setThreshold(2); advice.setHalfOpenAfter(1000); List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(advice); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); doFail.set(true); Message<String> message = new GenericMessage<String>("Hello, world!"); try { handler.handleMessage(message); fail("Expected failure"); } catch (Exception e) { assertEquals("foo", e.getCause().getMessage()); } try { handler.handleMessage(message); fail("Expected failure"); } catch (Exception e) { assertEquals("foo", e.getCause().getMessage()); } try { handler.handleMessage(message); fail("Expected failure"); } catch (Exception e) { assertEquals("Circuit Breaker is Open for baz", e.getCause().getMessage()); } Map metadataMap = TestUtils.getPropertyValue(advice, "metadataMap", Map.class); Object metadata = metadataMap.values().iterator().next(); DirectFieldAccessor metadataDfa = new DirectFieldAccessor(metadata); // Simulate some timeout in between requests metadataDfa.setPropertyValue("lastFailure", System.currentTimeMillis() - 10000); try { handler.handleMessage(message); fail("Expected failure"); } catch (Exception e) { assertEquals("foo", e.getCause().getMessage()); } try { handler.handleMessage(message); fail("Expected failure"); } catch (Exception e) { assertEquals("Circuit Breaker is Open for baz", e.getCause().getMessage()); } // Simulate some timeout in between requests metadataDfa.setPropertyValue("lastFailure", System.currentTimeMillis() - 10000); doFail.set(false); handler.handleMessage(message); doFail.set(true); try { handler.handleMessage(message); fail("Expected failure"); } catch (Exception e) { assertEquals("foo", e.getCause().getMessage()); } try { handler.handleMessage(message); fail("Expected failure"); } catch (Exception e) { assertEquals("foo", e.getCause().getMessage()); } try { handler.handleMessage(message); fail("Expected failure"); } catch (Exception e) { assertEquals("Circuit Breaker is Open for baz", e.getCause().getMessage()); } } @Test public void defaultRetrySucceedOnThirdTry() { final AtomicInteger counter = new AtomicInteger(2); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { if (counter.getAndDecrement() > 0) { throw new RuntimeException("foo"); } return "bar"; } }; QueueChannel replies = new QueueChannel(); handler.setOutputChannel(replies); RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(advice); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); Message<String> message = new GenericMessage<String>("Hello, world!"); handler.handleMessage(message); assertTrue(counter.get() == -1); Message<?> reply = replies.receive(10000); assertNotNull(reply); assertEquals("bar", reply.getPayload()); } @Test public void defaultStatefulRetrySucceedOnThirdTry() { final AtomicInteger counter = new AtomicInteger(2); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { if (counter.getAndDecrement() > 0) { throw new RuntimeException("foo"); } return "bar"; } }; QueueChannel replies = new QueueChannel(); handler.setOutputChannel(replies); RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); advice.setRetryStateGenerator(message -> new DefaultRetryState(message.getHeaders().getId())); List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(advice); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); Message<String> message = new GenericMessage<String>("Hello, world!"); for (int i = 0; i < 3; i++) { try { handler.handleMessage(message); } catch (Exception e) { assertTrue(i < 2); } } assertTrue(counter.get() == -1); Message<?> reply = replies.receive(10000); assertNotNull(reply); assertEquals("bar", reply.getPayload()); } @Test public void defaultStatefulRetryRecoverAfterThirdTry() { final AtomicInteger counter = new AtomicInteger(3); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { if (counter.getAndDecrement() > 0) { throw new RuntimeException("foo"); } return "bar"; } }; QueueChannel replies = new QueueChannel(); handler.setOutputChannel(replies); RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); advice.setRetryStateGenerator(message -> new DefaultRetryState(message.getHeaders().getId())); defaultStatefulRetryRecoverAfterThirdTryGuts(counter, handler, replies, advice); } @Test public void defaultStatefulRetryRecoverAfterThirdTrySpelState() { final AtomicInteger counter = new AtomicInteger(3); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { if (counter.getAndDecrement() > 0) { throw new RuntimeException("foo"); } return "bar"; } }; QueueChannel replies = new QueueChannel(); handler.setOutputChannel(replies); RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); advice.setRetryStateGenerator(new SpelExpressionRetryStateGenerator("headers['id']")); defaultStatefulRetryRecoverAfterThirdTryGuts(counter, handler, replies, advice); } private void defaultStatefulRetryRecoverAfterThirdTryGuts(final AtomicInteger counter, AbstractReplyProducingMessageHandler handler, QueueChannel replies, RequestHandlerRetryAdvice advice) { advice.setRecoveryCallback(context -> "baz"); List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(advice); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); Message<String> message = new GenericMessage<String>("Hello, world!"); for (int i = 0; i < 4; i++) { try { handler.handleMessage(message); } catch (Exception e) { } } assertTrue(counter.get() == 0); Message<?> reply = replies.receive(10000); assertNotNull(reply); assertEquals("baz", reply.getPayload()); } @Test public void errorMessageSendingRecovererTests() { AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { throw new RuntimeException("fooException"); } }; QueueChannel errors = new QueueChannel(); RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); ErrorMessageSendingRecoverer recoverer = new ErrorMessageSendingRecoverer(errors); advice.setRecoveryCallback(recoverer); List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(advice); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); Message<String> message = new GenericMessage<String>("Hello, world!"); handler.handleMessage(message); Message<?> error = errors.receive(10000); assertNotNull(error); assertEquals("fooException", ((Exception) error.getPayload()).getCause().getMessage()); } @Test public void errorMessageSendingRecovererTestsNoThrowable() { AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { throw new RuntimeException("fooException"); } }; QueueChannel errors = new QueueChannel(); RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); ErrorMessageSendingRecoverer recoverer = new ErrorMessageSendingRecoverer(errors); advice.setRecoveryCallback(recoverer); RetryTemplate retryTemplate = new RetryTemplate(); retryTemplate.setRetryPolicy(new SimpleRetryPolicy() { static final long serialVersionUID = -1; @Override public boolean canRetry(RetryContext context) { return false; } }); advice.setRetryTemplate(retryTemplate); advice.setBeanFactory(mock(BeanFactory.class)); advice.afterPropertiesSet(); List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(advice); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); Message<String> message = new GenericMessage<String>("Hello, world!"); handler.handleMessage(message); Message<?> error = errors.receive(10000); assertNotNull(error); assertTrue(error.getPayload() instanceof ErrorMessageSendingRecoverer.RetryExceptionNotAvailableException); assertNotNull(((MessagingException) error.getPayload()).getFailedMessage()); assertSame(message, ((MessagingException) error.getPayload()).getFailedMessage()); } @Test public void testINT2858RetryAdviceAsFirstInAdviceChain() { final AtomicInteger counter = new AtomicInteger(3); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { return "foo"; } }; List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(new RequestHandlerRetryAdvice()); adviceChain.add((MethodInterceptor) invocation -> { counter.getAndDecrement(); throw new RuntimeException("intentional"); }); handler.setBeanFactory(mock(BeanFactory.class)); handler.setAdviceChain(adviceChain); handler.afterPropertiesSet(); try { handler.handleMessage(new GenericMessage<String>("test")); } catch (Exception e) { Throwable cause = e.getCause(); assertEquals(RuntimeException.class, cause.getClass()); assertEquals("intentional", cause.getMessage()); } assertTrue(counter.get() == 0); } @Test public void testINT2858RetryAdviceAsNestedInAdviceChain() { final AtomicInteger counter = new AtomicInteger(0); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { return "foo"; } }; QueueChannel replies = new QueueChannel(); handler.setOutputChannel(replies); List<Advice> adviceChain = new ArrayList<Advice>(); ExpressionEvaluatingRequestHandlerAdvice expressionAdvice = new ExpressionEvaluatingRequestHandlerAdvice(); expressionAdvice.setBeanFactory(mock(BeanFactory.class)); // MessagingException / RuntimeException expressionAdvice.setOnFailureExpressionString("#exception.cause.message"); expressionAdvice.setReturnFailureExpressionResult(true); final AtomicInteger outerCounter = new AtomicInteger(); adviceChain.add(new AbstractRequestHandlerAdvice() { @Override protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception { outerCounter.incrementAndGet(); return callback.execute(); } }); adviceChain.add(expressionAdvice); adviceChain.add(new RequestHandlerRetryAdvice()); adviceChain.add((MethodInterceptor) invocation -> { throw new RuntimeException("intentional: " + counter.incrementAndGet()); }); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); handler.handleMessage(new GenericMessage<String>("test")); Message<?> receive = replies.receive(10000); assertNotNull(receive); assertEquals("intentional: 3", receive.getPayload()); assertEquals(1, outerCounter.get()); } @Test public void testINT2858ExpressionAdviceWithSendFailureOnEachRetry() { final AtomicInteger counter = new AtomicInteger(0); AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { return "foo"; } }; QueueChannel errors = new QueueChannel(); List<Advice> adviceChain = new ArrayList<Advice>(); ExpressionEvaluatingRequestHandlerAdvice expressionAdvice = new ExpressionEvaluatingRequestHandlerAdvice(); expressionAdvice.setBeanFactory(mock(BeanFactory.class)); expressionAdvice.setOnFailureExpressionString("#exception.message"); expressionAdvice.setFailureChannel(errors); adviceChain.add(new RequestHandlerRetryAdvice()); adviceChain.add(expressionAdvice); adviceChain.add((MethodInterceptor) invocation -> { throw new RuntimeException("intentional: " + counter.incrementAndGet()); }); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); try { handler.handleMessage(new GenericMessage<String>("test")); } catch (Exception e) { assertEquals("intentional: 3", e.getCause().getMessage()); } for (int i = 1; i <= 3; i++) { Message<?> receive = errors.receive(10000); assertNotNull(receive); assertEquals("intentional: " + i, ((MessageHandlingExpressionEvaluatingAdviceException) receive.getPayload()).getEvaluationResult()); } assertNull(errors.receive(1)); } /** * Verify that Errors such as OOM are properly propagated. */ @Test public void throwableProperlyPropagated() throws Exception { AbstractRequestHandlerAdvice advice = new AbstractRequestHandlerAdvice() { @Override protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception { Object result; try { result = callback.execute(); } catch (Exception e) { // should not be unwrapped because the cause is a Throwable throw this.unwrapExceptionIfNecessary(e); } return result; } }; final Throwable theThrowable = new Throwable("foo"); MethodInvocation methodInvocation = mock(MethodInvocation.class); Method method = AbstractReplyProducingMessageHandler.class.getDeclaredMethod("handleRequestMessage", Message.class); when(methodInvocation.getMethod()).thenReturn(method); when(methodInvocation.getArguments()).thenReturn(new Object[] { new GenericMessage<String>("foo") }); try { doAnswer(invocation -> { throw theThrowable; }).when(methodInvocation).proceed(); advice.invoke(methodInvocation); fail("Expected throwable"); } catch (Throwable t) { assertSame(theThrowable, t); } } /** * Verify that Errors such as OOM are properly propagated and we suppress the * ThrowableHolderException from the output message. */ @Test public void throwableProperlyPropagatedAndReported() throws Exception { QueueChannel errors = new QueueChannel(); ExpressionEvaluatingRequestHandlerAdvice expressionAdvice = new ExpressionEvaluatingRequestHandlerAdvice(); expressionAdvice.setBeanFactory(mock(BeanFactory.class)); expressionAdvice.setOnFailureExpressionString("'foo'"); expressionAdvice.setFailureChannel(errors); Throwable theThrowable = new Throwable("foo"); ProxyFactory proxyFactory = new ProxyFactory(new Foo(theThrowable)); proxyFactory.addAdvice(expressionAdvice); Bar fooHandler = (Bar) proxyFactory.getProxy(); try { fooHandler.handleRequestMessage(new GenericMessage<String>("foo")); fail("Expected throwable"); } catch (Throwable t) { assertSame(theThrowable, t); ErrorMessage error = (ErrorMessage) errors.receive(10000); assertNotNull(error); assertSame(theThrowable, error.getPayload().getCause()); } } @Test public void testInappropriateAdvice() throws Exception { final AtomicBoolean called = new AtomicBoolean(false); Advice advice = new AbstractRequestHandlerAdvice() { @Override protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception { called.set(true); return callback.execute(); } }; PollableChannel inputChannel = new QueueChannel(); PollingConsumer consumer = new PollingConsumer(inputChannel, message -> { }); consumer.setAdviceChain(Collections.singletonList(advice)); consumer.setTaskExecutor(new ErrorHandlingTaskExecutor(Executors.newSingleThreadExecutor(), t -> { })); consumer.setBeanFactory(mock(BeanFactory.class)); consumer.afterPropertiesSet(); consumer.setTaskScheduler(mock(TaskScheduler.class)); consumer.start(); Callable<?> pollingTask = TestUtils.getPropertyValue(consumer, "poller.pollingTask", Callable.class); assertTrue(AopUtils.isAopProxy(pollingTask)); Log logger = TestUtils.getPropertyValue(advice, "logger", Log.class); logger = spy(logger); when(logger.isWarnEnabled()).thenReturn(Boolean.TRUE); final AtomicReference<String> logMessage = new AtomicReference<String>(); doAnswer(invocation -> { logMessage.set(invocation.getArgument(0)); return null; }).when(logger).warn(Mockito.anyString()); DirectFieldAccessor accessor = new DirectFieldAccessor(advice); accessor.setPropertyValue("logger", logger); pollingTask.call(); assertFalse(called.get()); assertNotNull(logMessage.get()); assertThat(logMessage.get(), Matchers.containsString("can only be used for MessageHandlers; " + "an attempt to advise method 'call' in " + "'org.springframework.integration.endpoint.AbstractPollingEndpoint")); consumer.stop(); } public void filterDiscardNoAdvice() { MessageFilter filter = new MessageFilter(message -> false); QueueChannel discardChannel = new QueueChannel(); filter.setDiscardChannel(discardChannel); filter.handleMessage(new GenericMessage<String>("foo")); assertNotNull(discardChannel.receive(0)); } @Test public void filterDiscardWithinAdvice() { MessageFilter filter = new MessageFilter(message -> false); final QueueChannel discardChannel = new QueueChannel(); filter.setDiscardChannel(discardChannel); List<Advice> adviceChain = new ArrayList<Advice>(); final AtomicReference<Message<?>> discardedWithinAdvice = new AtomicReference<Message<?>>(); adviceChain.add(new AbstractRequestHandlerAdvice() { @Override protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception { Object result = callback.execute(); discardedWithinAdvice.set(discardChannel.receive(0)); return result; } }); filter.setAdviceChain(adviceChain); filter.setBeanFactory(mock(BeanFactory.class)); filter.afterPropertiesSet(); filter.handleMessage(new GenericMessage<String>("foo")); assertNotNull(discardedWithinAdvice.get()); assertNull(discardChannel.receive(0)); } @Test public void filterDiscardOutsideAdvice() { MessageFilter filter = new MessageFilter(message -> false); final QueueChannel discardChannel = new QueueChannel(); filter.setDiscardChannel(discardChannel); List<Advice> adviceChain = new ArrayList<Advice>(); final AtomicReference<Message<?>> discardedWithinAdvice = new AtomicReference<Message<?>>(); final AtomicBoolean adviceCalled = new AtomicBoolean(); adviceChain.add(new AbstractRequestHandlerAdvice() { @Override protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception { Object result = callback.execute(); discardedWithinAdvice.set(discardChannel.receive(0)); adviceCalled.set(true); return result; } }); filter.setAdviceChain(adviceChain); filter.setDiscardWithinAdvice(false); filter.setBeanFactory(mock(BeanFactory.class)); filter.afterPropertiesSet(); filter.handleMessage(new GenericMessage<String>("foo")); assertTrue(adviceCalled.get()); assertNull(discardedWithinAdvice.get()); assertNotNull(discardChannel.receive(0)); } @Test public void testInt2943RetryWithExceptionClassifierFalse() { testInt2943RetryWithExceptionClassifier(false, 1); } @Test public void testInt2943RetryWithExceptionClassifierTrue() { testInt2943RetryWithExceptionClassifier(true, 3); } private void testInt2943RetryWithExceptionClassifier(boolean retryForMyException, int expected) { final AtomicInteger counter = new AtomicInteger(0); @SuppressWarnings("serial") class MyException extends RuntimeException { } AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { counter.incrementAndGet(); throw new MyException(); } }; QueueChannel replies = new QueueChannel(); handler.setOutputChannel(replies); RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice(); RetryTemplate retryTemplate = new RetryTemplate(); Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<Class<? extends Throwable>, Boolean>(); retryableExceptions.put(MyException.class, retryForMyException); retryableExceptions.put(MessagingException.class, true); retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3, retryableExceptions)); advice.setRetryTemplate(retryTemplate); List<Advice> adviceChain = new ArrayList<Advice>(); adviceChain.add(advice); handler.setAdviceChain(adviceChain); handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); Message<String> message = new GenericMessage<String>("Hello, world!"); try { handler.handleMessage(message); fail("MessagingException expected."); } catch (Exception e) { assertThat(e, Matchers.instanceOf(MessagingException.class)); assertThat(e.getCause(), Matchers.instanceOf(MyException.class)); } assertEquals(expected, counter.get()); } @Test public void enhancedRecoverer() throws Exception { QueueChannel channel = new QueueChannel(); ErrorMessageSendingRecoverer recoverer = new ErrorMessageSendingRecoverer(channel); recoverer.publish(new GenericMessage<>("foo"), new GenericMessage<>("bar"), new RuntimeException("baz")); Message<?> error = channel.receive(0); assertThat(error, instanceOf(ErrorMessage.class)); assertThat(error.getPayload(), instanceOf(MessagingException.class)); MessagingException payload = (MessagingException) error.getPayload(); assertThat(payload.getCause(), instanceOf(RuntimeException.class)); assertThat(payload.getCause().getMessage(), equalTo("baz")); assertThat(payload.getFailedMessage().getPayload(), equalTo("bar")); assertThat(((ErrorMessage) error).getOriginalMessage().getPayload(), equalTo("foo")); } private interface Bar { Object handleRequestMessage(Message<?> message) throws Throwable; } private class Foo implements Bar { public final Throwable throwable; Foo(Throwable throwable) { this.throwable = throwable; } @Override public Object handleRequestMessage(Message<?> message) throws Throwable { throw this.throwable; } } }