1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.orchestra.conversation.spring;
21
22 import org.aopalliance.aop.Advice;
23 import org.apache.myfaces.orchestra.conversation.Conversation;
24 import org.apache.myfaces.orchestra.conversation.ConversationAware;
25 import org.apache.myfaces.orchestra.conversation.ConversationBindingEvent;
26 import org.apache.myfaces.orchestra.conversation.ConversationBindingListener;
27 import org.apache.myfaces.orchestra.conversation.ConversationFactory;
28 import org.apache.myfaces.orchestra.conversation.ConversationManager;
29 import org.apache.myfaces.orchestra.conversation.CurrentConversationAdvice;
30 import org.springframework.aop.framework.ProxyFactory;
31 import org.springframework.aop.scope.ScopedProxyFactoryBean;
32 import org.springframework.beans.BeansException;
33 import org.springframework.beans.factory.BeanFactory;
34 import org.springframework.beans.factory.BeanFactoryAware;
35 import org.springframework.beans.factory.ObjectFactory;
36 import org.springframework.beans.factory.config.BeanDefinition;
37 import org.springframework.beans.factory.config.BeanPostProcessor;
38 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
39 import org.springframework.beans.factory.config.Scope;
40 import org.springframework.context.ApplicationContext;
41 import org.springframework.context.ApplicationContextAware;
42 import org.springframework.context.ConfigurableApplicationContext;
43
44 /***
45 * Abstract basis class for all the Orchestra scopes.
46 * <p>
47 * A scope object has two quite different roles:
48 * <ol>
49 * <li>It handles the lookup of beans in a scope, and creates them if needed</li>
50 * <li>It handles the creation of Conversation objects, using the spring properties
51 * configured on the scope object.</li>
52 * </ol>
53 * <p>
54 * This base class handles item 1 above, and leaves item 2 to a subclass. The
55 * declaration of interface ConversationFactory needs to be on this class, however,
56 * as the createBean method needs to invoke it.
57 */
58 public abstract class AbstractSpringOrchestraScope implements ConversationFactory,
59 Scope, BeanFactoryAware, ApplicationContextAware
60 {
61 private ConfigurableApplicationContext applicationContext;
62 private Advice[] advices;
63
64 public AbstractSpringOrchestraScope()
65 {
66 }
67
68 /***
69 * The advices (interceptors) which will be applied to the conversation scoped bean.
70 */
71 public void setAdvices(Advice[] advices)
72 {
73 this.advices = advices;
74 }
75
76 /***
77 * Return the conversation context id.
78 * <p>
79 * Note: This conversationId is something spring requires. It has nothing to do with the Orchestra
80 * conversation id.
81 */
82 public String getConversationId()
83 {
84 ConversationManager manager = ConversationManager.getInstance();
85 if (manager.hasConversationContext())
86 {
87 return Long.toString(manager.getConversationContextId().longValue(), 10);
88 }
89
90 return null;
91 }
92
93 /***
94 * This is invoked by Spring whenever someone calls getBean(name) on a bean-factory
95 * and the bean-definition for that bean has a scope attribute that maps to an
96 * instance of this class.
97 * <p>
98 * First, the appropriate ConversationContext is retrieved.
99 * <p>
100 * Second, the appropriate Conversation is retrieved; if it does not yet exist then
101 * it is created and started. The conversation name is either specified on the
102 * bean-definition via a custom attribute, or defaults to the bean name.
103 * <p>
104 * Then if the bean already exists in the Conversation then it is returned. Otherwise
105 * a new instance is created, stored into the Conversation and returned.
106 * <p>
107 * When a bean is created, a proxy is actually created for it which has one or
108 * more AOP "advices" (ie method interceptors). The CurrentConversationAdvice class
109 * is always attached. Note that if the bean definition contains the aop:proxy
110 * tag (and most do) then the bean that spring creates is already a proxy, ie
111 * what is returned is a proxy of a proxy.
112 */
113 public Object get(String name, ObjectFactory objectFactory)
114 {
115 name = buildBeanName(name);
116
117 return getBean(name, objectFactory);
118 }
119
120 /*** See method get(name, objectFactory). */
121 protected Object getBean(String beanName, ObjectFactory objectFactory)
122 {
123 String conversationName = getConversationNameForBean(beanName);
124
125 ConversationManager manager = ConversationManager.getInstance();
126 Conversation conversation;
127
128
129 synchronized(manager)
130 {
131 conversation = manager.getConversation(conversationName);
132 if (conversation == null)
133 {
134
135
136 conversation = manager.startConversation(conversationName, this);
137 }
138 else
139 {
140 assertSameScope(beanName, conversation);
141 }
142 }
143
144
145 notifyAccessConversation(conversation);
146 synchronized(conversation)
147 {
148 if (!conversation.hasAttribute(beanName))
149 {
150
151 Object value = objectFactory.getObject();
152
153 ProxyFactory factory = new ProxyFactory(value);
154 factory.setProxyTargetClass(true);
155 factory.addAdvice(new CurrentConversationAdvice(conversation, beanName));
156
157 if (advices != null && advices.length > 0)
158 {
159 for (int i = 0; i < advices.length; i++)
160 {
161 factory.addAdvice(advices[i]);
162 }
163 }
164
165 value = factory.getProxy();
166
167 conversation.setAttribute(beanName, value);
168 }
169 }
170
171
172 return conversation.getAttribute(beanName);
173 }
174
175 protected void assertSameScope(String beanName, Conversation conversation)
176 {
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194 if (conversation.getFactory() != this)
195 {
196 throw new IllegalArgumentException(
197 "Inconsistent scope and conversation name detected for bean "
198 + beanName);
199 }
200 }
201
202 protected void notifyAccessConversation(Conversation conversation)
203 {
204 }
205
206 /***
207 * Set the {@link org.apache.myfaces.orchestra.conversation.Conversation} object to the bean if it implements the
208 * {@link org.apache.myfaces.orchestra.conversation.ConversationAware} interface.
209 */
210 public void setBeanFactory(BeanFactory beanFactory) throws BeansException
211 {
212 ((ConfigurableBeanFactory) beanFactory).addBeanPostProcessor(
213 new BeanPostProcessor()
214 {
215 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
216 {
217 if (bean instanceof ConversationAware)
218 {
219 Conversation conversation = getConversationForBean(beanName);
220
221 ((ConversationAware) bean).setConversation(conversation);
222 }
223
224 return bean;
225 }
226
227 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
228 {
229 return bean;
230 }
231 }
232 );
233 }
234
235 /***
236 * Get the conversation for the given beanName.
237 */
238 protected Conversation getConversationForBean(String beanName)
239 {
240 ConversationManager manager = ConversationManager.getInstance();
241 Conversation conversation = manager.getConversation(getConversationNameForBean(beanName));
242 return conversation;
243 }
244
245 /***
246 * Get the conversation name associated with the beanName.
247 */
248 protected String getConversationNameForBean(String beanName)
249 {
250 if (applicationContext != null)
251 {
252 BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName);
253 if (ScopedProxyFactoryBean.class.getName().equals(beanDefinition.getBeanClassName()))
254 {
255
256
257 beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(_SpringUtils.getAlternateBeanName(beanName));
258 }
259 if (beanDefinition.hasAttribute(BeanDefinitionConversationNameAttrDecorator.CONVERSATION_NAME_ATTRIBUTE))
260 {
261 return (String) beanDefinition.getAttribute(BeanDefinitionConversationNameAttrDecorator.CONVERSATION_NAME_ATTRIBUTE);
262 }
263 }
264
265 return beanName;
266 }
267
268 /***
269 * Strip off any Spring namespace (eg scopedTarget).
270 * <p>
271 * This method will simply strip off anything before the first dot.
272 */
273 protected String buildBeanName(String name)
274 {
275 if (name == null)
276 {
277 return null;
278 }
279
280 int pos = name.indexOf('.');
281 if (pos < 0)
282 {
283 return name;
284 }
285
286 return name.substring(pos + 1);
287 }
288
289 public Object remove(String name)
290 {
291 throw new UnsupportedOperationException();
292 }
293
294 /***
295 * Add the given runnable wrapped within an
296 * {@link org.apache.myfaces.orchestra.conversation.ConversationBindingListener} to
297 * the conversation map.
298 * <p>
299 * This ensures it will be called during conversation destroy.
300 */
301 public void registerDestructionCallback(String name, final Runnable runnable)
302 {
303 Conversation conversation = getConversationForBean(name);
304 conversation.setAttribute(
305 runnable.getClass().getName() + "@" + System.identityHashCode(runnable),
306 new ConversationBindingListener()
307 {
308 public void valueBound(ConversationBindingEvent event)
309 {
310 }
311
312 public void valueUnbound(ConversationBindingEvent event)
313 {
314 runnable.run();
315 }
316 }
317 );
318 }
319
320 /***
321 * Get an ApplicationContext injected by Spring. See ApplicationContextAware interface.
322 */
323 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
324 {
325 if (!(applicationContext instanceof ConfigurableApplicationContext))
326 {
327 throw new IllegalArgumentException("a ConfigurableApplicationContext is required");
328 }
329
330 this.applicationContext = (ConfigurableApplicationContext) applicationContext;
331 }
332 }