EMMA Coverage Report (generated Fri Aug 21 15:59:46 BST 2009)
[all classes][org.springframework.batch.core.scope]

COVERAGE SUMMARY FOR SOURCE FILE [StepScope.java]

nameclass, %method, %block, %line, %
StepScope.java100% (5/5)96%  (22/23)97%  (428/440)98%  (99.7/102)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class StepScope100% (1/1)93%  (13/14)96%  (242/252)96%  (53.8/56)
resolveContextualObject (String): Object 0%   (0/1)0%   (0/7)0%   (0/2)
get (String, ObjectFactory): Object 100% (1/1)94%  (48/51)98%  (9.8/10)
StepScope (): void 100% (1/1)100% (22/22)100% (6/6)
access$0 (String, BeanDefinition, BeanDefinitionRegistry, boolean): BeanDefin... 100% (1/1)100% (6/6)100% (1/1)
createScopedProxy (String, BeanDefinition, BeanDefinitionRegistry, boolean): ... 100% (1/1)100% (16/16)100% (4/4)
getContext (): StepContext 100% (1/1)100% (11/11)100% (4/4)
getConversationId (): String 100% (1/1)100% (6/6)100% (2/2)
getOrder (): int 100% (1/1)100% (3/3)100% (1/1)
postProcessBeanFactory (ConfigurableListableBeanFactory): void 100% (1/1)100% (71/71)100% (13/13)
registerDestructionCallback (String, Runnable): void 100% (1/1)100% (24/24)100% (4/4)
remove (String): Object 100% (1/1)100% (23/23)100% (3/3)
setName (String): void 100% (1/1)100% (4/4)100% (2/2)
setOrder (int): void 100% (1/1)100% (4/4)100% (2/2)
setProxyTargetClass (boolean): void 100% (1/1)100% (4/4)100% (2/2)
     
class StepScope$ExpressionHider100% (1/1)100% (3/3)97%  (69/71)99%  (17.9/18)
resolveValue (Object): Object 100% (1/1)96%  (51/53)99%  (12.9/13)
StepScope$ExpressionHider (String, boolean): void 100% (1/1)100% (13/13)100% (4/4)
StepScope$ExpressionHider (String, boolean, StepScope$ExpressionHider): void 100% (1/1)100% (5/5)100% (1/1)
     
class StepScope$ExpressionHider$1100% (1/1)100% (2/2)100% (24/24)100% (5/5)
StepScope$ExpressionHider$1 (boolean): void 100% (1/1)100% (6/6)100% (2/2)
resolveStringValue (String): String 100% (1/1)100% (18/18)100% (3/3)
     
class StepScope$Scopifier100% (1/1)100% (2/2)100% (88/88)100% (23/23)
StepScope$Scopifier (BeanDefinitionRegistry, String, boolean, boolean): void 100% (1/1)100% (18/18)100% (6/6)
resolveValue (Object): Object 100% (1/1)100% (70/70)100% (17/17)
     
class StepScope$Scopifier$1100% (1/1)100% (2/2)100% (5/5)100% (3/3)
StepScope$Scopifier$1 (): void 100% (1/1)100% (3/3)100% (2/2)
resolveStringValue (String): String 100% (1/1)100% (2/2)100% (1/1)

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

[all classes][org.springframework.batch.core.scope]
EMMA 2.0.5312 (C) Vladimir Roubtsov