View Javadoc

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 }