    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.SpringProxy;
    25  import org.springframework.aop.framework.Advised;
    26  import org.springframework.aop.scope.ScopedObject;
    27  import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
    28
    29  /**
    30   * Test the Conversation.bind method.
    31   * <p>
    32   * Normally a bean is associated with a conversation by defining it in Spring and setting its
    33   * scope to be an appropriate SpringConversationScope object. However that requires the object
    34   * to be fetched from Spring.
    35   * <p>
    36   * However sometimes an instance is created via some other means (eg directly via "new") and we
    37   * want to force a specific conversation to be entered whenever any of its methods is invoked. The
    38   * Conversation.bind(obj) method can be used to attach the object to the specified
    39   * conversation (which is normally the current conversation).
    40   */
    41  public class TestConversationBind extends AbstractDependencyInjectionSpringContextTests
    42  {
    43      protected String[] getConfigLocations()
    44      {
    45          return new String[]
    46              {
    47                  "classpath:org/apache/myfaces/orchestra/conversation/TestScope.xml"
    48              };
    49      }
    50
    51      protected void onSetUp() throws Exception
    52      {
    53          super.onSetUp();
    54      }
    55
    56      public static class Dummy
    57      {
    58          int count;
    59
    60          public Conversation getConversation()
    61          {
    62              return Conversation.getCurrentInstance();
    63          }
    64
    65          public int getCount()
    66          {
    67              return ++count;
    68          }
    69      }
    70
    71      public void testBind() throws Exception
    72      {
    73          // Set up the FrameworkAdapter. This is needed by any ConversationManager operation.
    74          LocalFrameworkAdapter frameworkAdapter = new LocalFrameworkAdapter();
    75          frameworkAdapter.setApplicationContext(applicationContext);
    76          frameworkAdapter.setConversationMessager(new LogConversationMessager());
    77          FrameworkAdapter.setCurrentInstance(frameworkAdapter);
    78
    79          Dummy dummy = new Dummy();
    80          assertFalse(dummy instanceof ScopedObject);
    81          assertFalse(dummy instanceof SpringProxy);
    82          assertNull(dummy.getConversation());
    83
    84          // Get a conversation-scoped object from Spring. What is returned is actually a
    85          // trivial "scope proxy" that simply wraps a ScopedBeanTargetSource.
    86          //
    87          // The "scope-proxy" handles interface ScopedObject itself, but otherwise invokes
    88          // the ScopedBeanTargetSource to get a target object then passes the call on. The
    89          // "target object" is actually another proxy.
    90          //
    91          // The "target-proxy" has advices attached to it that handle interfaces SpringProxy
    92          // and Advised. It also has attached to it all the advices that were specified on the
    93          // associated Orchestra scope object, eg CurrentConversationAdvice and (in this
    94          // test enviroment) MockAdvice.
    95          //
    96          // Note that the underlying real target object isn't created until the proxy is forced
    97          // to fetch its target object.
    98          final SimpleBean b1 = (SimpleBean) applicationContext.getBean("unscopedBean");
    99          assertTrue(b1 instanceof ScopedObject); // added by Spring to the scoped-proxy
    100         assertTrue(b1 instanceof SpringProxy); // added by Spring to the target-proxy
    101         assertTrue(b1 instanceof Advised); // added by Spring to the target-proxy
    102         b1.doSomething(); // force conversation to be created.
    103
    104         // Attach the dummy object to b1's conversation. The way that Conversation.getProxyFor
    105         // is normally used is that b1 will actually be proxying one of its internal members,
    106         // not an object passed in.
    107         Dummy dummyProxy = (Dummy) b1.bindToCurrentConversation(dummy);
    108         assertTrue(dummyProxy instanceof SpringProxy);
    109         assertNotNull(dummyProxy.getConversation());
    110         assertEquals("unscopedBean", dummyProxy.getConversation().getName());
    111
    112         // Proxy it again, this time from "outside" any conversation context.
    113         Conversation conv = ConversationManager.getInstance().getConversation("unscopedBean");
    114         Dummy dummyProxy2 = (Dummy) conv.bind(dummy);
    115         assertTrue(dummyProxy2 instanceof SpringProxy);
    116         assertNotSame(dummyProxy, dummyProxy2);
    117         assertNotNull(dummyProxy2.getConversation());
    118         assertEquals("unscopedBean", dummyProxy2.getConversation().getName());
    119     }
    120
    121     // Ensure that when an arbitrary object is bound to a conversation and the
    122     // conversation is invalidated, then using the proxy throws an IllegalStateException.
    123     public void testBindToInvalidatedConversation() throws Exception
    124     {
    125         // Set up the FrameworkAdapter. This is needed by any ConversationManager operation.
    126         LocalFrameworkAdapter frameworkAdapter = new LocalFrameworkAdapter();
    127         frameworkAdapter.setApplicationContext(applicationContext);
    128         frameworkAdapter.setConversationMessager(new LogConversationMessager());
    129         FrameworkAdapter.setCurrentInstance(frameworkAdapter);
    130
    131         Dummy dummy = new Dummy();
    132
    133         // force a conversation to be created
    134         final SimpleBean b1 = (SimpleBean) applicationContext.getBean("unscopedBean");
    135         b1.doSomething();
    136
    137         Conversation conv = ConversationManager.getInstance().getConversation("unscopedBean");
    138         Dummy dummyProxy = (Dummy) conv.bind(dummy);
    139
    140         // invoke proxy while the conversation is alive: should work
    141         Conversation convA = dummyProxy.getConversation();
    142         assertSame(conv, convA);
    143
    144         // invoke proxy after the conversation is destroyed: should fail
    145         conv.invalidate();
    146         assertNull(ConversationManager.getInstance().getConversation("unscopedBean"));
    147         try
    148         {
    149             dummyProxy.getConversation();
    150             fail("Exception expected but call failed");
    151         }
    152         catch(IllegalStateException e)
    153         {
    154             // ok, we correctly detected the stale conversation
    155         }
    156
    157         // and even if the conversation is recreated, the proxy is still invalid because
    158         // it references the old conversation.
    159         b1.doSomething();
    160         assertNotNull(ConversationManager.getInstance().getConversation("unscopedBean"));
    161         try
    162         {
    163             dummyProxy.getConversation();
    164             fail("Exception expected but call failed");
    165         }
    166         catch(IllegalStateException e)
    167         {
    168             // ok, we correctly detected the stale conversation
    169         }
    170     }
    171 }