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.Service; import org.wamblee.system.ServiceRegistry; import org.wamblee.system.SystemAssembler; 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 { /** * Singleton access to the service registry. Required while starting up. */ static ThreadLocal REGISTRY = 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, ServiceRegistry aRegistry, String[] aConfigFiles, Map aProvided, Map aRequired) { super(aName, aRegistry, 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, Service[] aRequiredServices) { ServiceRegistry oldRegistry = REGISTRY.get(); try { REGISTRY.set(getRegistry()); try { _parentContext = new GenericApplicationContext(); registerRequiredServices(aRequiredServices); _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 { REGISTRY.set(oldRegistry); } } 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(Service[] aRequiredServices) { // Register required services in a parent context for (Service svc: aRequiredServices) { String id = svc.getId(); ProvidedInterface descriptor = svc.getDescriptor(); RequiredInterface[] requiredServices = SystemAssembler.filterRequiredServices(descriptor, _required.keySet()); for (RequiredInterface required: requiredServices) { String beanName = _required.get(required); ConstructorArgumentValues cargs = new ConstructorArgumentValues(); cargs.addGenericArgumentValue(id); BeanDefinition definition = new RootBeanDefinition(RequiredServiceBean.class, cargs, new MutablePropertyValues()); _parentContext.registerBeanDefinition(beanName, definition); } } } @Override protected void doStop() { _context.close(); } }