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