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
20 package org.apache.myfaces.orchestra.conversation.spring;
21
22 import java.util.HashMap;
23 import java.util.Map;
24
25 import org.aopalliance.aop.Advice;
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.apache.myfaces.orchestra.conversation.Conversation;
29 import org.apache.myfaces.orchestra.conversation.ConversationAware;
30 import org.apache.myfaces.orchestra.conversation.ConversationBindingEvent;
31 import org.apache.myfaces.orchestra.conversation.ConversationBindingListener;
32 import org.apache.myfaces.orchestra.conversation.ConversationContext;
33 import org.apache.myfaces.orchestra.conversation.ConversationFactory;
34 import org.apache.myfaces.orchestra.conversation.ConversationManager;
35 import org.apache.myfaces.orchestra.conversation.CurrentConversationAdvice;
36 import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter;
37 import org.apache.myfaces.orchestra.lib.jsf.PortletOrchestraFacesContextFactory;
38 import org.springframework.aop.Advisor;
39 import org.springframework.aop.SpringProxy;
40 import org.springframework.aop.framework.ProxyFactory;
41 import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
42 import org.springframework.aop.scope.ScopedProxyFactoryBean;
43 import org.springframework.beans.BeansException;
44 import org.springframework.beans.factory.BeanFactory;
45 import org.springframework.beans.factory.BeanFactoryAware;
46 import org.springframework.beans.factory.ObjectFactory;
47 import org.springframework.beans.factory.config.BeanDefinition;
48 import org.springframework.beans.factory.config.BeanPostProcessor;
49 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
50 import org.springframework.beans.factory.config.Scope;
51 import org.springframework.context.ApplicationContext;
52 import org.springframework.context.ApplicationContextAware;
53 import org.springframework.context.ConfigurableApplicationContext;
54
55 /**
56 * Abstract basis class for all the Orchestra scopes.
57 * <p>
58 * A scope object has two quite different roles:
59 * <ol>
60 * <li>It handles the lookup of beans in a scope, and creates them if needed</li>
61 * <li>It handles the creation of Conversation objects, using the spring properties
62 * configured on the scope object.</li>
63 * </ol>
64 * <p>
65 * This base class handles item 1 above, and leaves item 2 to a subclass. The
66 * declaration of interface ConversationFactory needs to be on this class, however,
67 * as the createBean method needs to invoke it.
68 */
69 public abstract class AbstractSpringOrchestraScope implements
70 ConversationFactory, // Orchestra interfaces
71 Scope, BeanFactoryAware, ApplicationContextAware // Spring interfaces
72 {
73 private static final Advice[] NO_ADVICES = new Advice[0];
74 private static final String POST_PROCESSOR_BEAN_NAME =
75 AbstractSpringOrchestraScope.class.getName() + "_BeanPostProcessor";
76
77 private final Log log = LogFactory.getLog(AbstractSpringOrchestraScope.class);
78
79 private ConfigurableApplicationContext applicationContext;
80 private Advice[] advices;
81 private boolean autoProxy = true;
82
83 public AbstractSpringOrchestraScope()
84 {
85 }
86
87 /**
88 * The advices (interceptors) which will be applied to the conversation scoped bean.
89 * These are applied whenever a method is invoked on the bean [1].
90 * <p>
91 * An application's spring configuration uses this method to control what advices are
92 * applied to beans generated from this scope. One commonly applied advice is the
93 * Orchestra persistence interceptor, which ensures that whenever a method on a
94 * conversation-scoped bean is invoked the "global persistence context" is set
95 * to the context for the conversation that bean is in.
96 * <p>
97 * Note [1]: the advices are only applied when the bean is invoked via its proxy. If
98 * invoked via the "this" pointer of the object the interceptors will not run. This
99 * is generally a good thing, as they are not wanted when a method on the bean invokes
100 * another method on the same bean. However it is bad if the bean passes "this" as a
101 * parameter to some other object that makes a callback on it at some later time. In
102 * that case, the bean must take care to pass its proxy to the remote object, not
103 * itself. See method ConversationUtils.getCurrentBean().
104 */
105 public void setAdvices(Advice[] advices)
106 {
107 this.advices = advices;
108 }
109
110 /**
111 * @since 1.1
112 */
113 protected Advice[] getAdvices()
114 {
115 return advices;
116 }
117
118 /**
119 * Configuration for a scope object to control whether "scoped proxy" objects are
120 * automatically wrapped around each conversation bean.
121 * <p>
122 * Automatically applying scope proxies solves a lot of tricky problems with "stale"
123 * beans, and should generally be used. However it does require CGLIB to be present
124 * in the classpath. It also can impact performance in some cases. Where this is a
125 * problem, this flag can turn autoproxying off. Note that the standard spring
126 * aop:scoped-proxy bean can then be used on individual beans to re-enable
127 * proxying for specific beans if desired.
128 * <p>
129 * This defaults to true.
130 *
131 * @since 1.1
132 */
133 public void setAutoProxy(boolean state)
134 {
135 autoProxy = state;
136 }
137
138 /**
139 * Return the conversation context id.
140 * <p>
141 * Note: This conversationId is something spring requires. It has nothing to do with the Orchestra
142 * conversation id.
143 * <p>
144 * TODO: what does Spring use this for????
145 */
146 public String getConversationId()
147 {
148 ConversationManager manager = ConversationManager.getInstance();
149 ConversationContext ctx = manager.getCurrentConversationContext();
150 if (ctx != null)
151 {
152 return Long.toString(ctx.getId(), 10);
153 }
154
155 return null;
156 }
157
158 /**
159 * This is invoked by Spring whenever someone calls getBean(name) on a bean-factory
160 * and the bean-definition for that bean has a scope attribute that maps to an
161 * instance of this class.
162 * <p>
163 * In the normal case, this method returns an internally-created proxy object
164 * that fetches the "real" bean every time a method is invoked on the proxy
165 * (see method getRealBean). This does obviously have some performance impact.
166 * However it is necessary when beans from one conversation are referencing beans
167 * from another conversation as the conversation lifetimes are not the same;
168 * without this proxying there are many cases where "stale" references end up
169 * being used. Most references to conversation-scoped objects are made via EL
170 * expressions, and in this case the performance impact is not significant
171 * relative to the overhead of EL. Note that there is one case where this
172 * proxying is not "transparent" to user code: if a proxied object passes a
173 * "this" pointer to a longer-lived object that retains that pointer then
174 * that reference can be "stale", as it points directly to an instance rather
175 * than to the proxy.
176 * <p>
177 * When the Spring aop:scoped-proxy feature is applied to conversation-scoped
178 * beans, then this functionality is disabled as aop:scoped-proxy has a very
179 * similar effect. Therefore, when this method detects that it has been invoked
180 * by a proxy object generated by aop:scoped-proxy then it returns the real
181 * object (see getRealBean) rather than another proxy. Using aop:scoped-proxy
182 * is somewhat less efficient than Orchestra's customised proxying.
183 * <p>
184 * And when the orchestra proxy needs to access the real object, it won't
185 * call this method; instead, getRealBean is called directly. See class
186 * ScopedBeanTargetSource.
187 */
188 public Object get(String name, ObjectFactory objectFactory)
189 {
190 if (log.isDebugEnabled())
191 {
192 log.debug("Method get called for bean " + name);
193 }
194
195 if (_SpringUtils.isModifiedBeanName(name))
196 {
197 // Backwards compatibility with aop:scoped-proxy tag.
198 //
199 // The user must have included an aop:scoped-proxy within the bean definition,
200 // and here the proxy is firing to try to get the underlying bean. In this
201 // case, return a non-proxied instance of the referenced bean.
202 try
203 {
204 String originalBeanName = _SpringUtils.getOriginalBeanName(name);
205 String conversationName = getConversationNameForBean(name);
206 return getRealBean(conversationName, originalBeanName, objectFactory);
207 }
208 catch(RuntimeException e)
209 {
210 log.error("Exception while accessing bean '" + name + "'");
211 throw e;
212 }
213 }
214 else if (!autoProxy)
215 {
216 String conversationName = getConversationNameForBean(name);
217 return getRealBean(conversationName, name, objectFactory);
218 }
219 else
220 {
221 // A call has been made by the user to the Spring getBean method
222 // (directly, or via an EL expression). Or the bean is being fetched
223 // as part of spring injection into another object.
224 //
225 // In all these cases, just return a proxy.
226 return getProxy(name, objectFactory);
227 }
228 }
229
230 /**
231 * Return a CGLIB-generated proxy class for the beanclass that is
232 * specified by the provided beanName.
233 * <p>
234 * When any method is invoked on this proxy, it invokes method
235 * getRealBean on this same instance in order to locate a proper
236 * instance, then forwards the method call to it.
237 * <p>
238 * There is a separate proxy instance per beandef (shared across all
239 * instances of that bean). This instance is created when first needed,
240 * and cached for reuse.
241 *
242 * @since 1.1
243 */
244 protected Object getProxy(String beanName, ObjectFactory objectFactory)
245 {
246 if (log.isDebugEnabled())
247 {
248 log.debug("getProxy called for bean " + beanName);
249 }
250
251 BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName);
252 String conversationName = getConversationNameForBean(beanName);
253
254 // deal with proxies required for multiple conversations.
255 // This is required to make the viewController scope work where proxies are
256 // required for each conversation a bean has been requested.
257 Map proxies = (Map) beanDefinition.getAttribute(ScopedBeanTargetSource.class.getName());
258 if (proxies == null)
259 {
260 proxies = new HashMap();
261 beanDefinition.setAttribute(ScopedBeanTargetSource.class.getName(), proxies);
262 }
263
264 Object proxy = proxies.get(conversationName);
265 if (proxy == null)
266 {
267 if (log.isDebugEnabled())
268 {
269 log.debug("getProxy: creating proxy for " + beanName);
270 }
271 BeanFactory beanFactory = applicationContext.getBeanFactory();
272 proxy = _SpringUtils.newProxy(this, conversationName, beanName, objectFactory, beanFactory);
273 proxies.put(conversationName, proxy);
274 }
275
276 // Register the proxy in req scope. The first lookup of a variable using an EL expression during a
277 // request will therefore take the "long" path through JSF's VariableResolver and Spring to get here.
278 // But later lookups of this variable in the same request find the proxy directly in the request scope.
279 // The proxy could potentially be placed in the session or app scope, as there is just one instance
280 // for this spring context, and there is normally only one spring context for a webapp. However
281 // using the request scope is almost as efficient and seems safer.
282 //
283 // Note that the framework adapter might not be initialised during the Spring context initialisation
284 // phase (ie instantiation of singletons during startup), so just skip registration in those cases.
285 //
286 // Note also that when a conversation is invalidated, these objects cached in the request are NOT
287 // removed (the conversation management code is not aware that this code hidden deep in the spring
288 // adapters has done this caching). Leaving stale references in the request scope would be a very bad
289 // thing if they were real object references - which is why we do not do this caching when !autoProxy
290 // is set, or when the beandef is marked with the standard spring aop:scoped-proxy [see method
291 // get(String,ObjectFactory)]. However as these proxies always look up their target again, it is safe
292 // to leave them the request; a new bean will still be created if they are dereferenced after the target
293 // conversation is invalidated.
294 FrameworkAdapter fa = FrameworkAdapter.getCurrentInstance();
295 if (fa != null)
296 {
297 // ORCHESTRA-15 -= Leonardo Uribe =-
298 // If it is inside a portlet using JSR-301 bridge do not add it
299 // because there are portlet containers like liferay that
300 // could scan request attribute map to put in xml form. In that
301 // case, FrameworkAdapter will not be found and an exception is
302 // thrown when resolving its proxy.
303 // It is possible we could have this case too with other portlet
304 // bridge implementation (myfaces 1.1 and so) but since from this
305 // point we should not call for jsf specific code, we only can
306 // check if the current request contains the key "javax.portlet.faces.phase"
307 if (fa.containsRequestAttribute(PortletOrchestraFacesContextFactory.PORTLET_LIFECYCLE_PHASE))
308 {
309 fa.setRequestAttribute(beanName, proxy);
310 }
311 }
312
313
314 return proxy;
315 }
316
317 /**
318 * Get a real bean instance (not a scoped-proxy).
319 * <p>
320 * The appropriate Conversation is retrieved; if it does not yet exist then
321 * it is created and started. The conversation name is either specified on the
322 * bean-definition via a custom attribute, or defaults to the bean name.
323 * <p>
324 * Then if the bean already exists in the Conversation it is returned. Otherwise
325 * a new instance is created, stored into the Conversation and returned.
326 * <p>
327 * When a bean is created, a proxy is actually created for it which has one or
328 * more AOP "advices" (ie method interceptors). The CurrentConversationAdvice class
329 * is always attached. Note that if the bean definition contains the aop:proxy
330 * tag (and most do) then the bean that spring creates is already a proxy, ie
331 * what is returned is a proxy of a proxy.
332 *
333 * @param conversationName
334 * @param beanName is the key within the conversation of the bean we are interested in.
335 *
336 * @since 1.1
337 */
338 protected Object getRealBean(String conversationName, String beanName, ObjectFactory objectFactory)
339 {
340 if (log.isDebugEnabled())
341 {
342 log.debug("getRealBean called for bean " + beanName);
343 }
344 ConversationManager manager = ConversationManager.getInstance();
345 Conversation conversation;
346
347 // check if we have a conversation
348 synchronized(manager)
349 {
350 conversation = manager.getConversation(conversationName);
351 if (conversation == null)
352 {
353 // Start the conversation. This eventually results in a
354 // callback to the createConversation method on this class.
355 conversation = manager.startConversation(conversationName, this);
356 }
357 else
358 {
359 // sanity check: verify that two beans with the different scopes
360 // do not declare the same conversationName.
361 assertSameScope(beanName, conversation);
362 }
363 }
364
365 // get the conversation
366 notifyAccessConversation(conversation);
367 synchronized(conversation)
368 {
369 if (!conversation.hasAttribute(beanName))
370 {
371 Object value;
372
373 // Set the magic property that forces all proxies of this bean to be CGLIB proxies.
374 // It doesn't matter if we do this multiple times..
375 BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName);
376 beanDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
377
378 try
379 {
380 // Create the new bean. Note that this will run the
381 // OrchestraAdvisorBeanPostProcessor processor, which
382 // will cause the returned object to actually be a proxy
383 // with the CurrentConversationAdvice (at least) attached to it.
384 value = objectFactory.getObject();
385 }
386 catch(org.springframework.aop.framework.AopConfigException e)
387 {
388 throw new IllegalStateException(
389 "Unable to create Orchestra proxy"
390 + " for bean " + beanName, e);
391 }
392
393 conversation.setAttribute(beanName, value);
394
395 if (value instanceof ConversationAware)
396 {
397 ((ConversationAware) value).setConversation(conversation);
398 }
399 }
400 }
401
402 // get the bean
403 return conversation.getAttribute(beanName);
404 }
405
406 /**
407 * Verify that the specified conversation was created by this scope object.
408 *
409 * @param beanName is just used when generating an error message on failure.
410 * @param conversation is the conversation to validate.
411 */
412 protected void assertSameScope(String beanName, Conversation conversation)
413 {
414 // Check that the conversation's factory is this one.
415 //
416 // This handles the case where two different beans declare themselves
417 // as belonging to the same named conversation but with different scope
418 // objects. Allowing that would be nasty, as the conversation
419 // properties (eg lifetime of access or manual) would depend upon which
420 // bean got created first; some other ConversationFactory would have
421 // created the conversation using its configured properties then
422 // we are now adding to that conversation a bean that really wants
423 // the conversation properties defined on this ConversationFactory.
424 //
425 // Ideally the conversation properties would be defined using
426 // the conversation name, not the scope name; this problem would
427 // then not exist. However that would lead to some fairly clumsy
428 // configuration, particularly where lots of beans without explicit
429 // conversationName attributes are used.
430
431 if (conversation.getFactory() != this)
432 {
433 throw new IllegalArgumentException(
434 "Inconsistent scope and conversation name detected for bean "
435 + beanName);
436 }
437 }
438
439 protected void notifyAccessConversation(Conversation conversation)
440 {
441 }
442
443 /**
444 * Invoked by Spring to notify this object of the BeanFactory it is associated with.
445 * <p>
446 * This method is redundant as we also have setApplicationContext. However as this
447 * method (and the BeanFactoryAware interface on this class) were present in release
448 * 1.0 this needs to be kept for backwards compatibility.
449 */
450 public void setBeanFactory(BeanFactory beanFactory) throws BeansException
451 {
452 }
453
454 /**
455 * Register any BeanPostProcessors that are needed by this scope.
456 * <p>
457 * This is an alternative to requiring users to also add an orchestra BeanPostProcessor element
458 * to their xml configuration file manually.
459 * <p>
460 * When a bean <i>instance</i> is created by Spring, it always runs every single BeanPostProcessor
461 * that has been registered with it.
462 *
463 * @since 1.1
464 */
465 public void defineBeanPostProcessors(ConfigurableListableBeanFactory cbf) throws BeansException
466 {
467 if (!cbf.containsSingleton(POST_PROCESSOR_BEAN_NAME))
468 {
469 BeanPostProcessor processor = new OrchestraAdvisorBeanPostProcessor(applicationContext);
470
471 // Adding the bean to the singletons set causes it to be picked up by the standard
472 // AbstractApplicationContext.RegisterBeanPostProcessors method; that calls
473 // getBeanNamesForType(BeanPostProcessor.class, ...) which finds stuff in the
474 // singleton map even when there is no actual BeanDefinition for it.
475 //
476 // We cannot call cbf.addBeanPostProcessor even if we want to, as the singleton
477 // registration will be added again, making the processor run twice on each bean.
478 // And we need the singleton registration in order to avoid registering this once
479 // for each scope object defined in spring.
480 cbf.registerSingleton(POST_PROCESSOR_BEAN_NAME, processor);
481 }
482 }
483
484 /**
485 * Get the conversation for the given beanName.
486 * Returns null if the conversation does not exist.
487 */
488 protected Conversation getConversationForBean(String beanDefName)
489 {
490 ConversationManager manager = ConversationManager.getInstance();
491 String conversationName = getConversationNameForBean(beanDefName);
492 Conversation conversation = manager.getConversation(conversationName);
493 return conversation;
494 }
495
496 /**
497 * Get the conversation-name for bean instances created using the specified
498 * bean definition.
499 */
500 public String getConversationNameForBean(String beanName)
501 {
502 if (applicationContext == null)
503 {
504 throw new IllegalStateException("Null application context");
505 }
506
507 // Look up the definition with the specified name.
508 BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName);
509
510 if (ScopedProxyFactoryBean.class.getName().equals(beanDefinition.getBeanClassName()))
511 {
512 // Handle unusual case.
513 //
514 // The user bean must have been defined like this:
515 // <bean name="foo" class="example.Foo">
516 // <....>
517 // <aop:scopedProxy/>
518 // </bean>
519 // In this case, Spring's ScopedProxyUtils class creates two BeanDefinition objects, one
520 // with name "foo" that creates a proxy object, and one with name "scopedTarget.foo"
521 // that actually defines the bean of type example.Foo.
522 //
523 // So what we do here is find the renamed bean definition and look there.
524 //
525 // This case does not occur when this method is invoked from within this class; the
526 // spring scope-related callbacks always deal with the beandef that is scoped to
527 // this scope - which is the original (though renamed) beandef.
528 beanName = _SpringUtils.getModifiedBeanName(beanName);
529 beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName); // NON-NLS
530 }
531
532 String convName = getExplicitConversationName(beanDefinition);
533 if (convName == null)
534 {
535 // The beanname might have been of form "scopedTarget.foo" (esp from registerDestructionCallback).
536 // But in this case, the conversation name will just be "foo", so strip the prefix off.
537 //
538 // Note that this does happen quite often for calls from within this class when aop:scoped-proxy
539 // is being used (which is not recommended but is supported).
540 convName = _SpringUtils.getOriginalBeanName(beanName);
541 }
542 return convName;
543 }
544
545 /**
546 * Return the explicit conversation name for this bean definition, or null if there is none.
547 * <p>
548 * This is a separate method so that subclasses can determine conversation names via alternate methods.
549 * In particular, a subclass may want to look for an annotation on the class specified by the definition.
550 *
551 * @since 1.1
552 */
553 protected String getExplicitConversationName(BeanDefinition beanDef)
554 {
555 String attr = (String) beanDef.getAttribute(
556 BeanDefinitionConversationNameAttrDecorator.CONVERSATION_NAME_ATTRIBUTE);
557 return attr;
558 }
559
560 /**
561 * Strip off any Spring namespace (eg scopedTarget).
562 * <p>
563 * This method will simply strip off anything before the first dot.
564 *
565 * @deprecated Should not be necessary in user code.
566 */
567 protected String buildBeanName(String name)
568 {
569 if (name == null)
570 {
571 return null;
572 }
573
574 int pos = name.indexOf('.');
575 if (pos < 0)
576 {
577 return name;
578 }
579
580 return name.substring(pos + 1);
581 }
582
583 public Object remove(String name)
584 {
585 throw new UnsupportedOperationException();
586 }
587
588 /**
589 * Add the given runnable wrapped within an
590 * {@link org.apache.myfaces.orchestra.conversation.ConversationBindingListener} to
591 * the conversation map.
592 * <p>
593 * This ensures it will be called during conversation destroy.
594 * <p>
595 * Spring calls this method whenever a bean in this scope is created, if that bean
596 * has a "destroy method". Note however that it appears that it can also call it even
597 * for beans that do not have a destroy method when there is a "destruction aware"
598 * BeanPostProcessor attached to the context (spring version 2.5 at least).
599 * <p>
600 * When an aop:scoped-proxy has been used inside the bean, then the "new" definition
601 * does not have any scope attribute, so orchestra is not invoked for it. However
602 * the "renamed" bean does, and so this is called.
603 */
604 public void registerDestructionCallback(String name, final Runnable runnable)
605 {
606 if (log.isDebugEnabled())
607 {
608 log.debug("registerDestructionCallback for [" + name + "]");
609 }
610
611 Conversation conversation = getConversationForBean(name);
612 if (conversation == null)
613 {
614 // This should never happen because this should only be called after the bean
615 // instance has been created via scope.getBean, which always creates the
616 // conversation for the bean.
617 throw new IllegalStateException("No conversation for bean [" + name + "]");
618 }
619 if (runnable == null)
620 {
621 throw new IllegalStateException("No runnable object for bean [" + name + "]");
622 }
623
624 // Add an object to the conversation as a bean so that when the conversation is removed
625 // its valueUnbound method will be called. However we never need to retrieve this object
626 // from the context by name, so use a totally unique name as the bean key.
627 conversation.setAttribute(
628 runnable.getClass().getName() + "@" + System.identityHashCode(runnable),
629 new ConversationBindingListener()
630 {
631 public void valueBound(ConversationBindingEvent event)
632 {
633 }
634
635 public void valueUnbound(ConversationBindingEvent event)
636 {
637 runnable.run();
638 }
639 }
640 );
641 }
642
643 /**
644 * Get an ApplicationContext injected by Spring. See ApplicationContextAware interface.
645 */
646 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
647 {
648 if (!(applicationContext instanceof ConfigurableApplicationContext))
649 {
650 throw new IllegalArgumentException("a ConfigurableApplicationContext is required");
651 }
652
653 this.applicationContext = (ConfigurableApplicationContext) applicationContext;
654 defineBeanPostProcessors(this.applicationContext.getBeanFactory());
655 }
656
657 /**
658 * @since 1.2
659 */
660 protected ConfigurableApplicationContext getApplicationContext()
661 {
662 return applicationContext;
663 }
664
665 /**
666 * Return the Advisors that should be applied to beans associated with this scope object.
667 * <p>
668 * Note that logically Advisors are associated with a Conversation. It is an implementation
669 * artifact of the Spring implementation of Orchestra that we use a spring Scope object to
670 * hold the advices; theoretically with other dependency-injection frameworks we could
671 * configure things differently.
672 */
673 Advisor[] getAdvisors(Conversation conversation, String beanName)
674 {
675 Advice[] advices = this.getAdvices();
676 if ((advices == null) || (advices.length == 0))
677 {
678 advices = NO_ADVICES;
679 }
680
681 // wrap every Advice in an Advisor instance that returns it in all cases
682 int len = advices.length + 1;
683 Advisor[] advisors = new Advisor[len];
684
685 // always add the standard CurrentConversationAdvice, and do it FIRST, so it runs first
686 Advice currConvAdvice = new CurrentConversationAdvice(conversation, beanName);
687 advisors[0] = new SimpleAdvisor(currConvAdvice);
688 for(int i=0; i<advices.length; ++i)
689 {
690 advisors[i+1] = new SimpleAdvisor(advices[i]);
691 }
692
693 return advisors;
694 }
695
696 /**
697 * Return a proxy object that "enters" the specified conversation before forwarding the
698 * method call on to the specified instance.
699 * <p>
700 * Entering the conversation means running all the Advices associated with the conversation.
701 * The specified conversation object is assumed to use this Scope object.
702 */
703 Object createProxyFor(Conversation conversation, Object instance)
704 {
705 if (instance instanceof SpringProxy)
706 {
707 // This is already a proxy, so don't wrap it again. Doing this check means that
708 // user code can safely write things like
709 // return ConversationUtils.bindToCurrent(this)
710 // without worrying about whether "this" is a spring bean marked as conversation-scoped
711 // or not. Requiring beans to know about the configuration setup is bad practice.
712 //
713 // Ideally we would check here that this instance is indeed a proxy for the
714 // specified conversation and throw an exception. However that is just a
715 // nice-to-have.
716 return instance;
717 }
718
719 // The currentConversationAdvice constructor requires a beanName parameter. As the
720 // instance we are wrapping here is usually not defined in the dependency-injection
721 // framework configuration at all, we have to invent a dummy name here.
722 //
723 // The beanName affects ConversationUtils methods getCurrentBean and invalidateAndRestartCurrent.
724 // Neither should ever be called by beans artificially wrapped in a proxy like this, so any old
725 // "bean name" will do. Including the class-name of the bean we wrap seems helpful here..
726 String beanName = "dummy$" + instance.getClass().getName();
727
728 ProxyFactory proxyFactory = new ProxyFactory(instance);
729 proxyFactory.setProxyTargetClass(true);
730 Advisor[] advisors = getAdvisors(conversation, beanName);
731 for(int i=0; i<advisors.length; ++i)
732 {
733 proxyFactory.addAdvisor(advisors[i]);
734 }
735
736 proxyFactory.addInterface(SpringProxy.class);
737 return proxyFactory.getProxy(instance.getClass().getClassLoader());
738 }
739 }