    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 }