001/*
002 * Copyright 2006-2014 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.batch.core.configuration.support;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.List;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026
027import org.springframework.beans.BeansException;
028import org.springframework.beans.factory.BeanFactoryAware;
029import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
030import org.springframework.beans.factory.config.BeanPostProcessor;
031import org.springframework.beans.factory.config.ConfigurableBeanFactory;
032import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
033import org.springframework.beans.factory.config.CustomEditorConfigurer;
034import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
035import org.springframework.beans.factory.support.AbstractBeanFactory;
036import org.springframework.beans.factory.support.DefaultListableBeanFactory;
037import org.springframework.context.ApplicationContext;
038import org.springframework.context.ApplicationContextAware;
039import org.springframework.context.ConfigurableApplicationContext;
040import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
041import org.springframework.util.Assert;
042import org.springframework.util.ClassUtils;
043
044/**
045 * {@link ApplicationContextFactory} implementation that takes a parent context and a path to the context to create.
046 * When createApplicationContext method is called, the child {@link ApplicationContext} will be returned. The child
047 * context is not re-created every time it is requested, it is lazily initialized and cached. Clients should ensure that
048 * it is closed when it is no longer needed. If a path is not set, the parent will always be returned.
049 *
050 */
051public abstract class AbstractApplicationContextFactory implements ApplicationContextFactory, ApplicationContextAware {
052
053        private static final Log logger = LogFactory.getLog(AbstractApplicationContextFactory.class);
054
055        private Object[] resources;
056
057        private ConfigurableApplicationContext parent;
058
059        private boolean copyConfiguration = true;
060
061        private Collection<Class<? extends BeanFactoryPostProcessor>> beanFactoryPostProcessorClasses;
062
063        private Collection<Class<?>> beanPostProcessorExcludeClasses;
064
065        /**
066         * Create a factory instance with the resource specified. The resources are Spring configuration files or java
067         * packages containing configuration files.
068         *
069         * @param resource resource to be used in the creation of the ApplicationContext.
070         */
071        public AbstractApplicationContextFactory(Object... resource) {
072
073                this.resources = resource;
074                beanFactoryPostProcessorClasses = new ArrayList<Class<? extends BeanFactoryPostProcessor>>();
075                beanFactoryPostProcessorClasses.add(PropertyPlaceholderConfigurer.class);
076                beanFactoryPostProcessorClasses.add(PropertySourcesPlaceholderConfigurer.class);
077                beanFactoryPostProcessorClasses.add(CustomEditorConfigurer.class);
078                beanPostProcessorExcludeClasses = new ArrayList<Class<?>>();
079                /*
080                 * Assume that a BeanPostProcessor that is BeanFactoryAware must be specific to the parent and remove it from
081                 * the child (e.g. an AutoProxyCreator will not work properly). Unfortunately there might still be a a
082                 * BeanPostProcessor with a dependency that itself is BeanFactoryAware, but we can't legislate for that here.
083                 */
084                beanPostProcessorExcludeClasses.add(BeanFactoryAware.class);
085        }
086
087        /**
088         * Flag to indicate that configuration such as bean post processors and custom editors should be copied from the
089         * parent context. Defaults to true.
090         *
091         * @param copyConfiguration the flag value to set
092         */
093        public void setCopyConfiguration(boolean copyConfiguration) {
094                this.copyConfiguration = copyConfiguration;
095        }
096
097        /**
098         * Protected access for subclasses to the flag determining whether configuration should be copied from parent
099         * context.
100         *
101         * @return the flag value
102         */
103        protected final boolean isCopyConfiguration() {
104                return copyConfiguration;
105        }
106
107        /**
108         * Determines which bean factory post processors (like property placeholders) should be copied from the parent
109         * context. Defaults to {@link PropertyPlaceholderConfigurer} and {@link CustomEditorConfigurer}.
110         *
111         * @param beanFactoryPostProcessorClasses array of post processor types to be copied
112         */
113
114        public void setBeanFactoryPostProcessorClasses(
115                        Class<? extends BeanFactoryPostProcessor>[] beanFactoryPostProcessorClasses) {
116                this.beanFactoryPostProcessorClasses = new ArrayList<Class<? extends BeanFactoryPostProcessor>>();
117                for (int i = 0; i < beanFactoryPostProcessorClasses.length; i++) {
118                        this.beanFactoryPostProcessorClasses.add(beanFactoryPostProcessorClasses[i]);
119                }
120        }
121
122        /**
123         * Determines by exclusion which bean post processors should be copied from the parent context. Defaults to
124         * {@link BeanFactoryAware} (so any post processors that have a reference to the parent bean factory are not copied
125         * into the child). Note that these classes do not themselves have to be {@link BeanPostProcessor} implementations
126         * or sub-interfaces.
127         *
128         * @param beanPostProcessorExcludeClasses the classes to set
129         */
130        public void setBeanPostProcessorExcludeClasses(Class<?>[] beanPostProcessorExcludeClasses) {
131                this.beanPostProcessorExcludeClasses = new ArrayList<Class<?>>();
132                for (int i = 0; i < beanPostProcessorExcludeClasses.length; i++) {
133                        this.beanPostProcessorExcludeClasses.add(beanPostProcessorExcludeClasses[i]);
134                }
135
136        }
137
138        /**
139         * Protected access to the list of bean factory post processor classes that should be copied over to the context
140         * from the parent.
141         *
142         * @return the classes for post processors that were nominated for copying
143         */
144        protected final Collection<Class<? extends BeanFactoryPostProcessor>> getBeanFactoryPostProcessorClasses() {
145                return beanFactoryPostProcessorClasses;
146        }
147
148        /**
149         * Setter for the parent application context.
150         *
151         * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
152         */
153        @Override
154        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
155                if (applicationContext == null) {
156                        return;
157                }
158                Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
159                parent = (ConfigurableApplicationContext) applicationContext;
160        }
161
162        /**
163         * Creates an {@link ApplicationContext} from the provided path.
164         *
165         * @see ApplicationContextFactory#createApplicationContext()
166         */
167        @Override
168        public ConfigurableApplicationContext createApplicationContext() {
169
170                if (resources == null || resources.length == 0) {
171                        return parent;
172                }
173
174                return createApplicationContext(parent, resources);
175
176        }
177
178        protected abstract ConfigurableApplicationContext createApplicationContext(ConfigurableApplicationContext parent,
179                        Object... resources);
180
181        /**
182         * Extension point for special subclasses that want to do more complex things with the context prior to refresh. The
183         * default implementation does nothing.
184         *
185         * @param parent the parent for the new application context
186         * @param context the new application context before it is refreshed, but after bean factory is initialized
187         *
188         * @see AbstractApplicationContextFactory#setBeanFactoryPostProcessorClasses(Class[])
189         */
190        protected void prepareContext(ConfigurableApplicationContext parent, ConfigurableApplicationContext context) {
191        }
192
193        /**
194         * Extension point for special subclasses that want to do more complex things with the bean factory prior to
195         * refresh. The default implementation copies all configuration from the parent according to the
196         * {@link #setCopyConfiguration(boolean) flag} set.
197         *
198         * @param parent the parent bean factory for the new context (will never be null)
199         * @param beanFactory the new bean factory before bean definitions are loaded
200         *
201         * @see AbstractApplicationContextFactory#setCopyConfiguration(boolean)
202         * @see DefaultListableBeanFactory#copyConfigurationFrom(ConfigurableBeanFactory)
203         */
204        protected void prepareBeanFactory(ConfigurableListableBeanFactory parent,
205                        ConfigurableListableBeanFactory beanFactory) {
206                if (copyConfiguration && parent != null) {
207                        List<BeanPostProcessor> parentPostProcessors = new ArrayList<BeanPostProcessor>();
208                        List<BeanPostProcessor> childPostProcessors = new ArrayList<BeanPostProcessor>();
209
210                        childPostProcessors.addAll(beanFactory instanceof AbstractBeanFactory ? ((AbstractBeanFactory) beanFactory)
211                                        .getBeanPostProcessors() : new ArrayList<BeanPostProcessor>());
212                        parentPostProcessors.addAll(parent instanceof AbstractBeanFactory ? ((AbstractBeanFactory) parent)
213                                        .getBeanPostProcessors() : new ArrayList<BeanPostProcessor>());
214
215                        try {
216                                Class<?> applicationContextAwareProcessorClass =
217                                                ClassUtils.forName("org.springframework.context.support.ApplicationContextAwareProcessor",
218                                                                parent.getBeanClassLoader());
219
220                                for (BeanPostProcessor beanPostProcessor : new ArrayList<BeanPostProcessor>(parentPostProcessors)) {
221                                        if (applicationContextAwareProcessorClass.isAssignableFrom(beanPostProcessor.getClass())) {
222                                                logger.debug("Removing parent ApplicationContextAwareProcessor");
223                                                parentPostProcessors.remove(beanPostProcessor);
224                                        }
225                                }
226                        }
227                        catch (ClassNotFoundException e) {
228                                throw new IllegalStateException(e);
229                        }
230
231                        List<BeanPostProcessor> aggregatedPostProcessors = new ArrayList<BeanPostProcessor>();
232                        aggregatedPostProcessors.addAll(childPostProcessors);
233                        aggregatedPostProcessors.addAll(parentPostProcessors);
234
235                        for (BeanPostProcessor beanPostProcessor : new ArrayList<BeanPostProcessor>(aggregatedPostProcessors)) {
236                                for (Class<?> cls : beanPostProcessorExcludeClasses) {
237                                        if (cls.isAssignableFrom(beanPostProcessor.getClass())) {
238                                                if (logger.isDebugEnabled()) {
239                                                        logger.debug("Removing bean post processor: " + beanPostProcessor + " of type " + cls);
240                                                }
241                                                aggregatedPostProcessors.remove(beanPostProcessor);
242                                        }
243                                }
244                        }
245
246                        beanFactory.copyConfigurationFrom(parent);
247
248                        List<BeanPostProcessor> beanPostProcessors = beanFactory instanceof AbstractBeanFactory ? ((AbstractBeanFactory) beanFactory)
249                                        .getBeanPostProcessors() : new ArrayList<BeanPostProcessor>();
250
251                        beanPostProcessors.clear();
252                        beanPostProcessors.addAll(aggregatedPostProcessors);
253                }
254        }
255
256        @Override
257        public String toString() {
258                return "ApplicationContextFactory [resources=" + Arrays.toString(resources) + "]";
259        }
260
261        @Override
262        public int hashCode() {
263                return toString().hashCode();
264        }
265
266        @Override
267        public boolean equals(Object obj) {
268                if (this == obj) {
269                        return true;
270                }
271                if (obj == null) {
272                        return false;
273                }
274                return toString().equals(obj.toString());
275        }
276
277}