001/* 002 * Copyright 2006-2007 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.batch.repeat.interceptor; 018 019import org.aopalliance.intercept.MethodInterceptor; 020import org.aopalliance.intercept.MethodInvocation; 021import org.springframework.aop.ProxyMethodInvocation; 022import org.springframework.batch.repeat.RepeatStatus; 023import org.springframework.batch.repeat.RepeatCallback; 024import org.springframework.batch.repeat.RepeatContext; 025import org.springframework.batch.repeat.RepeatException; 026import org.springframework.batch.repeat.RepeatOperations; 027import org.springframework.batch.repeat.support.RepeatTemplate; 028import org.springframework.util.Assert; 029 030/** 031 * A {@link MethodInterceptor} that can be used to automatically repeat calls to 032 * a method on a service. The injected {@link RepeatOperations} is used to 033 * control the completion of the loop. Independent of the completion policy in 034 * the {@link RepeatOperations} the loop will repeat until the target method 035 * returns null or false. Be careful when injecting a bespoke 036 * {@link RepeatOperations} that the loop will actually terminate, because the 037 * default policy for a vanilla {@link RepeatTemplate} will never complete if 038 * the return type of the target method is void (the value returned is always 039 * not-null, representing the {@link Void#TYPE}). 040 * 041 * @author Dave Syer 042 */ 043public class RepeatOperationsInterceptor implements MethodInterceptor { 044 045 private RepeatOperations repeatOperations = new RepeatTemplate(); 046 047 /** 048 * Setter for the {@link RepeatOperations}. 049 * 050 * @param batchTemplate template to be used 051 * @throws IllegalArgumentException if the argument is null. 052 */ 053 public void setRepeatOperations(RepeatOperations batchTemplate) { 054 Assert.notNull(batchTemplate, "'repeatOperations' cannot be null."); 055 this.repeatOperations = batchTemplate; 056 } 057 058 /** 059 * Invoke the proceeding method call repeatedly, according to the properties 060 * of the injected {@link RepeatOperations}. 061 * 062 * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) 063 */ 064 @Override 065 public Object invoke(final MethodInvocation invocation) throws Throwable { 066 067 final ResultHolder result = new ResultHolder(); 068 // Cache void return value if intercepted method returns void 069 final boolean voidReturnType = Void.TYPE.equals(invocation.getMethod().getReturnType()); 070 if (voidReturnType) { 071 // This will be ignored anyway, but we want it to be non-null for 072 // convenience of checking that there is a result. 073 result.setValue(new Object()); 074 } 075 076 try { 077 repeatOperations.iterate(new RepeatCallback() { 078 079 @Override 080 public RepeatStatus doInIteration(RepeatContext context) throws Exception { 081 try { 082 083 MethodInvocation clone = invocation; 084 if (invocation instanceof ProxyMethodInvocation) { 085 clone = ((ProxyMethodInvocation) invocation).invocableClone(); 086 } 087 else { 088 throw new IllegalStateException( 089 "MethodInvocation of the wrong type detected - this should not happen with Spring AOP, so please raise an issue if you see this exception"); 090 } 091 092 Object value = clone.proceed(); 093 if (voidReturnType) { 094 return RepeatStatus.CONTINUABLE; 095 } 096 if (!isComplete(value)) { 097 // Save the last result 098 result.setValue(value); 099 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 @SuppressWarnings("serial") 148 private static class RepeatOperationsInterceptorException extends RepeatException { 149 /** 150 * @param message 151 * @param e 152 */ 153 public RepeatOperationsInterceptorException(String message, Throwable e) { 154 super(message, e); 155 } 156 } 157 158 /** 159 * Simple wrapper object for the result from a method invocation. 160 * 161 * @author Dave Syer 162 * 163 */ 164 private static class ResultHolder { 165 private Object value = null; 166 167 private boolean ready = false; 168 169 /** 170 * Public setter for the Object. 171 * @param value the value to set 172 */ 173 public void setValue(Object value) { 174 this.ready = true; 175 this.value = value; 176 } 177 178 /** 179 * @param value 180 */ 181 public void setFinalValue(Object value) { 182 if (ready) { 183 // Only set the value the last time if the last time was also 184 // the first time 185 return; 186 } 187 setValue(value); 188 } 189 190 /** 191 * Public getter for the Object. 192 * @return the value 193 */ 194 public Object getValue() { 195 return value; 196 } 197 198 /** 199 * @return true if a value has been set 200 */ 201 public boolean isReady() { 202 return ready; 203 } 204 } 205 206}