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 }