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.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 }