View Javadoc

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 }