EMMA Coverage Report (generated Thu Jan 24 13:37:04 CST 2013)
[all classes][org.springframework.batch.retry.interceptor]

COVERAGE SUMMARY FOR SOURCE FILE [StatefulRetryOperationsInterceptor.java]

nameclass, %method, %block, %line, %
StatefulRetryOperationsInterceptor.java75%  (3/4)83%  (10/12)86%  (173/201)82%  (36.8/45)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class StatefulRetryOperationsInterceptor100% (1/1)67%  (4/6)87%  (124/143)84%  (21.8/26)
setKeyGenerator (MethodArgumentsKeyGenerator): void 0%   (0/1)0%   (0/4)0%   (0/2)
setNewItemIdentifier (NewMethodArgumentsIdentifier): void 0%   (0/1)0%   (0/4)0%   (0/2)
invoke (MethodInvocation): Object 100% (1/1)89%  (93/104)98%  (10.8/11)
StatefulRetryOperationsInterceptor (): void 100% (1/1)100% (20/20)100% (6/6)
setRecoverer (MethodInvocationRecoverer): void 100% (1/1)100% (4/4)100% (2/2)
setRetryOperations (RetryOperations): void 100% (1/1)100% (7/7)100% (3/3)
     
class StatefulRetryOperationsInterceptor$10%   (0/1)100% (0/0)100% (0/0)100% (0/0)
     
class StatefulRetryOperationsInterceptor$MethodInvocationRetryCallback100% (1/1)100% (3/3)65%  (17/26)64%  (7/11)
doWithRetry (RetryContext): Object 100% (1/1)44%  (7/16)43%  (3/7)
StatefulRetryOperationsInterceptor$MethodInvocationRetryCallback (MethodInvoc... 100% (1/1)100% (6/6)100% (3/3)
StatefulRetryOperationsInterceptor$MethodInvocationRetryCallback (MethodInvoc... 100% (1/1)100% (4/4)100% (1/1)
     
class StatefulRetryOperationsInterceptor$ItemRecovererCallback100% (1/1)100% (3/3)100% (32/32)100% (8/8)
StatefulRetryOperationsInterceptor$ItemRecovererCallback (Object [], MethodIn... 100% (1/1)100% (11/11)100% (4/4)
StatefulRetryOperationsInterceptor$ItemRecovererCallback (Object [], MethodIn... 100% (1/1)100% (5/5)100% (1/1)
recover (RetryContext): Object 100% (1/1)100% (16/16)100% (3/3)

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

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