You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by sk...@apache.org on 2007/11/30 13:14:54 UTC

svn commit: r599792 - in /myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring: AbstractSpringOrchestraScope.java ScopedBeanTargetSource.java _SpringUtils.java

Author: skitching
Date: Fri Nov 30 04:14:32 2007
New Revision: 599792

URL: http://svn.apache.org/viewvc?rev=599792&view=rev
Log:
Implement ORCHESTRA-10 - autocreate scope-proxy for conversation-scoped beans.
Note that there is a binary-incompatible change in AbstractSpringOrchestraScope; protected method getBean has been removed.

Added:
    myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/ScopedBeanTargetSource.java
Modified:
    myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/AbstractSpringOrchestraScope.java
    myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/_SpringUtils.java

Modified: myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/AbstractSpringOrchestraScope.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/AbstractSpringOrchestraScope.java?rev=599792&r1=599791&r2=599792&view=diff
==============================================================================
--- myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/AbstractSpringOrchestraScope.java (original)
+++ myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/AbstractSpringOrchestraScope.java Fri Nov 30 04:14:32 2007
@@ -29,6 +29,7 @@
 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.springframework.aop.framework.ProxyFactory;
 import org.springframework.aop.scope.ScopedProxyFactoryBean;
 import org.springframework.beans.BeansException;
@@ -62,8 +63,10 @@
 {
 	private final Log log = LogFactory.getLog(AbstractSpringOrchestraScope.class);
 
+	private BeanFactory beanFactory;
 	private ConfigurableApplicationContext applicationContext;
 	private Advice[] advices;
+	private boolean autoProxy = true;
 
 	public AbstractSpringOrchestraScope()
 	{
@@ -71,6 +74,21 @@
 
 	/**
 	 * 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)
 	{
@@ -78,10 +96,30 @@
 	}
 
 	/**
+	 * 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.
+	 */
+	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()
 	{
@@ -99,13 +137,158 @@
 	 * and the bean-definition for that bean has a scope attribute that maps to an
 	 * instance of this class.
 	 * <p>
-	 * First, the appropriate ConversationContext is retrieved.
+	 * 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>
-	 * Second, the appropriate Conversation is retrieved; if it does not yet exist then
+	 * 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.
+	 */
+	protected Object getProxy(String beanName, ObjectFactory objectFactory)
+	{
+		// TODO: consider proxy scope
+		//
+		// Q: can proxies be added to the session or even app scope? It would seem
+		// so, as they always do a lookup each time. In that case, bean lookup
+		// to find the proxy will be much faster, and the lookup from the proxy will
+		// also be faster as it knows the Scope and the ConversationName. The only
+		// drawback is that session.containsKey will always be true even when the
+		// bean does not currently exist. And that app-variables that "shadow" the
+		// real bean will not work. But that's pretty ugly anyway.
+		//
+		// And if is stored in app scope, then there is no need to cache it inside the
+		// beandef..
+		//
+		// Or should the proxy then be stored in the spring "singleton" scope? Odd, as
+		// we have no bean definition for it...
+		//
+		// Hmm..maybe that is the reason for the ugly scoping "rename" hack - so that
+		// the proxy can have singleton scope even though the bean it references
+		// does not.
+		//
+	
+		// Check for a cached proxy under a key that is the name of ScopedBeanTargetSource.class.
+		// The "proxy class" cannot be used as a key, because it really has no class of its own :-)
+		
+		if (log.isDebugEnabled())
+		{
+			log.debug("getProxy called for bean " + beanName);
+		}
+
+		BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName);
+		Object proxy = beanDefinition.getAttribute(ScopedBeanTargetSource.class.getName());
+		if (proxy == null)
+		{
+			if (log.isDebugEnabled()) 
+			{
+				log.debug("getProxy: creating proxy for " + beanName);
+			}
+			String conversationName = (String) beanDefinition.getAttribute(BeanDefinitionConversationNameAttrDecorator.CONVERSATION_NAME_ATTRIBUTE);
+			if (conversationName == null)
+				conversationName = beanName;
+			proxy = _SpringUtils.newProxy(this, conversationName, beanName, objectFactory, beanFactory);
+			beanDefinition.setAttribute(ScopedBeanTargetSource.class.getName(), 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.
+		FrameworkAdapter fa = FrameworkAdapter.getCurrentInstance();
+		if (fa != null)
+		{
+			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 then it is returned. Otherwise
+	 * 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
@@ -113,19 +296,16 @@
 	 * 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.
 	 */
-	public Object get(String name, ObjectFactory objectFactory)
+	protected Object getRealBean(String conversationName, String beanName, ObjectFactory objectFactory)
 	{
-		name = buildBeanName(name);
-
-		return getBean(name, objectFactory);
-	}
-
-	/** See method get(name, objectFactory). */
-	protected Object getBean(String beanName, ObjectFactory objectFactory)
-	{
-		String conversationName = getConversationNameForBean(beanName);
-
+		if (log.isDebugEnabled())
+		{
+			log.debug("getRealBean called for bean " + beanName);
+		}
 		ConversationManager manager = ConversationManager.getInstance();
 		Conversation conversation;
 
@@ -136,11 +316,13 @@
 			if (conversation == null)
 			{
 				// Start the conversation. This eventually results in a 
-				// callback to the createConversation method on this class.
+				// callback to the createConversation method on t, beanFactoryhis 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);
             }
 		}
@@ -176,12 +358,18 @@
 		return conversation.getAttribute(beanName);
 	}
 
-    protected void assertSameScope(String beanName, Conversation conversation)
+	/**
+	 * 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 conversation but with different scope
+        // as belonging to the same named conversation but with different scope
         // objects. Allowing that would be nasty as the conversation
         // properties (eg flash or manual) would depend upon which bean
         // got created first; some other ConversationFactory would have
@@ -208,8 +396,24 @@
 	}
 
 	/**
-	 * Set the {@link org.apache.myfaces.orchestra.conversation.Conversation} object to the bean if it implements the
-	 * {@link org.apache.myfaces.orchestra.conversation.ConversationAware} interface.
+	 * When this scope object's declaration is first processed by Spring, take the opportunity to
+	 * register a global BeanPostProcessor.
+	 * <p>
+	 * When a bean <i>instance</i> is created by Spring, it always runs every single BeanPostProcessor
+	 * that has been registered with it.
+	 * <p>
+	 * The BeanPostProcessor implementation sets the {@link org.apache.myfaces.orchestra.conversation.Conversation}
+	 * object on the bean if it implements the {@link org.apache.myfaces.orchestra.conversation.ConversationAware}
+	 * interface.
+	 * <p>
+	 * Note: when an aop:scoped-proxy exists for the bean definition, then the scoped-proxy object is a
+	 * singleton. So the BeanPostProcessor will run once for it (but not do anything). Any time user code
+	 * references that (singleton) bean, an instance of the real bean will be fetched, which might trigger
+	 * an instance creation. If it does, then the BeanPostProcessor also runs for that instance; in this
+	 * case the beanName parameter passed to the methods will be the "scopedTarget" beanname as that is
+	 * the object that the ScopedProxyFactoryBean has asked Spring to fetch.
+	 * <p>
+	 * For Orchestra's "internal" scoped proxying, 
 	 */
 	public void setBeanFactory(BeanFactory beanFactory) throws BeansException
 	{
@@ -234,74 +438,97 @@
 				}
 			}
 		);
+		
+		this.beanFactory = beanFactory;
+	}
+
+	/**
+	 * Return the BeanFactory that this scope object exists within.
+	 */
+	protected BeanFactory getBeanFactory()
+	{
+		return beanFactory;
 	}
 
 	/**
 	 * Get the conversation for the given beanName.
 	 * Returns null if the conversation does not exist.
 	 */
-	protected Conversation getConversationForBean(String beanName)
+	protected Conversation getConversationForBean(String beanDefName)
 	{
 		ConversationManager manager = ConversationManager.getInstance();
-		String conversationName = getConversationNameForBean(beanName);
+		String conversationName = getConversationNameForBean(beanDefName);
 		Conversation conversation = manager.getConversation(conversationName);
 		return conversation;
 	}
 
 	/**
-	 * Get the conversation name associated with the beanName.
+	 * Get the conversation-name for bean instances created using the specified
+	 * bean definition.
 	 */
 	protected String getConversationNameForBean(String beanName)
 	{
 		if (applicationContext == null)
 		{
-			// TODO: when can this happen? A scope method is being invoked but no context exists??
-			return beanName;
+			throw new IllegalStateException("Null application context");
 		}
 		
-		// First, look up the definition with the specified name. 
+		// Look up the definition with the specified name. 
 		BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(beanName);
 
-		// When a definition is present in the config file like this:
-		//  <bean name="foo" class="example.Foo">
-		//    <....>
-		//    <aop:scopedProxy/>
-		//  </bean>
-		// then what Spring actually does is create 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 look up the definition by the provided name, then if that
-		// def is creating a proxy then look up the "renamed" definition.
 		if (ScopedProxyFactoryBean.class.getName().equals(beanDefinition.getBeanClassName()))
 		{
-			// bad hack to get access to the real definition
-			// TODO: is there a better way to do this?
-			
-			// Note that we deliberately change the value that will be returned, so that if there is no
-			// conversation name attribute then we return the 
-			beanName = _SpringUtils.getAlternateBeanName(beanName);
+			// 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
 		}
 
-		if (beanDefinition.hasAttribute(BeanDefinitionConversationNameAttrDecorator.CONVERSATION_NAME_ATTRIBUTE))
-		{
-			// The bean def explicitly included a conversation-name, so return that.
-			return (String) beanDefinition.getAttribute(BeanDefinitionConversationNameAttrDecorator.CONVERSATION_NAME_ATTRIBUTE);
-		}
-		else
+		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.
-			String convName = _SpringUtils.getRealBeanName(beanName);
-			return convName;
+			//
+			// 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.
+	 */
+	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)
 	{
@@ -335,6 +562,10 @@
 	 * 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)
 	{
@@ -356,6 +587,9 @@
 			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()

Added: myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/ScopedBeanTargetSource.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/ScopedBeanTargetSource.java?rev=599792&view=auto
==============================================================================
--- myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/ScopedBeanTargetSource.java (added)
+++ myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/ScopedBeanTargetSource.java Fri Nov 30 04:14:32 2007
@@ -0,0 +1,65 @@
+/*
+ * 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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.aop.target.AbstractBeanFactoryBasedTargetSource;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.ObjectFactory;
+
+/**
+ * Used with a "scoping proxy" object as generated by _SpringUtils.newProxy.
+ * <p>
+ * When user code invokes any method on the proxy, it invokes getTarget on its
+ * source object to get the "real" object, then invokes the same method on the
+ * returned object. 
+ * <p>
+ * Here the getTarget method is implemented by using an AbstractSpringOrchestraScope
+ * object to look up the ConversationContext for the user, then a particular
+ * Conversation instance (by name), then a bean within that Conversation.
+ */
+public class ScopedBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
+	private AbstractSpringOrchestraScope scope;
+	private String conversationName;
+	private String targetBeanName;
+	private ObjectFactory objectFactory;
+
+	public ScopedBeanTargetSource(
+			AbstractSpringOrchestraScope scope, 
+			String conversationName,
+			String targetBeanName,
+			ObjectFactory objectFactory,
+			BeanFactory beanFactory) {
+
+		this.scope = scope;
+		this.conversationName = conversationName;
+		this.targetBeanName = targetBeanName;
+		this.objectFactory = objectFactory;
+		
+		super.setTargetBeanName(targetBeanName);
+		super.setBeanFactory(beanFactory);
+	}
+
+	public Object getTarget() throws Exception {
+		final Log log = LogFactory.getLog(ScopedBeanTargetSource.class);
+		log.debug("getRealBean for " + targetBeanName);
+		return scope.getRealBean(conversationName, targetBeanName, objectFactory);
+	}
+}

Modified: myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/_SpringUtils.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/_SpringUtils.java?rev=599792&r1=599791&r2=599792&view=diff
==============================================================================
--- myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/_SpringUtils.java (original)
+++ myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/spring/_SpringUtils.java Fri Nov 30 04:14:32 2007
@@ -18,26 +18,63 @@
  */
 package org.apache.myfaces.orchestra.conversation.spring;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.aop.framework.AopInfrastructureBean;
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.aop.scope.DefaultScopedObject;
+import org.springframework.aop.scope.ScopedObject;
+import org.springframework.aop.support.DelegatingIntroductionInterceptor;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+
 /**
  * <p>Various Spring utilities used by the conversation framework</p>
  * <p>Notice: this class is not meant to be used outside of this library</p>
  */
 public final class _SpringUtils
 {
+	// Equal to ScopedProxyUtils.TARGET_NAME_PREFIX, but that is private.
 	private final static String SCOPED_TARGET_PREFIX = "scopedTarget.";
 
 	private _SpringUtils()
 	{
 	}
 
-	public static boolean isAlternateBeanName(String beanName)
+	/**
+	 * Detect the case where a class includes a nested aop:scoped-target tag.
+	 * <p>
+     * When a definition is present in the config file like this:
+     * <pre>
+     *  &lt;bean name="foo" class="example.Foo" orchestra:foo="foo"&gt;
+	 *   &lt;....&gt;
+	 *   &lt;aop:scopedProxy/&gt;
+	 *  &lt;/bean&gt;
+	 * </pre> 
+	 * then what Spring actually does is create 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 (see Spring class ScopedProxyUtils
+	 * for details.
+	 * <p>
+	 * Using this pattern is very common with Orchestra, so we need to detect it and
+	 * look for orchestra settings on the renamed bean definition rather than the
+	 * one with the original name. 
+	 */
+	public static boolean isModifiedBeanName(String beanName)
 	{
 		return beanName.startsWith(SCOPED_TARGET_PREFIX);
 	}
 
-	public static String getRealBeanName(String beanName)
+	/**
+	 * Given the name of a BeanDefinition, if this is a fake name created
+	 * by spring because an aop:scoped-proxy is now wrapping this object,
+	 * then return the name of that scoped-proxy bean (ie the name that the
+	 * user accesses this bean by).
+	 */
+	public static String getOriginalBeanName(String beanName)
 	{
-		if (beanName != null && isAlternateBeanName(beanName))
+		if (beanName != null && isModifiedBeanName(beanName))
 		{
 			return beanName.substring(SCOPED_TARGET_PREFIX.length());
 		}
@@ -45,9 +82,14 @@
 		return beanName;
 	}
 
-	public static String getAlternateBeanName(String beanName)
+	/**
+	 * Given a bean name "foo", if it refers to a scoped proxy then the bean
+	 * definition that holds the original settings is now under another name,
+	 * so return that other name so the original BeanDefinition can be found.
+	 */
+	public static String getModifiedBeanName(String beanName)
 	{
-		if (beanName != null && !isAlternateBeanName(beanName))
+		if (beanName != null && !isModifiedBeanName(beanName))
 		{
 			return SCOPED_TARGET_PREFIX + beanName;
 		}
@@ -55,26 +97,73 @@
 		return beanName;
 	}
 
-	/*
-	public static Set getConversationManagedScopeNames()
+	/** @deprecated use isModifiedBeanName instead. */
+	public static boolean isAlternateBeanName(String beanName) 
+	{
+		return isModifiedBeanName(beanName);
+	}
+
+	/** @deprecated use getModifiedBeanName instead. */
+	public static String getAlternateBeanName(String beanName) 
+	{
+		return getModifiedBeanName(beanName);
+	}
+
+	/** @deprecated use getOriginalBeanName instead. */
+	public static String getRealBeanName(String beanName) 
 	{
-		ConfigurableApplicationContext applicationContext = FrameworkAdapter.getInstance().getApplicationContext();
-		ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
+		return getOriginalBeanName(beanName);
+	}
+
+	/**
+	 * Create an object that subclasses the concrete class of the BeanDefinition
+	 * for the specified targetBeanName, and when invoked delegates to an instance
+	 * of that type fetched from a scope object.
+	 * <p>
+	 * The proxy returned currently also currently implements the standard Spring
+	 * ScopedObject interface; this is experimental and may be removed if not useful.
+	 */
+	public static Object newProxy(
+			AbstractSpringOrchestraScope scope, 
+			String conversationName,
+			String targetBeanName,
+			ObjectFactory objectFactory,
+			BeanFactory beanFactory)
+	{
+		// based on code from  Spring ScopedProxyFactoryBean (APL2.0)
+		final Log log = LogFactory.getLog(_SpringUtils.class);
+
+		log.debug("newProxy invoked for " + targetBeanName);
 
-		Set managedScopes = new TreeSet();
-		String[] scopes = beanFactory.getRegisteredScopeNames();
-		for (int i = 0; i < scopes.length; i++)
+		if (!(beanFactory instanceof ConfigurableBeanFactory))
 		{
-			String scopeName = scopes[i];
+			throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
+		}
+		ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
+
+		ScopedBeanTargetSource scopedTargetSource = new ScopedBeanTargetSource(
+				scope, conversationName, targetBeanName, objectFactory, beanFactory);
 
-			Scope scope = beanFactory.getRegisteredScope(scopeName);
-			if (scope instanceof SpringConversationScope)
-			{
-				managedScopes.add(scopeName);
-			}
+		ProxyFactory pf = new ProxyFactory();
+		pf.setProxyTargetClass(true);
+		pf.setTargetSource(scopedTargetSource);
+
+		Class beanType = beanFactory.getType(targetBeanName);
+		if (beanType == null) {
+			throw new IllegalStateException("Cannot create scoped proxy for bean '" + targetBeanName +
+					"': Target type could not be determined at the time of proxy creation.");
 		}
 
-		return managedScopes;
+		// Add an introduction that implements only the methods on ScopedObject.
+		// Not sure if this is useful...
+		ScopedObject scopedObject = new DefaultScopedObject(cbf, scopedTargetSource.getTargetBeanName());
+		pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
+
+		// Add the AopInfrastructureBean marker to indicate that the scoped proxy
+		// itself is not subject to auto-proxying! Only its target bean is.
+		// Not sure if this is needed....
+		pf.addInterface(AopInfrastructureBean.class);
+
+		return pf.getProxy(cbf.getBeanClassLoader());
 	}
-	*/
 }