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 }