/
RepeatOperationsInterceptor.java
203 lines (180 loc) · 5.9 KB
/
RepeatOperationsInterceptor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.repeat.interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.repeat.RepeatCallback;
import org.springframework.repeat.RepeatContext;
import org.springframework.repeat.RepeatException;
import org.springframework.repeat.RepeatOperations;
import org.springframework.repeat.RepeatStatus;
import org.springframework.repeat.support.RepeatTemplate;
import org.springframework.util.Assert;
/**
* A {@link MethodInterceptor} that can be used to automatically repeat calls to
* a method on a service. The injected {@link RepeatOperations} is used to
* control the completion of the loop. Independent of the completion policy in
* the {@link RepeatOperations} the loop will repeat until the target method
* returns null or false. Be careful when injecting a bespoke
* {@link RepeatOperations} that the loop will actually terminate, because the
* default policy for a vanilla {@link RepeatTemplate} will never complete if
* the return type of the target method is void (the value returned is always
* not-null, representing the {@link Void#TYPE}).
*
* @author Dave Syer
*/
public class RepeatOperationsInterceptor implements MethodInterceptor {
private RepeatOperations repeatOperations = new RepeatTemplate();
/**
* Setter for the {@link RepeatOperations}.
*
* @param batchTempate
* @throws IllegalArgumentException if the argument is null.
*/
public void setRepeatOperations(RepeatOperations batchTempate) {
Assert.notNull(batchTempate, "'repeatOperations' cannot be null.");
this.repeatOperations = batchTempate;
}
/**
* Invoke the proceeding method call repeatedly, according to the properties
* of the injected {@link RepeatOperations}.
*
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
public Object invoke(final MethodInvocation invocation) throws Throwable {
final ResultHolder result = new ResultHolder();
// Cache void return value if intercepted method returns void
final boolean voidReturnType = Void.TYPE.equals(invocation.getMethod().getReturnType());
if (voidReturnType) {
// This will be ignored anyway, but we want it to be non-null for
// convenience of checking that there is a result.
result.setValue(new Object());
}
try {
repeatOperations.iterate(new RepeatCallback() {
public RepeatStatus doInIteration(RepeatContext context) throws Exception {
try {
MethodInvocation clone = invocation;
if (invocation instanceof ProxyMethodInvocation) {
clone = ((ProxyMethodInvocation) invocation).invocableClone();
}
else {
throw new IllegalStateException(
"MethodInvocation of the wrong type detected - this should not happen with Spring AOP, so please raise an issue if you see this exception");
}
Object value = clone.proceed();
if (voidReturnType) {
return RepeatStatus.CONTINUABLE;
}
if (!isComplete(value)) {
// Save the last result
result.setValue(value);
return RepeatStatus.CONTINUABLE;
}
else {
result.setFinalValue(value);
return RepeatStatus.FINISHED;
}
}
catch (Throwable e) {
if (e instanceof Exception) {
throw (Exception) e;
}
else {
throw new RepeatOperationsInterceptorException("Unexpected error in batch interceptor", e);
}
}
}
});
}
catch (Throwable t) {
// The repeat exception should be unwrapped by the template
throw t;
}
if (result.isReady()) {
return result.getValue();
}
// No result means something weird happened
throw new IllegalStateException("No result available for attempted repeat call to " + invocation
+ ". The invocation was never called, so maybe there is a problem with the completion policy?");
}
/**
* @param result
* @return
*/
private boolean isComplete(Object result) {
return result == null || (result instanceof Boolean) && !((Boolean) result).booleanValue();
}
/**
* Simple wrapper exception class to enable nasty errors to be passed out of
* the scope of the repeat operations and handled by the caller.
*
* @author Dave Syer
*
*/
private static class RepeatOperationsInterceptorException extends RepeatException {
/**
* @param message
* @param e
*/
public RepeatOperationsInterceptorException(String message, Throwable e) {
super(message, e);
}
}
/**
* Simple wrapper object for the result from a method invocation.
*
* @author Dave Syer
*
*/
private static class ResultHolder {
private Object value = null;
private boolean ready = false;
/**
* Public setter for the Object.
* @param value the value to set
*/
public void setValue(Object value) {
this.ready = true;
this.value = value;
}
/**
* @param value
*/
public void setFinalValue(Object value) {
if (ready) {
// Only set the value the last time if the last time was also
// the first time
return;
}
setValue(value);
}
/**
* Public getter for the Object.
* @return the value
*/
public Object getValue() {
return value;
}
/**
* @return true if a value has been set
*/
public boolean isReady() {
return ready;
}
}
}