/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.myfaces.orchestra.conversation.spring;

import java.util.HashMap;
import java.util.Map;

import org.aopalliance.aop.Advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.orchestra.conversation.Conversation;
import org.apache.myfaces.orchestra.conversation.ConversationAware;
import org.apache.myfaces.orchestra.conversation.ConversationBindingEvent;
import org.apache.myfaces.orchestra.conversation.ConversationBindingListener;
import org.apache.myfaces.orchestra.conversation.ConversationContext;
import org.apache.myfaces.orchestra.conversation.ConversationFactory;
import org.apache.myfaces.orchestra.conversation.ConversationManager;
import org.apache.myfaces.orchestra.conversation.CurrentConversationAdvice;
import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter;
import org.apache.myfaces.orchestra.lib.jsf.PortletOrchestraFacesContextFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aop.scope.ScopedProxyFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * Abstract basis class for all the Orchestra scopes.
 * <p>
 * A scope object has two quite different roles:
 * <ol>
 * <li>It handles the lookup of beans in a scope, and creates them if needed</li>
 * <li>It handles the creation of Conversation objects, using the spring properties
 * configured on the scope object.</li>
 * </ol>
 * <p>
 * This base class handles item 1 above, and leaves item 2 to a subclass. The
 * declaration of interface ConversationFactory needs to be on this class, however,
 * as the createBean method needs to invoke it.
 */
public abstract class AbstractSpringOrchestraScope implements
    ConversationFactory, // Orchestra interfaces
    Scope, BeanFactoryAware, ApplicationContextAware // Spring interfaces
{
    private static final Advice[] NO_ADVICES = new Advice[0];
    private static final String POST_PROCESSOR_BEAN_NAME =
        AbstractSpringOrchestraScope.class.getName() + "_BeanPostProcessor";

    private final Log log = LogFactory.getLog(AbstractSpringOrchestraScope.class);

    private ConfigurableApplicationContext applicationContext;
    private Advice[] advices;
    private boolean autoProxy = true;

    public AbstractSpringOrchestraScope()
    {
    }

    /**
     * The advices (interceptors) which will be applied to the conversation scoped bean.
     * These are applied whenever a method is invoked on the bean [1].
     * <p>
     * An application's spring configuration uses this method to control what advices are
     * applied to beans generated from this scope. One commonly applied advice is the
     * Orchestra persistence interceptor, which ensures that whenever a method on a
     * conversation-scoped bean is invoked the "global persistence context" is set
     * to the context for the conversation that bean is in.
     * <p>
     * Note [1]: the advices are only applied when the bean is invoked via its proxy. If
     * invoked via the "this" pointer of the object the interceptors will not run. This
     * is generally a good thing, as they are not wanted when a method on the bean invokes
     * another method on the same bean. However it is bad if the bean passes "this" as a
     * parameter to some other object that makes a callback on it at some later time. In
     * that case, the bean must take care to pass its proxy to the remote object, not
     * itself. See method ConversationUtils.getCurrentBean().
     */
    public void setAdvices(Advice[] advices)
    {
        this.advices = advices;
    }

    /**
     * @since 1.1
     */
    protected Advice[] getAdvices()
    {
        return advices;
    }

    /**
     * Configuration for a scope object to control whether "scoped proxy" objects are
     * automatically wrapped around each conversation bean.
     * <p>
     * Automatically applying scope proxies solves a lot of tricky problems with "stale"
     * beans, and should generally be used. However it does require CGLIB to be present
     * in the classpath. It also can impact performance in some cases. Where this is a
     * problem, this flag can turn autoproxying off. Note that the standard spring
     * aop:scoped-proxy bean can then be used on individual beans to re-enable
     * proxying for specific beans if desired.
     * <p>
     * This defaults to true.
     *
     * @since 1.1
     */
    public void setAutoProxy(boolean state)
    {
        autoProxy = state;
    }

    /**
     * Return the conversation context id.
     * <p>
     * Note: This conversationId is something spring requires. It has nothing to do with the Orchestra
     * conversation id.
     * <p>
     * TODO: what does Spring use this for????
     */
    public String getConversationId()
    {
        ConversationManager manager = ConversationManager.getInstance();
        ConversationContext ctx = manager.getCurrentConversationContext();
        if (ctx != null)
        {
            return Long.toString(ctx.getId(), 10);
        }

        return null;
    }

    /**
     * This is invoked by Spring whenever someone calls getBean(name) on a bean-factory
     * and the bean-definition for that bean has a scope attribute that maps to an
     * instance of this class.
     * <p>
     * In the normal case, this method returns an internally-created proxy object
     * that fetches the "real" bean every time a method is invoked on the proxy
     * (see method getRealBean). This does obviously have some performance impact.
     * However it is necessary when beans from one conversation are referencing beans
     * from another conversation as the conversation lifetimes are not the same;
     * without this proxying there are many cases where "stale" references end up
     * being used. Most references to conversation-scoped objects are made via EL
     * expressions, and in this case the performance impact is not significant
     * relative to the overhead of EL. Note that there is one case where this
     * proxying is not "transparent" to user code: if a proxied object passes a
     * "this" pointer to a longer-lived object that retains that pointer then
     * that reference can be "stale", as it points directly to an instance rather
     * than to the proxy.
     * <p>
     * When the Spring aop:scoped-proxy feature is applied to conversation-scoped
     * beans, then this functionality is disabled as aop:scoped-proxy has a very
     * similar effect. Therefore, when this method detects that it has been invoked
     * by a proxy object generated by aop:scoped-proxy then it returns the real
     * object (see getRealBean) rather than another proxy. Using aop:scoped-proxy
     * is somewhat less efficient than Orchestra's customised proxying.
     * <p>
     * And when the orchestra proxy needs to access the real object, it won't
     * call this method; instead, getRealBean is called directly. See class
     * ScopedBeanTargetSource.
     */
    public Object get(String name, ObjectFactory objectFactory)
    {
        if (log.isDebugEnabled())
        {
            log.debug("Method get called for bean " + name);
        }

        if (_SpringUtils.isModifiedBeanName(name))
        {
            // Backwards compatibility with aop:scoped-proxy tag.
            //
            // The user must have included an aop:scoped-proxy within the bean definition,
            // and here the proxy is firing to try to get the underlying bean. In this
            // case, return a non-proxied instance of the referenced bean.
            try
            {
                String originalBeanName = _SpringUtils.getOriginalBeanName(name);
                String conversationName = getConversationNameForBean(name);
                return getRealBean(conversationName, originalBeanName, objectFactory);
            }
            catch(RuntimeException e)
            {
                log.error("Exception while accessing bean '" + name + "'");
                throw e;
            }
        }
        else if (!autoProxy)
        {
            String conversationName = getConversationNameForBean(name);
            return getRealBean(conversationName, name, objectFactory);
        }
        else
        {
            // A call has been made by the user to the Spring getBean method
            // (directly, or via an EL expression). Or the bean is being fetched
            // as part of spring injection into another object.
            //
            // In all these cases, just return a proxy.
            return getProxy(name, objectFactory);
        }
    }

    /**
     * Return a CGLIB-generated proxy class for the beanclass that is
     * specified by the provided beanName.
     * <p>
     * When any method is invoked on this proxy, it invokes method
     * getRealBean on this same instance in order to locate a proper
     * instance, then forwards the method call to it.
     * <p>
     * There is a separate proxy instance per beandef (shared across all
     * instances of that bean). This instance is created when first needed,
     * and cached for reuse.
     *
     * @since 1.1
     */
    protected Object getProxy(String beanName, ObjectFactory objectFactory)
    {
        if (log.isDebugEnabled())
        {
            log.debug("getProxy called for bean " + beanName);
        }

        BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName);
        String conversationName = getConversationNameForBean(beanName);

        // deal with proxies required for multiple conversations.
        // This is required to make the viewController scope work where proxies are
        // required for each conversation a bean has been requested.
        Map proxies = (Map) beanDefinition.getAttribute(ScopedBeanTargetSource.class.getName());
        if (proxies == null)
        {
            proxies = new HashMap();
            beanDefinition.setAttribute(ScopedBeanTargetSource.class.getName(), proxies);
        }

        Object proxy = proxies.get(conversationName);
        if (proxy == null)
        {
            if (log.isDebugEnabled())
            {
                log.debug("getProxy: creating proxy for " + beanName);
            }
            BeanFactory beanFactory = applicationContext.getBeanFactory();
            proxy = _SpringUtils.newProxy(this, conversationName, beanName, objectFactory, beanFactory);
            proxies.put(conversationName, proxy);
        }

        // Register the proxy in req scope. The first lookup of a variable using an EL expression during a
        // request will therefore take the "long" path through JSF's VariableResolver and Spring to get here.
        // But later lookups of this variable in the same request find the proxy directly in the request scope.
        // The proxy could potentially be placed in the session or app scope, as there is just one instance
        // for this spring context, and there is normally only one spring context for a webapp. However
        // using the request scope is almost as efficient and seems safer.
        //
        // Note that the framework adapter might not be initialised during the Spring context initialisation
        // phase (ie instantiation of singletons during startup), so just skip registration in those cases.
        //
        // Note also that when a conversation is invalidated, these objects cached in the request are NOT
        // removed (the conversation management code is not aware that this code hidden deep in the spring
        // adapters has done this caching). Leaving stale references in the request scope would be a very bad
        // thing if they were real object references - which is why we do not do this caching when !autoProxy
        // is set, or when the beandef is marked with the standard spring aop:scoped-proxy [see method
        // get(String,ObjectFactory)]. However as these proxies always look up their target again, it is safe
        // to leave them the request; a new bean will still be created if they are dereferenced after the target
        // conversation is invalidated.
        FrameworkAdapter fa = FrameworkAdapter.getCurrentInstance();
        if (fa != null)
        {
            // ORCHESTRA-15 -= Leonardo Uribe =- 
            // If it is inside a portlet using JSR-301 bridge do not add it 
            // because there are portlet containers like liferay that 
            // could scan request attribute map to put in xml form. In that
            // case, FrameworkAdapter will not be found and an exception is
            // thrown when resolving its proxy.
            // It is possible we could have this case too with other portlet
            // bridge implementation (myfaces 1.1 and so) but since from this
            // point we should not call for jsf specific code, we only can
            // check if the current request contains the key "javax.portlet.faces.phase"
            if (fa.containsRequestAttribute(PortletOrchestraFacesContextFactory.PORTLET_LIFECYCLE_PHASE))
            {
                fa.setRequestAttribute(beanName, proxy);
            }
        }


        return proxy;
    }

    /**
     * Get a real bean instance (not a scoped-proxy).
     * <p>
     * The appropriate Conversation is retrieved; if it does not yet exist then
     * it is created and started. The conversation name is either specified on the
     * bean-definition via a custom attribute, or defaults to the bean name.
     * <p>
     * Then if the bean already exists in the Conversation it is returned. Otherwise
     * a new instance is created, stored into the Conversation and returned.
     * <p>
     * When a bean is created, a proxy is actually created for it which has one or
     * more AOP "advices" (ie method interceptors). The CurrentConversationAdvice class
     * is always attached. Note that if the bean definition contains the aop:proxy
     * tag (and most do) then the bean that spring creates is already a proxy, ie
     * what is returned is a proxy of a proxy.
     *
     * @param conversationName
     * @param beanName is the key within the conversation of the bean we are interested in.
     *
     * @since 1.1
     */
    protected Object getRealBean(String conversationName, String beanName, ObjectFactory objectFactory)
    {
        if (log.isDebugEnabled())
        {
            log.debug("getRealBean called for bean " + beanName);
        }
        ConversationManager manager = ConversationManager.getInstance();
        Conversation conversation;

        // check if we have a conversation
        synchronized(manager)
        {
            conversation = manager.getConversation(conversationName);
            if (conversation == null)
            {
                // Start the conversation. This eventually results in a
                // callback to the createConversation method on this class.
                conversation = manager.startConversation(conversationName, this);
            }
            else
            {
                // sanity check: verify that two beans with the different scopes
                // do not declare the same conversationName.
                assertSameScope(beanName, conversation);
            }
        }

        // get the conversation
        notifyAccessConversation(conversation);
        synchronized(conversation)
        {
            if (!conversation.hasAttribute(beanName))
            {
                Object value;

                // Set the magic property that forces all proxies of this bean to be CGLIB proxies.
                // It doesn't matter if we do this multiple times..
                BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName);
                beanDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);

                try
                {
                    // Create the new bean. Note that this will run the
                    // OrchestraAdvisorBeanPostProcessor processor, which
                    // will cause the returned object to actually be a proxy
                    // with the CurrentConversationAdvice (at least) attached to it.
                    value = objectFactory.getObject();
                }
                catch(org.springframework.aop.framework.AopConfigException e)
                {
                    throw new IllegalStateException(
                        "Unable to create Orchestra proxy"
                            + " for bean " + beanName, e);
                }

                conversation.setAttribute(beanName, value);

                if (value instanceof ConversationAware)
                {
                    ((ConversationAware) value).setConversation(conversation);
                }
            }
        }

        // get the bean
        return conversation.getAttribute(beanName);
    }

    /**
     * Verify that the specified conversation was created by this scope object.
     *
     * @param beanName is just used when generating an error message on failure.
     * @param conversation is the conversation to validate.
     */
    protected void assertSameScope(String beanName, Conversation conversation)
    {
        // Check that the conversation's factory is this one.
        //
        // This handles the case where two different beans declare themselves
        // as belonging to the same named conversation but with different scope
        // objects. Allowing that would be nasty, as the conversation
        // properties (eg lifetime of access or manual) would depend upon which
        // bean got created first; some other ConversationFactory would have
        // created the conversation using its configured properties then
        // we are now adding to that conversation a bean that really wants
        // the conversation properties defined on this ConversationFactory.
        //
        // Ideally the conversation properties would be defined using
        // the conversation name, not the scope name; this problem would
        // then not exist. However that would lead to some fairly clumsy
        // configuration, particularly where lots of beans without explicit
        // conversationName attributes are used.

        if (conversation.getFactory() != this)
        {
            throw new IllegalArgumentException(
                "Inconsistent scope and conversation name detected for bean "
                    + beanName);
        }
    }

    protected void notifyAccessConversation(Conversation conversation)
    {
    }

    /**
     * Invoked by Spring to notify this object of the BeanFactory it is associated with.
     * <p>
     * This method is redundant as we also have setApplicationContext. However as this
     * method (and the BeanFactoryAware interface on this class) were present in release
     * 1.0 this needs to be kept for backwards compatibility.
     */
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException
    {
    }

    /**
     * Register any BeanPostProcessors that are needed by this scope.
     * <p>
     * This is an alternative to requiring users to also add an orchestra BeanPostProcessor element
     * to their xml configuration file manually.
     * <p>
     * When a bean <i>instance</i> is created by Spring, it always runs every single BeanPostProcessor
     * that has been registered with it.
     *
     * @since 1.1
     */
    public void defineBeanPostProcessors(ConfigurableListableBeanFactory cbf) throws BeansException
    {
        if (!cbf.containsSingleton(POST_PROCESSOR_BEAN_NAME))
        {
            BeanPostProcessor processor = new OrchestraAdvisorBeanPostProcessor(applicationContext);

            // Adding the bean to the singletons set causes it to be picked up by the standard
            // AbstractApplicationContext.RegisterBeanPostProcessors method; that calls
            // getBeanNamesForType(BeanPostProcessor.class, ...) which finds stuff in the
            // singleton map even when there is no actual BeanDefinition for it.
            //
            // We cannot call cbf.addBeanPostProcessor even if we want to, as the singleton
            // registration will be added again, making the processor run twice on each bean.
            // And we need the singleton registration in order to avoid registering this once
            // for each scope object defined in spring.
            cbf.registerSingleton(POST_PROCESSOR_BEAN_NAME, processor);
        }
    }

    /**
     * Get the conversation for the given beanName.
     * Returns null if the conversation does not exist.
     */
    protected Conversation getConversationForBean(String beanDefName)
    {
        ConversationManager manager = ConversationManager.getInstance();
        String conversationName = getConversationNameForBean(beanDefName);
        Conversation conversation = manager.getConversation(conversationName);
        return conversation;
    }

    /**
     * Get the conversation-name for bean instances created using the specified
     * bean definition.
     */
    public String getConversationNameForBean(String beanName)
    {
        if (applicationContext == null)
        {
            throw new IllegalStateException("Null application context");
        }

        // Look up the definition with the specified name.
        BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName);

        if (ScopedProxyFactoryBean.class.getName().equals(beanDefinition.getBeanClassName()))
        {
            // Handle unusual case.
            //
            // The user bean must have been defined like this:
            //  <bean name="foo" class="example.Foo">
            //    <....>
            //    <aop:scopedProxy/>
            //  </bean>
            // In this case, Spring's ScopedProxyUtils class creates two BeanDefinition objects, one
            // with name "foo" that creates a proxy object, and one with name "scopedTarget.foo"
            // that actually defines the bean of type example.Foo.
            //
            // So what we do here is find the renamed bean definition and look there.
            //
            // This case does not occur when this method is invoked from within this class; the
            // spring scope-related callbacks always deal with the beandef that is scoped to
            // this scope - which is the original (though renamed) beandef.
            beanName = _SpringUtils.getModifiedBeanName(beanName);
            beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName); // NON-NLS
        }

        String convName = getExplicitConversationName(beanDefinition);
        if (convName == null)
        {
            // The beanname might have been of form "scopedTarget.foo" (esp from registerDestructionCallback).
            // But in this case, the conversation name will just be "foo", so strip the prefix off.
            //
            // Note that this does happen quite often for calls from within this class when aop:scoped-proxy
            // is being used (which is not recommended but is supported).
            convName = _SpringUtils.getOriginalBeanName(beanName);
        }
        return convName;
    }

    /**
     * Return the explicit conversation name for this bean definition, or null if there is none.
     * <p>
     * This is a separate method so that subclasses can determine conversation names via alternate methods.
     * In particular, a subclass may want to look for an annotation on the class specified by the definition.
     *
     * @since 1.1
     */
    protected String getExplicitConversationName(BeanDefinition beanDef)
    {
        String attr = (String) beanDef.getAttribute(
                BeanDefinitionConversationNameAttrDecorator.CONVERSATION_NAME_ATTRIBUTE);
        return attr;
    }

    /**
     * Strip off any Spring namespace (eg scopedTarget).
     * <p>
     * This method will simply strip off anything before the first dot.
     *
     * @deprecated Should not be necessary in user code.
     */
    protected String buildBeanName(String name)
    {
        if (name == null)
        {
            return null;
        }

        int pos = name.indexOf('.');
        if (pos < 0)
        {
            return name;
        }

        return name.substring(pos + 1);
    }

    public Object remove(String name)
    {
        throw new UnsupportedOperationException();
    }

    /**
     * Add the given runnable wrapped within an
     * {@link org.apache.myfaces.orchestra.conversation.ConversationBindingListener} to
     * the conversation map.
     * <p>
     * This ensures it will be called during conversation destroy.
     * <p>
     * Spring calls this method whenever a bean in this scope is created, if that bean
     * has a "destroy method". Note however that it appears that it can also call it even
     * for beans that do not have a destroy method when there is a "destruction aware"
     * BeanPostProcessor attached to the context (spring version 2.5 at least).
     * <p>
     * When an aop:scoped-proxy has been used inside the bean, then the "new" definition
     * does not have any scope attribute, so orchestra is not invoked for it. However
     * the "renamed" bean does, and so this is called.
     */
    public void registerDestructionCallback(String name, final Runnable runnable)
    {
        if (log.isDebugEnabled())
        {
            log.debug("registerDestructionCallback for [" + name + "]");
        }

        Conversation conversation = getConversationForBean(name);
        if (conversation == null)
        {
            // This should never happen because this should only be called after the bean
            // instance has been created via scope.getBean, which always creates the
            // conversation for the bean.
            throw new IllegalStateException("No conversation for bean [" + name + "]");
        }
        if (runnable == null)
        {
            throw new IllegalStateException("No runnable object for bean [" + name + "]");
        }

        // Add an object to the conversation as a bean so that when the conversation is removed
        // its valueUnbound method will be called. However we never need to retrieve this object
        // from the context by name, so use a totally unique name as the bean key.
        conversation.setAttribute(
            runnable.getClass().getName() + "@" + System.identityHashCode(runnable),
            new ConversationBindingListener()
            {
                public void valueBound(ConversationBindingEvent event)
                {
                }

                public void valueUnbound(ConversationBindingEvent event)
                {
                    runnable.run();
                }
            }
        );
    }

    /**
     * Get an ApplicationContext injected by Spring. See ApplicationContextAware interface.
     */
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
    {
        if (!(applicationContext instanceof ConfigurableApplicationContext))
        {
            throw new IllegalArgumentException("a ConfigurableApplicationContext is required");
        }

        this.applicationContext = (ConfigurableApplicationContext) applicationContext;
        defineBeanPostProcessors(this.applicationContext.getBeanFactory());
    }

    /**
     * @since 1.2
     */
    protected ConfigurableApplicationContext getApplicationContext()
    {
        return applicationContext;
    }

    /**
     * Return the Advisors that should be applied to beans associated with this scope object.
     * <p>
     * Note that logically Advisors are associated with a Conversation. It is an implementation
     * artifact of the Spring implementation of Orchestra that we use a spring Scope object to
     * hold the advices; theoretically with other dependency-injection frameworks we could
     * configure things differently. 
     */
    Advisor[] getAdvisors(Conversation conversation, String beanName)
    {
        Advice[] advices = this.getAdvices();
        if ((advices == null) || (advices.length == 0))
        {
            advices = NO_ADVICES;
        }

        // wrap every Advice in an Advisor instance that returns it in all cases
        int len = advices.length + 1;
        Advisor[] advisors = new Advisor[len];

        // always add the standard CurrentConversationAdvice, and do it FIRST, so it runs first
        Advice currConvAdvice = new CurrentConversationAdvice(conversation, beanName);
        advisors[0] = new SimpleAdvisor(currConvAdvice);
        for(int i=0; i<advices.length; ++i) 
        {
            advisors[i+1] = new SimpleAdvisor(advices[i]);
        }

        return advisors;
    }

    /**
     * Return a proxy object that "enters" the specified conversation before forwarding the
     * method call on to the specified instance.
     * <p>
     * Entering the conversation means running all the Advices associated with the conversation.
     * The specified conversation object is assumed to use this Scope object.
     */
    Object createProxyFor(Conversation conversation, Object instance)
    {
        if (instance instanceof SpringProxy)
        {
            // This is already a proxy, so don't wrap it again. Doing this check means that
            // user code can safely write things like
            //    return ConversationUtils.bindToCurrent(this)
            // without worrying about whether "this" is a spring bean marked as conversation-scoped
            // or not. Requiring beans to know about the configuration setup is bad practice.
            //
            // Ideally we would check here that this instance is indeed a proxy for the
            // specified conversation and throw an exception. However that is just a
            // nice-to-have.
            return instance;
        }
        
        // The currentConversationAdvice constructor requires a beanName parameter. As the
        // instance we are wrapping here is usually not defined in the dependency-injection
        // framework configuration at all, we have to invent a dummy name here.
        //
        // The beanName affects ConversationUtils methods getCurrentBean and invalidateAndRestartCurrent.
        // Neither should ever be called by beans artificially wrapped in a proxy like this, so any old
        // "bean name" will do. Including the class-name of the bean we wrap seems helpful here..
        String beanName = "dummy$" + instance.getClass().getName();

        ProxyFactory proxyFactory = new ProxyFactory(instance);
        proxyFactory.setProxyTargetClass(true);
        Advisor[] advisors = getAdvisors(conversation, beanName);
        for(int i=0; i<advisors.length; ++i)
        {
            proxyFactory.addAdvisor(advisors[i]);
        }

        proxyFactory.addInterface(SpringProxy.class);
        return proxyFactory.getProxy(instance.getClass().getClassLoader());
    }
}
