package org.jboss.seam.ioc.spring;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.contexts.Lifecycle;
import org.jboss.seam.init.Initialization;
import org.jboss.seam.ioc.IoCComponent;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.util.ClassUtils;
/**
* An extension of Component that allows spring to provide the base instance for a seam component.
*
* @author youngm
*/
public class SpringComponent extends IoCComponent
{
private static final String SPRING_COMPONENT_NAME_MAP = "org.jboss.seam.SpringComponentNameMap";
public static final String DESTRUCTION_CALLBACK_NAME_PREFIX = IoCComponent.class.getName()
+ ".DESTRUCTION_CALLBACK.";
private BeanFactory beanfactory;
private Boolean interceptionEnabled;
private String springBeanName;
private static final ThreadLocal<ObjectFactory> objectFactory = new ThreadLocal<ObjectFactory>();
public static ObjectFactory getObjectFactory()
{
return objectFactory.get();
}
public static void setObjectFactory(ObjectFactory bean)
{
objectFactory.set(bean);
}
/**
* Utility to add a SpringComponent to the seam component ApplicationContext.
*
* @param componentName the seam component name to use
* @param springBeanName the spring bean name to map to this seam component
* @param beanClassName the seam beanClass to use
* @param scopeType the scope of this component
* @param beanFactory the beanfactory this spring bean exists in
* @param intercept the InterceptorTyp to force the bean to use. Will override any annotations on the bean.
*/
public static void addSpringComponent(String componentName, String springBeanName, String beanClassName,
ScopeType scopeType, BeanFactory beanFactory, Boolean intercept)
{
// mock the application context
// TODO reuse
boolean unmockApplication = false;
if (!Contexts.isApplicationContextActive())
{
Lifecycle.setupApplication();
unmockApplication = true;
}
try
{
if (Component.forName(componentName) != null)
{
throw new IllegalStateException("Cannot add spring component to seam with name: " + componentName
+ ". There is already a seam component with that name.");
}
Map<String, String> springComponentNameMap = getSpringComponentNameMap();
// Add an entry to the spring+seam name association map
springComponentNameMap.put(springBeanName, componentName);
Class beanClass = ClassUtils.forName(beanClassName);
// Add the component to seam
Contexts.getApplicationContext().set(
componentName + Initialization.COMPONENT_SUFFIX,
new SpringComponent(beanClass, componentName, springBeanName, scopeType, beanFactory,
intercept));
}
catch (ClassNotFoundException e)
{
throw new FatalBeanException("Error", e);
}
finally
{
if (unmockApplication)
{
Lifecycle.cleanupApplication();
}
}
}
@SuppressWarnings("unchecked")
private static Map<String, String> getSpringComponentNameMap()
{
if (Contexts.getApplicationContext().get(SPRING_COMPONENT_NAME_MAP) == null)
{
Contexts.getApplicationContext().set(SPRING_COMPONENT_NAME_MAP, new HashMap<String, String>());
}
return (Map<String, String>) Contexts.getApplicationContext().get(SPRING_COMPONENT_NAME_MAP);
}
/**
* Just like Component.forName() but mocks the applicationContext and you provide it with the spring bean name
* instead of the seam component name.
*
* @param springBeanName the spring bean name.
* @return the SpringComponent mapped to that spring bean name.
*/
public static SpringComponent forSpringBeanName(String springBeanName)
{
// TODO reuse
boolean unmockApplication = false;
if (!Contexts.isApplicationContextActive())
{
Lifecycle.setupApplication();
unmockApplication = true;
}
try
{
return (SpringComponent) Component.forName(getSpringComponentNameMap().get(springBeanName));
}
finally
{
if (unmockApplication)
{
Lifecycle.cleanupApplication();
}
}
}
/**
* Creates a Spring Seam Component given a beanFactory.
*
* @param clazz the seam beanClass to use
* @param componentName component name
* @param springBeanName the spring bean name
* @param scope component scope
* @param factory the beanfactory this spring component should use
* @param intercept the interception type
*/
public SpringComponent(Class clazz, String componentName, String springBeanName, ScopeType scope,
BeanFactory factory, Boolean intercept)
{
super(clazz, componentName, scope);
this.springBeanName = springBeanName;
this.beanfactory = factory;
this.interceptionEnabled = intercept;
}
@Override
protected String getIoCName()
{
return "Spring";
}
@Override
protected Object instantiateIoCBean() throws Exception
{
ObjectFactory objectFactory = getObjectFactory();
if (objectFactory == null)
{
return beanfactory.getBean(springBeanName);
}
setObjectFactory(null);
return objectFactory.getObject();
}
/**
* Calls the spring destroy callback when seam destroys the component
*
* @see org.jboss.seam.Component#callDestroyMethod(Object)
*/
@Override
public void destroy(Object instance)
{
super.destroy(instance);
// Cannot call the callback on a STATELESS bean because we have no way of storing it.
if (getScope() != ScopeType.STATELESS)
{
Runnable callback = (Runnable) getScope().getContext().get(DESTRUCTION_CALLBACK_NAME_PREFIX + getName());
if (callback != null)
{
callback.run();
}
}
}
/**
* Registers a destruction callback with this bean.
*
* @param name bean name
* @param destroy the destroy to set
*/
public void registerDestroyCallback(String name, Runnable destroy)
{
// Not sure yet how to register a stateless bean's Destruction callback.
if (getScope() != ScopeType.STATELESS)
{
getScope().getContext().set(DESTRUCTION_CALLBACK_NAME_PREFIX + name, destroy);
}
}
/**
* Overrides Components inject to unwrap all of the spring AOP layers so that fields can be injected into this bean.
*
* @see org.jboss.seam.Component#inject(java.lang.Object, boolean)
*/
@Override
public void inject(Object bean, boolean enforceRequired)
{
if (bean instanceof Advised)
{
try
{
inject(((Advised) bean).getTargetSource().getTarget(), enforceRequired);
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
super.inject(bean, enforceRequired);
}
/**
* Use the InterceptionType override if available otherwise use the annotation or seam default.
*
* @see org.jboss.seam.Component#isInterceptionEnabled()
*/
@Override
public boolean isInterceptionEnabled()
{
if (interceptionEnabled == null)
{
return super.isInterceptionEnabled();
}
return interceptionEnabled;
}
@Override
protected void checkSynchronizedForComponentType() {}
@Override
protected void checkPersistenceContextForComponentType() {}
/**
* Ignore PersistenceContextAttributes if scope is stateless
*/
@Override
public List<BijectedAttribute> getPersistenceContextAttributes()
{
if(getScope().equals(ScopeType.STATELESS)) {
return Collections.emptyList();
}
return super.getPersistenceContextAttributes();
}
}