1 /*
2  * Copyright 2002-2011 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.scheduling.annotation;
18 
19 import static org.easymock.EasyMock.createMock;
20 import static org.easymock.EasyMock.replay;
21 import static org.hamcrest.CoreMatchers.is;
22 import static org.hamcrest.Matchers.greaterThan;
23 import static org.junit.Assert.assertThat;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 
27 import java.util.concurrent.atomic.AtomicInteger;
28 
29 import org.junit.Test;
30 import org.springframework.aop.support.AopUtils;
31 import org.springframework.beans.factory.BeanCreationException;
32 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
33 import org.springframework.context.annotation.Bean;
34 import org.springframework.context.annotation.Configuration;
35 import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
36 import org.springframework.dao.support.PersistenceExceptionTranslator;
37 import org.springframework.scheduling.annotation.EnableScheduling;
38 import org.springframework.stereotype.Repository;
39 import org.springframework.transaction.CallCountingTransactionManager;
40 import org.springframework.transaction.PlatformTransactionManager;
41 import org.springframework.transaction.annotation.EnableTransactionManagement;
42 import org.springframework.transaction.annotation.Transactional;
43 
44 /**
45  * Integration tests cornering bug SPR-8651, which revealed that @Scheduled methods may
46  * not work well with beans that have already been proxied for other reasons such
47  * as @Transactional or @Async processing.
48  *
49  * @author Chris Beams
50  * @since 3.1
51  */
52 public class ScheduledAndTransactionalAnnotationIntegrationTests {
53 
54 	@Test
failsWhenJdkProxyAndScheduledMethodNotPresentOnInterface()55 	public void failsWhenJdkProxyAndScheduledMethodNotPresentOnInterface() {
56 		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
57 		ctx.register(Config.class, JdkProxyTxConfig.class, RepoConfigA.class);
58 		try {
59 			ctx.refresh();
60 			fail("expected exception");
61 		} catch (BeanCreationException ex) {
62 			assertTrue(ex.getRootCause().getMessage().startsWith("@Scheduled method 'scheduled' found"));
63 		}
64 	}
65 
66 	@Test
succeedsWhenSubclassProxyAndScheduledMethodNotPresentOnInterface()67 	public void succeedsWhenSubclassProxyAndScheduledMethodNotPresentOnInterface() throws InterruptedException {
68 		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
69 		ctx.register(Config.class, SubclassProxyTxConfig.class, RepoConfigA.class);
70 		ctx.refresh();
71 
72 		Thread.sleep(10); // allow @Scheduled method to be called several times
73 
74 		MyRepository repository = ctx.getBean(MyRepository.class);
75 		CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
76 		assertThat("repository is not a proxy", AopUtils.isAopProxy(repository), is(true));
77 		assertThat("@Scheduled method never called", repository.getInvocationCount(), greaterThan(0));
78 		assertThat("no transactions were committed", txManager.commits, greaterThan(0));
79 	}
80 
81 	@Test
succeedsWhenJdkProxyAndScheduledMethodIsPresentOnInterface()82 	public void succeedsWhenJdkProxyAndScheduledMethodIsPresentOnInterface() throws InterruptedException {
83 		AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
84 		ctx.register(Config.class, JdkProxyTxConfig.class, RepoConfigB.class);
85 		ctx.refresh();
86 
87 		Thread.sleep(10); // allow @Scheduled method to be called several times
88 
89 		MyRepositoryWithScheduledMethod repository = ctx.getBean(MyRepositoryWithScheduledMethod.class);
90 		CallCountingTransactionManager txManager = ctx.getBean(CallCountingTransactionManager.class);
91 		assertThat("repository is not a proxy", AopUtils.isAopProxy(repository), is(true));
92 		assertThat("@Scheduled method never called", repository.getInvocationCount(), greaterThan(0));
93 		assertThat("no transactions were committed", txManager.commits, greaterThan(0));
94 	}
95 
96 	@Configuration
97 	@EnableTransactionManagement
98 	static class JdkProxyTxConfig { }
99 
100 	@Configuration
101 	@EnableTransactionManagement(proxyTargetClass=true)
102 	static class SubclassProxyTxConfig { }
103 
104 	@Configuration
105 	static class RepoConfigA {
106 		@Bean
repository()107 		public MyRepository repository() {
108 			return new MyRepositoryImpl();
109 		}
110 	}
111 
112 	@Configuration
113 	static class RepoConfigB {
114 		@Bean
repository()115 		public MyRepositoryWithScheduledMethod repository() {
116 			return new MyRepositoryWithScheduledMethodImpl();
117 		}
118 	}
119 
120 	@Configuration
121 	@EnableScheduling
122 	static class Config {
123 
124 		@Bean
peTranslationPostProcessor()125 		public PersistenceExceptionTranslationPostProcessor peTranslationPostProcessor() {
126 			return new PersistenceExceptionTranslationPostProcessor();
127 		}
128 
129 		@Bean
txManager()130 		public PlatformTransactionManager txManager() {
131 			return new CallCountingTransactionManager();
132 		}
133 
134 		@Bean
peTranslator()135 		public PersistenceExceptionTranslator peTranslator() {
136 			PersistenceExceptionTranslator txlator = createMock(PersistenceExceptionTranslator.class);
137 			replay(txlator);
138 			return txlator;
139 		}
140 	}
141 
142 	public interface MyRepository {
getInvocationCount()143 		int getInvocationCount();
144 	}
145 
146 	@Repository
147 	static class MyRepositoryImpl implements MyRepository {
148 
149 		private final AtomicInteger count = new AtomicInteger(0);
150 
151 		@Transactional
152 		@Scheduled(fixedDelay = 5)
scheduled()153 		public void scheduled() {
154 			this.count.incrementAndGet();
155 		}
156 
getInvocationCount()157 		public int getInvocationCount() {
158 			return this.count.get();
159 		}
160 	}
161 
162 	public interface MyRepositoryWithScheduledMethod {
getInvocationCount()163 		int getInvocationCount();
scheduled()164 		public void scheduled();
165 	}
166 
167 	@Repository
168 	static class MyRepositoryWithScheduledMethodImpl implements MyRepositoryWithScheduledMethod {
169 
170 		private final AtomicInteger count = new AtomicInteger(0);
171 
172 		@Transactional
173 		@Scheduled(fixedDelay = 5)
scheduled()174 		public void scheduled() {
175 			this.count.incrementAndGet();
176 		}
177 
getInvocationCount()178 		public int getInvocationCount() {
179 			return this.count.get();
180 		}
181 	}
182 }