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.core.step.item; |
18 | |
19 | import java.util.ArrayList; |
20 | import java.util.Collection; |
21 | import java.util.Iterator; |
22 | import java.util.List; |
23 | |
24 | import org.springframework.batch.classify.Classifier; |
25 | import org.springframework.batch.retry.ExhaustedRetryException; |
26 | import org.springframework.batch.retry.RecoveryCallback; |
27 | import org.springframework.batch.retry.RetryCallback; |
28 | import org.springframework.batch.retry.RetryContext; |
29 | import org.springframework.batch.retry.RetryListener; |
30 | import org.springframework.batch.retry.RetryOperations; |
31 | import org.springframework.batch.retry.RetryPolicy; |
32 | import org.springframework.batch.retry.RetryState; |
33 | import org.springframework.batch.retry.backoff.BackOffPolicy; |
34 | import org.springframework.batch.retry.context.RetryContextSupport; |
35 | import org.springframework.batch.retry.policy.RetryContextCache; |
36 | import org.springframework.batch.retry.support.DefaultRetryState; |
37 | import org.springframework.batch.retry.support.RetrySynchronizationManager; |
38 | import org.springframework.batch.retry.support.RetryTemplate; |
39 | |
40 | /** |
41 | * A special purpose retry template that deals specifically with multi-valued |
42 | * stateful retry. This is useful in the case where the operation to be retried |
43 | * operates on multiple items, and when it fails there is no way to decide which |
44 | * (if any) of the items was responsible. The {@link RetryState} used in the |
45 | * execute methods is composite, and when a failure occurs, all of the keys in |
46 | * the composite are "tarred with the same brush". Subsequent attempts to |
47 | * execute with any of the keys that have failed previously results in a new |
48 | * attempt and the previous state is used to check the {@link RetryPolicy}. If |
49 | * one of the failed items eventually succeeds then the others in the current |
50 | * composite for that attempt will be cleared from the context cache (as |
51 | * normal), but there may still be entries in the cache for the original failed |
52 | * items. This might mean that an item that did not cause a failure is never |
53 | * retried because other items in the same batch fail fatally first. |
54 | * |
55 | * @author Dave Syer |
56 | * |
57 | */ |
58 | public class BatchRetryTemplate implements RetryOperations { |
59 | |
60 | private class BatchRetryState extends DefaultRetryState { |
61 | |
62 | private final Collection<RetryState> keys; |
63 | |
64 | public BatchRetryState(Collection<RetryState> keys) { |
65 | super(keys); |
66 | this.keys = new ArrayList<RetryState>(keys); |
67 | } |
68 | |
69 | } |
70 | |
71 | private static class BatchRetryContext extends RetryContextSupport { |
72 | |
73 | private final Collection<RetryContext> contexts; |
74 | |
75 | public BatchRetryContext(RetryContext parent, Collection<RetryContext> contexts) { |
76 | |
77 | super(parent); |
78 | |
79 | this.contexts = contexts; |
80 | int count = 0; |
81 | |
82 | for (RetryContext context : contexts) { |
83 | int retryCount = context.getRetryCount(); |
84 | if (retryCount > count) { |
85 | count = retryCount; |
86 | registerThrowable(context.getLastThrowable()); |
87 | } |
88 | } |
89 | |
90 | } |
91 | |
92 | } |
93 | |
94 | private static class InnerRetryTemplate extends RetryTemplate { |
95 | |
96 | @Override |
97 | protected boolean canRetry(RetryPolicy retryPolicy, RetryContext context) { |
98 | |
99 | BatchRetryContext batchContext = (BatchRetryContext) context; |
100 | |
101 | for (RetryContext nextContext : batchContext.contexts) { |
102 | if (!super.canRetry(retryPolicy, nextContext)) { |
103 | return false; |
104 | } |
105 | } |
106 | |
107 | return true; |
108 | |
109 | } |
110 | |
111 | @Override |
112 | protected RetryContext open(RetryPolicy retryPolicy, RetryState state) { |
113 | |
114 | BatchRetryState batchState = (BatchRetryState) state; |
115 | |
116 | Collection<RetryContext> contexts = new ArrayList<RetryContext>(); |
117 | for (RetryState retryState : batchState.keys) { |
118 | contexts.add(super.open(retryPolicy, retryState)); |
119 | } |
120 | |
121 | return new BatchRetryContext(RetrySynchronizationManager.getContext(), contexts); |
122 | |
123 | } |
124 | |
125 | @Override |
126 | protected void registerThrowable(RetryPolicy retryPolicy, RetryState state, RetryContext context, Exception e) { |
127 | |
128 | BatchRetryState batchState = (BatchRetryState) state; |
129 | BatchRetryContext batchContext = (BatchRetryContext) context; |
130 | |
131 | Iterator<RetryContext> contextIterator = batchContext.contexts.iterator(); |
132 | for (RetryState retryState : batchState.keys) { |
133 | RetryContext nextContext = contextIterator.next(); |
134 | super.registerThrowable(retryPolicy, retryState, nextContext, e); |
135 | } |
136 | |
137 | } |
138 | |
139 | @Override |
140 | protected void close(RetryPolicy retryPolicy, RetryContext context, RetryState state, boolean succeeded) { |
141 | |
142 | BatchRetryState batchState = (BatchRetryState) state; |
143 | BatchRetryContext batchContext = (BatchRetryContext) context; |
144 | |
145 | Iterator<RetryContext> contextIterator = batchContext.contexts.iterator(); |
146 | for (RetryState retryState : batchState.keys) { |
147 | RetryContext nextContext = contextIterator.next(); |
148 | super.close(retryPolicy, nextContext, retryState, succeeded); |
149 | } |
150 | |
151 | } |
152 | |
153 | @Override |
154 | protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback, RetryContext context, |
155 | RetryState state) throws Exception { |
156 | |
157 | BatchRetryState batchState = (BatchRetryState) state; |
158 | BatchRetryContext batchContext = (BatchRetryContext) context; |
159 | |
160 | // Accumulate exceptions to be thrown so all the keys get a crack |
161 | Exception rethrowable = null; |
162 | ExhaustedRetryException exhausted = null; |
163 | |
164 | Iterator<RetryContext> contextIterator = batchContext.contexts.iterator(); |
165 | for (RetryState retryState : batchState.keys) { |
166 | |
167 | RetryContext nextContext = contextIterator.next(); |
168 | |
169 | try { |
170 | super.handleRetryExhausted(null, nextContext, retryState); |
171 | } |
172 | catch (ExhaustedRetryException e) { |
173 | exhausted = e; |
174 | } |
175 | catch (Exception e) { |
176 | rethrowable = e; |
177 | } |
178 | |
179 | } |
180 | |
181 | if (recoveryCallback != null) { |
182 | return recoveryCallback.recover(context); |
183 | } |
184 | |
185 | if (exhausted != null) { |
186 | throw exhausted; |
187 | } |
188 | |
189 | throw rethrowable; |
190 | |
191 | } |
192 | |
193 | } |
194 | |
195 | private final InnerRetryTemplate delegate = new InnerRetryTemplate(); |
196 | |
197 | private final RetryTemplate regular = new RetryTemplate(); |
198 | |
199 | public <T> T execute(RetryCallback<T> retryCallback, Collection<RetryState> states) throws ExhaustedRetryException, |
200 | Exception { |
201 | RetryState batchState = new BatchRetryState(states); |
202 | return delegate.execute(retryCallback, batchState); |
203 | } |
204 | |
205 | public <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback, |
206 | Collection<RetryState> states) throws ExhaustedRetryException, Exception { |
207 | RetryState batchState = new BatchRetryState(states); |
208 | return delegate.execute(retryCallback, recoveryCallback, batchState); |
209 | } |
210 | |
211 | public final <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback, |
212 | RetryState retryState) throws Exception, ExhaustedRetryException { |
213 | return regular.execute(retryCallback, recoveryCallback, retryState); |
214 | } |
215 | |
216 | public final <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback) throws Exception { |
217 | return regular.execute(retryCallback, recoveryCallback); |
218 | } |
219 | |
220 | public final <T> T execute(RetryCallback<T> retryCallback, RetryState retryState) throws Exception, |
221 | ExhaustedRetryException { |
222 | return regular.execute(retryCallback, retryState); |
223 | } |
224 | |
225 | public final <T> T execute(RetryCallback<T> retryCallback) throws Exception { |
226 | return regular.execute(retryCallback); |
227 | } |
228 | |
229 | public static List<RetryState> createState(List<?> keys) { |
230 | List<RetryState> states = new ArrayList<RetryState>(); |
231 | for (Object key : keys) { |
232 | states.add(new DefaultRetryState(key)); |
233 | } |
234 | return states; |
235 | } |
236 | |
237 | public static List<RetryState> createState(List<?> keys, Classifier<? super Throwable, Boolean> classifier) { |
238 | List<RetryState> states = new ArrayList<RetryState>(); |
239 | for (Object key : keys) { |
240 | states.add(new DefaultRetryState(key, classifier)); |
241 | } |
242 | return states; |
243 | } |
244 | |
245 | public void registerListener(RetryListener listener) { |
246 | delegate.registerListener(listener); |
247 | regular.registerListener(listener); |
248 | } |
249 | |
250 | public void setBackOffPolicy(BackOffPolicy backOffPolicy) { |
251 | delegate.setBackOffPolicy(backOffPolicy); |
252 | regular.setBackOffPolicy(backOffPolicy); |
253 | } |
254 | |
255 | public void setListeners(RetryListener[] listeners) { |
256 | delegate.setListeners(listeners); |
257 | regular.setListeners(listeners); |
258 | } |
259 | |
260 | public void setRetryContextCache(RetryContextCache retryContextCache) { |
261 | delegate.setRetryContextCache(retryContextCache); |
262 | regular.setRetryContextCache(retryContextCache); |
263 | } |
264 | |
265 | public void setRetryPolicy(RetryPolicy retryPolicy) { |
266 | delegate.setRetryPolicy(retryPolicy); |
267 | regular.setRetryPolicy(retryPolicy); |
268 | } |
269 | |
270 | } |