/*
* Copyright 2014-2016 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.retry.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Properties;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.backoff.Sleeper;
import org.springframework.retry.interceptor.RetryInterceptorBuilder;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
/**
* @author Dave Syer
* @author Artem Bilan
* @author Gary Russell
* @since 1.1
*/
public class EnableRetryTests {
@Test
public void vanilla() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TestConfiguration.class);
Service service = context.getBean(Service.class);
Foo foo = context.getBean(Foo.class);
assertFalse(AopUtils.isAopProxy(foo));
assertTrue(AopUtils.isAopProxy(service));
service.service();
assertEquals(3, service.getCount());
context.close();
}
@Test
public void multipleMethods() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TestConfiguration.class);
MultiService service = context.getBean(MultiService.class);
service.service();
assertEquals(3, service.getCount());
service.other();
assertEquals(4, service.getCount());
context.close();
}
@Test
public void proxyTargetClass() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TestProxyConfiguration.class);
Service service = context.getBean(Service.class);
assertTrue(AopUtils.isCglibProxy(service));
context.close();
}
@Test
public void marker() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TestConfiguration.class);
Service service = context.getBean(Service.class);
assertTrue(AopUtils.isCglibProxy(service));
assertTrue(service instanceof org.springframework.retry.interceptor.Retryable);
context.close();
}
@Test
public void recovery() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TestConfiguration.class);
RecoverableService service = context.getBean(RecoverableService.class);
service.service();
assertEquals(3, service.getCount());
assertNotNull(service.getCause());
context.close();
}
@Test
public void type() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TestConfiguration.class);
RetryableService service = context.getBean(RetryableService.class);
service.service();
assertEquals(3, service.getCount());
context.close();
}
@Test
public void excludes() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TestConfiguration.class);
ExcludesService service = context.getBean(ExcludesService.class);
try {
service.service();
fail("Expected IllegalStateException");
}
catch (IllegalStateException e) {
}
assertEquals(1, service.getCount());
context.close();
}
@Test
public void stateful() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
TestConfiguration.class);
StatefulService service = context.getBean(StatefulService.class);
for (int i = 0; i < 3; i++) {
try {
service.service(1);
}
catch (Exception e) {
assertEquals("Planned", e.getMessage());
}
}
assertEquals(3, service.getCount());
context.close();
}
@Test
public void testExternalInterceptor() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
InterceptableService service = context.getBean(InterceptableService.class);
service.service();
assertEquals(5, service.getCount());
context.close();
}
@Test
public void testInterface() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
TheInterface service = context.getBean(TheInterface.class);
service.service1();
service.service2();
assertEquals(4, service.getCount());
context.close();
}
@Test
public void testExpression() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
ExpressionService service = context.getBean(ExpressionService.class);
service.service1();
assertEquals(3, service.getCount());
try {
service.service2();
fail("expected exception");
}
catch (RuntimeException e) {
assertEquals("this cannot be retried", e.getMessage());
}
assertEquals(4, service.getCount());
service.service3();
assertEquals(9, service.getCount());
RetryConfiguration config = context.getBean(RetryConfiguration.class);
AnnotationAwareRetryOperationsInterceptor advice =
(AnnotationAwareRetryOperationsInterceptor) new DirectFieldAccessor(config).getPropertyValue("advice");
@SuppressWarnings("unchecked")
Map<Method, MethodInterceptor> delegates = (Map<Method, MethodInterceptor>) new DirectFieldAccessor(advice)
.getPropertyValue("delegates");
MethodInterceptor interceptor = delegates
.get(ExpressionService.class.getDeclaredMethod("service3"));
RetryTemplate template = (RetryTemplate) new DirectFieldAccessor(interceptor)
.getPropertyValue("retryOperations");
DirectFieldAccessor templateAccessor = new DirectFieldAccessor(template);
ExponentialBackOffPolicy backOff = (ExponentialBackOffPolicy) templateAccessor
.getPropertyValue("backOffPolicy");
assertEquals(1, backOff.getInitialInterval());
assertEquals(5, backOff.getMaxInterval());
assertEquals(1.1, backOff.getMultiplier(), 0.1);
SimpleRetryPolicy retryPolicy = (SimpleRetryPolicy) templateAccessor.getPropertyValue("retryPolicy");
assertEquals(5, retryPolicy.getMaxAttempts());
context.close();
}
@Configuration
@EnableRetry(proxyTargetClass = true)
protected static class TestProxyConfiguration {
@Bean
public Service service() {
return new Service();
}
}
@Configuration
@EnableRetry
protected static class TestConfiguration {
@Bean
public static PropertySourcesPlaceholderConfigurer pspc() {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
Properties properties = new Properties();
properties.setProperty("one", "1");
properties.setProperty("five", "5");
properties.setProperty("onePointOne", "1.1");
properties.setProperty("retryMethod", "shouldRetry");
pspc.setProperties(properties);
return pspc;
}
@SuppressWarnings("serial")
@Bean
public Sleeper sleeper() {
return new Sleeper() {
@Override
public void sleep(long period) throws InterruptedException {
}
};
}
@Bean
public Service service() {
return new Service();
}
@Bean
public MultiService multiService() {
return new MultiService();
}
@Bean
public RecoverableService recoverable() {
return new RecoverableService();
}
@Bean
public RetryableService retryable() {
return new RetryableService();
}
@Bean
public StatefulService stateful() {
return new StatefulService();
}
@Bean
public ExcludesService excludes() {
return new ExcludesService();
}
@Bean
public MethodInterceptor retryInterceptor() {
return RetryInterceptorBuilder.stateless()
.maxAttempts(5)
.build();
}
@Bean
public InterceptableService serviceWithExternalInterceptor() {
return new InterceptableService();
}
@Bean
public ExpressionService expressionService() {
return new ExpressionService();
}
@Bean
public ExceptionChecker exceptionChecker() {
return new ExceptionChecker();
}
@Bean
public Integer integerFiveBean() {
return Integer.valueOf(5);
}
@Bean
public Foo foo() {
return new Foo();
}
@Bean
public TheInterface anInterface() {
return new TheClass();
}
}
protected static class Service {
private int count = 0;
@Retryable(RuntimeException.class)
public void service() {
if (count++ < 2) {
throw new RuntimeException("Planned");
}
}
public int getCount() {
return count;
}
}
protected static class MultiService {
private int count = 0;
@Retryable(RuntimeException.class)
public void service() {
if (count++ < 2) {
throw new RuntimeException("Planned");
}
}
@Retryable(RuntimeException.class)
public void other() {
if (count++ < 3) {
throw new RuntimeException("Other");
}
}
public int getCount() {
return count;
}
}
protected static class RecoverableService {
private int count = 0;
private Throwable cause;
@Retryable(RuntimeException.class)
public void service() {
count++;
throw new RuntimeException("Planned");
}
@Recover
public void recover(Throwable cause) {
this.cause = cause;
}
public Throwable getCause() {
return cause;
}
public int getCount() {
return count;
}
}
@Retryable(RuntimeException.class)
protected static class RetryableService {
private int count = 0;
public void service() {
if (count++ < 2) {
throw new RuntimeException("Planned");
}
}
public int getCount() {
return count;
}
}
protected static class ExcludesService {
private int count = 0;
@Retryable(include = RuntimeException.class, exclude = IllegalStateException.class)
public void service() {
if (count++ < 2) {
throw new IllegalStateException("Planned");
}
}
public int getCount() {
return count;
}
}
protected static class StatefulService {
private int count = 0;
@Retryable(stateful = true)
public void service(int value) {
if (count++ < 2) {
throw new RuntimeException("Planned");
}
}
public int getCount() {
return count;
}
}
private static class InterceptableService {
private int count = 0;
@Retryable(interceptor = "retryInterceptor")
public void service() {
if (count++ < 4) {
throw new RuntimeException("Planned");
}
}
public int getCount() {
return count;
}
}
private static class ExpressionService {
private int count = 0;
@Retryable(exceptionExpression="#{message.contains('this can be retried')}")
public void service1() {
if (count++ < 2) {
throw new RuntimeException("this can be retried");
}
}
@Retryable(exceptionExpression="#{message.contains('this can be retried')}")
public void service2() {
count++;
throw new RuntimeException("this cannot be retried");
}
@Retryable(exceptionExpression="#{@exceptionChecker.${retryMethod}(#root)}",
maxAttemptsExpression = "#{@integerFiveBean}",
backoff = @Backoff(delayExpression = "#{${one}}", maxDelayExpression = "#{${five}}",
multiplierExpression = "#{${onePointOne}}"))
public void service3() {
if (count++ < 8) {
throw new RuntimeException();
}
}
public int getCount() {
return count;
}
}
public static class ExceptionChecker {
public boolean shouldRetry(Throwable t) {
return true;
}
}
private static class Foo {
}
public static interface TheInterface {
void service1();
@Retryable
void service2();
int getCount();
}
public static class TheClass implements TheInterface {
private int count = 0;
@Override
@Retryable
public void service1() {
if (count++ < 1) {
throw new RuntimeException("Planned");
}
}
@Override
public void service2() {
if (count++ < 3) {
throw new RuntimeException("Planned");
}
}
@Override
public int getCount() {
return count;
}
}
}