1 | /* |
2 | * Copyright 2006-2013 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.springframework.batch.core.scope; |
17 | |
18 | import org.apache.commons.logging.Log; |
19 | import org.apache.commons.logging.LogFactory; |
20 | import org.springframework.aop.scope.ScopedProxyUtils; |
21 | import org.springframework.batch.core.scope.context.StepContext; |
22 | import org.springframework.batch.core.scope.context.StepSynchronizationManager; |
23 | import org.springframework.beans.BeanWrapper; |
24 | import org.springframework.beans.BeanWrapperImpl; |
25 | import org.springframework.beans.BeansException; |
26 | import org.springframework.beans.factory.ObjectFactory; |
27 | import org.springframework.beans.factory.config.BeanDefinition; |
28 | import org.springframework.beans.factory.config.BeanDefinitionHolder; |
29 | import org.springframework.beans.factory.config.BeanDefinitionVisitor; |
30 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; |
31 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; |
32 | import org.springframework.beans.factory.config.Scope; |
33 | import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; |
34 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; |
35 | import org.springframework.core.Ordered; |
36 | import org.springframework.util.Assert; |
37 | import org.springframework.util.StringValueResolver; |
38 | |
39 | /** |
40 | * Scope for step context. Objects in this scope use the Spring container as an |
41 | * object factory, so there is only one instance of such a bean per executing |
42 | * step. All objects in this scope are <aop:scoped-proxy/> (no need to |
43 | * decorate the bean definitions).<br/> |
44 | * <br/> |
45 | * |
46 | * In addition, support is provided for late binding of references accessible |
47 | * from the {@link StepContext} using #{..} placeholders. Using this feature, |
48 | * bean properties can be pulled from the step or job execution context and the |
49 | * job parameters. E.g. |
50 | * |
51 | * <pre> |
52 | * <bean id="..." class="..." scope="step"> |
53 | * <property name="parent" ref="#{stepExecutionContext[helper]}" /> |
54 | * </bean> |
55 | * |
56 | * <bean id="..." class="..." scope="step"> |
57 | * <property name="name" value="#{stepExecutionContext['input.name']}" /> |
58 | * </bean> |
59 | * |
60 | * <bean id="..." class="..." scope="step"> |
61 | * <property name="name" value="#{jobParameters[input]}" /> |
62 | * </bean> |
63 | * |
64 | * <bean id="..." class="..." scope="step"> |
65 | * <property name="name" value="#{jobExecutionContext['input.stem']}.txt" /> |
66 | * </bean> |
67 | * </pre> |
68 | * |
69 | * The {@link StepContext} is referenced using standard bean property paths (as |
70 | * per {@link BeanWrapper}). The examples above all show the use of the Map |
71 | * accessors provided as a convenience for step and job attributes. |
72 | * |
73 | * @author Dave Syer |
74 | * @author Michael Minella |
75 | * @since 2.0 |
76 | */ |
77 | public class StepScope implements Scope, BeanFactoryPostProcessor, Ordered { |
78 | |
79 | private Log logger = LogFactory.getLog(getClass()); |
80 | |
81 | private int order = Ordered.LOWEST_PRECEDENCE; |
82 | |
83 | private boolean autoProxy = true; |
84 | |
85 | private final Object mutex = new Object(); |
86 | |
87 | /** |
88 | * @param order the order value to set priority of callback execution for |
89 | * the {@link BeanFactoryPostProcessor} part of this scope bean. |
90 | */ |
91 | public void setOrder(int order) { |
92 | this.order = order; |
93 | } |
94 | |
95 | @Override |
96 | public int getOrder() { |
97 | return order; |
98 | } |
99 | |
100 | /** |
101 | * Context key for clients to use for conversation identifier. |
102 | */ |
103 | public static final String ID_KEY = "STEP_IDENTIFIER"; |
104 | |
105 | private String name = "step"; |
106 | |
107 | private boolean proxyTargetClass = false; |
108 | |
109 | /** |
110 | * Flag to indicate that proxies should use dynamic subclassing. This allows |
111 | * classes with no interface to be proxied. Defaults to false. |
112 | * |
113 | * @param proxyTargetClass set to true to have proxies created using dynamic |
114 | * subclasses |
115 | */ |
116 | public void setProxyTargetClass(boolean proxyTargetClass) { |
117 | this.proxyTargetClass = proxyTargetClass; |
118 | } |
119 | |
120 | /** |
121 | * Flag to indicate that bean definitions need not be auto proxied. This gives control back to the declarer of the |
122 | * bean definition (e.g. in an @Configuration class). |
123 | * |
124 | * @param autoProxy the flag value to set (default true) |
125 | */ |
126 | public void setAutoProxy(boolean autoProxy) { |
127 | this.autoProxy = autoProxy; |
128 | } |
129 | |
130 | /** |
131 | * This will be used to resolve expressions in step-scoped beans. |
132 | */ |
133 | @Override |
134 | public Object resolveContextualObject(String key) { |
135 | StepContext context = getContext(); |
136 | // TODO: support for attributes as well maybe (setters not exposed yet |
137 | // so not urgent). |
138 | return new BeanWrapperImpl(context).getPropertyValue(key); |
139 | } |
140 | |
141 | /** |
142 | * @see Scope#get(String, ObjectFactory) |
143 | */ |
144 | @SuppressWarnings("rawtypes") |
145 | @Override |
146 | public Object get(String name, ObjectFactory objectFactory) { |
147 | StepContext context = getContext(); |
148 | Object scopedObject = context.getAttribute(name); |
149 | |
150 | if (scopedObject == null) { |
151 | |
152 | synchronized (mutex) { |
153 | scopedObject = context.getAttribute(name); |
154 | if (scopedObject == null) { |
155 | |
156 | logger.debug(String.format("Creating object in scope=%s, name=%s", this.name, name)); |
157 | |
158 | scopedObject = objectFactory.getObject(); |
159 | context.setAttribute(name, scopedObject); |
160 | |
161 | } |
162 | |
163 | } |
164 | |
165 | } |
166 | return scopedObject; |
167 | } |
168 | |
169 | /** |
170 | * @see Scope#getConversationId() |
171 | */ |
172 | @Override |
173 | public String getConversationId() { |
174 | StepContext context = getContext(); |
175 | return context.getId(); |
176 | } |
177 | |
178 | /** |
179 | * @see Scope#registerDestructionCallback(String, Runnable) |
180 | */ |
181 | @Override |
182 | public void registerDestructionCallback(String name, Runnable callback) { |
183 | StepContext context = getContext(); |
184 | logger.debug(String.format("Registered destruction callback in scope=%s, name=%s", this.name, name)); |
185 | context.registerDestructionCallback(name, callback); |
186 | } |
187 | |
188 | /** |
189 | * @see Scope#remove(String) |
190 | */ |
191 | @Override |
192 | public Object remove(String name) { |
193 | StepContext context = getContext(); |
194 | logger.debug(String.format("Removing from scope=%s, name=%s", this.name, name)); |
195 | return context.removeAttribute(name); |
196 | } |
197 | |
198 | /** |
199 | * Get an attribute accessor in the form of a {@link StepContext} that can |
200 | * be used to store scoped bean instances. |
201 | * |
202 | * @return the current step context which we can use as a scope storage |
203 | * medium |
204 | */ |
205 | private StepContext getContext() { |
206 | StepContext context = StepSynchronizationManager.getContext(); |
207 | if (context == null) { |
208 | throw new IllegalStateException("No context holder available for step scope"); |
209 | } |
210 | return context; |
211 | } |
212 | |
213 | /** |
214 | * Register this scope with the enclosing BeanFactory. |
215 | * |
216 | * @see BeanFactoryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory) |
217 | * |
218 | * @param beanFactory the BeanFactory to register with |
219 | * @throws BeansException if there is a problem. |
220 | */ |
221 | @Override |
222 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { |
223 | |
224 | beanFactory.registerScope(name, this); |
225 | |
226 | if(!autoProxy) { |
227 | return; |
228 | } |
229 | |
230 | Assert.state(beanFactory instanceof BeanDefinitionRegistry, |
231 | "BeanFactory was not a BeanDefinitionRegistry, so StepScope cannot be used."); |
232 | BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; |
233 | |
234 | for (String beanName : beanFactory.getBeanDefinitionNames()) { |
235 | BeanDefinition definition = beanFactory.getBeanDefinition(beanName); |
236 | // Replace this or any of its inner beans with scoped proxy if it |
237 | // has this scope |
238 | boolean scoped = name.equals(definition.getScope()); |
239 | Scopifier scopifier = new Scopifier(registry, name, proxyTargetClass, scoped); |
240 | scopifier.visitBeanDefinition(definition); |
241 | |
242 | if (scoped && !definition.isAbstract()) { |
243 | createScopedProxy(beanName, definition, registry, proxyTargetClass); |
244 | } |
245 | } |
246 | |
247 | } |
248 | |
249 | /** |
250 | * Public setter for the name property. This can then be used as a bean |
251 | * definition attribute, e.g. scope="step". Defaults to "step". |
252 | * |
253 | * @param name the name to set for this scope. |
254 | */ |
255 | public void setName(String name) { |
256 | this.name = name; |
257 | } |
258 | |
259 | /** |
260 | * Wrap a target bean definition in a proxy that defers initialization until |
261 | * after the {@link StepContext} is available. Amounts to adding |
262 | * <aop-auto-proxy/> to a step scoped bean. |
263 | * |
264 | * @param beanName the bean name to replace |
265 | * @param definition the bean definition to replace |
266 | * @param registry the enclosing {@link BeanDefinitionRegistry} |
267 | * @param proxyTargetClass true if we need to force use of dynamic |
268 | * subclasses |
269 | * @return a {@link BeanDefinitionHolder} for the new representation of the |
270 | * target. Caller should register it if needed to be visible at top level in |
271 | * bean factory. |
272 | */ |
273 | private static BeanDefinitionHolder createScopedProxy(String beanName, BeanDefinition definition, |
274 | BeanDefinitionRegistry registry, boolean proxyTargetClass) { |
275 | |
276 | BeanDefinitionHolder proxyHolder; |
277 | |
278 | proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry, |
279 | proxyTargetClass); |
280 | |
281 | registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition()); |
282 | |
283 | return proxyHolder; |
284 | |
285 | } |
286 | |
287 | /** |
288 | * Helper class to scan a bean definition hierarchy and force the use of |
289 | * auto-proxy for step scoped beans. |
290 | * |
291 | * @author Dave Syer |
292 | * |
293 | */ |
294 | private static class Scopifier extends BeanDefinitionVisitor { |
295 | |
296 | private final boolean proxyTargetClass; |
297 | |
298 | private final BeanDefinitionRegistry registry; |
299 | |
300 | private final String scope; |
301 | |
302 | private final boolean scoped; |
303 | |
304 | public Scopifier(BeanDefinitionRegistry registry, String scope, boolean proxyTargetClass, boolean scoped) { |
305 | super(new StringValueResolver() { |
306 | @Override |
307 | public String resolveStringValue(String value) { |
308 | return value; |
309 | } |
310 | }); |
311 | this.registry = registry; |
312 | this.proxyTargetClass = proxyTargetClass; |
313 | this.scope = scope; |
314 | this.scoped = scoped; |
315 | } |
316 | |
317 | @Override |
318 | protected Object resolveValue(Object value) { |
319 | |
320 | BeanDefinition definition = null; |
321 | String beanName = null; |
322 | if (value instanceof BeanDefinition) { |
323 | definition = (BeanDefinition) value; |
324 | beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry); |
325 | } |
326 | else if (value instanceof BeanDefinitionHolder) { |
327 | BeanDefinitionHolder holder = (BeanDefinitionHolder) value; |
328 | definition = holder.getBeanDefinition(); |
329 | beanName = holder.getBeanName(); |
330 | } |
331 | |
332 | if (definition != null) { |
333 | boolean nestedScoped = scope.equals(definition.getScope()); |
334 | boolean scopeChangeRequiresProxy = !scoped && nestedScoped; |
335 | if (scopeChangeRequiresProxy) { |
336 | // Exit here so that nested inner bean definitions are not |
337 | // analysed |
338 | return createScopedProxy(beanName, definition, registry, proxyTargetClass); |
339 | } |
340 | } |
341 | |
342 | // Nested inner bean definitions are recursively analysed here |
343 | value = super.resolveValue(value); |
344 | return value; |
345 | |
346 | } |
347 | |
348 | } |
349 | } |