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