package org.wamblee.system.spring; import java.util.Map; import java.util.Properties; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.wamblee.system.AbstractComponent; import org.wamblee.system.ProvidedInterface; import org.wamblee.system.RequiredInterface; import org.wamblee.system.SystemAssemblyException; /** * Represents a system configured based on spring. The spring config files that * are configured should not contain any PropertyPlaceholderConfigurer objects. * * @author Erik Brakkee */ public class SpringComponent extends AbstractComponent { static final ThreadLocal THIS = new ThreadLocal(); private Properties _properties; private String[] _configFiles; private Map _provided; private Map _required; /** * Parent application context containing required services. */ private GenericApplicationContext _parentContext; /** * Application context containing parsed objects. */ private AbstractApplicationContext _context; /** * Constructs a spring system. * * @param aName * Name of the system. * @param aConfigFil * Spring config files to read. * @param aProvided * Map of bean name to service descriptor describing the bean * names that the spring config files use for each required * service. * @param aRequired * Map of bean name to service descriptor describing the bean * names that the spring config files use for each required * service. */ public SpringComponent(String aName, String[] aConfigFiles, Map aProvided, Map aRequired) { super(aName, aProvided.values().toArray(new ProvidedInterface[0]), aRequired.keySet().toArray(new RequiredInterface[0])); _properties = new Properties(); _configFiles = aConfigFiles; _provided = aProvided; _required = aRequired; } /** * Must be called to make a property available in the application context. * * @param aKey * Property key. * @param aValue * Property value. */ public void setProperty(String aKey, String aValue) { _properties.put(aKey, aValue); } public void addProperties(Properties aProperties) { for (Object key : aProperties.keySet()) { setProperty((String) key, aProperties.getProperty((String) key)); } } @Override protected void doStart(String aContext) { SpringComponent old = THIS.get(); THIS.set(this); try { _parentContext = new GenericApplicationContext(); registerRequiredServices(); _parentContext.refresh(); parseConfigFiles(); _context .addBeanFactoryPostProcessor(new PropertySetter(_properties)); _context.refresh(); exposeProvidedServices(aContext); } catch (Exception e) { throw new SystemAssemblyException( "Failed to assemble spring system", e); } finally { THIS.set(old); } } private void exposeProvidedServices(String aContext) { // Call addService for each provided service. for (String name : _provided.keySet()) { Object svc = _context.getBean(name); if (svc == null) { throw new IllegalArgumentException(aContext + ": service '" + name + "' is null"); } addService(aContext, _provided.get(name), svc); } } private void parseConfigFiles() { // Parse spring config files _context = new ClassPathXmlApplicationContext((String[]) _configFiles, _parentContext); } private void registerRequiredServices() { // Register required services in a parent context for (RequiredInterface required: getRequiredServices()) { String beanName = _required.get(required); ConstructorArgumentValues cargs = new ConstructorArgumentValues(); cargs.addGenericArgumentValue(required.getName()); BeanDefinition definition = new RootBeanDefinition( RequiredServiceBean.class, cargs, new MutablePropertyValues()); _parentContext.registerBeanDefinition(beanName, definition); } } @Override protected void doStop() { _context.close(); } }