INT-2943: Retry: Don't Wrap in MessagingException

Previously, `RequestHandlerRetryAdvice` wrapped a Handler's (business) Exceptions in a
`MessagingException` on each retry within `RetryCallback#doWithRetry`
and before `RetryPolicy#canRetry`.
It made useless some retry framework out-of-the-box features like `BinaryExceptionClassifier`

* Push wrapping to `MessagingException` after `retryTemplate#execute`
* Polishing `ErrorMessageSendingRecoverer` regarding new logic
* Add `retryableExceptions` test

You can now properly specify Business exceptions with the retry policy to
perform conditional retry.

JIRA: https://jira.springsource.org/browse/INT-2943

Polishing

Only wrap Throwable in ThrowableHolderException if the Throwable
is not an Exception.

INT-2943: Polishing ErrorMessageSendingRecoverer
This commit is contained in:
Artem Bilan
2013-09-23 17:55:32 +03:00
committed by Gary Russell
parent 347c3f03e5
commit e8ecbac7fd
4 changed files with 90 additions and 24 deletions

View File

@@ -71,6 +71,9 @@ public abstract class AbstractRequestHandlerAdvice extends IntegrationObjectSupp
try {
return invocation.proceed();
}
catch (Exception e) {
throw e;
}
catch (Throwable e) {
throw new ThrowableHolderException(e);
}
@@ -91,6 +94,9 @@ public abstract class AbstractRequestHandlerAdvice extends IntegrationObjectSupp
" so please raise an issue if you see this exception");
}
}
catch (Exception e) {
throw e;
}
catch (Throwable e) {
throw new ThrowableHolderException(e);
}
@@ -98,12 +104,7 @@ public abstract class AbstractRequestHandlerAdvice extends IntegrationObjectSupp
}, invocationThis, message);
}
catch (Exception e) {
if (e instanceof ThrowableHolderException) {
throw e.getCause();
}
else {
throw e;
}
throw this.unwrapThrowableIfNecessary(e);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -17,6 +17,7 @@ package org.springframework.integration.handler.advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessagingException;
@@ -29,7 +30,9 @@ import org.springframework.util.Assert;
/**
* RecoveryCallback that sends the final throwable as an ErrorMessage after
* retry exhaustion.
*
* @author Gary Russell
* @author Artem Bilan
* @since 2.2
*
*/
@@ -57,11 +60,11 @@ public class ErrorMessageSendingRecoverer implements RecoveryCallback<Object> {
"this can occur, for example, if the RetryPolicy allowed zero attempts to execute the handler; " +
"RetryContext: " + context.toString());
}
else if (!(lastThrowable instanceof MessagingException)) {
lastThrowable = new MessagingException((Message<?>) context.getAttribute("message"), lastThrowable);
}
if (logger.isDebugEnabled()) {
String supplement = "";
if (lastThrowable instanceof MessagingException) {
supplement = ":failedMessage:" + ((MessagingException) lastThrowable).getFailedMessage();
}
String supplement = ":failedMessage:" + ((MessagingException) lastThrowable).getFailedMessage();
logger.debug("Sending ErrorMessage " + supplement, lastThrowable);
}
messagingTemplate.send(new ErrorMessage(lastThrowable));

View File

@@ -83,22 +83,20 @@ public class RequestHandlerRetryAdvice extends AbstractRequestHandlerAdvice
try {
return retryTemplate.execute(new RetryCallback<Object>() {
public Object doWithRetry(RetryContext context) throws Exception {
try {
return callback.cloneAndExecute();
}
catch (MessagingException e) {
if (e.getFailedMessage() == null) {
e.setFailedMessage(message);
}
throw e;
}
catch (Exception e) {
throw new MessagingException(message, "Failed to invoke handler",
unwrapExceptionIfNecessary(e));
}
return callback.cloneAndExecute();
}
}, this.recoveryCallback, retryState);
}
catch (MessagingException e) {
if (e.getFailedMessage() == null) {
e.setFailedMessage(message);
}
throw e;
}
catch (Exception e) {
throw new MessagingException(message, "Failed to invoke handler",
unwrapExceptionIfNecessary(e));
}
finally {
messageHolder.remove();
}

View File

@@ -20,6 +20,7 @@ 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;
@@ -30,7 +31,9 @@ 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;
@@ -41,6 +44,7 @@ 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.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
@@ -931,6 +935,66 @@ public class AdvisedMessageHandlerTests {
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.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());
}
private interface Bar {
Object handleRequestMessage(Message<?> message) throws Throwable;
}