1 /*
2  * Copyright 2006-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5  * the License. You may obtain a copy of the License at
6  *
7  * https://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11  * specific language governing permissions and limitations under the License.
12  */

13
14 package org.springframework.retry.interceptor;
15
16 import java.util.Arrays;
17
18 import org.aopalliance.intercept.MethodInterceptor;
19 import org.aopalliance.intercept.MethodInvocation;
20
21 import org.springframework.aop.ProxyMethodInvocation;
22 import org.springframework.retry.RecoveryCallback;
23 import org.springframework.retry.RetryCallback;
24 import org.springframework.retry.RetryContext;
25 import org.springframework.retry.RetryOperations;
26 import org.springframework.retry.support.RetryTemplate;
27 import org.springframework.util.Assert;
28 import org.springframework.util.StringUtils;
29
30 /**
31  * A {@link MethodInterceptor} that can be used to automatically retry calls to a method
32  * on a service if it fails. The injected {@link RetryOperations} is used to control the
33  * number of retries. By default it will retry a fixed number of times, according to the
34  * defaults in {@link RetryTemplate}.
35  *
36  * Hint about transaction boundaries. If you want to retry a failed transaction you need
37  * to make sure that the transaction boundary is inside the retry, otherwise the
38  * successful attempt will roll back with the whole transaction. If the method being
39  * intercepted is also transactional, then use the ordering hints in the advice
40  * declarations to ensure that this one is before the transaction interceptor in the
41  * advice chain.
42  *
43  * @author Rob Harrop
44  * @author Dave Syer
45  */

46 public class RetryOperationsInterceptor implements MethodInterceptor {
47
48     private RetryOperations retryOperations = new RetryTemplate();
49
50     private MethodInvocationRecoverer<?> recoverer;
51
52     private String label;
53
54     public void setLabel(String label) {
55         this.label = label;
56     }
57
58     public void setRetryOperations(RetryOperations retryTemplate) {
59         Assert.notNull(retryTemplate, "'retryOperations' cannot be null.");
60         this.retryOperations = retryTemplate;
61     }
62
63     public void setRecoverer(MethodInvocationRecoverer<?> recoverer) {
64         this.recoverer = recoverer;
65     }
66
67     public Object invoke(final MethodInvocation invocation) throws Throwable {
68
69         String name;
70         if (StringUtils.hasText(label)) {
71             name = label;
72         } else {
73             name = invocation.getMethod().toGenericString();
74         }
75         final String label = name;
76
77         RetryCallback<Object, Throwable> retryCallback = new RetryCallback<Object, Throwable>() {
78
79             public Object doWithRetry(RetryContext context) throws Exception {
80                 
81                 context.setAttribute(RetryContext.NAME, label);
82
83                 /*
84                  * If we don't copy the invocation carefully it won't keep a reference to
85                  * the other interceptors in the chain. We don't have a choice here but to
86                  * specialise to ReflectiveMethodInvocation (but how often would another
87                  * implementation come along?).
88                  */

89                 if (invocation instanceof ProxyMethodInvocation) {
90                     try {
91                         return ((ProxyMethodInvocation) invocation).invocableClone().proceed();
92                     }
93                     catch (Exception e) {
94                         throw e;
95                     }
96                     catch (Error e) {
97                         throw e;
98                     }
99                     catch (Throwable e) {
100                         throw new IllegalStateException(e);
101                     }
102                 }
103                 else {
104                     throw new IllegalStateException(
105                             "MethodInvocation of the wrong type detected - this should not happen with Spring AOP, " +
106                                     "so please raise an issue if you see this exception");
107                 }
108             }
109
110         };
111
112         if (recoverer != null) {
113             ItemRecovererCallback recoveryCallback = new ItemRecovererCallback(
114                     invocation.getArguments(), recoverer);
115             return this.retryOperations.execute(retryCallback, recoveryCallback);
116         }
117
118         return this.retryOperations.execute(retryCallback);
119
120     }
121
122     /**
123      * @author Dave Syer
124      *
125      */

126     private static final class ItemRecovererCallback implements RecoveryCallback<Object> {
127
128         private final Object[] args;
129
130         private final MethodInvocationRecoverer<?> recoverer;
131
132         /**
133          * @param args the item that failed.
134          */

135         private ItemRecovererCallback(Object[] args, MethodInvocationRecoverer<?> recoverer) {
136             this.args = Arrays.asList(args).toArray();
137             this.recoverer = recoverer;
138         }
139
140         public Object recover(RetryContext context) {
141             return recoverer.recover(args, context.getLastThrowable());
142         }
143
144     }
145
146 }
147