/*
* 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.batch.repeat.interceptor;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatException;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.batch.repeat.support.RepeatTemplate;
public class RepeatOperationsInterceptorTests extends TestCase {
private RepeatOperationsInterceptor interceptor;
private Service service;
private ServiceImpl target;
@Override
protected void setUp() throws Exception {
super.setUp();
interceptor = new RepeatOperationsInterceptor();
target = new ServiceImpl();
ProxyFactory factory = new ProxyFactory(RepeatOperations.class.getClassLoader());
factory.setInterfaces(Service.class);
factory.setTarget(target);
service = (Service) factory.getProxy();
}
public void testDefaultInterceptorSunnyDay() throws Exception {
((Advised) service).addAdvice(interceptor);
service.service();
assertEquals(3, target.count);
}
public void testCompleteOnFirstInvocation() throws Exception {
((Advised) service).addAdvice(interceptor);
target.setMaxService(0);
service.service();
assertEquals(1, target.count);
}
public void testSetTemplate() throws Exception {
final List<Object> calls = new ArrayList<Object>();
interceptor.setRepeatOperations(new RepeatOperations() {
@Override
public RepeatStatus iterate(RepeatCallback callback) {
try {
Object result = callback.doInIteration(null);
calls.add(result);
}
catch (Exception e) {
throw new RepeatException("Encountered exception in repeat.", e);
}
return RepeatStatus.CONTINUABLE;
}
});
((Advised) service).addAdvice(interceptor);
service.service();
assertEquals(1, calls.size());
}
public void testCallbackNotExecuted() throws Exception {
final List<Object> calls = new ArrayList<Object>();
interceptor.setRepeatOperations(new RepeatOperations() {
@Override
public RepeatStatus iterate(RepeatCallback callback) {
calls.add(null);
return RepeatStatus.FINISHED;
}
});
((Advised) service).addAdvice(interceptor);
try {
service.service();
fail("Expected IllegalStateException");
} catch (IllegalStateException e) {
String message = e.getMessage();
assertTrue("Wrong exception message: "+message, message.toLowerCase().contains("no result available"));
}
assertEquals(1, calls.size());
}
public void testVoidServiceSunnyDay() throws Exception {
((Advised) service).addAdvice(interceptor);
RepeatTemplate template = new RepeatTemplate();
// N.B. the default completion policy results in an infinite loop, so we
// need to set the chunk size.
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
interceptor.setRepeatOperations(template);
service.alternate();
assertEquals(2, target.count);
}
public void testCallbackWithException() throws Exception {
((Advised) service).addAdvice(interceptor);
try {
service.exception();
fail("Expected RuntimeException");
}
catch (RuntimeException e) {
assertEquals("Duh", e.getMessage().substring(0, 3));
}
}
public void testCallbackWithThrowable() throws Exception {
((Advised) service).addAdvice(interceptor);
try {
service.error();
fail("Expected Error");
}
catch (Error e) {
assertEquals("Duh", e.getMessage().substring(0, 3));
}
}
public void testCallbackWithBoolean() throws Exception {
RepeatTemplate template = new RepeatTemplate();
// N.B. the default completion policy results in an infinite loop, so we
// need to set the chunk size.
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
interceptor.setRepeatOperations(template);
((Advised) service).addAdvice(interceptor);
assertTrue(service.isContinuable());
assertEquals(2, target.count);
}
public void testCallbackWithBooleanReturningFalseFirstTime() throws Exception {
target.setComplete(true);
((Advised) service).addAdvice(interceptor);
// Complete without repeat when boolean return value is false
assertFalse(service.isContinuable());
assertEquals(1, target.count);
}
public void testInterceptorChainWithRetry() throws Exception {
((Advised) service).addAdvice(interceptor);
final List<Object> list = new ArrayList<Object>();
((Advised) service).addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
list.add("chain");
return invocation.proceed();
}
});
RepeatTemplate template = new RepeatTemplate();
template.setCompletionPolicy(new SimpleCompletionPolicy(2));
interceptor.setRepeatOperations(template);
service.service();
assertEquals(2, target.count);
assertEquals(2, list.size());
}
public void testIllegalMethodInvocationType() throws Throwable {
try {
interceptor.invoke(new MethodInvocation() {
@Override
public Method getMethod() {
try {
return Object.class.getMethod("toString");
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Object[] getArguments() {
return null;
}
@Override
public AccessibleObject getStaticPart() {
return null;
}
@Override
public Object getThis() {
return null;
}
@Override
public Object proceed() throws Throwable {
return null;
}
});
fail("IllegalStateException expected");
}
catch (IllegalStateException e) {
assertTrue("Exception message should contain MethodInvocation: " + e.getMessage(), e.getMessage().indexOf(
"MethodInvocation") >= 0);
}
}
private interface Service {
Object service() throws Exception;
void alternate() throws Exception;
Object exception() throws Exception;
Object error() throws Exception;
boolean isContinuable() throws Exception;
}
private static class ServiceImpl implements Service {
private int count = 0;
private boolean complete;
private int maxService = 2;
/**
* Public setter for the maximum number of times to call service().
* @param maxService the maxService to set
*/
public void setMaxService(int maxService) {
this.maxService = maxService;
}
@Override
public Object service() throws Exception {
count++;
if (count <= maxService) {
return Integer.valueOf(count);
}
else {
return null;
}
}
public void setComplete(boolean complete) {
this.complete = complete;
}
@Override
public void alternate() throws Exception {
count++;
}
@Override
public Object exception() throws Exception {
throw new RuntimeException("Duh! Stupid.");
}
@Override
public Object error() throws Exception {
throw new Error("Duh! Stupid error.");
}
@Override
public boolean isContinuable() throws Exception {
count++;
return !complete;
}
}
}