1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.batch.retry.support;
18
19 import static org.easymock.EasyMock.createStrictMock;
20 import static org.easymock.EasyMock.expect;
21 import static org.easymock.EasyMock.expectLastCall;
22 import static org.easymock.EasyMock.isA;
23 import static org.easymock.EasyMock.replay;
24 import static org.easymock.EasyMock.verify;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertNotSame;
28 import static org.junit.Assert.assertSame;
29 import static org.junit.Assert.fail;
30
31 import java.util.Collections;
32
33 import org.junit.Ignore;
34 import org.junit.Test;
35 import org.springframework.batch.classify.BinaryExceptionClassifier;
36 import org.springframework.batch.retry.ExhaustedRetryException;
37 import org.springframework.batch.retry.RecoveryCallback;
38 import org.springframework.batch.retry.RetryCallback;
39 import org.springframework.batch.retry.RetryContext;
40 import org.springframework.batch.retry.backoff.BackOffContext;
41 import org.springframework.batch.retry.backoff.BackOffInterruptedException;
42 import org.springframework.batch.retry.backoff.BackOffPolicy;
43 import org.springframework.batch.retry.backoff.StatelessBackOffPolicy;
44 import org.springframework.batch.retry.policy.NeverRetryPolicy;
45 import org.springframework.batch.retry.policy.SimpleRetryPolicy;
46
47
48
49
50
51 public class RetryTemplateTests {
52
53 RetryContext context;
54
55 int count = 0;
56
57 @Test
58 public void testSuccessfulRetry() throws Exception {
59 for (int x = 1; x <= 10; x++) {
60 MockRetryCallback callback = new MockRetryCallback();
61 callback.setAttemptsBeforeSuccess(x);
62 RetryTemplate retryTemplate = new RetryTemplate();
63 retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x, Collections
64 .<Class<? extends Throwable>, Boolean> singletonMap(
65 Exception.class, true)));
66 retryTemplate.execute(callback);
67 assertEquals(x, callback.attempts);
68 }
69 }
70
71 @Test
72 public void testSuccessfulRecovery() throws Exception {
73 MockRetryCallback callback = new MockRetryCallback();
74 callback.setAttemptsBeforeSuccess(3);
75 RetryTemplate retryTemplate = new RetryTemplate();
76 retryTemplate.setRetryPolicy(new SimpleRetryPolicy(2, Collections
77 .<Class<? extends Throwable>, Boolean> singletonMap(
78 Exception.class, true)));
79 final Object value = new Object();
80 Object result = retryTemplate.execute(callback,
81 new RecoveryCallback<Object>() {
82 public Object recover(RetryContext context)
83 throws Exception {
84 return value;
85 }
86 });
87 assertEquals(2, callback.attempts);
88 assertEquals(value, result);
89 }
90
91 @Test
92 public void testAlwaysTryAtLeastOnce() throws Exception {
93 MockRetryCallback callback = new MockRetryCallback();
94 RetryTemplate retryTemplate = new RetryTemplate();
95 retryTemplate.setRetryPolicy(new NeverRetryPolicy());
96 retryTemplate.execute(callback);
97 assertEquals(1, callback.attempts);
98 }
99
100 @Test
101 public void testNoSuccessRetry() throws Exception {
102 MockRetryCallback callback = new MockRetryCallback();
103
104 callback.setExceptionToThrow(new IllegalArgumentException());
105 callback.setAttemptsBeforeSuccess(Integer.MAX_VALUE);
106 RetryTemplate retryTemplate = new RetryTemplate();
107 int retryAttempts = 2;
108 retryTemplate.setRetryPolicy(new SimpleRetryPolicy(retryAttempts,
109 Collections.<Class<? extends Throwable>, Boolean> singletonMap(
110 Exception.class, true)));
111 try {
112 retryTemplate.execute(callback);
113 fail("Expected IllegalArgumentException");
114 } catch (IllegalArgumentException e) {
115 assertNotNull(e);
116 assertEquals(retryAttempts, callback.attempts);
117 return;
118 }
119 fail("Expected IllegalArgumentException");
120 }
121
122 @Test
123 public void testDefaultConfigWithExceptionSubclass() throws Exception {
124 MockRetryCallback callback = new MockRetryCallback();
125 int attempts = 3;
126 callback.setAttemptsBeforeSuccess(attempts);
127 callback.setExceptionToThrow(new IllegalArgumentException());
128
129 RetryTemplate retryTemplate = new RetryTemplate();
130 retryTemplate.setRetryPolicy(new SimpleRetryPolicy(attempts,
131 Collections.<Class<? extends Throwable>, Boolean> singletonMap(
132 Exception.class, true)));
133 retryTemplate.execute(callback);
134 assertEquals(attempts, callback.attempts);
135 }
136
137 @Test
138 public void testRollbackClassifierOverridesRetryPolicy() throws Exception {
139 MockRetryCallback callback = new MockRetryCallback();
140 int attempts = 3;
141 callback.setAttemptsBeforeSuccess(attempts);
142 callback.setExceptionToThrow(new IllegalArgumentException());
143
144 RetryTemplate retryTemplate = new RetryTemplate();
145 retryTemplate.setRetryPolicy(new SimpleRetryPolicy(attempts,
146 Collections.<Class<? extends Throwable>, Boolean> singletonMap(
147 Exception.class, true)));
148 BinaryExceptionClassifier classifier = new BinaryExceptionClassifier(
149 Collections
150 .<Class<? extends Throwable>> singleton(IllegalArgumentException.class),
151 false);
152 retryTemplate.execute(callback,
153 new DefaultRetryState("foo", classifier));
154 assertEquals(attempts, callback.attempts);
155 }
156
157 @Test
158 public void testSetExceptions() throws Exception {
159 RetryTemplate template = new RetryTemplate();
160 SimpleRetryPolicy policy = new SimpleRetryPolicy(3,
161 Collections.<Class<? extends Throwable>, Boolean> singletonMap(
162 RuntimeException.class, true));
163 template.setRetryPolicy(policy);
164
165 int attempts = 3;
166
167 MockRetryCallback callback = new MockRetryCallback();
168 callback.setAttemptsBeforeSuccess(attempts);
169
170 try {
171 template.execute(callback);
172 } catch (Exception e) {
173 assertNotNull(e);
174 assertEquals(1, callback.attempts);
175 }
176 callback.setExceptionToThrow(new RuntimeException());
177
178 template.execute(callback);
179 assertEquals(attempts, callback.attempts);
180 }
181
182 @Test
183 public void testBackOffInvoked() throws Exception {
184 for (int x = 1; x <= 10; x++) {
185 MockRetryCallback callback = new MockRetryCallback();
186 MockBackOffStrategy backOff = new MockBackOffStrategy();
187 callback.setAttemptsBeforeSuccess(x);
188 RetryTemplate retryTemplate = new RetryTemplate();
189 retryTemplate.setBackOffPolicy(backOff);
190 retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x, Collections
191 .<Class<? extends Throwable>, Boolean> singletonMap(
192 Exception.class, true)));
193 retryTemplate.execute(callback);
194 assertEquals(x, callback.attempts);
195 assertEquals(1, backOff.startCalls);
196 assertEquals(x - 1, backOff.backOffCalls);
197 }
198 }
199
200 @Test
201 public void testEarlyTermination() throws Exception {
202 try {
203 RetryTemplate retryTemplate = new RetryTemplate();
204 retryTemplate.execute(new RetryCallback<Object>() {
205 public Object doWithRetry(RetryContext status) throws Exception {
206 status.setExhaustedOnly();
207 throw new IllegalStateException("Retry this operation");
208 }
209 });
210 fail("Expected ExhaustedRetryException");
211 } catch (ExhaustedRetryException ex) {
212
213
214 assertEquals("Retry this operation", ex.getCause().getMessage());
215 }
216 }
217
218 @Test
219 public void testNestedContexts() throws Exception {
220 RetryTemplate outer = new RetryTemplate();
221 final RetryTemplate inner = new RetryTemplate();
222 outer.execute(new RetryCallback<Object>() {
223 public Object doWithRetry(RetryContext status) throws Exception {
224 context = status;
225 count++;
226 Object result = inner.execute(new RetryCallback<Object>() {
227 public Object doWithRetry(RetryContext status)
228 throws Exception {
229 count++;
230 assertNotNull(context);
231 assertNotSame(status, context);
232 assertSame(context, status.getParent());
233 assertSame("The context should be the child", status,
234 RetrySynchronizationManager.getContext());
235 return null;
236 }
237 });
238 assertSame("The context should be restored", status,
239 RetrySynchronizationManager.getContext());
240 return result;
241 }
242 });
243 assertEquals(2, count);
244 }
245
246 @Test
247 public void testRethrowError() throws Exception {
248 RetryTemplate retryTemplate = new RetryTemplate();
249 retryTemplate.setRetryPolicy(new NeverRetryPolicy());
250 try {
251 retryTemplate.execute(new RetryCallback<Object>() {
252 public Object doWithRetry(RetryContext context)
253 throws Exception {
254 throw new Error("Realllly bad!");
255 }
256 });
257 fail("Expected Error");
258 } catch (Error e) {
259 assertEquals("Realllly bad!", e.getMessage());
260 }
261 }
262
263 @Test
264 public void testBackOffInterrupted() throws Exception {
265 RetryTemplate retryTemplate = new RetryTemplate();
266 retryTemplate.setBackOffPolicy(new StatelessBackOffPolicy() {
267 protected void doBackOff() throws BackOffInterruptedException {
268 throw new BackOffInterruptedException("foo");
269 }
270 });
271 try {
272 retryTemplate.execute(new RetryCallback<Object>() {
273 public Object doWithRetry(RetryContext context)
274 throws Exception {
275 throw new RuntimeException("Bad!");
276 }
277 });
278 fail("Expected RuntimeException");
279 } catch (BackOffInterruptedException e) {
280 assertEquals("foo", e.getMessage());
281 }
282 }
283
284
285
286
287
288 @Test
289 public void testNoBackOffForRethrownException() throws Exception {
290
291 RetryTemplate tested = new RetryTemplate();
292 tested.setRetryPolicy(new SimpleRetryPolicy(1, Collections
293 .<Class<? extends Throwable>, Boolean> singletonMap(
294 Exception.class, true)));
295
296 BackOffPolicy bop = createStrictMock(BackOffPolicy.class);
297 BackOffContext backOffContext = new BackOffContext() {
298 };
299 tested.setBackOffPolicy(bop);
300
301 expect(bop.start(isA(RetryContext.class))).andReturn(backOffContext);
302 replay(bop);
303
304 DefaultRetryState state = new DefaultRetryState(tested) {
305
306 @Override
307 public boolean rollbackFor(Throwable exception) {
308 return true;
309 }
310
311 };
312
313 RetryCallback<Object> callback = new RetryCallback<Object>() {
314
315 public Object doWithRetry(RetryContext context) throws Exception {
316 throw new Exception("maybe next time!");
317 }
318
319 };
320
321 try {
322 tested.execute(callback, null, state);
323 fail();
324 } catch (Exception expected) {
325 assertEquals("maybe next time!", expected.getMessage());
326 }
327
328 verify(bop);
329 }
330
331
332
333
334
335 @Test
336 @Ignore
337 public void testBackOffContextSavedForStatefulRetry() throws Exception {
338
339 RetryTemplate tested = new RetryTemplate();
340 tested.setRetryPolicy(new SimpleRetryPolicy(2, Collections
341 .<Class<? extends Throwable>, Boolean> singletonMap(
342 Exception.class, true)));
343
344 BackOffPolicy bop = createStrictMock(BackOffPolicy.class);
345 BackOffContext backOffContext = new BackOffContext() {
346 };
347 tested.setBackOffPolicy(bop);
348
349 expect(bop.start(isA(RetryContext.class))).andReturn(backOffContext).once();
350 bop.backOff(backOffContext);
351 expectLastCall().anyTimes();
352 replay(bop);
353
354 DefaultRetryState state = new DefaultRetryState(tested) {
355
356 @Override
357 public boolean rollbackFor(Throwable exception) {
358 return true;
359 }
360
361 };
362
363 RetryCallback<Object> callback = new RetryCallback<Object>() {
364
365 public Object doWithRetry(RetryContext context) throws Exception {
366 throw new Exception("maybe next time!");
367 }
368
369 };
370
371 for (int i = 0; i < 2; i++) {
372 try {
373 tested.execute(callback, null, state);
374 fail();
375 } catch (Exception expected) {
376 assertEquals("maybe next time!", expected.getMessage());
377 }
378 }
379
380 try {
381 tested.execute(callback, null, state);
382 fail();
383 } catch (ExhaustedRetryException expected) {
384 assertEquals("maybe next time!", expected.getCause().getMessage());
385 }
386
387 verify(bop);
388 }
389
390 private static class MockRetryCallback implements RetryCallback<Object> {
391
392 private int attempts;
393
394 private int attemptsBeforeSuccess;
395
396 private Exception exceptionToThrow = new Exception();
397
398 public Object doWithRetry(RetryContext status) throws Exception {
399 this.attempts++;
400 if (attempts < attemptsBeforeSuccess) {
401 throw this.exceptionToThrow;
402 }
403 return null;
404 }
405
406 public void setAttemptsBeforeSuccess(int attemptsBeforeSuccess) {
407 this.attemptsBeforeSuccess = attemptsBeforeSuccess;
408 }
409
410 public void setExceptionToThrow(Exception exceptionToThrow) {
411 this.exceptionToThrow = exceptionToThrow;
412 }
413 }
414
415 private static class MockBackOffStrategy implements BackOffPolicy {
416
417 public int backOffCalls;
418
419 public int startCalls;
420
421 public BackOffContext start(RetryContext status) {
422 startCalls++;
423 return null;
424 }
425
426 public void backOff(BackOffContext backOffContext)
427 throws BackOffInterruptedException {
428 backOffCalls++;
429 }
430 }
431 }