EMMA Coverage Report (generated Fri Jan 30 13:20:29 EST 2009)
[all classes][org.springframework.batch.retry.support]

COVERAGE SUMMARY FOR SOURCE FILE [RetryTemplate.java]

nameclass, %method, %block, %line, %
RetryTemplate.java100% (2/2)100% (12/12)90%  (286/318)90%  (61.2/68)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class RetryTemplate100% (1/1)100% (11/11)90%  (281/313)90%  (59.2/66)
execute (RetryCallback): Object 100% (1/1)81%  (133/165)79%  (25.2/32)
RetryTemplate (): void 100% (1/1)100% (22/22)100% (6/6)
doCloseInterceptors (RetryCallback, RetryContext, Throwable): void 100% (1/1)100% (20/20)100% (3/3)
doOnErrorInterceptors (RetryCallback, RetryContext, Throwable): void 100% (1/1)100% (20/20)100% (3/3)
doOpenInterceptors (RetryCallback, RetryContext): boolean 100% (1/1)100% (27/27)100% (4/4)
registerListener (RetryListener): void 100% (1/1)100% (21/21)100% (4/4)
rethrow (Throwable): void 100% (1/1)100% (18/18)100% (5/5)
setBackOffPolicy (BackOffPolicy): void 100% (1/1)100% (4/4)100% (2/2)
setListeners (RetryListener []): void 100% (1/1)100% (4/4)100% (2/2)
setRetryPolicy (RetryPolicy): void 100% (1/1)100% (4/4)100% (2/2)
unwrapIfRethrown (Throwable): Throwable 100% (1/1)100% (8/8)100% (3/3)
     
class RetryTemplate$UnclassifiedRetryException100% (1/1)100% (1/1)100% (5/5)100% (2/2)
RetryTemplate$UnclassifiedRetryException (String, Throwable): void 100% (1/1)100% (5/5)100% (2/2)

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 
17package org.springframework.batch.retry.support;
18 
19import java.util.ArrayList;
20import java.util.Arrays;
21import java.util.List;
22 
23import org.apache.commons.logging.Log;
24import org.apache.commons.logging.LogFactory;
25import org.springframework.batch.retry.RetryCallback;
26import org.springframework.batch.retry.RetryContext;
27import org.springframework.batch.retry.RetryException;
28import org.springframework.batch.retry.RetryListener;
29import org.springframework.batch.retry.RetryOperations;
30import org.springframework.batch.retry.RetryPolicy;
31import org.springframework.batch.retry.TerminatedRetryException;
32import org.springframework.batch.retry.backoff.BackOffContext;
33import org.springframework.batch.retry.backoff.BackOffInterruptedException;
34import org.springframework.batch.retry.backoff.BackOffPolicy;
35import org.springframework.batch.retry.backoff.NoBackOffPolicy;
36import org.springframework.batch.retry.policy.SimpleRetryPolicy;
37 
38/**
39 * Template class that simplifies the execution of operations with retry
40 * semantics. <br/> Retryable operations are encapsulated in implementations of
41 * the {@link RetryCallback} interface and are executed using one of the
42 * supplied {@link #execute} methods. <br/>
43 * 
44 * By default, an operation is retried if is throws any {@link Exception} or
45 * subclass of {@link Exception}. This behaviour can be changed by using the
46 * {@link #setRetryPolicy(RetryPolicy)} method. <br/>
47 * 
48 * Also by default, each operation is retried for a maximum of three attempts
49 * with no back off in between. This behaviour can be configured using the
50 * {@link #setRetryPolicy(RetryPolicy)} and
51 * {@link #setBackOffPolicy(BackOffPolicy)} properties. The
52 * {@link org.springframework.batch.retry.backoff.BackOffPolicy} controls how
53 * long the pause is between each individual retry attempt. <br/>
54 * 
55 * This class is thread-safe and suitable for concurrent access when executing
56 * operations and when performing configuration changes. As such, it is possible
57 * to change the number of retries on the fly, as well as the
58 * {@link BackOffPolicy} used and no in progress retryable operations will be
59 * affected.
60 * 
61 * @author Rob Harrop
62 * @author Dave Syer
63 */
64public class RetryTemplate implements RetryOperations {
65 
66        protected final Log logger = LogFactory.getLog(getClass());
67 
68        private volatile BackOffPolicy backOffPolicy = new NoBackOffPolicy();
69 
70        private volatile RetryPolicy retryPolicy = new SimpleRetryPolicy();
71 
72        private volatile RetryListener[] listeners = new RetryListener[0];
73 
74        /**
75         * Setter for listeners. The listeners are executed before and after a retry
76         * block (i.e. before and after all the attempts), and on an error (every
77         * attempt).
78         * @param listeners
79         * @see RetryListener
80         */
81        public void setListeners(RetryListener[] listeners) {
82                this.listeners = listeners;
83        }
84 
85        /**
86         * Register an additional listener.
87         * @param listener
88         * @see #setListeners(RetryListener[])
89         */
90        public void registerListener(RetryListener listener) {
91                List list = new ArrayList(Arrays.asList(listeners));
92                list.add(listener);
93                listeners = (RetryListener[]) list.toArray(new RetryListener[list.size()]);
94        }
95 
96        /**
97         * Setter for {@link BackOffPolicy}.
98         * @param backOffPolicy
99         */
100        public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
101                this.backOffPolicy = backOffPolicy;
102        }
103 
104        /**
105         * Setter for {@link RetryPolicy}.
106         * 
107         * @param retryPolicy
108         */
109        public void setRetryPolicy(RetryPolicy retryPolicy) {
110                this.retryPolicy = retryPolicy;
111        }
112 
113        /**
114         * Keep executing the callback until it either succeeds or the policy
115         * dictates that we stop, in which case the most recent exception thrown by
116         * the callback will be rethrown.
117         * 
118         * @see org.springframework.batch.retry.RetryOperations#execute(org.springframework.batch.retry.RetryCallback)
119         * 
120         * @throws TerminatedRetryException if the retry has been manually
121         * terminated through the {@link RetryContext}.
122         */
123        public final Object execute(RetryCallback callback) throws Exception {
124 
125                /*
126                 * Read all needed data into local variables to prevent any
127                 * reference/primitive changes on other threads affecting this retry
128                 * attempt.
129                 */
130                BackOffPolicy backOffPolicy = this.backOffPolicy;
131                RetryPolicy retryPolicy = this.retryPolicy;
132 
133                // Allow the retry policy to initialise itself...
134                // TODO: catch and rethrow abnormal retry exception?
135                RetryContext context = retryPolicy.open(callback, RetrySynchronizationManager.getContext());
136 
137                // Make sure the context is available globally for clients who need
138                // it...
139                RetrySynchronizationManager.register(context);
140 
141                Throwable lastException = null;
142 
143                try {
144 
145                        // Give clients a chance to enhance the context...
146                        boolean running = doOpenInterceptors(callback, context);
147 
148                        if (!running) {
149                                throw new TerminatedRetryException("Retry terminated abnormally by interceptor before first attempt");
150                        }
151 
152                        // Start the backoff context...
153                        BackOffContext backOffContext = backOffPolicy.start(context);
154 
155                        /*
156                         * We allow the whole loop to be skipped if the policy or context
157                         * already forbid the first try. This is used in the case of
158                         * external retry to allow a recovery in handleRetryExhausted
159                         * without the callback processing (which would throw an exception).
160                         */
161                        while (retryPolicy.canRetry(context) && !context.isExhaustedOnly()) {
162 
163                                try {
164                                        logger.debug("Retry: count=" + context.getRetryCount());
165                                        // Reset the last exception, so if we are successful
166                                        // the close interceptors will not think we failed...
167                                        lastException = null;
168                                        return callback.doWithRetry(context);
169                                }
170                                catch (Throwable ex) {
171                                        Throwable throwable = unwrapIfRethrown(ex);
172                                        lastException = throwable;
173 
174                                        doOnErrorInterceptors(callback, context, throwable);
175 
176                                        retryPolicy.registerThrowable(context, throwable);
177 
178                                        if (retryPolicy.shouldRethrow(context)) {
179                                                logger.debug("Rethrow in retry for policy: count=" + context.getRetryCount());
180                                                rethrow(throwable);
181                                        }
182 
183                                }
184 
185                                try {
186                                        backOffPolicy.backOff(backOffContext);
187                                }
188                                catch (BackOffInterruptedException e) {
189                                        lastException = e;
190                                        // back off was prevented by another thread - fail the
191                                        // retry
192                                        logger.debug("Abort retry because interrupted: count=" + context.getRetryCount());
193                                        rethrow(e);
194                                }
195 
196                                /*
197                                 * A stateful policy that can retry should have rethrown the
198                                 * exception by now - i.e. we shouldn't get this far for a
199                                 * stateful policy if it can retry.
200                                 */
201                        }
202 
203                        logger.debug("Retry failed last attempt: count=" + context.getRetryCount());
204                        return retryPolicy.handleRetryExhausted(context);
205 
206                }
207                finally {
208                        retryPolicy.close(context);
209                        doCloseInterceptors(callback, context, lastException);
210                        RetrySynchronizationManager.clear();
211                }
212        }
213 
214        private boolean doOpenInterceptors(RetryCallback callback, RetryContext context) {
215 
216                boolean result = true;
217 
218                for (int i = 0; i < listeners.length; i++) {
219                        result = result && listeners[i].open(context, callback);
220                }
221 
222                return result;
223 
224        }
225 
226        private void doCloseInterceptors(RetryCallback callback, RetryContext context, Throwable lastException) {
227                for (int i = listeners.length; i-- > 0;) {
228                        listeners[i].close(context, callback, lastException);
229                }
230        }
231 
232        private void doOnErrorInterceptors(RetryCallback callback, RetryContext context, Throwable throwable) {
233                for (int i = listeners.length; i-- > 0;) {
234                        listeners[i].onError(context, callback, throwable);
235                }
236        }
237 
238        /**
239         * Re-throw the exception directly if possible, wrap custom Throwables into
240         * {@link UnclassifiedRetryException}.
241         */
242        private static void rethrow(Throwable throwable) throws Exception {
243                if (throwable instanceof Exception) {
244                        throw (Exception) throwable;
245                }
246                else if (throwable instanceof Error) {
247                        throw (Error) throwable;
248                }
249                else {
250                        throw new UnclassifiedRetryException("Unclassified Throwable encountered", throwable);
251                }
252        }
253 
254        /**
255         * Undo the wrapping done in {@link #rethrow(Throwable)}
256         */
257        private static Throwable unwrapIfRethrown(Throwable throwable) {
258                if (throwable instanceof UnclassifiedRetryException) {
259                        return throwable.getCause();
260                }
261                else {
262                        return throwable;
263                }
264        }
265 
266        /**
267         * Runtime exception wrapper for Throwables that are neither Exception nor
268         * Error.
269         */
270        private static class UnclassifiedRetryException extends RetryException {
271 
272                public UnclassifiedRetryException(String msg, Throwable cause) {
273                        super(msg, cause);
274                }
275 
276        }
277 
278}

[all classes][org.springframework.batch.retry.support]
EMMA 2.0.5312 (C) Vladimir Roubtsov