0e4c62eb1fe26ae2ccb858342053d818e9af0749
[utils] / system / spring / src / main / java / org / wamblee / system / spring / SpringComponent.java
1 /*
2  * Copyright 2007 the original author or authors.
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */ 
16 package org.wamblee.system.spring;
17
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Properties;
21
22 import org.springframework.beans.MutablePropertyValues;
23 import org.springframework.beans.factory.config.BeanDefinition;
24 import org.springframework.beans.factory.config.ConstructorArgumentValues;
25 import org.springframework.beans.factory.support.RootBeanDefinition;
26 import org.springframework.context.support.AbstractApplicationContext;
27 import org.springframework.context.support.ClassPathXmlApplicationContext;
28 import org.springframework.context.support.GenericApplicationContext;
29 import org.wamblee.system.core.AbstractComponent;
30 import org.wamblee.system.core.DefaultScope;
31 import org.wamblee.system.core.ProvidedInterface;
32 import org.wamblee.system.core.RequiredInterface;
33 import org.wamblee.system.core.Scope;
34 import org.wamblee.system.core.SystemAssemblyException;
35
36 /**
37  * Represents a system configured based on spring. The spring config files that
38  * are configured should not contain any PropertyPlaceholderConfigurer objects.
39  * 
40  * @author Erik Brakkee
41  */
42 public class SpringComponent extends AbstractComponent<Scope> {
43
44         private static final String CONTEXT_KEY = "context";
45
46         static final ThreadLocal<SpringComponent> THIS = new ThreadLocal<SpringComponent>();
47         static final ThreadLocal<Scope> SCOPE = new ThreadLocal<Scope>();
48
49         private Properties properties;
50         private String[] configFiles;
51         private Map<String, ProvidedInterface> provided;
52         private Map<RequiredInterface, String> required;
53         private Map<String, Properties> propertyObjects;
54
55         /**
56          * Constructs a spring system.
57          * 
58          * @param aName
59          *            Name of the system.
60          * @param aConfigFil
61          *            Spring config files to read.
62          * @param aProvided
63          *            Map of bean name to service descriptor describing the bean
64          *            names that the spring config files use for each required
65          *            service.
66          * @param aRequired
67          *            Map of bean name to service descriptor describing the bean
68          *            names that the spring config files use for each required
69          *            service.
70          */
71         public SpringComponent(String aName, String[] aConfigFiles,
72                         Map<String, ProvidedInterface> aProvided,
73                         Map<RequiredInterface, String> aRequired) {
74                 super(aName, aProvided.values().toArray(new ProvidedInterface[0]),
75                                 aRequired.keySet().toArray(new RequiredInterface[0]));
76                 properties = new Properties();
77                 configFiles = aConfigFiles;
78                 provided = aProvided;
79                 required = aRequired;
80                 propertyObjects = new HashMap<String, Properties>(); 
81                 
82         }
83
84         /**
85          * Must be called to make a property available in the application context.
86          * 
87          * @param aKey
88          *            Property key.
89          * @param aValue
90          *            Property value.
91          */
92         public void setProperty(String aKey, String aValue) {
93                 properties.put(aKey, aValue);
94         }
95
96         public void addProperties(Properties aProperties) {
97                 for (Object key : aProperties.keySet()) {
98                         setProperty((String) key, aProperties.getProperty((String) key));
99                 }
100         }
101         
102         public void addProperties(String aBeanname, Properties aProperties) { 
103             propertyObjects.put(aBeanname, aProperties);
104         }
105         
106         public Properties getProperties(String aBeanname) { 
107             return propertyObjects.get(aBeanname);
108         }
109
110         @Override
111         protected Scope doStart(Scope aExternalScope) {
112
113                 SpringComponent old = THIS.get();
114                 Scope oldScope = SCOPE.get();
115                 THIS.set(this);
116                 Scope scope = new DefaultScope(getProvidedInterfaces().toArray(new ProvidedInterface[0]), aExternalScope);
117                 SCOPE.set(scope);
118                 try {
119                         GenericApplicationContext parentContext = new GenericApplicationContext();
120
121                         registerRequiredServices(parentContext);
122                         registerPropertyObjects(parentContext);
123
124                         parentContext.refresh();
125                         
126                         System.out.println("Parent context " + parentContext);
127                     
128                         AbstractApplicationContext context = parseConfigFiles(parentContext);
129
130                         context
131                                         .addBeanFactoryPostProcessor(new PropertySetter(properties));
132                         context.refresh();
133
134                         exposeProvidedServices(context, aExternalScope);
135                         
136                         scope.put(CONTEXT_KEY, context);
137                         return scope; 
138                 } catch (Exception e) {
139                         throw new SystemAssemblyException(
140                                         "Failed to assemble spring system " + getName(), e);
141                 } finally {
142                         THIS.set(old);
143                         SCOPE.set(oldScope);
144                 }
145         }
146
147         private void exposeProvidedServices(AbstractApplicationContext aContext, Scope aScope) {
148                 // Call addService for each provided service.
149
150                 for (String name : provided.keySet()) {
151                         Object svc = aContext.getBean(name);
152                         if (svc == null) {
153                                 throw new IllegalArgumentException(getQualifiedName() + ": service '"
154                                                 + name + "' is null");
155                         }
156                         addInterface(provided.get(name), svc, aScope);
157                         System.out.println("addService " + provided.get(name) + " " + svc);
158                 }
159         }
160
161         private AbstractApplicationContext parseConfigFiles(GenericApplicationContext aParentContext) {
162                 // Parse spring config files
163
164                 return new ClassPathXmlApplicationContext((String[]) configFiles,
165                                 false, aParentContext);
166         }
167
168         private void registerRequiredServices(GenericApplicationContext aParentContext) {
169                 // Register required services in a parent context
170                 for (RequiredInterface requiredIntf: getRequiredInterfaces()) { 
171                         String beanName = required.get(requiredIntf);
172                         if ( beanName != null && beanName.length() > 0) { 
173                             ConstructorArgumentValues cargs = new ConstructorArgumentValues();
174                     cargs.addGenericArgumentValue(requiredIntf.getName());
175                     BeanDefinition definition = new RootBeanDefinition(
176                             RequiredServiceBean.class, cargs,
177                             new MutablePropertyValues());
178                     aParentContext.registerBeanDefinition(beanName, definition);
179                         } else { 
180                             // The required interface is not required by the spring config but by the sub-class directly.
181                         }
182                 }
183         }
184         
185         private void registerPropertyObjects(GenericApplicationContext aParentContext) {
186         for (String beanName: propertyObjects.keySet()) { 
187             ConstructorArgumentValues cargs = new ConstructorArgumentValues();
188             cargs.addGenericArgumentValue(PropertySetter.createPropertyFile(propertyObjects.get(beanName)));
189             BeanDefinition definition = new RootBeanDefinition(
190                     ConfiguredProperties.class, cargs,
191                     new MutablePropertyValues());
192             aParentContext.registerBeanDefinition(beanName, definition);
193         }
194     }
195
196
197         @Override
198         protected void doStop(Scope aRuntime) {
199                 AbstractApplicationContext context = (AbstractApplicationContext)aRuntime.get(CONTEXT_KEY);
200                 context.close();
201         }
202 }