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

COVERAGE SUMMARY FOR SOURCE FILE [StatefulRetryOperationsInterceptor.java]

nameclass, %method, %block, %line, %
StatefulRetryOperationsInterceptor.java75%  (3/4)80%  (8/10)89%  (164/184)86%  (32.8/38)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class StatefulRetryOperationsInterceptor100% (1/1)67%  (4/6)87%  (129/149)81%  (21.8/27)
setKeyGenerator (ItemKeyGenerator): void 0%   (0/1)0%   (0/4)0%   (0/2)
setNewItemIdentifier (NewItemIdentifier): void 0%   (0/1)0%   (0/4)0%   (0/2)
invoke (MethodInvocation): Object 100% (1/1)89%  (95/107)92%  (12.8/14)
StatefulRetryOperationsInterceptor (): void 100% (1/1)100% (22/22)100% (5/5)
setRecoverer (ItemRecoverer): void 100% (1/1)100% (4/4)100% (2/2)
setRetryPolicy (RetryPolicy): void 100% (1/1)100% (8/8)100% (2/2)
     
class StatefulRetryOperationsInterceptor$10%   (0/1)100% (0/0)100% (0/0)100% (0/0)
     
class StatefulRetryOperationsInterceptor$ItemRecovererCallback100% (1/1)100% (2/2)100% (25/25)100% (7/7)
StatefulRetryOperationsInterceptor$ItemRecovererCallback (Object, ItemRecover... 100% (1/1)100% (9/9)100% (4/4)
recover (RetryContext): Object 100% (1/1)100% (16/16)100% (3/3)
     
class StatefulRetryOperationsInterceptor$MethodInvocationRetryCallback100% (1/1)100% (2/2)100% (10/10)100% (4/4)
StatefulRetryOperationsInterceptor$MethodInvocationRetryCallback (MethodInvoc... 100% (1/1)100% (6/6)100% (3/3)
doWithRetry (RetryContext): Object 100% (1/1)100% (4/4)100% (1/1)

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.interceptor;
18 
19import org.aopalliance.intercept.MethodInterceptor;
20import org.aopalliance.intercept.MethodInvocation;
21import org.apache.commons.logging.Log;
22import org.apache.commons.logging.LogFactory;
23import org.springframework.batch.item.ItemKeyGenerator;
24import org.springframework.batch.item.ItemRecoverer;
25import org.springframework.batch.item.NewItemIdentifier;
26import org.springframework.batch.retry.ExhaustedRetryException;
27import org.springframework.batch.retry.RecoveryCallback;
28import org.springframework.batch.retry.RetryCallback;
29import org.springframework.batch.retry.RetryContext;
30import org.springframework.batch.retry.RetryOperations;
31import org.springframework.batch.retry.RetryPolicy;
32import org.springframework.batch.retry.callback.RecoveryRetryCallback;
33import org.springframework.batch.retry.policy.NeverRetryPolicy;
34import org.springframework.batch.retry.policy.RecoveryCallbackRetryPolicy;
35import org.springframework.batch.retry.support.RetryTemplate;
36import org.springframework.util.Assert;
37import org.springframework.util.ObjectUtils;
38 
39/**
40 * A {@link MethodInterceptor} that can be used to automatically retry calls to
41 * a method on a service if it fails. The argument to the service method is
42 * treated as an item to be remembered in case the call fails. So the retry
43 * operation is stateful, and the item that failed is tracked by its unique key
44 * (via {@link ItemKeyGenerator}) until the retry is exhausted, at which point
45 * the {@link ItemRecoverer} is called.<br/>
46 * 
47 * The main use case for this is where the service is transactional, via a
48 * transaction interceptor on the interceptor chain. In this case the retry (and
49 * recovery on exhausted) always happens in a new transaction.<br/>
50 * 
51 * The injected {@link RetryOperations} is used to control the number of
52 * retries. By default it will retry a fixed number of times, according to the
53 * defaults in {@link RetryTemplate}.<br/>
54 * 
55 * @author Dave Syer
56 */
57public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
58 
59        private transient Log logger = LogFactory.getLog(getClass());
60 
61        private ItemKeyGenerator keyGenerator;
62 
63        private ItemRecoverer recoverer;
64 
65        private NewItemIdentifier newItemIdentifier;
66 
67        private final RetryTemplate retryTemplate = new RetryTemplate();
68 
69        /**
70         * 
71         */
72        public StatefulRetryOperationsInterceptor() {
73                super();
74                retryTemplate.setRetryPolicy(new RecoveryCallbackRetryPolicy(new NeverRetryPolicy()));
75        }
76 
77        /**
78         * Public setter for the {@link ItemRecoverer} to use if the retry is
79         * exhausted. The recoverer should be able to return an object of the same
80         * type as the target object because its return value will be used to return
81         * to the caller in the case of a recovery.<br/>
82         * 
83         * If no recoverer is set then an exhausted retry will result in an
84         * {@link ExhaustedRetryException}.
85         * 
86         * @param recoverer the {@link ItemRecoverer} to set
87         */
88        public void setRecoverer(ItemRecoverer recoverer) {
89                this.recoverer = recoverer;
90        }
91 
92        public void setKeyGenerator(ItemKeyGenerator keyGenerator) {
93                this.keyGenerator = keyGenerator;
94        }
95 
96        /**
97         * Public setter for the retryPolicy. The value provided should be a normal
98         * stateless policy, which is wrapped into a stateful policy inside this
99         * method.
100         * @param retryPolicy the retryPolicy to set
101         */
102        public void setRetryPolicy(RetryPolicy retryPolicy) {
103                retryTemplate.setRetryPolicy(new RecoveryCallbackRetryPolicy(retryPolicy));
104        }
105 
106        /**
107         * Public setter for the {@link NewItemIdentifier}. Only set this if the
108         * arguments to the intercepted method can be inspected to find out if they
109         * have never been processed before.
110         * @param newItemIdentifier the {@link NewItemIdentifier} to set
111         */
112        public void setNewItemIdentifier(NewItemIdentifier newItemIdentifier) {
113                this.newItemIdentifier = newItemIdentifier;
114        }
115 
116        /**
117         * Wrap the method invocation in a stateful retry with the policy and other
118         * helpers provided. If there is a failure the exception will generally be
119         * re-thrown. The only time it is not re-thrown is when retry is exhausted
120         * and the recovery path is taken (though the {@link ItemRecoverer} provided
121         * if there is one). In that case the value returned from the method
122         * invocation will be the value returned by the recoverer (so the return
123         * type for that should be the same as the intercepted method).
124         * 
125         * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
126         * @see ItemRecoverer#recover(Object, Throwable)
127         * 
128         * @throws ExhaustedRetryException if the retry is exhausted and no
129         * {@link ItemRecoverer} is provided.
130         */
131        public Object invoke(final MethodInvocation invocation) throws Throwable {
132 
133                logger.debug("Executing proxied method in stateful retry: " + invocation.getStaticPart() + "("
134                                + ObjectUtils.getIdentityHexString(invocation) + ")");
135 
136                Object[] args = invocation.getArguments();
137                Assert.state(args.length > 0, "Stateful retry applied to method that takes no arguments: "
138                                + invocation.getStaticPart());
139                Object arg = args;
140                if (args.length == 1) {
141                        arg = args[0];
142                }
143                final Object item = arg;
144 
145                RecoveryRetryCallback callback = new RecoveryRetryCallback(item, new MethodInvocationRetryCallback(invocation),
146                                keyGenerator != null ? keyGenerator.getKey(item) : item);
147                callback.setRecoveryCallback(new ItemRecovererCallback(item, recoverer));
148                if (newItemIdentifier != null) {
149                        callback.setForceRefresh(newItemIdentifier.isNew(item));
150                }
151 
152                Object result = retryTemplate.execute(callback);
153 
154                logger.debug("Exiting proxied method in stateful retry with result: (" + result + ")");
155 
156                return result;
157 
158        }
159 
160        /**
161         * @author Dave Syer
162         * 
163         */
164        private static final class MethodInvocationRetryCallback implements RetryCallback {
165                /**
166                 * 
167                 */
168                private final MethodInvocation invocation;
169 
170                /**
171                 * @param invocation
172                 */
173                private MethodInvocationRetryCallback(MethodInvocation invocation) {
174                        this.invocation = invocation;
175                }
176 
177                public Object doWithRetry(RetryContext context) throws Throwable {
178                        return invocation.proceed();
179                }
180        }
181 
182        /**
183         * @author Dave Syer
184         * 
185         */
186        private static final class ItemRecovererCallback implements RecoveryCallback {
187 
188                private final Object item;
189 
190                private final ItemRecoverer recoverer;
191 
192                /**
193                 * @param item the item that failed.
194                 */
195                private ItemRecovererCallback(Object item, ItemRecoverer recoverer) {
196                        this.item = item;
197                        this.recoverer = recoverer;
198                }
199 
200                public Object recover(RetryContext context) {
201                        if (recoverer != null) {
202                                return recoverer.recover(item, context.getLastThrowable());
203                        }
204                        throw new ExhaustedRetryException("Retry was exhausted but there was no recovery path.");
205                }
206 
207        }
208 
209}

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