OPEN - issue BATCH-777: Parametrise RetryCallback and related interfaces
Make stateful retry explicit in the RepeatOperations interface instead of hidden in a RetryPolicy
This commit is contained in:
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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.batch.retry.callback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.batch.retry.RetryCallback;
|
||||
import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.RetryException;
|
||||
import org.springframework.batch.retry.StubItemKeyGeneratorRecoverer;
|
||||
import org.springframework.batch.retry.TerminatedRetryException;
|
||||
import org.springframework.batch.retry.context.RetryContextSupport;
|
||||
import org.springframework.batch.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.batch.retry.support.RetryTemplate;
|
||||
|
||||
public class RecoveryRetryCallbackTests extends TestCase {
|
||||
|
||||
List<Object> calls = new ArrayList<Object>();
|
||||
|
||||
int count = 0;
|
||||
|
||||
RetryTemplate template;
|
||||
|
||||
StubItemKeyGeneratorRecoverer recoverer;
|
||||
|
||||
RecoveryRetryCallback callback;
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
template = new RetryTemplate();
|
||||
recoverer = new StubItemKeyGeneratorRecoverer() {
|
||||
public Object recover(Object data, Throwable cause) {
|
||||
count++;
|
||||
calls.add(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public Object getKey(Object item) {
|
||||
return "key" + (count++);
|
||||
}
|
||||
};
|
||||
callback = new RecoveryRetryCallback("foo", new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testDoWithRetrySuccessfulFirstTime() throws Exception {
|
||||
template.execute(callback);
|
||||
assertEquals(1, count);
|
||||
}
|
||||
|
||||
public void testContextInitializedWithItemAndCanRetry() throws Exception {
|
||||
// We can use the policy to intercept the context and do something with
|
||||
// the item...
|
||||
callback = new RecoveryRetryCallback("bar", new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
throw new IllegalStateException("Detected bar");
|
||||
}
|
||||
});
|
||||
assertEquals(0, calls.size());
|
||||
template.setRetryPolicy(new NeverRetryPolicy() {
|
||||
public boolean canRetry(RetryContext context) {
|
||||
// ...register the failed item
|
||||
calls.add("item(" + count + ")=" + callback.getKey());
|
||||
// Do not call the base class method - the attempt counts as
|
||||
// successful now
|
||||
if (count < 2) // only retry once
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
try {
|
||||
template.execute(callback);
|
||||
fail("Expected IllegalStateException");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
System.err.println(calls);
|
||||
assertEquals(2, count);
|
||||
// Two from initial attempt (one in shouldRethrow and one at start of
|
||||
// do loop), two from the final attempt (same)...
|
||||
assertEquals(4, calls.size());
|
||||
assertEquals("item(1)=bar", calls.get(1));
|
||||
}
|
||||
|
||||
public void testContextInitializedWithItemAndRegisterThrowable() throws Exception {
|
||||
// We can use the policy to intercept the context and do something with
|
||||
// the item...
|
||||
callback = new RecoveryRetryCallback("bar", new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
throw new IllegalStateException("Detected bar");
|
||||
}
|
||||
});
|
||||
assertEquals(0, calls.size());
|
||||
template.setRetryPolicy(new NeverRetryPolicy() {
|
||||
public void registerThrowable(RetryContext context, Exception throwable) throws TerminatedRetryException {
|
||||
// ...register the failed item
|
||||
calls.add("item=" + callback.getKey());
|
||||
// Call the base class method so that the next attempt is a
|
||||
// failure.
|
||||
super.registerThrowable(context, throwable);
|
||||
}
|
||||
});
|
||||
try {
|
||||
template.execute(callback);
|
||||
fail("Expected IllegalStateException");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
// One call from the callback itself and one from the retry policy
|
||||
assertEquals(1, count);
|
||||
assertEquals(1, calls.size());
|
||||
assertEquals("item=bar", calls.get(0));
|
||||
}
|
||||
|
||||
public void testContextMarkedExhausted() throws Exception {
|
||||
RetryContext context = new RetryContextSupport(null);
|
||||
context.setExhaustedOnly();
|
||||
try {
|
||||
callback.doWithRetry(context);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
assertTrue(t instanceof RetryException);
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetKey() throws Exception {
|
||||
callback = new RecoveryRetryCallback("foo", null, "key0");
|
||||
assertEquals("key0", callback.getKey());
|
||||
}
|
||||
|
||||
public void testRecoverWithoutSession() throws Exception {
|
||||
recoverer.recover("foo", null);
|
||||
assertEquals(1, count);
|
||||
assertEquals(1, calls.size());
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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.batch.retry.policy;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.batch.retry.RetryCallback;
|
||||
import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.TerminatedRetryException;
|
||||
import org.springframework.batch.retry.context.RetryContextSupport;
|
||||
import org.springframework.batch.retry.support.RetryTemplate;
|
||||
|
||||
public class ExternalRetryPolicyTests extends TestCase {
|
||||
|
||||
public void testExternalRetryStopsLoop() throws Exception {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setExceptionToThrow(new IllegalArgumentException());
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setRetryPolicy(new MockExternalRetryPolicy(3));
|
||||
|
||||
Object result = "start_foo";
|
||||
try {
|
||||
result = retryTemplate.execute(callback);
|
||||
// If template is external and retry is still permitted, then
|
||||
// we expect the exception to be propagated.
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertNull(e.getMessage());
|
||||
}
|
||||
assertEquals(1, callback.attempts);
|
||||
assertEquals("start_foo", result);
|
||||
}
|
||||
|
||||
public void testExternalRetryWithFailAndNoRetry() throws Exception {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setExceptionToThrow(new IllegalArgumentException());
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
|
||||
// Allow one unsuccessful attempt (plus one for recovery):
|
||||
retryTemplate.setRetryPolicy(new MockExternalRetryPolicy(1));
|
||||
|
||||
Object result = "start_foo";
|
||||
try {
|
||||
result = retryTemplate.execute(callback);
|
||||
// The first failed attempt we expect to retry...
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
assertNull(e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
result = retryTemplate.execute(callback);
|
||||
// We always get a second attempt...
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// This is now the "exhausted" message:
|
||||
assertNotNull(e.getMessage());
|
||||
// But if template is external we should
|
||||
// swallow the exception when retry is impossible.
|
||||
fail("Did not expect IllegalArgumentException");
|
||||
}
|
||||
// Callback is called once: the recovery path should be called in
|
||||
// handleRetryExhausted (so not in this test)...
|
||||
assertEquals(1, callback.attempts);
|
||||
assertEquals(null, result);
|
||||
}
|
||||
|
||||
public void testNonThrowableIsNotRecoverable() throws Exception {
|
||||
|
||||
try {
|
||||
MockExternalRetryPolicy policy = new MockExternalRetryPolicy(1);
|
||||
policy.setRecoverableExceptionClasses(new Class[] { String.class });
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// Expected
|
||||
assertTrue(Pattern.matches(".*not.*Throwable.*", e.getMessage()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testSuclassIsRecoverable() throws Exception {
|
||||
|
||||
MockExternalRetryPolicy policy = new MockExternalRetryPolicy(1);
|
||||
policy.setRecoverableExceptionClasses(new Class[] { IllegalArgumentException.class });
|
||||
|
||||
RetryContextSupport context = new RetryContextSupport(null);
|
||||
|
||||
assertTrue(policy.shouldRethrow(context));
|
||||
|
||||
context.registerThrowable(new IllegalStateException());
|
||||
assertTrue(policy.shouldRethrow(context));
|
||||
|
||||
context.registerThrowable(new IllegalArgumentException());
|
||||
assertFalse(policy.shouldRethrow(context));
|
||||
|
||||
context.registerThrowable(new IllegalArgumentException() {
|
||||
// subclass
|
||||
});
|
||||
assertFalse(policy.shouldRethrow(context));
|
||||
|
||||
}
|
||||
|
||||
public void testRecoverableException() throws Exception {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setExceptionToThrow(new IllegalArgumentException());
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
|
||||
// Allow one unsuccessful attempt (should take recovery path):
|
||||
MockExternalRetryPolicy policy = new MockExternalRetryPolicy(1);
|
||||
policy
|
||||
.setRecoverableExceptionClasses(new Class[] { IllegalArgumentException.class,
|
||||
IllegalStateException.class });
|
||||
retryTemplate.setRetryPolicy(policy);
|
||||
|
||||
Object result = "start_foo";
|
||||
try {
|
||||
result = retryTemplate.execute(callback);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// This is the "exhausted" message:
|
||||
assertNotNull(e.getMessage());
|
||||
// But if template is external we should
|
||||
// swallow the exception when retry is impossible.
|
||||
fail("Did not expect IllegalArgumentException");
|
||||
}
|
||||
// Callback is called once: the recovery path should be called in
|
||||
// handleRetryExhausted (so not in this test)...
|
||||
assertEquals(1, callback.attempts);
|
||||
assertEquals(null, result);
|
||||
}
|
||||
|
||||
private static class MockRetryCallback implements RetryCallback {
|
||||
|
||||
private int attempts;
|
||||
|
||||
public static String EXHAUSTED = "complete";
|
||||
|
||||
private Exception exceptionToThrow = new Exception();
|
||||
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
this.attempts++;
|
||||
if (((Boolean) context.getAttribute(EXHAUSTED)).booleanValue()) {
|
||||
// This is now a recovery step...
|
||||
return null;
|
||||
}
|
||||
// Otherwise just barf...
|
||||
throw this.exceptionToThrow;
|
||||
}
|
||||
|
||||
public void setExceptionToThrow(Exception exceptionToThrow) {
|
||||
this.exceptionToThrow = exceptionToThrow;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MockExternalRetryPolicy extends AbstractStatefulRetryPolicy {
|
||||
|
||||
private int retryLimit = 0;
|
||||
|
||||
// This one is stateful for testing only - normally this state would
|
||||
// have to be managed by the context.
|
||||
private int attempts = 0;
|
||||
|
||||
public MockExternalRetryPolicy(int retryLimit) {
|
||||
super();
|
||||
this.retryLimit = retryLimit;
|
||||
}
|
||||
|
||||
public boolean canRetry(RetryContext context) {
|
||||
return attempts < retryLimit;
|
||||
}
|
||||
|
||||
public void close(RetryContext context, boolean succeeded) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public RetryContext open(RetryCallback callback, RetryContext parent) {
|
||||
RetryContextSupport context = new RetryContextSupport(null);
|
||||
context.setAttribute(MockRetryCallback.EXHAUSTED, Boolean.valueOf(!canRetry(context)));
|
||||
return context;
|
||||
}
|
||||
|
||||
public void registerThrowable(RetryContext context, Exception throwable) throws TerminatedRetryException {
|
||||
((RetryContextSupport) context).registerThrowable(throwable);
|
||||
attempts++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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.batch.retry.policy;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.batch.retry.RecoveryCallback;
|
||||
import org.springframework.batch.retry.RetryCallback;
|
||||
import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.RetryState;
|
||||
import org.springframework.batch.retry.support.RetryTemplate;
|
||||
|
||||
public class FatalExceptionRetryPolicyTests extends TestCase {
|
||||
|
||||
public void testFatalExceptionWithoutState() throws Exception {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setExceptionToThrow(new IllegalArgumentException());
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
// Allow multiple attempts in general...
|
||||
SimpleRetryPolicy policy = new SimpleRetryPolicy(3);
|
||||
retryTemplate.setRetryPolicy(policy);
|
||||
|
||||
// ...but make sure certain exceptions are fatal
|
||||
policy.setFatalExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(IllegalArgumentException.class);
|
||||
add(IllegalStateException.class);
|
||||
}
|
||||
});
|
||||
RecoveryCallback recoveryCallback = new RecoveryCallback() {
|
||||
public Object recover(RetryContext context) throws Exception {
|
||||
return "bar";
|
||||
}
|
||||
};
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
result = retryTemplate.execute(callback, recoveryCallback);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// We should swallow the exception when recovery is possible
|
||||
fail("Did not expect IllegalArgumentException");
|
||||
}
|
||||
// Callback is called once: the recovery path should also be called
|
||||
assertEquals(1, callback.attempts);
|
||||
assertEquals("bar", result);
|
||||
}
|
||||
|
||||
public void testFatalExceptionWithState() throws Exception {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setExceptionToThrow(new IllegalArgumentException());
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
SimpleRetryPolicy policy = new SimpleRetryPolicy(3);
|
||||
retryTemplate.setRetryPolicy(policy);
|
||||
|
||||
policy.setFatalExceptionClasses(new HashSet<Class<? extends Throwable>>() {
|
||||
{
|
||||
add(IllegalArgumentException.class);
|
||||
add(IllegalStateException.class);
|
||||
}
|
||||
});
|
||||
RecoveryCallback recoveryCallback = new RecoveryCallback() {
|
||||
public Object recover(RetryContext context) throws Exception {
|
||||
return "bar";
|
||||
}
|
||||
};
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
retryTemplate.execute(callback, recoveryCallback, new RetryState("foo"));
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// If stateful we have to always rethrow. Clients who want special
|
||||
// cases have to implement them in the callback
|
||||
}
|
||||
result = retryTemplate.execute(callback, recoveryCallback, new RetryState("foo"));
|
||||
// Callback is called once: the recovery path should also be called
|
||||
assertEquals(1, callback.attempts);
|
||||
assertEquals("bar", result);
|
||||
}
|
||||
|
||||
private static class MockRetryCallback implements RetryCallback {
|
||||
|
||||
private int attempts;
|
||||
|
||||
private Exception exceptionToThrow = new Exception();
|
||||
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
this.attempts++;
|
||||
// Otherwise just barf...
|
||||
throw this.exceptionToThrow;
|
||||
}
|
||||
|
||||
public void setExceptionToThrow(Exception exceptionToThrow) {
|
||||
this.exceptionToThrow = exceptionToThrow;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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.batch.retry.policy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.context.RepeatContextSupport;
|
||||
import org.springframework.batch.repeat.support.RepeatSynchronizationManager;
|
||||
import org.springframework.batch.retry.RecoveryCallback;
|
||||
import org.springframework.batch.retry.RetryCallback;
|
||||
import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.RetryException;
|
||||
import org.springframework.batch.retry.callback.RecoveryRetryCallback;
|
||||
import org.springframework.batch.retry.support.RetryTemplate;
|
||||
|
||||
public class RecoveryRetryPolicyTests extends TestCase {
|
||||
|
||||
private RecoveryCallbackRetryPolicy policy = new RecoveryCallbackRetryPolicy();
|
||||
|
||||
private int count = 0;
|
||||
|
||||
private List<String> list = new ArrayList<String>();
|
||||
|
||||
public void testOpenSunnyDay() throws Exception {
|
||||
|
||||
final StringHolder item = new StringHolder("foo");
|
||||
RetryCallback writer = new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
list.add(item.string);
|
||||
return item;
|
||||
}
|
||||
};
|
||||
|
||||
RetryContext context = policy.open(new RecoveryRetryCallback("foo", writer), null);
|
||||
assertNotNull(context);
|
||||
// we haven't called the processor yet...
|
||||
assertEquals(0, count);
|
||||
}
|
||||
|
||||
public void testOpenWithWrongCallbackType() {
|
||||
try {
|
||||
policy.open(new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
return null;
|
||||
}
|
||||
}, null);
|
||||
fail("Expected IllegalStateException");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
assertTrue(e.getMessage().indexOf("must be RecoveryRetryCallback") >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCanRetry() {
|
||||
policy.setDelegate(new AlwaysRetryPolicy());
|
||||
|
||||
RetryContext context = policy.open(new RecoveryRetryCallback("foo", new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
return null;
|
||||
}
|
||||
}), null);
|
||||
assertNotNull(context);
|
||||
|
||||
// We can always retry if delegate says so...
|
||||
assertTrue(policy.canRetry(context));
|
||||
}
|
||||
|
||||
public void testRegisterThrowable() {
|
||||
policy.setDelegate(new NeverRetryPolicy());
|
||||
RetryContext context = policy.open(new RecoveryRetryCallback("foo", new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
return null;
|
||||
}
|
||||
}), null);
|
||||
assertNotNull(context);
|
||||
policy.registerThrowable(context, new Exception());
|
||||
assertFalse(policy.canRetry(context));
|
||||
}
|
||||
|
||||
public void testClose() throws Exception {
|
||||
policy.setDelegate(new NeverRetryPolicy());
|
||||
RetryContext context = policy.open(new RecoveryRetryCallback("foo", new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
return null;
|
||||
}
|
||||
}), null);
|
||||
assertNotNull(context);
|
||||
policy.registerThrowable(context, new Exception());
|
||||
assertFalse(policy.canRetry(context));
|
||||
policy.close(context, true);
|
||||
// still can't retry, even if policy is closed
|
||||
// (not that this would happen in practice)...
|
||||
assertFalse(policy.canRetry(context));
|
||||
}
|
||||
|
||||
public void testOpenTwice() throws Exception {
|
||||
RecoveryRetryCallback callback = new RecoveryRetryCallback("foo", new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
policy.setDelegate(new SimpleRetryPolicy(2));
|
||||
|
||||
// First call...
|
||||
RetryContext context = policy.open(callback, null);
|
||||
assertNotNull(context);
|
||||
policy.registerThrowable(context, new Exception());
|
||||
assertTrue(policy.canRetry(context));
|
||||
policy.close(context, false);
|
||||
|
||||
// Second call...
|
||||
context = policy.open(callback, null);
|
||||
assertNotNull(context);
|
||||
policy.registerThrowable(context, new Exception());
|
||||
assertFalse(policy.canRetry(context));
|
||||
policy.close(context, true);
|
||||
|
||||
}
|
||||
|
||||
public void testRecover() throws Exception {
|
||||
policy = new RecoveryCallbackRetryPolicy();
|
||||
policy.setDelegate(new SimpleRetryPolicy(1));
|
||||
final String input = "foo";
|
||||
RecoveryRetryCallback callback = new RecoveryRetryCallback(input, new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
callback.setRecoveryCallback(new RecoveryCallback() {
|
||||
public Object recover(RetryContext context) {
|
||||
count++;
|
||||
list.add(input);
|
||||
return input;
|
||||
}
|
||||
});
|
||||
RetryContext context = policy.open(callback, null);
|
||||
assertNotNull(context);
|
||||
assertTrue(policy.canRetry(context));
|
||||
policy.registerThrowable(context, new Exception());
|
||||
assertFalse(policy.canRetry(context));
|
||||
assertEquals(0, count);
|
||||
context = policy.open(callback, null);
|
||||
// On the second retry, the recovery path is taken...
|
||||
Object result = policy.handleRetryExhausted(context);
|
||||
assertEquals("foo", result); // the recoverer returns the item
|
||||
assertEquals(1, count);
|
||||
assertFalse(policy.canRetry(context));
|
||||
assertEquals("foo", list.get(0));
|
||||
}
|
||||
|
||||
public void testRecoverWithParent() throws Exception {
|
||||
RepeatContext parent = new RepeatContextSupport(null);
|
||||
RepeatSynchronizationManager.register(new RepeatContextSupport(parent));
|
||||
testRecover();
|
||||
assertFalse(parent.isCompleteOnly());
|
||||
RepeatSynchronizationManager.clear();
|
||||
}
|
||||
|
||||
public void testRecoverWithTemplate() throws Exception {
|
||||
policy = new RecoveryCallbackRetryPolicy();
|
||||
policy.setDelegate(new SimpleRetryPolicy(1));
|
||||
final String input = "foo";
|
||||
RecoveryRetryCallback callback = new RecoveryRetryCallback(input, new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
});
|
||||
callback.setRecoveryCallback(new RecoveryCallback() {
|
||||
public Object recover(RetryContext context) {
|
||||
count++;
|
||||
list.add(input);
|
||||
return input;
|
||||
}
|
||||
});
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
template.setRetryPolicy(policy);
|
||||
Object result = null;
|
||||
try {
|
||||
result = template.execute(callback);
|
||||
fail("Expected exception on first try");
|
||||
}
|
||||
catch (Exception e) {
|
||||
// expected...
|
||||
}
|
||||
// On the second retry, the recovery path is taken...
|
||||
result = template.execute(callback);
|
||||
assertEquals(input, result); // default result is the item
|
||||
assertEquals(1, count);
|
||||
assertEquals(input, list.get(0));
|
||||
}
|
||||
|
||||
public void testExhaustedClearsHistoryAfterLastAttempt() throws Exception {
|
||||
RecoveryRetryCallback callback = new RecoveryRetryCallback("foo", new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
policy.setDelegate(new SimpleRetryPolicy(1));
|
||||
|
||||
RetryContext context = policy.open(callback, null);
|
||||
assertNotNull(context);
|
||||
|
||||
assertEquals(0, count);
|
||||
policy.registerThrowable(context, new Exception());
|
||||
|
||||
// False before close...
|
||||
assertFalse(policy.canRetry(context));
|
||||
policy.close(context, true);
|
||||
Object result = policy.handleRetryExhausted(context);
|
||||
assertNull(result); // default result is null
|
||||
|
||||
context = policy.open(callback, null);
|
||||
// True after exhausted - the history is reset...
|
||||
assertTrue(policy.canRetry(context));
|
||||
}
|
||||
|
||||
public void testRetryCount() throws Exception {
|
||||
policy = new RecoveryCallbackRetryPolicy();
|
||||
policy.setDelegate(new SimpleRetryPolicy(1));
|
||||
RetryContext context = policy.open(new RecoveryRetryCallback("foo", new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
return null;
|
||||
}
|
||||
}), null);
|
||||
assertNotNull(context);
|
||||
policy.registerThrowable(context, null);
|
||||
assertEquals(0, context.getRetryCount());
|
||||
policy.registerThrowable(context, new RuntimeException("foo"));
|
||||
assertEquals(1, context.getRetryCount());
|
||||
assertEquals("foo", context.getLastThrowable().getMessage());
|
||||
}
|
||||
|
||||
public void testRetryCountPreservedBetweenRetries() throws Exception {
|
||||
RecoveryRetryCallback callback = new RecoveryRetryCallback("bar", new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
policy = new RecoveryCallbackRetryPolicy();
|
||||
policy.setDelegate(new SimpleRetryPolicy(1));
|
||||
RetryContext context = policy.open(callback, null);
|
||||
assertNotNull(context);
|
||||
policy.registerThrowable(context, new RuntimeException("foo"));
|
||||
assertEquals(1, context.getRetryCount());
|
||||
context = policy.open(callback, null);
|
||||
assertEquals(1, context.getRetryCount());
|
||||
policy.registerThrowable(context, new RuntimeException("foo"));
|
||||
assertEquals(2, context.getRetryCount());
|
||||
}
|
||||
|
||||
public void testKeyGeneratorNotConsistentAfterFailure() throws Throwable {
|
||||
|
||||
policy = new RecoveryCallbackRetryPolicy();
|
||||
policy.setDelegate(new SimpleRetryPolicy(3));
|
||||
final StringHolder item = new StringHolder("bar");
|
||||
|
||||
RetryCallback writer = new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
// This simulates what happens if someone uses a primary key
|
||||
// for hasCode and equals and then relies on default key
|
||||
// generator
|
||||
((StringHolder) item).string = ((StringHolder) item).string + (count++);
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
};
|
||||
|
||||
RecoveryRetryCallback callback = new RecoveryRetryCallback(item, writer);
|
||||
RetryContext context = policy.open(callback, null);
|
||||
assertNotNull(context);
|
||||
try {
|
||||
callback.doWithRetry(context);
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Barf!", e.getMessage());
|
||||
try {
|
||||
policy.registerThrowable(context, e);
|
||||
fail("Expected RetryException");
|
||||
}
|
||||
catch (RetryException ex) {
|
||||
String message = ex.getMessage();
|
||||
assertTrue("Message doesn't contain 'inconsistent': " + message, message.indexOf("inconsistent") >= 0);
|
||||
}
|
||||
assertEquals(0, context.getRetryCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testCacheCapacity() throws Exception {
|
||||
policy = new RecoveryCallbackRetryPolicy();
|
||||
policy.setDelegate(new SimpleRetryPolicy(1));
|
||||
policy.setRetryContextCache(new MapRetryContextCache(1));
|
||||
final StringHolder item = new StringHolder("foo");
|
||||
|
||||
RetryCallback writer = new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
list.add(item.string);
|
||||
return item;
|
||||
}
|
||||
};
|
||||
RetryContext context;
|
||||
context = policy.open(new RecoveryRetryCallback(item, writer), null);
|
||||
policy.registerThrowable(context, null);
|
||||
assertEquals(0, context.getRetryCount());
|
||||
item.string = "bar";
|
||||
context = policy.open(new RecoveryRetryCallback(item, writer), null);
|
||||
try {
|
||||
policy.registerThrowable(context, new RuntimeException("foo"));
|
||||
fail("Expected RetryException");
|
||||
}
|
||||
catch (RetryException e) {
|
||||
String message = e.getMessage();
|
||||
assertTrue("Message does not contain 'capacity': " + message, message.indexOf("capacity") >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCacheCapacityNotReachedIfRecovered() throws Exception {
|
||||
policy = new RecoveryCallbackRetryPolicy();
|
||||
policy.setDelegate(new SimpleRetryPolicy(1));
|
||||
policy.setRetryContextCache(new MapRetryContextCache(2));
|
||||
final StringHolder item = new StringHolder("foo");
|
||||
|
||||
RetryCallback writer = new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
list.add(item.string);
|
||||
return item;
|
||||
}
|
||||
};
|
||||
|
||||
RetryContext context;
|
||||
context = policy.open(new RecoveryRetryCallback(item, writer), null);
|
||||
policy.registerThrowable(context, null);
|
||||
assertEquals(0, context.getRetryCount());
|
||||
policy.registerThrowable(context, new RuntimeException("foo"));
|
||||
context = policy.open(new RecoveryRetryCallback("bar", writer), null);
|
||||
policy.registerThrowable(context, null);
|
||||
policy.handleRetryExhausted(context);
|
||||
context = policy.open(new RecoveryRetryCallback("spam", writer), null);
|
||||
policy.registerThrowable(context, null);
|
||||
assertEquals(0, context.getRetryCount());
|
||||
}
|
||||
|
||||
private static class StringHolder {
|
||||
|
||||
private String string;
|
||||
|
||||
/**
|
||||
* @param string
|
||||
*/
|
||||
public StringHolder(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
return string.equals(((StringHolder) obj).string);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
public int hashCode() {
|
||||
return string.hashCode();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return "String: " + string + " (hash = " + hashCode() + ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,9 +23,10 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.retry.ExhaustedRetryException;
|
||||
import org.springframework.batch.retry.RetryCallback;
|
||||
import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.callback.RecoveryRetryCallback;
|
||||
import org.springframework.batch.retry.RetryState;
|
||||
import org.springframework.batch.retry.support.RetryTemplate;
|
||||
|
||||
/**
|
||||
@@ -38,38 +39,34 @@ public class StatefulRetryIntegrationTests {
|
||||
public void testExternalRetryWithFailAndNoRetry() throws Exception {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
|
||||
RecoveryRetryCallback recoveryCallback = new RecoveryRetryCallback("foo", callback);
|
||||
RetryState retryState = new RetryState("foo");
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
RecoveryCallbackRetryPolicy retryPolicy = new RecoveryCallbackRetryPolicy(new SimpleRetryPolicy(1));
|
||||
MapRetryContextCache cache = new MapRetryContextCache();
|
||||
retryPolicy.setRetryContextCache(cache);
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
retryTemplate.setRetryContextCache(cache);
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(1));
|
||||
|
||||
assertFalse(cache.containsKey("foo"));
|
||||
|
||||
Object result = "start_foo";
|
||||
try {
|
||||
result = retryTemplate.execute(recoveryCallback);
|
||||
retryTemplate.execute(callback, retryState);
|
||||
// The first failed attempt we expect to retry...
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertNull(e.getMessage());
|
||||
assertEquals(null, e.getMessage());
|
||||
}
|
||||
|
||||
assertTrue(cache.containsKey("foo"));
|
||||
|
||||
try {
|
||||
result = retryTemplate.execute(recoveryCallback);
|
||||
// We always get a second attempt...
|
||||
retryTemplate.execute(callback, retryState);
|
||||
// We don't get a second attempt...
|
||||
fail("Expected ExhaustedRetryException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
catch (ExhaustedRetryException e) {
|
||||
// This is now the "exhausted" message:
|
||||
assertNotNull(e.getMessage());
|
||||
// But if template is external we should
|
||||
// swallow the exception when retry is impossible.
|
||||
fail("Did not expect RuntimeException: "+e);
|
||||
}
|
||||
|
||||
assertFalse(cache.containsKey("foo"));
|
||||
@@ -77,26 +74,24 @@ public class StatefulRetryIntegrationTests {
|
||||
// Callback is called once: the recovery path should be called in
|
||||
// handleRetryExhausted (so not in this test)...
|
||||
assertEquals(1, callback.attempts);
|
||||
assertEquals(null, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalRetryWithSuccessOnRetry() throws Exception {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
|
||||
RecoveryRetryCallback recoveryCallback = new RecoveryRetryCallback("foo", callback);
|
||||
RetryState retryState = new RetryState("foo");
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
RecoveryCallbackRetryPolicy retryPolicy = new RecoveryCallbackRetryPolicy(new SimpleRetryPolicy(2));
|
||||
MapRetryContextCache cache = new MapRetryContextCache();
|
||||
retryPolicy.setRetryContextCache(cache);
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
retryTemplate.setRetryContextCache(cache);
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(2));
|
||||
|
||||
assertFalse(cache.containsKey("foo"));
|
||||
|
||||
Object result = "start_foo";
|
||||
try {
|
||||
result = retryTemplate.execute(recoveryCallback);
|
||||
result = retryTemplate.execute(callback, retryState);
|
||||
// The first failed attempt we expect to retry...
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
@@ -106,7 +101,7 @@ public class StatefulRetryIntegrationTests {
|
||||
|
||||
assertTrue(cache.containsKey("foo"));
|
||||
|
||||
result = retryTemplate.execute(recoveryCallback);
|
||||
result = retryTemplate.execute(callback, retryState);
|
||||
|
||||
assertFalse(cache.containsKey("foo"));
|
||||
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright 2006-2007 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.batch.retry.support;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.repeat.RepeatContext;
|
||||
import org.springframework.batch.repeat.context.RepeatContextSupport;
|
||||
import org.springframework.batch.repeat.support.RepeatSynchronizationManager;
|
||||
import org.springframework.batch.retry.ExhaustedRetryException;
|
||||
import org.springframework.batch.retry.RecoveryCallback;
|
||||
import org.springframework.batch.retry.RetryCallback;
|
||||
import org.springframework.batch.retry.RetryContext;
|
||||
import org.springframework.batch.retry.RetryException;
|
||||
import org.springframework.batch.retry.RetryPolicy;
|
||||
import org.springframework.batch.retry.RetryState;
|
||||
import org.springframework.batch.retry.policy.MapRetryContextCache;
|
||||
import org.springframework.batch.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
|
||||
|
||||
public class StatefulRecoveryRetryTests {
|
||||
|
||||
private RetryTemplate retryTemplate = new RetryTemplate();
|
||||
|
||||
private int count = 0;
|
||||
|
||||
private List<String> list = new ArrayList<String>();
|
||||
|
||||
@Test
|
||||
public void testOpenSunnyDay() throws Exception {
|
||||
|
||||
final StringHolder item = new StringHolder("foo");
|
||||
RetryCallback writer = new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
list.add(item.string);
|
||||
return item;
|
||||
}
|
||||
};
|
||||
|
||||
RetryContext context = retryTemplate.open(writer, new NeverRetryPolicy(), new RetryState("foo"));
|
||||
assertNotNull(context);
|
||||
// we haven't called the processor yet...
|
||||
assertEquals(0, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterThrowable() {
|
||||
NeverRetryPolicy retryPolicy = new NeverRetryPolicy();
|
||||
RetryState state = new RetryState("foo");
|
||||
RetryContext context = retryTemplate.open(new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
return null;
|
||||
}
|
||||
}, retryPolicy, state);
|
||||
assertNotNull(context);
|
||||
retryTemplate.registerThrowable(retryPolicy, state, context, new Exception());
|
||||
assertFalse(retryPolicy.canRetry(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClose() throws Exception {
|
||||
NeverRetryPolicy retryPolicy = new NeverRetryPolicy();
|
||||
RetryState state = new RetryState("foo");
|
||||
RetryContext context = retryTemplate.open(new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
return null;
|
||||
}
|
||||
}, retryPolicy, state);
|
||||
assertNotNull(context);
|
||||
retryTemplate.registerThrowable(retryPolicy, state, context, new Exception());
|
||||
assertFalse(retryPolicy.canRetry(context));
|
||||
retryTemplate.close(retryPolicy, context, state, true);
|
||||
// still can't retry, even if policy is closed
|
||||
// (not that this would happen in practice)...
|
||||
assertFalse(retryPolicy.canRetry(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecoverWithParent() throws Exception {
|
||||
RepeatContext parent = new RepeatContextSupport(null);
|
||||
RepeatSynchronizationManager.register(new RepeatContextSupport(parent));
|
||||
testRecover();
|
||||
assertFalse(parent.isCompleteOnly());
|
||||
RepeatSynchronizationManager.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecover() throws Exception {
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(1));
|
||||
final String input = "foo";
|
||||
RetryState state = new RetryState(input);
|
||||
RetryCallback callback = new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
};
|
||||
RecoveryCallback recoveryCallback = new RecoveryCallback() {
|
||||
public Object recover(RetryContext context) {
|
||||
count++;
|
||||
list.add(input);
|
||||
return input;
|
||||
}
|
||||
};
|
||||
Object result = null;
|
||||
try {
|
||||
result = retryTemplate.execute(callback, recoveryCallback, state);
|
||||
fail("Expected exception on first try");
|
||||
}
|
||||
catch (Exception e) {
|
||||
// expected...
|
||||
}
|
||||
// On the second retry, the recovery path is taken...
|
||||
result = retryTemplate.execute(callback, recoveryCallback, state);
|
||||
assertEquals(input, result); // default result is the item
|
||||
assertEquals(1, count);
|
||||
assertEquals(input, list.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExhaustedClearsHistoryAfterLastAttempt() throws Exception {
|
||||
RetryPolicy retryPolicy = new SimpleRetryPolicy(1);
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
|
||||
final String input = "foo";
|
||||
RetryState state = new RetryState(input);
|
||||
RetryCallback callback = new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
retryTemplate.execute(callback, state);
|
||||
fail("Expected ExhaustedRetryException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Barf!", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
retryTemplate.execute(callback, state);
|
||||
fail("Expected ExhaustedRetryException");
|
||||
}
|
||||
catch (ExhaustedRetryException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
RetryContext context = retryTemplate.open(callback, retryPolicy, state);
|
||||
// True after exhausted - the history is reset...
|
||||
assertTrue(retryPolicy.canRetry(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyGeneratorNotConsistentAfterFailure() throws Throwable {
|
||||
|
||||
RetryPolicy retryPolicy = new SimpleRetryPolicy(3);
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
final StringHolder item = new StringHolder("bar");
|
||||
RetryState state = new RetryState(item);
|
||||
|
||||
RetryCallback callback = new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
// This simulates what happens if someone uses a primary key
|
||||
// for hashCode and equals and then relies on default key
|
||||
// generator
|
||||
((StringHolder) item).string = ((StringHolder) item).string + (count++);
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// TODO: test this
|
||||
// retryTemplate.execute(callback, state);
|
||||
// fail("Expected RetryException");
|
||||
}
|
||||
catch (RetryException ex) {
|
||||
String message = ex.getMessage();
|
||||
assertTrue("Message doesn't contain 'inconsistent': " + message, message.indexOf("inconsistent") >= 0);
|
||||
}
|
||||
|
||||
RetryContext context = retryTemplate.open(callback, retryPolicy, state);
|
||||
// True after exhausted - the history is reset...
|
||||
assertEquals(0, context.getRetryCount());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheCapacity() throws Exception {
|
||||
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(1));
|
||||
retryTemplate.setRetryContextCache(new MapRetryContextCache(1));
|
||||
|
||||
RetryCallback callback = new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
retryTemplate.execute(callback, new RetryState("foo"));
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Barf!", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
retryTemplate.execute(callback, new RetryState("bar"));
|
||||
fail("Expected RetryException");
|
||||
}
|
||||
catch (RetryException e) {
|
||||
String message = e.getMessage();
|
||||
assertTrue("Message does not contain 'capacity': " + message, message.indexOf("capacity") >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheCapacityNotReachedIfRecovered() throws Exception {
|
||||
|
||||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(1);
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
retryTemplate.setRetryContextCache(new MapRetryContextCache(2));
|
||||
final StringHolder item = new StringHolder("foo");
|
||||
RetryState state = new RetryState(item);
|
||||
|
||||
RetryCallback callback = new RetryCallback() {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
count++;
|
||||
throw new RuntimeException("Barf!");
|
||||
}
|
||||
};
|
||||
RecoveryCallback recoveryCallback = new RecoveryCallback() {
|
||||
public Object recover(RetryContext context) throws Exception {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
retryTemplate.execute(callback, recoveryCallback, state);
|
||||
fail("Expected RuntimeException");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
assertEquals("Barf!", e.getMessage());
|
||||
}
|
||||
retryTemplate.execute(callback, recoveryCallback, state);
|
||||
|
||||
RetryContext context = retryTemplate.open(callback, retryPolicy, state);
|
||||
// True after exhausted - the history is reset...
|
||||
assertEquals(0, context.getRetryCount());
|
||||
|
||||
}
|
||||
|
||||
private static class StringHolder {
|
||||
|
||||
private String string;
|
||||
|
||||
/**
|
||||
* @param string
|
||||
*/
|
||||
public StringHolder(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
return string.equals(((StringHolder) obj).string);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
public int hashCode() {
|
||||
return string.hashCode();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return "String: " + string + " (hash = " + hashCode() + ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user