1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.myfaces.orchestra.conversation;
20
21 import org.apache.myfaces.orchestra.conversation.basic.LogConversationMessager;
22 import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter;
23 import org.apache.myfaces.orchestra.frameworkAdapter.local.LocalFrameworkAdapter;
24 import org.springframework.aop.scope.ScopedObject;
25 import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
26
27 /**
28 * Test various aspects of the conversation handling, particularly as they interact
29 * with Spring's AOP proxying of various forms.
30 * <p>
31 * Spring can generate aop proxies in two ways: with CGLIB and java.lang.reflect.Proxy.
32 * <p>
33 * The CGLIB implementation can proxy concrete classes, by creating a new class
34 * instance that subclasses the original (but whose methods all simply forward to
35 * the attached advices, which then forward to the real target object). These CGLIB
36 * proxy classes are not final, ie they can themselves be proxied if necessary
37 * (though that is inefficient).
38 * <p>
39 * The java.lang implementation can only proxy interfaces; it creates a concrete
40 * final class that implements those interfaces, where the method implementations
41 * again forward to the attached handlers that then forward to a real target object.
42 * Because these generated classes are final, they cannot be subclassed.
43 * <p>
44 * Spring doesn't really care about proxies; what it cares about is being able to
45 * attach a set of Advice objects to a bean. But in practice, if Advices are to
46 * be attached, then there must be a proxy object to attach them to (unless
47 * load-time-weaving is supported). Note that "introductory advices" can also be
48 * defined, which dynamically add interfaces to the target bean.
49 * <p>
50 * Of course Spring doesn't hard-wire advices to beans. Instead when a bean
51 * instance is created, the set of registered BeanPostProcessors are run. Some
52 * of those may then choose to attach advices; for example Spring's declarative
53 * transaction-handling support uses a proxy. Any post-processor that wants to
54 * create a proxy should really subclass AbstractAutoProxyCreator. This class
55 * handles caching of proxy classes for beans. It also handles the case where
56 * multiple BeanPostProcessors want to proxy the same bean, by merging the
57 * advices together so that one single proxy instance is needed.
58 * <p>
59 * The BeanNameAutoProxyCreator is an interesting BeanPostProcessor that triggers
60 * when creating beans with specific names; it simply looks for all registered beans
61 * of type Advisor, asks each whether they want to apply to a particular bean, and
62 * if so then asks it for an Advice instance to use. Of course the presence of
63 * an Advice then causes the ancestor AbstractAutoProxyCreator to create a proxy
64 * for the target bean.
65 * <p>
66 * When a proxy is created, there is some complex logic to determine whether the
67 * jdk or cglib library is used. If the target bean has no interfaces, then
68 * cglib is of course used. If the bean has been explicitly marked as "use cglib"
69 * then cglib is used. Otherwise java.lang.reflect.Proxy is used.
70 * <p>
71 * Orchestra needs to attach its own advices to orchestra beans, in order to
72 * implement its persistence support.
73 * <p>
74 * There is a standard "scoped proxying" feature that can be applied to a bean by
75 * adding aop:scoped-proxy as an element inside a bean definition. This is somewhat
76 * different from the above. This proxy always subclasses the real original class
77 * of the bean (ie does not include any interfaces added via "introductory advices").
78 * However as a side-effect, the presence of this tag deliberately forces the target
79 * bean to be created with CGLIB (not sure why this is done). A "real" bean can
80 * therefore effectively have two proxies: a "scoping" one, and an "advising" one.
81 * Orchestra generally recommends that aop:scoped-proxy be avoided, and instead
82 * implements its own equivalent. However when an aop:scoped-proxy *is* defined
83 * for an orchestra-scoped bean, then it detects this and skips the creation of
84 * its own equivalent proxy object.
85 * <p>
86 * This test case tries to test that Orchestra works correctly when in the presence
87 * of all these various proxying options.
88 */
89 public class TestScope extends AbstractDependencyInjectionSpringContextTests
90 {
91 protected String[] getConfigLocations()
92 {
93 return new String[]
94 {
95 "classpath:org/apache/myfaces/orchestra/conversation/TestScope.xml"
96 };
97 }
98
99 protected void onSetUp() throws Exception
100 {
101 super.onSetUp();
102 }
103
104 public void testFoo() throws Exception {
105
106 // Set up the FrameworkAdapter. This is needed by any ConversationManager operation.
107 LocalFrameworkAdapter frameworkAdapter = new LocalFrameworkAdapter();
108 frameworkAdapter.setApplicationContext(applicationContext);
109 frameworkAdapter.setConversationMessager(new LogConversationMessager());
110 FrameworkAdapter.setCurrentInstance(frameworkAdapter);
111
112 // Get the object from spring. Orchestra should wrap it in a proxy that implements ScopedObject
113 SimpleBean b1 = (SimpleBean) applicationContext.getBean("unscopedBean");
114 assertNotNull(b1);
115 assertTrue(b1 instanceof ScopedObject);
116
117 // The proxy also checks any return values, and modifies them so that methods on the target
118 // that return the target object actually return the proxy. Tricky! This means that the
119 // "method chaining" pattern can work, eg foo.doX().doY().doZ() and all invocations pass
120 // through the proxy.
121 SimpleBean b1a = b1.getThis();
122 assertTrue(b1 == b1a);
123 assertTrue(b1 == b1.getThisRef());
124
125 // However the proxy cannot completely hide itself. The most obvious way is that the proxy
126 // has fields (because it subclasses SimpleBean) but they are not initialised by the call
127 // to getThis(), because it is the target object that ran that method, not the proxy.
128 assertNull(b1.thisRef);
129 assertNull(b1.thisRefHolder);
130
131 // And it cannot perform its "ref replacement" trick when the ref is nested inside some
132 // more complicated object. Note that this means that when the target object passes its
133 // "this" parameter to another object, it is the raw unproxied this that gets passed.
134 SimpleBean[] refHolder = b1.getThisRefHolder();
135 assertNotNull(refHolder);
136 assertFalse(b1 == refHolder[0]);
137
138 b1.setData("hello, world");
139 String s1 = b1.getData();
140 assertEquals("hello, world", s1);
141
142 assertEquals(1, b1.getConversationAwareCount());
143 }
144
145 public void testCorrectConversation() throws Exception
146 {
147 // Set up the FrameworkAdapter. This is needed by any ConversationManager operation.
148 LocalFrameworkAdapter frameworkAdapter = new LocalFrameworkAdapter();
149 frameworkAdapter.setApplicationContext(applicationContext);
150 frameworkAdapter.setConversationMessager(new LogConversationMessager());
151 FrameworkAdapter.setCurrentInstance(frameworkAdapter);
152
153 final SimpleBean b1 = (SimpleBean) applicationContext.getBean("unscopedBean");
154 assertNotNull(b1);
155
156 // We are not within any method of an orchestra bean, so no current bean exists
157 Object currentBean = ConversationUtils.getCurrentBean();
158 assertNull(currentBean);
159
160 b1.callback(new Runnable() {
161 public void run()
162 {
163 // We are within a method of the orchestra bean b1, so it should be the
164 // current bean.
165 Object currentBean = ConversationUtils.getCurrentBean();
166 assertTrue(b1 == currentBean);
167 }
168 });
169 }
170
171 public void testPlainBean() throws Exception
172 {
173 // Set up the FrameworkAdapter. This is needed by any ConversationManager operation.
174 LocalFrameworkAdapter frameworkAdapter = new LocalFrameworkAdapter();
175 frameworkAdapter.setApplicationContext(applicationContext);
176 frameworkAdapter.setConversationMessager(new LogConversationMessager());
177 FrameworkAdapter.setCurrentInstance(frameworkAdapter);
178
179 // The object is proxied by a JDK proxy; this is the default behaviour. It therefore
180 // has all the interfaces of a SimpleBean, and is backed by a SimpleBean, but cannot
181 // be cast to one.
182 final SimpleInterface b1 = (SimpleInterface) applicationContext.getBean("plainBean");
183 assertNotNull(b1);
184 assertFalse(b1 instanceof SimpleBean);
185 b1.doSomething();
186 }
187 }