001/*
002 * Copyright 2013 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 */
016package org.springframework.batch.core.scope;
017
018import org.springframework.aop.scope.ScopedProxyUtils;
019import org.springframework.batch.core.scope.context.StepContext;
020import org.springframework.beans.BeansException;
021import org.springframework.beans.factory.config.BeanDefinition;
022import org.springframework.beans.factory.config.BeanDefinitionHolder;
023import org.springframework.beans.factory.config.BeanDefinitionVisitor;
024import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
025import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
026import org.springframework.beans.factory.config.Scope;
027import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
028import org.springframework.beans.factory.support.BeanDefinitionRegistry;
029import org.springframework.core.Ordered;
030import org.springframework.util.Assert;
031import org.springframework.util.StringValueResolver;
032
033/**
034 * ScopeSupport.
035 *
036 * @author Michael Minella
037 * @since 3.0
038 */
039public abstract class BatchScopeSupport implements Scope, BeanFactoryPostProcessor, Ordered {
040
041        private boolean autoProxy = true;
042
043        private boolean proxyTargetClass = false;
044
045        private String name;
046
047        private int order = Ordered.LOWEST_PRECEDENCE;
048
049        /**
050         * @param order the order value to set priority of callback execution for
051         * the {@link BeanFactoryPostProcessor} part of this scope bean.
052         */
053        public void setOrder(int order) {
054                this.order = order;
055        }
056
057        @Override
058        public int getOrder() {
059                return order;
060        }
061
062        public String getName() {
063                return this.name;
064        }
065
066        /**
067         * Public setter for the name property. This can then be used as a bean
068         * definition attribute, e.g. scope="job".
069         *
070         * @param name the name to set for this scope.
071         */
072        public void setName(String name) {
073                this.name = name;
074        }
075
076        /**
077         * Flag to indicate that proxies should use dynamic subclassing. This allows
078         * classes with no interface to be proxied. Defaults to false.
079         *
080         * @param proxyTargetClass set to true to have proxies created using dynamic
081         * subclasses
082         */
083        public void setProxyTargetClass(boolean proxyTargetClass) {
084                this.proxyTargetClass = proxyTargetClass;
085        }
086
087        /**
088         * Flag to indicate that bean definitions need not be auto proxied. This gives control back to the declarer of the
089         * bean definition (e.g. in an @Configuration class).
090         *
091         * @param autoProxy the flag value to set (default true)
092         */
093        public void setAutoProxy(boolean autoProxy) {
094                this.autoProxy = autoProxy;
095        }
096
097        public abstract String getTargetNamePrefix();
098
099        /**
100         * Register this scope with the enclosing BeanFactory.
101         *
102         * @see BeanFactoryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory)
103         *
104         * @param beanFactory the BeanFactory to register with
105         * @throws BeansException if there is a problem.
106         */
107        @Override
108        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
109
110                beanFactory.registerScope(name, this);
111
112                if(!autoProxy) {
113                        return;
114                }
115
116                Assert.state(beanFactory instanceof BeanDefinitionRegistry,
117                                "BeanFactory was not a BeanDefinitionRegistry, so JobScope cannot be used.");
118                BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
119
120                for (String beanName : beanFactory.getBeanDefinitionNames()) {
121                        if (!beanName.startsWith(getTargetNamePrefix())) {
122                                BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
123                                // Replace this or any of its inner beans with scoped proxy if it
124                                // has this scope
125                                boolean scoped = name.equals(definition.getScope());
126                                Scopifier scopifier = new Scopifier(registry, name, proxyTargetClass, scoped);
127                                scopifier.visitBeanDefinition(definition);
128
129                                if (scoped && !definition.isAbstract()) {
130                                        createScopedProxy(beanName, definition, registry, proxyTargetClass);
131                                }
132                        }
133                }
134
135        }
136
137        /**
138         * Wrap a target bean definition in a proxy that defers initialization until
139         * after the {@link StepContext} is available. Amounts to adding
140         * <aop-auto-proxy/> to a step scoped bean.
141         *
142         * @param beanName the bean name to replace
143         * @param definition the bean definition to replace
144         * @param registry the enclosing {@link BeanDefinitionRegistry}
145         * @param proxyTargetClass true if we need to force use of dynamic
146         * subclasses
147         * @return a {@link BeanDefinitionHolder} for the new representation of the
148         * target. Caller should register it if needed to be visible at top level in
149         * bean factory.
150         */
151        protected static BeanDefinitionHolder createScopedProxy(String beanName, BeanDefinition definition,
152                        BeanDefinitionRegistry registry, boolean proxyTargetClass) {
153
154                BeanDefinitionHolder proxyHolder;
155
156                proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry,
157                                proxyTargetClass);
158
159                registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition());
160
161                return proxyHolder;
162
163        }
164
165        /**
166         * Helper class to scan a bean definition hierarchy and force the use of
167         * auto-proxy for step scoped beans.
168         *
169         * @author Dave Syer
170         *
171         */
172        protected static class Scopifier extends BeanDefinitionVisitor {
173
174                private final boolean proxyTargetClass;
175
176                private final BeanDefinitionRegistry registry;
177
178                private final String scope;
179
180                private final boolean scoped;
181
182                public Scopifier(BeanDefinitionRegistry registry, String scope, boolean proxyTargetClass, boolean scoped) {
183                        super(new StringValueResolver() {
184                                @Override
185                                public String resolveStringValue(String value) {
186                                        return value;
187                                }
188                        });
189                        this.registry = registry;
190                        this.proxyTargetClass = proxyTargetClass;
191                        this.scope = scope;
192                        this.scoped = scoped;
193                }
194
195                @Override
196                protected Object resolveValue(Object value) {
197
198                        BeanDefinition definition = null;
199                        String beanName = null;
200                        if (value instanceof BeanDefinition) {
201                                definition = (BeanDefinition) value;
202                                beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry);
203                        }
204                        else if (value instanceof BeanDefinitionHolder) {
205                                BeanDefinitionHolder holder = (BeanDefinitionHolder) value;
206                                definition = holder.getBeanDefinition();
207                                beanName = holder.getBeanName();
208                        }
209
210                        if (definition != null) {
211                                boolean nestedScoped = scope.equals(definition.getScope());
212                                boolean scopeChangeRequiresProxy = !scoped && nestedScoped;
213                                if (scopeChangeRequiresProxy) {
214                                        // Exit here so that nested inner bean definitions are not
215                                        // analysed
216                                        return createScopedProxy(beanName, definition, registry, proxyTargetClass);
217                                }
218                        }
219
220                        // Nested inner bean definitions are recursively analysed here
221                        value = super.resolveValue(value);
222                        return value;
223
224                }
225        }
226}