1 | /* |
2 | * Copyright 2006-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 | |
17 | package org.springframework.batch.core.configuration.support; |
18 | |
19 | import java.io.IOException; |
20 | import java.util.ArrayList; |
21 | import java.util.Collection; |
22 | import java.util.List; |
23 | |
24 | import org.apache.commons.logging.Log; |
25 | import org.apache.commons.logging.LogFactory; |
26 | import org.springframework.beans.BeansException; |
27 | import org.springframework.beans.factory.BeanFactoryAware; |
28 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; |
29 | import org.springframework.beans.factory.config.BeanPostProcessor; |
30 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; |
31 | import org.springframework.beans.factory.config.CustomEditorConfigurer; |
32 | import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; |
33 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
34 | import org.springframework.context.ApplicationContext; |
35 | import org.springframework.context.ApplicationContextAware; |
36 | import org.springframework.context.ConfigurableApplicationContext; |
37 | import org.springframework.context.support.AbstractXmlApplicationContext; |
38 | import org.springframework.core.io.Resource; |
39 | import org.springframework.util.Assert; |
40 | |
41 | /** |
42 | * {@link ApplicationContextFactory} implementation that takes a parent context |
43 | * and a path to the context to create. When createApplicationContext method is |
44 | * called, the child {@link ApplicationContext} will be returned. The child |
45 | * context is not re-created every time it is requested, it is lazily |
46 | * initialized and cached. Clients should ensure that it is closed when it is no |
47 | * longer needed. If a path is not set, the parent will always be returned. |
48 | * |
49 | */ |
50 | public class ClassPathXmlApplicationContextFactory implements ApplicationContextFactory, ApplicationContextAware { |
51 | |
52 | private static final Log logger = LogFactory.getLog(ClassPathXmlApplicationContextFactory.class); |
53 | |
54 | private Resource resource; |
55 | |
56 | private ConfigurableApplicationContext parent; |
57 | |
58 | private boolean copyConfiguration = true; |
59 | |
60 | private Collection<Class<? extends BeanFactoryPostProcessor>> beanFactoryPostProcessorClasses; |
61 | |
62 | private Collection<Class<?>> beanPostProcessorExcludeClasses; |
63 | |
64 | /** |
65 | * Convenient constructor for configuration purposes. |
66 | */ |
67 | public ClassPathXmlApplicationContextFactory() { |
68 | this(null); |
69 | } |
70 | |
71 | /** |
72 | * Create a factory instance with the resource specified. The resource is a |
73 | * Spring XML configuration file. |
74 | */ |
75 | public ClassPathXmlApplicationContextFactory(Resource resource) { |
76 | |
77 | this.resource = resource; |
78 | beanFactoryPostProcessorClasses = new ArrayList<Class<? extends BeanFactoryPostProcessor>>(); |
79 | beanFactoryPostProcessorClasses.add(PropertyPlaceholderConfigurer.class); |
80 | beanFactoryPostProcessorClasses.add(CustomEditorConfigurer.class); |
81 | beanPostProcessorExcludeClasses = new ArrayList<Class<?>>(); |
82 | /* |
83 | * Assume that a BeanPostProcessor that is BeanFactoryAware must be |
84 | * specific to the parent and remove it from the child (e.g. an |
85 | * AutoProxyCreator will not work properly). Unfortunately there might |
86 | * still be a a BeanPostProcessor with a dependency that itself is |
87 | * BeanFactoryAware, but we can't legislate for that here. |
88 | */ |
89 | beanPostProcessorExcludeClasses.add(BeanFactoryAware.class); |
90 | } |
91 | |
92 | /** |
93 | * Setter for the path to the xml to load to create an |
94 | * {@link ApplicationContext}. Use imports to centralise the configuration |
95 | * in one file. |
96 | * |
97 | * @param resource the resource path to the xml to load for the child |
98 | * context. |
99 | */ |
100 | public void setResource(Resource resource) { |
101 | this.resource = resource; |
102 | } |
103 | |
104 | /** |
105 | * Flag to indicate that configuration such as bean post processors and |
106 | * custom editors should be copied from the parent context. Defaults to |
107 | * true. |
108 | * |
109 | * @param copyConfiguration the flag value to set |
110 | */ |
111 | public void setCopyConfiguration(boolean copyConfiguration) { |
112 | this.copyConfiguration = copyConfiguration; |
113 | } |
114 | |
115 | /** |
116 | * Protected access for subclasses to the flag determining whether |
117 | * configuration should be copied from parent context. |
118 | * |
119 | * @return the flag value |
120 | */ |
121 | protected final boolean isCopyConfiguration() { |
122 | return copyConfiguration; |
123 | } |
124 | |
125 | /** |
126 | * Determines which bean factory post processors (like property |
127 | * placeholders) should be copied from the parent context. Defaults to |
128 | * {@link PropertyPlaceholderConfigurer} and {@link CustomEditorConfigurer}. |
129 | * |
130 | * @param copyBeanFactoryPostProcessors the flag value to set |
131 | */ |
132 | |
133 | public void setBeanFactoryPostProcessorClasses( |
134 | Class<? extends BeanFactoryPostProcessor>[] beanFactoryPostProcessorClasses) { |
135 | this.beanFactoryPostProcessorClasses = new ArrayList<Class<? extends BeanFactoryPostProcessor>>(); |
136 | for (int i = 0; i < beanFactoryPostProcessorClasses.length; i++) { |
137 | this.beanFactoryPostProcessorClasses.add(beanFactoryPostProcessorClasses[i]); |
138 | } |
139 | } |
140 | |
141 | /** |
142 | * Determines by exclusion which bean post processors should be copied from |
143 | * the parent context. Defaults to {@link BeanFactoryAware} (so any post |
144 | * processors that have a reference to the parent bean factory are not |
145 | * copied into the child). Note that these classes do not themselves have to |
146 | * be {@link BeanPostProcessor} implementations or sub-interfaces. |
147 | * |
148 | * @param beanPostProcessorExcludeClasses the classes to set |
149 | */ |
150 | public void setBeanPostProcessorExcludeClasses(Class<?>[] beanPostProcessorExcludeClasses) { |
151 | this.beanPostProcessorExcludeClasses = new ArrayList<Class<?>>(); |
152 | for (int i = 0; i < beanPostProcessorExcludeClasses.length; i++) { |
153 | this.beanPostProcessorExcludeClasses.add(beanPostProcessorExcludeClasses[i]); |
154 | } |
155 | |
156 | } |
157 | |
158 | /** |
159 | * Protected access to the list of bean factory post processor classes that |
160 | * should be copied over to the context from the parent. |
161 | * |
162 | * @return the classes for post processors that were nominated for copying |
163 | */ |
164 | protected final Collection<Class<? extends BeanFactoryPostProcessor>> getBeanFactoryPostProcessorClasses() { |
165 | return beanFactoryPostProcessorClasses; |
166 | } |
167 | |
168 | /** |
169 | * Setter for the parent application context. |
170 | * |
171 | * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) |
172 | */ |
173 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { |
174 | if (applicationContext == null) { |
175 | return; |
176 | } |
177 | Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext); |
178 | parent = (ConfigurableApplicationContext) applicationContext; |
179 | } |
180 | |
181 | /** |
182 | * Creates an {@link ApplicationContext} from the provided path. |
183 | * |
184 | * @see ApplicationContextFactory#createApplicationContext() |
185 | */ |
186 | public ConfigurableApplicationContext createApplicationContext() { |
187 | |
188 | if (resource == null) { |
189 | return parent; |
190 | } |
191 | |
192 | return new ResourceXmlApplicationContext(parent); |
193 | |
194 | } |
195 | |
196 | /** |
197 | * @author Dave Syer |
198 | * |
199 | */ |
200 | private final class ResourceXmlApplicationContext extends AbstractXmlApplicationContext { |
201 | |
202 | private final DefaultListableBeanFactory parentBeanFactory; |
203 | |
204 | /** |
205 | * @param parent |
206 | */ |
207 | public ResourceXmlApplicationContext(ConfigurableApplicationContext parent) { |
208 | super(parent); |
209 | setId(generateId(resource)); |
210 | if (parent != null) { |
211 | Assert.isTrue(parent.getBeanFactory() instanceof DefaultListableBeanFactory, |
212 | "The parent application context must have a bean factory of type DefaultListableBeanFactory"); |
213 | parentBeanFactory = (DefaultListableBeanFactory) parent.getBeanFactory(); |
214 | refreshBeanFactory(); |
215 | prepareContext(parent, this); |
216 | } |
217 | else { |
218 | parentBeanFactory = null; |
219 | } |
220 | refresh(); |
221 | } |
222 | |
223 | /** |
224 | * @param resource |
225 | * @return an identifier for the context |
226 | */ |
227 | private String generateId(Resource resource) { |
228 | try { |
229 | return resource.getURI().toString(); |
230 | } |
231 | catch (IOException e) { |
232 | return resource.toString(); |
233 | } |
234 | } |
235 | |
236 | @Override |
237 | protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { |
238 | super.customizeBeanFactory(beanFactory); |
239 | if (parentBeanFactory != null) { |
240 | ClassPathXmlApplicationContextFactory.this.prepareBeanFactory(parentBeanFactory, beanFactory); |
241 | for (Class<? extends BeanFactoryPostProcessor> cls : beanFactoryPostProcessorClasses) { |
242 | for (String name : parent.getBeanNamesForType(cls)) { |
243 | beanFactory.registerSingleton(name, ((BeanFactoryPostProcessor) parent.getBean(name))); |
244 | } |
245 | } |
246 | } |
247 | } |
248 | |
249 | @Override |
250 | protected Resource[] getConfigResources() { |
251 | return new Resource[] { resource }; |
252 | } |
253 | |
254 | @Override |
255 | public String toString() { |
256 | return "ResourceXmlApplicationContext:" + getId(); |
257 | } |
258 | |
259 | } |
260 | |
261 | /** |
262 | * Extension point for special subclasses that want to do more complex |
263 | * things with the context prior to refresh. The default implementation |
264 | * does nothing. |
265 | * |
266 | * @param parent the parent for the new application context |
267 | * @param context the new application context before it is refreshed, but |
268 | * after bean factory is initialized |
269 | * |
270 | * @see ClassPathXmlApplicationContextFactory#setBeanFactoryPostProcessorClasses(Class[]) |
271 | */ |
272 | protected void prepareContext(ConfigurableApplicationContext parent, ConfigurableApplicationContext context) { |
273 | } |
274 | |
275 | /** |
276 | * Extension point for special subclasses that want to do more complex |
277 | * things with the bean factory prior to refresh. The default implementation |
278 | * copies all configuration from the parent according to the |
279 | * {@link #setCopyConfiguration(boolean) flag} set. |
280 | * |
281 | * @param parent the parent bean factory for the new context (will never be |
282 | * null) |
283 | * @param beanFactory the new bean factory before bean definitions are |
284 | * loaded |
285 | * |
286 | * @see ClassPathXmlApplicationContextFactory#setCopyConfiguration(boolean) |
287 | * @see DefaultListableBeanFactory#copyConfigurationFrom(ConfigurableBeanFactory) |
288 | */ |
289 | protected void prepareBeanFactory(DefaultListableBeanFactory parent, DefaultListableBeanFactory beanFactory) { |
290 | if (copyConfiguration && parent != null) { |
291 | beanFactory.copyConfigurationFrom(parent); |
292 | @SuppressWarnings("unchecked") |
293 | List<BeanPostProcessor> beanPostProcessors = beanFactory.getBeanPostProcessors(); |
294 | for (BeanPostProcessor beanPostProcessor : new ArrayList<BeanPostProcessor>(beanPostProcessors)) { |
295 | for (Class<?> cls : beanPostProcessorExcludeClasses) { |
296 | if (cls.isAssignableFrom(beanPostProcessor.getClass())) { |
297 | logger.debug("Removing bean post processor: " + beanPostProcessor + " of type " + cls); |
298 | beanPostProcessors.remove(beanPostProcessor); |
299 | } |
300 | } |
301 | } |
302 | } |
303 | } |
304 | |
305 | @Override |
306 | public String toString() { |
307 | return "ClassPathXmlApplicationContextFactory [resource=" + resource + "]"; |
308 | } |
309 | |
310 | @Override |
311 | public int hashCode() { |
312 | return toString().hashCode(); |
313 | } |
314 | |
315 | @Override |
316 | public boolean equals(Object obj) { |
317 | if (this == obj) |
318 | return true; |
319 | if (obj == null) |
320 | return false; |
321 | return toString().equals(obj.toString()); |
322 | } |
323 | |
324 | } |