1 /*
2  * Copyright 2002-2009 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.context.annotation.jsr330;
18 
19 import java.lang.annotation.ElementType;
20 import java.lang.annotation.Retention;
21 import java.lang.annotation.RetentionPolicy;
22 import java.lang.annotation.Target;
23 import javax.inject.Named;
24 import javax.inject.Singleton;
25 
26 import org.junit.After;
27 import static org.junit.Assert.*;
28 import org.junit.Before;
29 import org.junit.Test;
30 
31 import org.springframework.aop.support.AopUtils;
32 import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
33 import org.springframework.beans.factory.config.BeanDefinition;
34 import org.springframework.context.ApplicationContext;
35 import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
36 import org.springframework.context.annotation.ScopeMetadata;
37 import org.springframework.context.annotation.ScopeMetadataResolver;
38 import org.springframework.context.annotation.ScopedProxyMode;
39 import org.springframework.mock.web.MockHttpServletRequest;
40 import org.springframework.mock.web.MockHttpSession;
41 import org.springframework.web.context.request.RequestContextHolder;
42 import org.springframework.web.context.request.ServletRequestAttributes;
43 import org.springframework.web.context.support.GenericWebApplicationContext;
44 
45 /**
46  * @author Mark Fisher
47  * @author Juergen Hoeller
48  * @author Chris Beams
49  */
50 public class ClassPathBeanDefinitionScannerJsr330ScopeIntegrationTests {
51 
52 	private static final String DEFAULT_NAME = "default";
53 
54 	private static final String MODIFIED_NAME = "modified";
55 
56 	private ServletRequestAttributes oldRequestAttributes;
57 
58 	private ServletRequestAttributes newRequestAttributes;
59 
60 	private ServletRequestAttributes oldRequestAttributesWithSession;
61 
62 	private ServletRequestAttributes newRequestAttributesWithSession;
63 
64 
65 	@Before
setUp()66 	public void setUp() {
67 		this.oldRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest());
68 		this.newRequestAttributes = new ServletRequestAttributes(new MockHttpServletRequest());
69 
70 		MockHttpServletRequest oldRequestWithSession = new MockHttpServletRequest();
71 		oldRequestWithSession.setSession(new MockHttpSession());
72 		this.oldRequestAttributesWithSession = new ServletRequestAttributes(oldRequestWithSession);
73 
74 		MockHttpServletRequest newRequestWithSession = new MockHttpServletRequest();
75 		newRequestWithSession.setSession(new MockHttpSession());
76 		this.newRequestAttributesWithSession = new ServletRequestAttributes(newRequestWithSession);
77 	}
78 
79 	@After
tearDown()80 	public void tearDown() throws Exception {
81 		RequestContextHolder.setRequestAttributes(null);
82 	}
83 
84 
85 	@Test
testPrototype()86 	public void testPrototype() {
87 		ApplicationContext context = createContext(ScopedProxyMode.NO);
88 		ScopedTestBean bean = (ScopedTestBean) context.getBean("prototype");
89 		assertTrue(context.isPrototype("prototype"));
90 		assertFalse(context.isSingleton("prototype"));
91 	}
92 
93 	@Test
testSingletonScopeWithNoProxy()94 	public void testSingletonScopeWithNoProxy() {
95 		RequestContextHolder.setRequestAttributes(oldRequestAttributes);
96 		ApplicationContext context = createContext(ScopedProxyMode.NO);
97 		ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
98 		assertTrue(context.isSingleton("singleton"));
99 		assertFalse(context.isPrototype("singleton"));
100 
101 		// should not be a proxy
102 		assertFalse(AopUtils.isAopProxy(bean));
103 
104 		assertEquals(DEFAULT_NAME, bean.getName());
105 		bean.setName(MODIFIED_NAME);
106 
107 		RequestContextHolder.setRequestAttributes(newRequestAttributes);
108 		// not a proxy so this should not have changed
109 		assertEquals(MODIFIED_NAME, bean.getName());
110 
111 		// singleton bean, so name should be modified even after lookup
112 		ScopedTestBean bean2 = (ScopedTestBean) context.getBean("singleton");
113 		assertEquals(MODIFIED_NAME, bean2.getName());
114 	}
115 
116 	@Test
testSingletonScopeIgnoresProxyInterfaces()117 	public void testSingletonScopeIgnoresProxyInterfaces() {
118 		RequestContextHolder.setRequestAttributes(oldRequestAttributes);
119 		ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
120 		ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
121 
122 		// should not be a proxy
123 		assertFalse(AopUtils.isAopProxy(bean));
124 
125 		assertEquals(DEFAULT_NAME, bean.getName());
126 		bean.setName(MODIFIED_NAME);
127 
128 		RequestContextHolder.setRequestAttributes(newRequestAttributes);
129 		// not a proxy so this should not have changed
130 		assertEquals(MODIFIED_NAME, bean.getName());
131 
132 		// singleton bean, so name should be modified even after lookup
133 		ScopedTestBean bean2 = (ScopedTestBean) context.getBean("singleton");
134 		assertEquals(MODIFIED_NAME, bean2.getName());
135 	}
136 
137 	@Test
testSingletonScopeIgnoresProxyTargetClass()138 	public void testSingletonScopeIgnoresProxyTargetClass() {
139 		RequestContextHolder.setRequestAttributes(oldRequestAttributes);
140 		ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
141 		ScopedTestBean bean = (ScopedTestBean) context.getBean("singleton");
142 
143 		// should not be a proxy
144 		assertFalse(AopUtils.isAopProxy(bean));
145 
146 		assertEquals(DEFAULT_NAME, bean.getName());
147 		bean.setName(MODIFIED_NAME);
148 
149 		RequestContextHolder.setRequestAttributes(newRequestAttributes);
150 		// not a proxy so this should not have changed
151 		assertEquals(MODIFIED_NAME, bean.getName());
152 
153 		// singleton bean, so name should be modified even after lookup
154 		ScopedTestBean bean2 = (ScopedTestBean) context.getBean("singleton");
155 		assertEquals(MODIFIED_NAME, bean2.getName());
156 	}
157 
158 	@Test
testRequestScopeWithNoProxy()159 	public void testRequestScopeWithNoProxy() {
160 		RequestContextHolder.setRequestAttributes(oldRequestAttributes);
161 		ApplicationContext context = createContext(ScopedProxyMode.NO);
162 		ScopedTestBean bean = (ScopedTestBean) context.getBean("request");
163 
164 		// should not be a proxy
165 		assertFalse(AopUtils.isAopProxy(bean));
166 
167 		assertEquals(DEFAULT_NAME, bean.getName());
168 		bean.setName(MODIFIED_NAME);
169 
170 		RequestContextHolder.setRequestAttributes(newRequestAttributes);
171 		// not a proxy so this should not have changed
172 		assertEquals(MODIFIED_NAME, bean.getName());
173 
174 		// but a newly retrieved bean should have the default name
175 		ScopedTestBean bean2 = (ScopedTestBean) context.getBean("request");
176 		assertEquals(DEFAULT_NAME, bean2.getName());
177 	}
178 
179 	@Test
testRequestScopeWithProxiedInterfaces()180 	public void testRequestScopeWithProxiedInterfaces() {
181 		RequestContextHolder.setRequestAttributes(oldRequestAttributes);
182 		ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
183 		IScopedTestBean bean = (IScopedTestBean) context.getBean("request");
184 
185 		// should be dynamic proxy, implementing both interfaces
186 		assertTrue(AopUtils.isJdkDynamicProxy(bean));
187 		assertTrue(bean instanceof AnotherScopeTestInterface);
188 
189 		assertEquals(DEFAULT_NAME, bean.getName());
190 		bean.setName(MODIFIED_NAME);
191 
192 		RequestContextHolder.setRequestAttributes(newRequestAttributes);
193 		// this is a proxy so it should be reset to default
194 		assertEquals(DEFAULT_NAME, bean.getName());
195 
196 		RequestContextHolder.setRequestAttributes(oldRequestAttributes);
197 		assertEquals(MODIFIED_NAME, bean.getName());
198 	}
199 
200 	@Test
testRequestScopeWithProxiedTargetClass()201 	public void testRequestScopeWithProxiedTargetClass() {
202 		RequestContextHolder.setRequestAttributes(oldRequestAttributes);
203 		ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
204 		IScopedTestBean bean = (IScopedTestBean) context.getBean("request");
205 
206 		// should be a class-based proxy
207 		assertTrue(AopUtils.isCglibProxy(bean));
208 		assertTrue(bean instanceof RequestScopedTestBean);
209 
210 		assertEquals(DEFAULT_NAME, bean.getName());
211 		bean.setName(MODIFIED_NAME);
212 
213 		RequestContextHolder.setRequestAttributes(newRequestAttributes);
214 		// this is a proxy so it should be reset to default
215 		assertEquals(DEFAULT_NAME, bean.getName());
216 
217 		RequestContextHolder.setRequestAttributes(oldRequestAttributes);
218 		assertEquals(MODIFIED_NAME, bean.getName());
219 	}
220 
221 	@Test
testSessionScopeWithNoProxy()222 	public void testSessionScopeWithNoProxy() {
223 		RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
224 		ApplicationContext context = createContext(ScopedProxyMode.NO);
225 		ScopedTestBean bean = (ScopedTestBean) context.getBean("session");
226 
227 		// should not be a proxy
228 		assertFalse(AopUtils.isAopProxy(bean));
229 
230 		assertEquals(DEFAULT_NAME, bean.getName());
231 		bean.setName(MODIFIED_NAME);
232 
233 		RequestContextHolder.setRequestAttributes(newRequestAttributesWithSession);
234 		// not a proxy so this should not have changed
235 		assertEquals(MODIFIED_NAME, bean.getName());
236 
237 		// but a newly retrieved bean should have the default name
238 		ScopedTestBean bean2 = (ScopedTestBean) context.getBean("session");
239 		assertEquals(DEFAULT_NAME, bean2.getName());
240 	}
241 
242 	@Test
testSessionScopeWithProxiedInterfaces()243 	public void testSessionScopeWithProxiedInterfaces() {
244 		RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
245 		ApplicationContext context = createContext(ScopedProxyMode.INTERFACES);
246 		IScopedTestBean bean = (IScopedTestBean) context.getBean("session");
247 
248 		// should be dynamic proxy, implementing both interfaces
249 		assertTrue(AopUtils.isJdkDynamicProxy(bean));
250 		assertTrue(bean instanceof AnotherScopeTestInterface);
251 
252 		assertEquals(DEFAULT_NAME, bean.getName());
253 		bean.setName(MODIFIED_NAME);
254 
255 		RequestContextHolder.setRequestAttributes(newRequestAttributesWithSession);
256 		// this is a proxy so it should be reset to default
257 		assertEquals(DEFAULT_NAME, bean.getName());
258 		bean.setName(MODIFIED_NAME);
259 
260 		IScopedTestBean bean2 = (IScopedTestBean) context.getBean("session");
261 		assertEquals(MODIFIED_NAME, bean2.getName());
262 		bean2.setName(DEFAULT_NAME);
263 		assertEquals(DEFAULT_NAME, bean.getName());
264 
265 		RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
266 		assertEquals(MODIFIED_NAME, bean.getName());
267 	}
268 
269 	@Test
testSessionScopeWithProxiedTargetClass()270 	public void testSessionScopeWithProxiedTargetClass() {
271 		RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
272 		ApplicationContext context = createContext(ScopedProxyMode.TARGET_CLASS);
273 		IScopedTestBean bean = (IScopedTestBean) context.getBean("session");
274 
275 		// should be a class-based proxy
276 		assertTrue(AopUtils.isCglibProxy(bean));
277 		assertTrue(bean instanceof ScopedTestBean);
278 		assertTrue(bean instanceof SessionScopedTestBean);
279 
280 		assertEquals(DEFAULT_NAME, bean.getName());
281 		bean.setName(MODIFIED_NAME);
282 
283 		RequestContextHolder.setRequestAttributes(newRequestAttributesWithSession);
284 		// this is a proxy so it should be reset to default
285 		assertEquals(DEFAULT_NAME, bean.getName());
286 		bean.setName(MODIFIED_NAME);
287 
288 		IScopedTestBean bean2 = (IScopedTestBean) context.getBean("session");
289 		assertEquals(MODIFIED_NAME, bean2.getName());
290 		bean2.setName(DEFAULT_NAME);
291 		assertEquals(DEFAULT_NAME, bean.getName());
292 
293 		RequestContextHolder.setRequestAttributes(oldRequestAttributesWithSession);
294 		assertEquals(MODIFIED_NAME, bean.getName());
295 	}
296 
297 
createContext(final ScopedProxyMode scopedProxyMode)298 	private ApplicationContext createContext(final ScopedProxyMode scopedProxyMode) {
299 		GenericWebApplicationContext context = new GenericWebApplicationContext();
300 		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
301 		scanner.setIncludeAnnotationConfig(false);
302 		scanner.setScopeMetadataResolver(new ScopeMetadataResolver() {
303 			public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
304 				ScopeMetadata metadata = new ScopeMetadata();
305 				if (definition instanceof AnnotatedBeanDefinition) {
306 					AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
307 					for (String type : annDef.getMetadata().getAnnotationTypes()) {
308 						if (type.equals(javax.inject.Singleton.class.getName())) {
309 							metadata.setScopeName(BeanDefinition.SCOPE_SINGLETON);
310 							break;
311 						}
312 						else if (annDef.getMetadata().getMetaAnnotationTypes(type).contains(javax.inject.Scope.class.getName())) {
313 							metadata.setScopeName(type.substring(type.length() - 13, type.length() - 6).toLowerCase());
314 							metadata.setScopedProxyMode(scopedProxyMode);
315 							break;
316 						}
317 						else if (type.startsWith("javax.inject")) {
318 							metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE);
319 						}
320 					}
321 				}
322 				return metadata;
323 			}
324 		});
325 
326 		// Scan twice in order to find errors in the bean definition compatibility check.
327 		scanner.scan(getClass().getPackage().getName());
328 		scanner.scan(getClass().getPackage().getName());
329 
330 		context.registerAlias("classPathBeanDefinitionScannerJsr330ScopeIntegrationTests.SessionScopedTestBean", "session");
331 		context.refresh();
332 		return context;
333 	}
334 
335 
336  	public static interface IScopedTestBean {
337 
getName()338  		String getName();
339 
setName(String name)340  		void setName(String name);
341  	}
342 
343 
344 	public static abstract class ScopedTestBean implements IScopedTestBean {
345 
346 		private String name = DEFAULT_NAME;
347 
getName()348 		public String getName() { return this.name; }
349 
setName(String name)350 		public void setName(String name) { this.name = name; }
351 	}
352 
353 
354 	@Named("prototype")
355 	public static class PrototypeScopedTestBean extends ScopedTestBean {
356 	}
357 
358 
359 	@Named("singleton")
360 	@Singleton
361 	public static class SingletonScopedTestBean extends ScopedTestBean {
362 	}
363 
364 
365 	public static interface AnotherScopeTestInterface {
366 	}
367 
368 
369 	@Named("request")
370 	@RequestScoped
371 	public static class RequestScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface {
372 	}
373 
374 
375 	@Named
376 	@SessionScoped
377 	public static class SessionScopedTestBean extends ScopedTestBean implements AnotherScopeTestInterface {
378 	}
379 
380 
381 	@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
382 	@Retention(RetentionPolicy.RUNTIME)
383 	@javax.inject.Scope
384 	public static @interface RequestScoped {
385 	}
386 
387 
388 	@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
389 	@Retention(RetentionPolicy.RUNTIME)
390 	@javax.inject.Scope
391 	public static @interface SessionScoped {
392 	}
393 
394 }
395