View Javadoc

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   * &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
53   * 	&lt;property name=&quot;parent&quot; ref=&quot;#{stepExecutionContext[helper]}&quot; /&gt;
54   * &lt;/bean&gt;
55   *
56   * &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
57   * 	&lt;property name=&quot;name&quot; value=&quot;#{stepExecutionContext['input.name']}&quot; /&gt;
58   * &lt;/bean&gt;
59   *
60   * &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
61   * 	&lt;property name=&quot;name&quot; value=&quot;#{jobParameters[input]}&quot; /&gt;
62   * &lt;/bean&gt;
63   *
64   * &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
65   * 	&lt;property name=&quot;name&quot; value=&quot;#{jobExecutionContext['input.stem']}.txt&quot; /&gt;
66   * &lt;/bean&gt;
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 &#64;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 
148 		StepContext context = getContext();
149 		Object scopedObject = context.getAttribute(name);
150 
151 		if (scopedObject == null) {
152 
153 			synchronized (mutex) {
154 				scopedObject = context.getAttribute(name);
155 				if (scopedObject == null) {
156 
157 					logger.debug(String.format("Creating object in scope=%s, name=%s", this.name, name));
158 
159 					scopedObject = objectFactory.getObject();
160 					context.setAttribute(name, scopedObject);
161 
162 				}
163 
164 			}
165 
166 		}
167 		return scopedObject;
168 	}
169 
170 	/**
171 	 * @see Scope#getConversationId()
172 	 */
173 	@Override
174 	public String getConversationId() {
175 		StepContext context = getContext();
176 		return context.getId();
177 	}
178 
179 	/**
180 	 * @see Scope#registerDestructionCallback(String, Runnable)
181 	 */
182 	@Override
183 	public void registerDestructionCallback(String name, Runnable callback) {
184 		StepContext context = getContext();
185 		logger.debug(String.format("Registered destruction callback in scope=%s, name=%s", this.name, name));
186 		context.registerDestructionCallback(name, callback);
187 	}
188 
189 	/**
190 	 * @see Scope#remove(String)
191 	 */
192 	@Override
193 	public Object remove(String name) {
194 		StepContext context = getContext();
195 		logger.debug(String.format("Removing from scope=%s, name=%s", this.name, name));
196 		return context.removeAttribute(name);
197 	}
198 
199 	/**
200 	 * Get an attribute accessor in the form of a {@link StepContext} that can
201 	 * be used to store scoped bean instances.
202 	 *
203 	 * @return the current step context which we can use as a scope storage
204 	 * medium
205 	 */
206 	private StepContext getContext() {
207 		StepContext context = StepSynchronizationManager.getContext();
208 		if (context == null) {
209 			throw new IllegalStateException("No context holder available for step scope");
210 		}
211 		return context;
212 	}
213 
214 	/**
215 	 * Register this scope with the enclosing BeanFactory.
216 	 *
217 	 * @see BeanFactoryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory)
218 	 *
219 	 * @param beanFactory the BeanFactory to register with
220 	 * @throws BeansException if there is a problem.
221 	 */
222 	@Override
223 	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
224 
225 		beanFactory.registerScope(name, this);
226 
227 		if(!autoProxy) {
228 			return;
229 		}
230 
231 		Assert.state(beanFactory instanceof BeanDefinitionRegistry,
232 				"BeanFactory was not a BeanDefinitionRegistry, so StepScope cannot be used.");
233 		BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
234 
235 		for (String beanName : beanFactory.getBeanDefinitionNames()) {
236 			BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
237 			// Replace this or any of its inner beans with scoped proxy if it
238 			// has this scope
239 			boolean scoped = name.equals(definition.getScope());
240 			Scopifier scopifier = new Scopifier(registry, name, proxyTargetClass, scoped);
241 			scopifier.visitBeanDefinition(definition);
242 			if (scoped) {
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 	 * &lt;aop-auto-proxy/&gt; 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 }