/* * Copyright 2007 the original author or authors. * * Licensed 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.wamblee.system.spring; 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.core.AbstractComponent; import org.wamblee.system.core.DefaultScope; import org.wamblee.system.core.ProvidedInterface; import org.wamblee.system.core.RequiredInterface; import org.wamblee.system.core.Scope; import org.wamblee.system.core.SystemAssemblyException; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 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 { /** * DOCUMENT ME! */ private static final String CONTEXT_KEY = "context"; /** * DOCUMENT ME! */ static final ThreadLocal THIS = new ThreadLocal(); /** * DOCUMENT ME! */ static final ThreadLocal SCOPE = new ThreadLocal(); /** * DOCUMENT ME! */ private Properties properties; /** * DOCUMENT ME! */ private String[] configFiles; /** * DOCUMENT ME! */ private Map provided; /** * DOCUMENT ME! */ private Map required; /** * DOCUMENT ME! */ private Map propertyObjects; /** * 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; propertyObjects = new HashMap(); } /** * 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); } /** * DOCUMENT ME! * * @param aProperties DOCUMENT ME! */ public void addProperties(Properties aProperties) { for (Object key : aProperties.keySet()) { setProperty((String) key, aProperties.getProperty((String) key)); } } /** * DOCUMENT ME! * * @param aBeanname DOCUMENT ME! * @param aProperties DOCUMENT ME! */ public void addProperties(String aBeanname, Properties aProperties) { propertyObjects.put(aBeanname, aProperties); } /** * DOCUMENT ME! * * @param aBeanname DOCUMENT ME! * * @return DOCUMENT ME! */ public Properties getProperties(String aBeanname) { return propertyObjects.get(aBeanname); } /** * DOCUMENT ME! * * @param aExternalScope DOCUMENT ME! * * @return DOCUMENT ME! * * @throws SystemAssemblyException DOCUMENT ME! */ @Override protected Scope doStart(Scope aExternalScope) { SpringComponent old = THIS.get(); Scope oldScope = SCOPE.get(); THIS.set(this); Scope scope = new DefaultScope(getProvidedInterfaces() .toArray(new ProvidedInterface[0]), aExternalScope); SCOPE.set(scope); try { GenericApplicationContext parentContext = new GenericApplicationContext(); registerRequiredServices(parentContext); registerPropertyObjects(parentContext); parentContext.refresh(); System.out.println("Parent context " + parentContext); AbstractApplicationContext context = parseConfigFiles(parentContext); context.addBeanFactoryPostProcessor(new PropertySetter(properties)); context.refresh(); exposeProvidedServices(context, aExternalScope); scope.put(CONTEXT_KEY, context); return scope; } catch (Exception e) { throw new SystemAssemblyException( "Failed to assemble spring system " + getName(), e); } finally { THIS.set(old); SCOPE.set(oldScope); } } /** * DOCUMENT ME! * * @param aContext DOCUMENT ME! * @param aScope DOCUMENT ME! * * @throws IllegalArgumentException DOCUMENT ME! */ private void exposeProvidedServices(AbstractApplicationContext aContext, Scope aScope) { // Call addService for each provided service. for (String name : provided.keySet()) { Object svc = aContext.getBean(name); if (svc == null) { throw new IllegalArgumentException(getQualifiedName() + ": service '" + name + "' is null"); } addInterface(provided.get(name), svc, aScope); System.out.println("addService " + provided.get(name) + " " + svc); } } /** * DOCUMENT ME! * * @param aParentContext DOCUMENT ME! * * @return DOCUMENT ME! */ private AbstractApplicationContext parseConfigFiles( GenericApplicationContext aParentContext) { // Parse spring config files return new ClassPathXmlApplicationContext((String[]) configFiles, false, aParentContext); } /** * DOCUMENT ME! * * @param aParentContext DOCUMENT ME! */ private void registerRequiredServices( GenericApplicationContext aParentContext) { // Register required services in a parent context for (RequiredInterface requiredIntf : getRequiredInterfaces()) { String beanName = required.get(requiredIntf); if ((beanName != null) && (beanName.length() > 0)) { ConstructorArgumentValues cargs = new ConstructorArgumentValues(); cargs.addGenericArgumentValue(requiredIntf.getName()); BeanDefinition definition = new RootBeanDefinition(RequiredServiceBean.class, cargs, new MutablePropertyValues()); aParentContext.registerBeanDefinition(beanName, definition); } else { // The required interface is not required by the spring config but by the sub-class directly. } } } /** * DOCUMENT ME! * * @param aParentContext DOCUMENT ME! */ private void registerPropertyObjects( GenericApplicationContext aParentContext) { for (String beanName : propertyObjects.keySet()) { ConstructorArgumentValues cargs = new ConstructorArgumentValues(); cargs.addGenericArgumentValue(PropertySetter.createPropertyFile( propertyObjects.get(beanName))); BeanDefinition definition = new RootBeanDefinition(ConfiguredProperties.class, cargs, new MutablePropertyValues()); aParentContext.registerBeanDefinition(beanName, definition); } } /** * DOCUMENT ME! * * @param aRuntime DOCUMENT ME! */ @Override protected void doStop(Scope aRuntime) { AbstractApplicationContext context = (AbstractApplicationContext) aRuntime .get(CONTEXT_KEY); context.close(); } }