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