View Javadoc

1   /*
2    * Copyright 2006-2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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   * @author Rob Harrop
49   * @author Dave Syer
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 		// Something that won't be thrown by JUnit...
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 			// Expected for internal retry policy (external would recover
213 			// gracefully)
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 	 * {@link BackOffPolicy} should apply also for exceptions that are
286 	 * re-thrown.
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 	 * {@link BackOffPolicy} should be saved between invocations for a stateful
333 	 * execution.
334 	 */
335 	@Test
336 	@Ignore // TODO: fix for BATCH-1795
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 }