/*
* 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.TargetSource;
import org.springframework.aop.framework.Advised;
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()
{
}
/**
* 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>
* <bean name="foo" class="example.Foo" orchestra:foo="foo">
* <....>
* <aop:scopedProxy/>
* </bean>
* </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.
*
* @since 1.1
*/
public static boolean isModifiedBeanName(String beanName)
{
return beanName.startsWith(SCOPED_TARGET_PREFIX);
}
/**
* 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).
*
* @since 1.1
*/
public static String getOriginalBeanName(String beanName)
{
if (beanName != null && isModifiedBeanName(beanName))
{
return beanName.substring(SCOPED_TARGET_PREFIX.length());
}
return 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.
*
* @since 1.1
*/
public static String getModifiedBeanName(String beanName)
{
if (beanName != null && !isModifiedBeanName(beanName))
{
return SCOPED_TARGET_PREFIX + beanName;
}
return beanName;
}
/** @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)
{
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.
*
* @since 1.1
*/
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);
if (!(beanFactory instanceof ConfigurableBeanFactory))
{
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
ScopedBeanTargetSource scopedTargetSource = new ScopedBeanTargetSource(
scope, conversationName, targetBeanName, objectFactory, beanFactory);
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.");
}
// 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());
}
/**
* Given a proxy object, return the real underlying object that it currently refers to.
* <p>
* This method is currently experimental; it works for the current Spring implementation
* of Orchestra but at the current time it is not known whether this functionality can
* be implemented for all dependency-injection frameworks. If it does, then it might
* later make sense to promote this up to the public Orchestra API.
* <p>
* Note that invoking this method will create the "real bean" if it does not yet exist.
* There is currently no way of testing whether this will happen, ie null will never
* be returned from this method.
*/
public static Object getRealBean(Object proxy) throws Exception
{
Advised advised = (Advised) proxy;
TargetSource targetSource = advised.getTargetSource();
Object real = targetSource.getTarget();
// Possibly we could add a method on the ScopedBeanTargetSource class to test
// whether the target bean exists. Then here we could cast TargetSource to
// ScopedBeanTargetSource and return null if the target does not exist. This
// might be useful, but let's leave that until someone actually has a use-case
// for that functionality.
return real;
}
}