Huge refactoring.
[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.Map;
19 import java.util.Properties;
20
21 import org.springframework.beans.MutablePropertyValues;
22 import org.springframework.beans.factory.config.BeanDefinition;
23 import org.springframework.beans.factory.config.ConstructorArgumentValues;
24 import org.springframework.beans.factory.support.RootBeanDefinition;
25 import org.springframework.context.support.AbstractApplicationContext;
26 import org.springframework.context.support.ClassPathXmlApplicationContext;
27 import org.springframework.context.support.GenericApplicationContext;
28 import org.wamblee.system.core.AbstractComponent;
29 import org.wamblee.system.core.DefaultScope;
30 import org.wamblee.system.core.ProvidedInterface;
31 import org.wamblee.system.core.RequiredInterface;
32 import org.wamblee.system.core.Scope;
33 import org.wamblee.system.core.SystemAssemblyException;
34
35 /**
36  * Represents a system configured based on spring. The spring config files that
37  * are configured should not contain any PropertyPlaceholderConfigurer objects.
38  * 
39  * @author Erik Brakkee
40  */
41 public class SpringComponent extends AbstractComponent<Scope> {
42
43         private static final String CONTEXT_KEY = "context";
44
45         static final ThreadLocal<SpringComponent> THIS = new ThreadLocal<SpringComponent>();
46         static final ThreadLocal<Scope> SCOPE = new ThreadLocal<Scope>();
47
48         private Properties _properties;
49         private String[] _configFiles;
50         private Map<String, ProvidedInterface> _provided;
51         private Map<RequiredInterface, String> _required;
52
53         /**
54          * Constructs a spring system.
55          * 
56          * @param aName
57          *            Name of the system.
58          * @param aConfigFil
59          *            Spring config files to read.
60          * @param aProvided
61          *            Map of bean name to service descriptor describing the bean
62          *            names that the spring config files use for each required
63          *            service.
64          * @param aRequired
65          *            Map of bean name to service descriptor describing the bean
66          *            names that the spring config files use for each required
67          *            service.
68          */
69         public SpringComponent(String aName, String[] aConfigFiles,
70                         Map<String, ProvidedInterface> aProvided,
71                         Map<RequiredInterface, String> aRequired) {
72                 super(aName, aProvided.values().toArray(new ProvidedInterface[0]),
73                                 aRequired.keySet().toArray(new RequiredInterface[0]));
74                 _properties = new Properties();
75                 _configFiles = aConfigFiles;
76                 _provided = aProvided;
77                 _required = aRequired;
78         }
79
80         /**
81          * Must be called to make a property available in the application context.
82          * 
83          * @param aKey
84          *            Property key.
85          * @param aValue
86          *            Property value.
87          */
88         public void setProperty(String aKey, String aValue) {
89                 _properties.put(aKey, aValue);
90         }
91
92         public void addProperties(Properties aProperties) {
93                 for (Object key : aProperties.keySet()) {
94                         setProperty((String) key, aProperties.getProperty((String) key));
95                 }
96         }
97         
98         public Scope start() { 
99                 return super.start(new DefaultScope(new ProvidedInterface[0]));
100         }
101
102         @Override
103         protected Scope doStart(Scope aExternalScope) {
104
105                 SpringComponent old = THIS.get();
106                 Scope oldScope = SCOPE.get();
107                 THIS.set(this);
108                 Scope scope = new DefaultScope(getProvidedInterfaces(), aExternalScope);
109                 SCOPE.set(scope);
110                 try {
111                         GenericApplicationContext parentContext = new GenericApplicationContext();
112
113                         registerRequiredServices(parentContext);
114
115                         parentContext.refresh();
116
117                         AbstractApplicationContext context = parseConfigFiles(parentContext);
118
119                         context
120                                         .addBeanFactoryPostProcessor(new PropertySetter(_properties));
121                         context.refresh();
122
123                         exposeProvidedServices(context, scope);
124                         
125                         scope.put(CONTEXT_KEY, context);
126                         return scope; 
127                 } catch (Exception e) {
128                         throw new SystemAssemblyException(
129                                         "Failed to assemble spring system", e);
130                 } finally {
131                         THIS.set(old);
132                         SCOPE.set(oldScope);
133                 }
134         }
135
136         private void exposeProvidedServices(AbstractApplicationContext aContext, Scope aScope) {
137                 // Call addService for each provided service.
138
139                 for (String name : _provided.keySet()) {
140                         Object svc = aContext.getBean(name);
141                         if (svc == null) {
142                                 throw new IllegalArgumentException(getQualifiedName() + ": service '"
143                                                 + name + "' is null");
144                         }
145                         addInterface(_provided.get(name), svc, aScope);
146                         System.out.println("addService " + _provided.get(name) + " " + svc);
147                         aScope.publishInterface(_provided.get(name), svc);
148                 }
149         }
150
151         private AbstractApplicationContext parseConfigFiles(GenericApplicationContext aParentContext) {
152                 // Parse spring config files
153
154                 return new ClassPathXmlApplicationContext((String[]) _configFiles,
155                                 aParentContext);
156         }
157
158         private void registerRequiredServices(GenericApplicationContext aParentContext) {
159                 // Register required services in a parent context
160                 for (RequiredInterface required: getRequiredInterfaces()) { 
161                         String beanName = _required.get(required);
162                         ConstructorArgumentValues cargs = new ConstructorArgumentValues();
163                         cargs.addGenericArgumentValue(required.getName());
164                         BeanDefinition definition = new RootBeanDefinition(
165                                         RequiredServiceBean.class, cargs,
166                                         new MutablePropertyValues());
167                         aParentContext.registerBeanDefinition(beanName, definition);
168                 }
169         }
170
171         @Override
172         protected void doStop(Scope aRuntime) {
173                 AbstractApplicationContext context = (AbstractApplicationContext)aRuntime.get(CONTEXT_KEY);
174                 context.close();
175         }
176 }