1 /*
2  * Copyright 2002-2008 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.aop.framework.autoproxy;
18 
19 import static org.junit.Assert.*;
20 
21 import java.io.IOException;
22 import java.lang.reflect.Method;
23 import java.util.List;
24 
25 import javax.servlet.ServletException;
26 
27 import org.junit.Test;
28 import org.springframework.aop.support.AopUtils;
29 import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
30 import org.springframework.beans.factory.BeanFactory;
31 import org.springframework.beans.factory.InitializingBean;
32 import org.springframework.context.support.ClassPathXmlApplicationContext;
33 import org.springframework.transaction.NoTransactionException;
34 import org.springframework.transaction.TransactionDefinition;
35 import org.springframework.transaction.interceptor.TransactionInterceptor;
36 import org.springframework.transaction.support.AbstractPlatformTransactionManager;
37 import org.springframework.transaction.support.DefaultTransactionStatus;
38 
39 import test.advice.CountingBeforeAdvice;
40 import test.advice.MethodCounter;
41 import test.beans.ITestBean;
42 import test.interceptor.NopInterceptor;
43 
44 /**
45  * Integration tests for auto proxy creation by advisor recognition working in
46  * conjunction with transaction managment resources.
47  *
48  * @see org.springframework.aop.framework.autoproxy.AdvisorAutoProxyCreatorTests;
49  *
50  * @author Rod Johnson
51  * @author Chris Beams
52  */
53 public final class AdvisorAutoProxyCreatorIntegrationTests {
54 
55 	private static final Class<?> CLASS = AdvisorAutoProxyCreatorIntegrationTests.class;
56 	private static final String CLASSNAME = CLASS.getSimpleName();
57 
58 	private static final String DEFAULT_CONTEXT = CLASSNAME + "-context.xml";
59 
60 	private static final String ADVISOR_APC_BEAN_NAME = "aapc";
61 	private static final String TXMANAGER_BEAN_NAME = "txManager";
62 
63 	/**
64 	 * Return a bean factory with attributes and EnterpriseServices configured.
65 	 */
getBeanFactory()66 	protected BeanFactory getBeanFactory() throws IOException {
67 		return new ClassPathXmlApplicationContext(DEFAULT_CONTEXT, CLASS);
68 	}
69 
70 	@Test
testDefaultExclusionPrefix()71 	public void testDefaultExclusionPrefix() throws Exception {
72 		DefaultAdvisorAutoProxyCreator aapc = (DefaultAdvisorAutoProxyCreator) getBeanFactory().getBean(ADVISOR_APC_BEAN_NAME);
73 		assertEquals(ADVISOR_APC_BEAN_NAME + DefaultAdvisorAutoProxyCreator.SEPARATOR, aapc.getAdvisorBeanNamePrefix());
74 		assertFalse(aapc.isUsePrefix());
75 	}
76 
77 	/**
78 	 * If no pointcuts match (no attrs) there should be proxying.
79 	 */
80 	@Test
testNoProxy()81 	public void testNoProxy() throws Exception {
82 		BeanFactory bf = getBeanFactory();
83 		Object o = bf.getBean("noSetters");
84 		assertFalse(AopUtils.isAopProxy(o));
85 	}
86 
87 	@Test
testTxIsProxied()88 	public void testTxIsProxied() throws Exception {
89 		BeanFactory bf = getBeanFactory();
90 		ITestBean test = (ITestBean) bf.getBean("test");
91 		assertTrue(AopUtils.isAopProxy(test));
92 	}
93 
94 	@Test
testRegexpApplied()95 	public void testRegexpApplied() throws Exception {
96 		BeanFactory bf = getBeanFactory();
97 		ITestBean test = (ITestBean) bf.getBean("test");
98 		MethodCounter counter = (MethodCounter) bf.getBean("countingAdvice");
99 		assertEquals(0, counter.getCalls());
100 		test.getName();
101 		assertEquals(1, counter.getCalls());
102 	}
103 
104 	@Test
testTransactionAttributeOnMethod()105 	public void testTransactionAttributeOnMethod() throws Exception {
106 		BeanFactory bf = getBeanFactory();
107 		ITestBean test = (ITestBean) bf.getBean("test");
108 
109 		CallCountingTransactionManager txMan = (CallCountingTransactionManager) bf.getBean(TXMANAGER_BEAN_NAME);
110 		OrderedTxCheckAdvisor txc = (OrderedTxCheckAdvisor) bf.getBean("orderedBeforeTransaction");
111 		assertEquals(0, txc.getCountingBeforeAdvice().getCalls());
112 
113 		assertEquals(0, txMan.commits);
114 		assertEquals("Initial value was correct", 4, test.getAge());
115 		int newAge = 5;
116 		test.setAge(newAge);
117 		assertEquals(1, txc.getCountingBeforeAdvice().getCalls());
118 
119 		assertEquals("New value set correctly", newAge, test.getAge());
120 		assertEquals("Transaction counts match", 1, txMan.commits);
121 	}
122 
123 	/**
124 	 * Should not roll back on servlet exception.
125 	 */
126 	@Test
testRollbackRulesOnMethodCauseRollback()127 	public void testRollbackRulesOnMethodCauseRollback() throws Exception {
128 		BeanFactory bf = getBeanFactory();
129 		Rollback rb = (Rollback) bf.getBean("rollback");
130 
131 		CallCountingTransactionManager txMan = (CallCountingTransactionManager) bf.getBean(TXMANAGER_BEAN_NAME);
132 		OrderedTxCheckAdvisor txc = (OrderedTxCheckAdvisor) bf.getBean("orderedBeforeTransaction");
133 		assertEquals(0, txc.getCountingBeforeAdvice().getCalls());
134 
135 		assertEquals(0, txMan.commits);
136 		rb.echoException(null);
137 		// Fires only on setters
138 		assertEquals(0, txc.getCountingBeforeAdvice().getCalls());
139 		assertEquals("Transaction counts match", 1, txMan.commits);
140 
141 		assertEquals(0, txMan.rollbacks);
142 		Exception ex = new Exception();
143 		try {
144 			rb.echoException(ex);
145 		}
146 		catch (Exception actual) {
147 			assertEquals(ex, actual);
148 		}
149 		assertEquals("Transaction counts match", 1, txMan.rollbacks);
150 	}
151 
152 	@Test
testRollbackRulesOnMethodPreventRollback()153 	public void testRollbackRulesOnMethodPreventRollback() throws Exception {
154 		BeanFactory bf = getBeanFactory();
155 		Rollback rb = (Rollback) bf.getBean("rollback");
156 
157 		CallCountingTransactionManager txMan = (CallCountingTransactionManager) bf.getBean(TXMANAGER_BEAN_NAME);
158 
159 		assertEquals(0, txMan.commits);
160 		// Should NOT roll back on ServletException
161 		try {
162 			rb.echoException(new ServletException());
163 		}
164 		catch (ServletException ex) {
165 
166 		}
167 		assertEquals("Transaction counts match", 1, txMan.commits);
168 	}
169 
170 	@Test
testProgrammaticRollback()171 	public void testProgrammaticRollback() throws Exception {
172 		BeanFactory bf = getBeanFactory();
173 
174 		Object bean = bf.getBean(TXMANAGER_BEAN_NAME);
175 		assertTrue(bean instanceof CallCountingTransactionManager);
176 		CallCountingTransactionManager txMan = (CallCountingTransactionManager) bf.getBean(TXMANAGER_BEAN_NAME);
177 
178 		Rollback rb = (Rollback) bf.getBean("rollback");
179 		assertEquals(0, txMan.commits);
180 		rb.rollbackOnly(false);
181 		assertEquals("Transaction counts match", 1, txMan.commits);
182 		assertEquals(0, txMan.rollbacks);
183 		// Will cause rollback only
184 		rb.rollbackOnly(true);
185 		assertEquals(1, txMan.rollbacks);
186 	}
187 
188 }
189 
190 
191 @SuppressWarnings("serial")
192 class NeverMatchAdvisor extends StaticMethodMatcherPointcutAdvisor {
193 
NeverMatchAdvisor()194 	public NeverMatchAdvisor() {
195 		super(new NopInterceptor());
196 	}
197 
198 	/**
199 	 * This method is solely to allow us to create a mixture of dependencies in
200 	 * the bean definitions. The dependencies don't have any meaning, and don't
201 	 * <b>do</b> anything.
202 	 */
setDependencies(List<?> l)203 	public void setDependencies(List<?> l) {
204 
205 	}
206 
207 	/**
208 	 * @see org.springframework.aop.MethodMatcher#matches(java.lang.reflect.Method, java.lang.Class)
209 	 */
matches(Method m, Class<?> targetClass)210 	public boolean matches(Method m, Class<?> targetClass) {
211 		return false;
212 	}
213 
214 }
215 
216 
217 class NoSetters {
218 
A()219 	public void A() {
220 
221 	}
222 
getB()223 	public int getB() {
224 		return -1;
225 	}
226 
227 }
228 
229 
230 @SuppressWarnings("serial")
231 class OrderedTxCheckAdvisor extends StaticMethodMatcherPointcutAdvisor implements InitializingBean {
232 
233 	/**
234 	 * Should we insist on the presence of a transaction attribute or refuse to accept one?
235 	 */
236 	private boolean requireTransactionContext = false;
237 
238 
setRequireTransactionContext(boolean requireTransactionContext)239 	public void setRequireTransactionContext(boolean requireTransactionContext) {
240 		this.requireTransactionContext = requireTransactionContext;
241 	}
242 
isRequireTransactionContext()243 	public boolean isRequireTransactionContext() {
244 		return requireTransactionContext;
245 	}
246 
247 
getCountingBeforeAdvice()248 	public CountingBeforeAdvice getCountingBeforeAdvice() {
249 		return (CountingBeforeAdvice) getAdvice();
250 	}
251 
afterPropertiesSet()252 	public void afterPropertiesSet() throws Exception {
253 		setAdvice(new TxCountingBeforeAdvice());
254 	}
255 
matches(Method method, Class<?> targetClass)256 	public boolean matches(Method method, Class<?> targetClass) {
257 		return method.getName().startsWith("setAge");
258 	}
259 
260 
261 	private class TxCountingBeforeAdvice extends CountingBeforeAdvice {
262 
before(Method method, Object[] args, Object target)263 		public void before(Method method, Object[] args, Object target) throws Throwable {
264 			// do transaction checks
265 			if (requireTransactionContext) {
266 				TransactionInterceptor.currentTransactionStatus();
267 			}
268 			else {
269 				try {
270 					TransactionInterceptor.currentTransactionStatus();
271 					throw new RuntimeException("Shouldn't have a transaction");
272 				}
273 				catch (NoTransactionException ex) {
274 					// this is Ok
275 				}
276 			}
277 			super.before(method, args, target);
278 		}
279 	}
280 
281 }
282 
283 
284 class Rollback {
285 
286 	/**
287 	 * Inherits transaction attribute.
288 	 * Illustrates programmatic rollback.
289 	 * @param rollbackOnly
290 	 */
rollbackOnly(boolean rollbackOnly)291 	public void rollbackOnly(boolean rollbackOnly) {
292 		if (rollbackOnly) {
293 			setRollbackOnly();
294 		}
295 	}
296 
297 	/**
298 	 * Extracted in a protected method to facilitate testing
299 	 */
setRollbackOnly()300 	protected void setRollbackOnly() {
301 		TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
302 	}
303 
304 	/**
305 	 * @org.springframework.transaction.interceptor.RuleBasedTransaction ( timeout=-1 )
306 	 * @org.springframework.transaction.interceptor.RollbackRule ( "java.lang.Exception" )
307 	 * @org.springframework.transaction.interceptor.NoRollbackRule ( "ServletException" )
308 	 */
echoException(Exception ex)309 	public void echoException(Exception ex) throws Exception {
310 		if (ex != null)
311 			throw ex;
312 	}
313 
314 }
315 
316 
317 @SuppressWarnings("serial")
318 class CallCountingTransactionManager extends AbstractPlatformTransactionManager {
319 
320 	public TransactionDefinition lastDefinition;
321 	public int begun;
322 	public int commits;
323 	public int rollbacks;
324 	public int inflight;
325 
doGetTransaction()326 	protected Object doGetTransaction() {
327 		return new Object();
328 	}
329 
doBegin(Object transaction, TransactionDefinition definition)330 	protected void doBegin(Object transaction, TransactionDefinition definition) {
331 		this.lastDefinition = definition;
332 		++begun;
333 		++inflight;
334 	}
335 
doCommit(DefaultTransactionStatus status)336 	protected void doCommit(DefaultTransactionStatus status) {
337 		++commits;
338 		--inflight;
339 	}
340 
doRollback(DefaultTransactionStatus status)341 	protected void doRollback(DefaultTransactionStatus status) {
342 		++rollbacks;
343 		--inflight;
344 	}
345 
clear()346 	public void clear() {
347 		begun = commits = rollbacks = inflight = 0;
348 	}
349 
350 }
351