package jfun.yan.spring;
import java.util.Properties;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.interceptor.TransactionProxyFactoryBean;
import jfun.yan.Component;
import jfun.yan.Components;
import jfun.yan.Container;
import jfun.yan.Map;
import jfun.yan.Map3;
import jfun.yan.Monad;
import jfun.yan.Mutation;
import jfun.yan.factory.ThreadLocalScope;
import jfun.yan.xml.NutsUtils;
import jfun.yan.xml.SingletonMode;
/**
* The main adapter class to between Yan and Spring.
* <p>
* @author Ben Yu
* Nov 17, 2005 12:01:20 AM
*/
public class SpringAdapter {
private static Object injectFactoryBeanResult(final SpringContext ctxt, Object obj)
throws ClassNotFoundException, Exception {
//injectBasicBean(obj, ctxt);
if(obj instanceof FactoryBean){
obj = ((FactoryBean)obj).getObject();
//Spring 1.2.6 source code doesn't handle FactoryBean results. so we skip.
//injectBasicBean(obj, ctxt);
}
return obj;
}
private static Map3 getFactoryBeanMap3(final SpringContext ctxt,
final String[] itfs){
return new Map3(){
public Object map(Object tx, Object attrs, Object obj)
throws Exception{
final PlatformTransactionManager man =(PlatformTransactionManager)tx;
final Properties tattrs = (Properties)attrs;
final Object r = injectFactoryBeanResult(ctxt, obj);
return proxyTransaction(r, ctxt, man, tattrs, itfs);
}
};
}
private static Map getFactoryBeanMap(final SpringContext ctxt){
return new Map(){
public Object map(Object obj)
throws Exception{
return injectFactoryBeanResult(ctxt, obj);
}
};
}
private static Mutation getBeanMutation(final SpringContext ctxt){
return new Mutation(){
public void mutate(Object obj)
throws Exception{
injectBasicBean(obj, ctxt);
}
};
}
/**
* Inject spring dependencies into an object when it is of any
* Spring marker interface. Run FactoryBean if any.
* Add transaction proxy if any.
* @param obj the object to inject dependencies.
* @param ctxt the SpringContext object hosting all pertinent
* runtime information.
* @param transaction_manager the Component that instantiates the transaction manager.
* Can be null if transaction_attributes is null.
* @param transaction_attributes the Component that instantiates a Properties object
* that contains transaction attributes.
* This is the indicator parameter. When it is null, no transaction proxying is done.
* And when it is not null, the transaction_manager cannot be null.
* @param interfaces the interface names to proxy on.
* Can be null.
* @return the final result object.
*/
public static Object injectBean(Object obj, SpringContext ctxt,
PlatformTransactionManager transaction_manager,
Properties transaction_attributes, String[] interfaces)
throws Exception{
injectBasicBean(obj, ctxt);
return proxyTransaction(obj, ctxt,
transaction_manager, transaction_attributes, interfaces);
}
private static void injectApplicationContext(Object obj, ApplicationContext actxt){
if(obj instanceof ApplicationContextAware){
((ApplicationContextAware)obj).setApplicationContext(actxt);
}
else{
if(obj instanceof BeanFactoryAware){
((BeanFactoryAware)obj).setBeanFactory(actxt);
}
if(obj instanceof ResourceLoaderAware){
((ResourceLoaderAware)obj).setResourceLoader(actxt);
}
if(obj instanceof ApplicationEventPublisherAware){
((ApplicationEventPublisherAware)obj).setApplicationEventPublisher(actxt);
}
if(obj instanceof MessageSourceAware){
((MessageSourceAware)obj).setMessageSource(actxt);
}
}
}
/**
* Inject spring dependencies into an object when it is of any
* Spring marker interface.
* @param obj the object to inject dependencies.
* @param ctxt the SpringContext object hosting all pertinent
* runtime information.
* @param transaction_manager the Component that instantiates the transaction manager.
* @param transaction_attributes the Component that instantiates a Properties object
* that contains transaction attributes.
* @param interfaces the interface names to proxy on.
* @return the final result object.
*/
private static void injectBasicBean(Object obj, SpringContext ctxt)
throws Exception{
if(ctxt.isGloballyDefined()){
//only ids of global components make sense.
final String id = ctxt.getId();
if(id!=null && obj instanceof BeanNameAware){
((BeanNameAware)obj).setBeanName(id);
}
}
injectApplicationContext(obj, ctxt.getApplicationContext());
initBean(obj, ctxt);
}
private static void initBean(Object obj, SpringContext ctxt)
throws Exception{
final Object original = obj;
// applyBeanPostProcessorsBeforeInitialization(bean, beanName, mergedBeanDefinition)
if(obj instanceof InitializingBean){
((InitializingBean)obj).afterPropertiesSet();
}
//runInitializer(obj, ctxt.getInitializer());
//applyBeanPostProcessorsAfterInitialization(bean, beanName, mergedBeanDefinition);
if(original instanceof DisposableBean){
final DisposableBean db = (DisposableBean)original;
ctxt.manageDisposableBean(db);
}
}
private static final Component autowire_txman =
Components.useType(PlatformTransactionManager.class);
/**
* Decorate a Component with spring context so that
* dependencies can be injected automatically, and
* FactoryBean can be automatically instantiated.
* @param c the Component object.
* @param ctxt the SpringContext object.
* @param transaction_manager the Component that instantiates the transaction manager.
* Can be null if transaction_attributes is null.
* @param transaction_attributes the Component that instantiates a Properties object
* that contains transaction attributes.
* This is the indicator parameter. When it is null, no transaction proxying is done.
* And when it is not null, the transaction_manager cannot be null.
* @param interfaces the interface names to proxy on.
* Can be null.
* @return the new Component that does all the spring-related work.
*/
public static Component adapt(Component c,
SpringContext ctxt, Component transaction_manager,
Component transaction_attributes, String[] interfaces){
if(transaction_attributes!=null && transaction_manager==null){
transaction_manager =
ctxt.cast(PlatformTransactionManager.class,
autowire_txman
).label("transaction manager auto detection");
}
final Class type = c.getType();
final Component r = createSpringBeanComponent(c, ctxt, transaction_manager, transaction_attributes, interfaces);
if(type!=null && BeanPostProcessor.class.isAssignableFrom(type)){
//does bpp suffer from proxies?
ctxt.addBeanPostProcessor(ctxt.getTagName(), c);
}
if(type!=null && FactoryBean.class.isAssignableFrom(type)){
//for factory bean, we do not know its type in advance.
return r;
}
if(transaction_attributes!=null){
//for beans to be proxied, we do not know the type.
return r;
}
if(r.getType()==null && type!=null)
return ctxt.cast(type, r);
else return r;
}
private static Component createSpringBeanComponent(Component c, SpringContext ctxt,
Component transaction_manager, Component transaction_attributes,
String[] interfaces) {
final Class type = c.getType();
c = c.mutate(getBeanMutation(ctxt));
if(type!=null && SpringUtils.isSpringInfrastructureClass(type)){
//no proxy for spring special cases.
return c;//.map(getBeanMap(ctxt));
}
final boolean isfactorybean = type!=null && FactoryBean.class.isAssignableFrom(type);
if(isfactorybean){
if(ctxt.isSingletonAttributeSet()){
final SingletonMode mode = ctxt.getSingleton();
if(mode!=null)
c = mode.decorate(c);
}
else{
//factroy bean is by default singleton.
c = c.singleton(new ThreadLocalScope());
}
}
if(transaction_attributes==null){
//no transaction to do
if(type==null || isfactorybean){
return singletonedFactoryBean(isfactorybean,c,
c.map(getFactoryBeanMap(ctxt)));
}
else{
return c;
}
}
else{
return singletonedFactoryBean(isfactorybean, c,
Monad.map(transaction_manager, transaction_attributes, c, getFactoryBeanMap3(ctxt, interfaces)));
}
}
private static Component singletonedFactoryBean(boolean factorybean,
Component original_cc, Component c){
if(factorybean){
return decorateFactoryBean(new NonSingletonComponent(c), original_cc);
}
else{
return c;
}
}
private static Object proxyTransaction(Object target, SpringContext ctxt,
PlatformTransactionManager tx, Properties attrs, String[] itfs)
throws Exception{
if(tx==null){
if(attrs==null)
return target;
else
throw new IllegalArgumentException("transaction manager is missing to perform declarative transaction");
}
final TransactionProxyFactoryBean tpfb = new TransactionProxyFactoryBean();
//tpfb.setBeanFactory(ctxt.getApplicationContext());
tpfb.setTarget(target);
tpfb.setTransactionManager(tx);
if(itfs!=null)
tpfb.setProxyInterfaces(itfs);
tpfb.setTransactionAttributes(attrs);
//injectBasicBean(tpfb, ctxt);
tpfb.setTarget(target);
tpfb.setTransactionManager(tx);
tpfb.setTransactionAttributes(attrs);
if(itfs!=null){
tpfb.setProxyInterfaces(itfs);
}
injectBasicBean(tpfb, ctxt);
return tpfb.getObject();
}
static final SpringKey factory_bean_key = new SpringKey("org.springframework.FactoryBean");
static Component decorateFactoryBean(Component c, Component factorybean_cc){
return NutsUtils.setState(c, factory_bean_key, factorybean_cc);
}
static boolean isFactoryBeanComponent(Component c){
return getFactoryBeanComponent(c) != null;
}
static Component getFactoryBeanComponent(Component c){
final Class type = c.getType();
if(type!=null && FactoryBean.class.isAssignableFrom(type)){
return c;
}
else{
final Component fbc = (Component)NutsUtils.getState(c, factory_bean_key);
return fbc;
}
}
/**
* Get FactoryBean instance from a Spring integrated Container object.
* @param yan the Container object.
* @param key the key of the FactoryBean component. The key doesn't need to be
* prefixed with a '&' as one has to do in Spring.
* @return the FactoryBean instance.
*/
public static FactoryBean getFactoryBean(Container yan, Object key){
final Component c = yan.getComponent(key);
if(c==null){
return null;
}
final Component fbc = getFactoryBeanComponent(c);
if(fbc==null){
return null;
}
return (FactoryBean)yan.instantiateComponent(fbc);
}
}