BATCH-1701: Implementation of JobScope

* Updated Job and Step scopes to use common class introduced by jpraet
* Updated Job and Step Synchronization Managers to use common class
 introduced by jpraet
* Updated JobScope related code to not be concerned with Spring 2.5
 (which is what batch was on when the PR was opened).
This commit is contained in:
Michael Minella
2013-12-13 00:00:02 -06:00
parent 2436d84210
commit 241d9f101f
20 changed files with 439 additions and 592 deletions

View File

@@ -335,9 +335,9 @@ InitializingBean {
if (execution.getStatus().isLessThanOrEqualTo(BatchStatus.STOPPED)
&& execution.getStepExecutions().isEmpty()) {
ExitStatus exitStatus = execution.getExitStatus();
execution
.setExitStatus(exitStatus.and(ExitStatus.NOOP
.addExitDescription("All steps already completed or no steps configured for this job.")));
ExitStatus newExitStatus =
ExitStatus.NOOP.addExitDescription("All steps already completed or no steps configured for this job.");
execution.setExitStatus(exitStatus.and(newExitStatus));
}
execution.setEndTime(new Date());

View File

@@ -0,0 +1,226 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.scope;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.batch.core.scope.context.StepContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
import org.springframework.util.StringValueResolver;
/**
* ScopeSupport.
*
* @author Michael Minella
* @since 3.0
*/
public abstract class BatchScopeSupport implements Scope, BeanFactoryPostProcessor, Ordered {
private boolean autoProxy = true;
private boolean proxyTargetClass = false;
private String name;
private int order = Ordered.LOWEST_PRECEDENCE;
/**
* @param order the order value to set priority of callback execution for
* the {@link BeanFactoryPostProcessor} part of this scope bean.
*/
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return order;
}
public String getName() {
return this.name;
}
/**
* Public setter for the name property. This can then be used as a bean
* definition attribute, e.g. scope="job".
*
* @param name the name to set for this scope.
*/
public void setName(String name) {
this.name = name;
}
/**
* Flag to indicate that proxies should use dynamic subclassing. This allows
* classes with no interface to be proxied. Defaults to false.
*
* @param proxyTargetClass set to true to have proxies created using dynamic
* subclasses
*/
public void setProxyTargetClass(boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
}
/**
* Flag to indicate that bean definitions need not be auto proxied. This gives control back to the declarer of the
* bean definition (e.g. in an @Configuration class).
*
* @param autoProxy the flag value to set (default true)
*/
public void setAutoProxy(boolean autoProxy) {
this.autoProxy = autoProxy;
}
public abstract String getTargetNamePrefix();
/**
* Register this scope with the enclosing BeanFactory.
*
* @see BeanFactoryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory)
*
* @param beanFactory the BeanFactory to register with
* @throws BeansException if there is a problem.
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope(name, this);
if(!autoProxy) {
return;
}
Assert.state(beanFactory instanceof BeanDefinitionRegistry,
"BeanFactory was not a BeanDefinitionRegistry, so JobScope cannot be used.");
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (String beanName : beanFactory.getBeanDefinitionNames()) {
if (!beanName.startsWith(getTargetNamePrefix())) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
// Replace this or any of its inner beans with scoped proxy if it
// has this scope
boolean scoped = name.equals(definition.getScope());
Scopifier scopifier = new Scopifier(registry, name, proxyTargetClass, scoped);
scopifier.visitBeanDefinition(definition);
if (scoped && !definition.isAbstract()) {
createScopedProxy(beanName, definition, registry, proxyTargetClass);
}
}
}
}
/**
* Wrap a target bean definition in a proxy that defers initialization until
* after the {@link StepContext} is available. Amounts to adding
* <aop-auto-proxy/> to a step scoped bean.
*
* @param beanName the bean name to replace
* @param definition the bean definition to replace
* @param registry the enclosing {@link BeanDefinitionRegistry}
* @param proxyTargetClass true if we need to force use of dynamic
* subclasses
* @return a {@link BeanDefinitionHolder} for the new representation of the
* target. Caller should register it if needed to be visible at top level in
* bean factory.
*/
protected static BeanDefinitionHolder createScopedProxy(String beanName, BeanDefinition definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
BeanDefinitionHolder proxyHolder;
proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry,
proxyTargetClass);
registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition());
return proxyHolder;
}
/**
* Helper class to scan a bean definition hierarchy and force the use of
* auto-proxy for step scoped beans.
*
* @author Dave Syer
*
*/
protected static class Scopifier extends BeanDefinitionVisitor {
private final boolean proxyTargetClass;
private final BeanDefinitionRegistry registry;
private final String scope;
private final boolean scoped;
public Scopifier(BeanDefinitionRegistry registry, String scope, boolean proxyTargetClass, boolean scoped) {
super(new StringValueResolver() {
@Override
public String resolveStringValue(String value) {
return value;
}
});
this.registry = registry;
this.proxyTargetClass = proxyTargetClass;
this.scope = scope;
this.scoped = scoped;
}
@Override
protected Object resolveValue(Object value) {
BeanDefinition definition = null;
String beanName = null;
if (value instanceof BeanDefinition) {
definition = (BeanDefinition) value;
beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry);
}
else if (value instanceof BeanDefinitionHolder) {
BeanDefinitionHolder holder = (BeanDefinitionHolder) value;
definition = holder.getBeanDefinition();
beanName = holder.getBeanName();
}
if (definition != null) {
boolean nestedScoped = scope.equals(definition.getScope());
boolean scopeChangeRequiresProxy = !scoped && nestedScoped;
if (scopeChangeRequiresProxy) {
// Exit here so that nested inner bean definitions are not
// analysed
return createScopedProxy(beanName, definition, registry, proxyTargetClass);
}
}
// Nested inner bean definitions are recursively analysed here
value = super.resolveValue(value);
return value;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* Copyright 2006-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.scope.context.JobContext;
import org.springframework.batch.core.scope.context.JobSynchronizationManager;
import org.springframework.batch.core.scope.util.ContextFactory;
import org.springframework.batch.core.scope.util.JobContextFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.ObjectFactory;
@@ -32,31 +30,34 @@ import org.springframework.beans.factory.config.Scope;
* job. All objects in this scope are <aop:scoped-proxy/> (no need to
* decorate the bean definitions).<br/>
* <br/>
*
*
* In addition, support is provided for late binding of references accessible
* from the {@link JobContext} using #{..} placeholders. Using this feature,
* bean properties can be pulled from the job or job execution context and the
* job parameters. E.g.
*
*
* <pre>
* &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;job&quot;&gt;
* &lt;property name=&quot;name&quot; value=&quot;#{jobParameters[input]}&quot; /&gt;
* &lt;/bean&gt;
*
*
* &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;job&quot;&gt;
* &lt;property name=&quot;name&quot; value=&quot;#{jobExecutionContext['input.stem']}.txt&quot; /&gt;
* &lt;/bean&gt;
* </pre>
*
*
* The {@link JobContext} is referenced using standard bean property paths (as
* per {@link BeanWrapper}). The examples above all show the use of the Map
* accessors provided as a convenience for job attributes.
*
*
* @author Dave Syer
* @author Jimmy Praet (create JobScope based on {@link StepScope})
* @since 2.0
* @author Michael Minella
* @since 3.0
*/
public class JobScope extends ScopeSupport {
public class JobScope extends BatchScopeSupport {
private static final String TARGET_NAME_PREFIX = "jobScopedTarget.";
private Log logger = LogFactory.getLog(getClass());
@@ -67,20 +68,15 @@ public class JobScope extends ScopeSupport {
*/
public static final String ID_KEY = "JOB_IDENTIFIER";
/**
* The ContextFactory.
*/
private static final ContextFactory CONTEXT_FACTORY = new JobContextFactory();
public JobScope() {
super("job", CONTEXT_FACTORY);
super();
setName("job");
}
/**
* If Spring 3.0 is available, this will be used to resolve expressions in
* job-scoped beans. This method is part of the Scope SPI in Spring 3.0,
* but should just be ignored by earlier versions of Spring.
* This will be used to resolve expressions in job-scoped beans.
*/
@Override
public Object resolveContextualObject(String key) {
JobContext context = getContext();
// TODO: support for attributes as well maybe (setters not exposed yet
@@ -91,8 +87,9 @@ public class JobScope extends ScopeSupport {
/**
* @see Scope#get(String, ObjectFactory)
*/
@SuppressWarnings("rawtypes")
@Override
public Object get(String name, ObjectFactory objectFactory) {
JobContext context = getContext();
Object scopedObject = context.getAttribute(name);
@@ -118,6 +115,7 @@ public class JobScope extends ScopeSupport {
/**
* @see Scope#getConversationId()
*/
@Override
public String getConversationId() {
JobContext context = getContext();
return context.getId();
@@ -126,6 +124,7 @@ public class JobScope extends ScopeSupport {
/**
* @see Scope#registerDestructionCallback(String, Runnable)
*/
@Override
public void registerDestructionCallback(String name, Runnable callback) {
JobContext context = getContext();
logger.debug(String.format("Registered destruction callback in scope=%s, name=%s", this.getName(), name));
@@ -135,6 +134,7 @@ public class JobScope extends ScopeSupport {
/**
* @see Scope#remove(String)
*/
@Override
public Object remove(String name) {
JobContext context = getContext();
logger.debug(String.format("Removing from scope=%s, name=%s", this.getName(), name));
@@ -144,7 +144,7 @@ public class JobScope extends ScopeSupport {
/**
* Get an attribute accessor in the form of a {@link JobContext} that can
* be used to store scoped bean instances.
*
*
* @return the current job context which we can use as a scope storage
* medium
*/
@@ -156,4 +156,8 @@ public class JobScope extends ScopeSupport {
return context;
}
}
@Override
public String getTargetNamePrefix() {
return TARGET_NAME_PREFIX;
}
}

View File

@@ -1,316 +0,0 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.scope;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.batch.core.scope.util.ContextFactory;
import org.springframework.batch.core.scope.util.PlaceholderProxyFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringValueResolver;
/**
* ScopeSupport.
*
* @author Dave Syer
*/
public abstract class ScopeSupport implements Scope, BeanFactoryPostProcessor, Ordered {
private static boolean springThreeDetected;
private static boolean cachedSpringThreeResult;
private int order = Ordered.LOWEST_PRECEDENCE;
private String name;
private boolean proxyTargetClass = false;
private ContextFactory contextFactory;
/**
* ScopeSupport constructor.
*
* @param defaultScopeName the default scope name
* @param contextFactory the ContextFactory
*/
protected ScopeSupport(String defaultScopeName, ContextFactory contextFactory) {
this.name = defaultScopeName;
this.contextFactory = contextFactory;
}
/**
* Flag to indicate that proxies should use dynamic subclassing. This allows
* classes with no interface to be proxied. Defaults to false.
*
* @param proxyTargetClass set to true to have proxies created using dynamic
* subclasses
*/
public void setProxyTargetClass(boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
}
/**
* @param order the order value to set priority of callback execution for
* the {@link BeanFactoryPostProcessor} part of this scope bean.
*/
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return order;
}
/**
* Public setter for the name property. This can then be used as a bean
* definition attribute, e.g. scope="step".
*
* @param name the name to set for this scope.
*/
public void setName(String name) {
this.name = name;
}
/**
* Return the scope name.
*
* @return the scope name
*/
public String getName() {
return name;
}
/**
* Register this scope with the enclosing BeanFactory.
*
* @see BeanFactoryPostProcessor#postProcessBeanFactory(ConfigurableListableBeanFactory)
*
* @param beanFactory the BeanFactory to register with
* @throws BeansException if there is a problem.
*/
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope(name, this);
Assert.state(beanFactory instanceof BeanDefinitionRegistry,
"BeanFactory was not a BeanDefinitionRegistry, so scope cannot be used.");
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
// Replace this or any of its inner beans with scoped proxy if it
// has this scope
boolean scoped = name.equals(definition.getScope());
Scopifier scopifier = new Scopifier(registry, name, proxyTargetClass, scoped, contextFactory);
scopifier.visitBeanDefinition(definition);
if (scoped) {
if (!isSpringThree()) {
new ExpressionHider(name, scoped).visitBeanDefinition(definition);
}
createScopedProxy(beanName, definition, registry, proxyTargetClass, contextFactory);
}
}
}
private static boolean isSpringThree() {
if (!cachedSpringThreeResult) {
springThreeDetected = ReflectionUtils.findMethod(Scope.class, "resolveContextualObject",
new Class<?>[] { String.class }) != null;
cachedSpringThreeResult = true;
}
return springThreeDetected;
}
/**
* Wrap a target bean definition in a proxy that defers initialization until
* after the context is available. Amounts to adding
* &lt;aop-auto-proxy/&gt; to a scoped bean. Also if Spring EL is not
* available will enable a weak version of late binding as described in the
* class-level docs.
*
* @param beanName the bean name to replace
* @param definition the bean definition to replace
* @param registry the enclosing {@link BeanDefinitionRegistry}
* @param proxyTargetClass true if we need to force use of dynamic
* subclasses
* @param contextFactory the ContextFactory
* @return a {@link BeanDefinitionHolder} for the new representation of the
* target. Caller should register it if needed to be visible at top level in
* bean factory.
*/
private static BeanDefinitionHolder createScopedProxy(String beanName, BeanDefinition definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass, ContextFactory contextFactory) {
BeanDefinitionHolder proxyHolder;
if (isSpringThree()) {
proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry,
proxyTargetClass);
}
else {
// Create the scoped proxy...
proxyHolder = PlaceholderProxyFactoryBean.createScopedProxy(new BeanDefinitionHolder(definition, beanName),
registry, proxyTargetClass, contextFactory);
// ...and register it under the original target name
}
registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition());
return proxyHolder;
}
/**
* Helper class to scan a bean definition hierarchy and force the use of
* auto-proxy for scoped beans.
*
* @author Dave Syer
*
*/
private static class Scopifier extends BeanDefinitionVisitor {
private final boolean proxyTargetClass;
private final BeanDefinitionRegistry registry;
private final String scope;
private final boolean scoped;
private final ContextFactory contextFactory;
public Scopifier(BeanDefinitionRegistry registry, String scope, boolean proxyTargetClass, boolean scoped,
ContextFactory contextFactory) {
super(new StringValueResolver() {
public String resolveStringValue(String value) {
return value;
}
});
this.registry = registry;
this.proxyTargetClass = proxyTargetClass;
this.scope = scope;
this.scoped = scoped;
this.contextFactory = contextFactory;
}
@Override
protected Object resolveValue(Object value) {
BeanDefinition definition = null;
String beanName = null;
if (value instanceof BeanDefinition) {
definition = (BeanDefinition) value;
beanName = BeanDefinitionReaderUtils.generateBeanName(definition, registry);
}
else if (value instanceof BeanDefinitionHolder) {
BeanDefinitionHolder holder = (BeanDefinitionHolder) value;
definition = holder.getBeanDefinition();
beanName = holder.getBeanName();
}
if (definition != null) {
boolean nestedScoped = scope.equals(definition.getScope());
boolean scopeChangeRequiresProxy = !scoped && nestedScoped;
if (!isSpringThree()) {
new ExpressionHider(scope, nestedScoped).visitBeanDefinition(definition);
}
if (scopeChangeRequiresProxy) {
// Exit here so that nested inner bean definitions are not
// analysed
return createScopedProxy(beanName, definition, registry, proxyTargetClass, contextFactory);
}
}
// Nested inner bean definitions are recursively analysed here
value = super.resolveValue(value);
return value;
}
}
/**
* Helper class to scan a bean definition hierarchy and hide placeholders
* from Spring EL.
*
* @author Dave Syer
*
*/
private static class ExpressionHider extends BeanDefinitionVisitor {
private static final String PLACEHOLDER_PREFIX = "#{";
private static final String PLACEHOLDER_SUFFIX = "}";
private static final String REPLACEMENT_PREFIX = "%{";
private final String scope;
private final boolean scoped;
private ExpressionHider(String scope, final boolean scoped) {
super(new StringValueResolver() {
public String resolveStringValue(String value) {
if (scoped && value.contains(PLACEHOLDER_PREFIX) && value.contains(PLACEHOLDER_SUFFIX)) {
value = value.replace(PLACEHOLDER_PREFIX, REPLACEMENT_PREFIX);
}
return value;
}
});
this.scope = scope;
this.scoped = scoped;
}
@Override
protected Object resolveValue(Object value) {
BeanDefinition definition = null;
if (value instanceof BeanDefinition) {
definition = (BeanDefinition) value;
}
else if (value instanceof BeanDefinitionHolder) {
BeanDefinitionHolder holder = (BeanDefinitionHolder) value;
definition = holder.getBeanDefinition();
}
if (definition != null) {
String otherScope = definition.getScope();
boolean scopeChange = !scope.equals(otherScope);
if (scopeChange) {
new ExpressionHider(otherScope == null ? scope : otherScope, !scoped)
.visitBeanDefinition(definition);
// Exit here so that nested inner bean definitions are not
// analysed by both visitors
return value;
}
}
// Nested inner bean definitions are recursively analysed here
value = super.resolveValue(value);
return value;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* Copyright 2006-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.scope.context.StepContext;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
import org.springframework.batch.core.scope.util.ContextFactory;
import org.springframework.batch.core.scope.util.StepContextFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.ObjectFactory;
@@ -32,38 +30,41 @@ import org.springframework.beans.factory.config.Scope;
* step. All objects in this scope are &lt;aop:scoped-proxy/&gt; (no need to
* decorate the bean definitions).<br/>
* <br/>
*
*
* In addition, support is provided for late binding of references accessible
* from the {@link StepContext} using #{..} placeholders. Using this feature,
* bean properties can be pulled from the step or job execution context and the
* job parameters. E.g.
*
*
* <pre>
* &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
* &lt;property name=&quot;parent&quot; ref=&quot;#{stepExecutionContext[helper]}&quot; /&gt;
* &lt;/bean&gt;
*
*
* &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
* &lt;property name=&quot;name&quot; value=&quot;#{stepExecutionContext['input.name']}&quot; /&gt;
* &lt;/bean&gt;
*
*
* &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
* &lt;property name=&quot;name&quot; value=&quot;#{jobParameters[input]}&quot; /&gt;
* &lt;/bean&gt;
*
*
* &lt;bean id=&quot;...&quot; class=&quot;...&quot; scope=&quot;step&quot;&gt;
* &lt;property name=&quot;name&quot; value=&quot;#{jobExecutionContext['input.stem']}.txt&quot; /&gt;
* &lt;/bean&gt;
* </pre>
*
*
* The {@link StepContext} is referenced using standard bean property paths (as
* per {@link BeanWrapper}). The examples above all show the use of the Map
* accessors provided as a convenience for step and job attributes.
*
*
* @author Dave Syer
* @author Michael Minella
* @since 2.0
*/
public class StepScope extends ScopeSupport {
public class StepScope extends BatchScopeSupport {
private static final String TARGET_NAME_PREFIX = "stepScopedTarget.";
private Log logger = LogFactory.getLog(getClass());
@@ -74,20 +75,15 @@ public class StepScope extends ScopeSupport {
*/
public static final String ID_KEY = "STEP_IDENTIFIER";
/**
* The ContextFactory.
*/
private static final ContextFactory CONTEXT_FACTORY = new StepContextFactory();
public StepScope() {
super("step", CONTEXT_FACTORY);
super();
setName("step");
}
/**
* If Spring 3.0 is available, this will be used to resolve expressions in
* step-scoped beans. This method is part of the Scope SPI in Spring 3.0,
* but should just be ignored by earlier versions of Spring.
* This will be used to resolve expressions in step-scoped beans.
*/
@Override
public Object resolveContextualObject(String key) {
StepContext context = getContext();
// TODO: support for attributes as well maybe (setters not exposed yet
@@ -98,8 +94,9 @@ public class StepScope extends ScopeSupport {
/**
* @see Scope#get(String, ObjectFactory)
*/
@SuppressWarnings("rawtypes")
@Override
public Object get(String name, ObjectFactory objectFactory) {
StepContext context = getContext();
Object scopedObject = context.getAttribute(name);
@@ -125,6 +122,7 @@ public class StepScope extends ScopeSupport {
/**
* @see Scope#getConversationId()
*/
@Override
public String getConversationId() {
StepContext context = getContext();
return context.getId();
@@ -133,6 +131,7 @@ public class StepScope extends ScopeSupport {
/**
* @see Scope#registerDestructionCallback(String, Runnable)
*/
@Override
public void registerDestructionCallback(String name, Runnable callback) {
StepContext context = getContext();
logger.debug(String.format("Registered destruction callback in scope=%s, name=%s", this.getName(), name));
@@ -142,6 +141,7 @@ public class StepScope extends ScopeSupport {
/**
* @see Scope#remove(String)
*/
@Override
public Object remove(String name) {
StepContext context = getContext();
logger.debug(String.format("Removing from scope=%s, name=%s", this.getName(), name));
@@ -151,7 +151,7 @@ public class StepScope extends ScopeSupport {
/**
* Get an attribute accessor in the form of a {@link StepContext} that can
* be used to store scoped bean instances.
*
*
* @return the current step context which we can use as a scope storage
* medium
*/
@@ -163,4 +163,8 @@ public class StepScope extends ScopeSupport {
return context;
}
@Override
public String getTargetNamePrefix() {
return TARGET_NAME_PREFIX;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* Copyright 2006-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,9 +41,10 @@ import org.springframework.util.Assert;
* based on bean paths. Has public getters for the job execution and
* convenience methods for accessing commonly used properties like the {@link ExecutionContext} associated with the job
* execution.
*
*
* @author Dave Syer
* @author Jimmy Praet (create JobContext based on {@link StepContext})
* @since 3.0
*/
public class JobContext extends SynchronizedAttributeAccessor {
@@ -59,19 +60,19 @@ public class JobContext extends SynchronizedAttributeAccessor {
/**
* Convenient accessor for current job name identifier.
*
*
* @return the job name identifier of the enclosing {@link JobInstance} associated with the current
* {@link JobExecution}
*/
public String getJobName() {
Assert.state(jobExecution.getJobInstance() != null, "StepExecution does not have a JobInstance");
Assert.state(jobExecution.getJobInstance() != null, "JobExecution does not have a JobInstance");
return jobExecution.getJobInstance().getJobName();
}
/**
* Convenient accessor for System properties to make it easy to access them
* from placeholder expressions.
*
*
* @return the current System properties
*/
public Properties getSystemProperties() {
@@ -94,7 +95,7 @@ public class JobContext extends SynchronizedAttributeAccessor {
*/
public Map<String, Object> getJobParameters() {
Map<String, Object> result = new HashMap<String, Object>();
for (Entry<String, JobParameter> entry : jobExecution.getJobInstance().getJobParameters().getParameters()
for (Entry<String, JobParameter> entry : jobExecution.getJobParameters().getParameters()
.entrySet()) {
result.put(entry.getKey(), entry.getValue().getValue());
}
@@ -103,7 +104,7 @@ public class JobContext extends SynchronizedAttributeAccessor {
/**
* Allow clients to register callbacks for clean up on close.
*
*
* @param name
* the callback id (unique attribute key in this context)
* @param callback
@@ -129,7 +130,7 @@ public class JobContext extends SynchronizedAttributeAccessor {
/**
* Override base class behaviour to ensure destruction callbacks are
* unregistered as well as the default behaviour.
*
*
* @see SynchronizedAttributeAccessor#removeAttribute(String)
*/
@Override
@@ -182,7 +183,7 @@ public class JobContext extends SynchronizedAttributeAccessor {
/**
* The current {@link JobExecution} that is active in this context.
*
*
* @return the current {@link JobExecution}
*/
public JobExecution getJobExecution() {
@@ -204,10 +205,12 @@ public class JobContext extends SynchronizedAttributeAccessor {
*/
@Override
public boolean equals(Object other) {
if (!(other instanceof JobContext))
if (!(other instanceof JobContext)) {
return false;
if (other == this)
}
if (other == this) {
return true;
}
JobContext context = (JobContext) other;
if (context.jobExecution == jobExecution) {
return true;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,9 +25,10 @@ import org.springframework.batch.core.JobExecution;
* Convenient aspect to wrap a single threaded job execution, where the
* implementation of the {@link Job} is not job scope aware (i.e. not the ones
* provided by the framework).
*
*
* @author Dave Syer
* @author Jimmy Praet
* @since 3.0
*/
@Aspect
public class JobScopeManager {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package org.springframework.batch.core.scope.context;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext;
/**
* Central convenience class for framework use in managing the job scope
@@ -24,71 +25,68 @@ import org.springframework.batch.core.JobExecution;
* it is the responsibility of every {@link Job} implementation to ensure that
* a {@link JobContext} is available on every thread that might be involved in
* a job execution, including worker threads from a pool.
*
*
* @author Dave Syer
* @author Jimmy Praet
* @since 3.0
*/
public class JobSynchronizationManager {
private static final SynchronizationManagerSupport<JobExecution, JobContext> synchronizationManager =
new SynchronizationManagerSupport<JobExecution, JobContext>() {
private static final SynchronizationManagerSupport<JobExecution, JobContext> manager = new SynchronizationManagerSupport<JobExecution, JobContext>() {
@Override
protected JobContext createNewContext(JobExecution execution) {
return new JobContext(execution);
}
@Override
protected JobContext createNewContext(JobExecution execution, BatchPropertyContext args) {
return new JobContext(execution);
}
@Override
protected void close(JobContext context) {
context.close();
}
};
/*
* We have to deal with single and multi-threaded execution, with a single
* and with multiple job execution instances. That's 2x2 = 4 scenarios.
*/
@Override
protected void close(JobContext context) {
context.close();
}
};
/**
* Getter for the current context if there is one, otherwise returns null.
*
*
* @return the current {@link JobContext} or null if there is none (if one
* has not been registered for this thread).
* has not been registered for this thread).
*/
public static JobContext getContext() {
return synchronizationManager.getContext();
return manager.getContext();
}
/**
* Register a context with the current thread - always put a matching {@link #close()} call in a finally block to
* ensure that the correct context is available in the enclosing block.
*
* @param jobExecution the job context to register
* @return a new {@link JobContext} or the current one if it has the same {@link JobExecution}
* Register a context with the current thread - always put a matching
* {@link #close()} call in a finally block to ensure that the correct
* context is available in the enclosing block.
*
* @param JobExecution the step context to register
* @return a new {@link JobContext} or the current one if it has the same
* {@link JobExecution}
*/
public static JobContext register(JobExecution jobExecution) {
return synchronizationManager.register(jobExecution);
public static JobContext register(JobExecution JobExecution) {
return manager.register(JobExecution);
}
/**
* Method for de-registering the current context - should always and only be
* used by in conjunction with a matching {@link #register(JobExecution)} to ensure that {@link #getContext()}
* always returns the correct value.
* used by in conjunction with a matching {@link #register(JobExecution)}
* to ensure that {@link #getContext()} always returns the correct value.
* Does not call {@link JobContext#close()} - that is left up to the caller
* because he has a reference to the context (having registered it) and only
* he has knowledge of when the job actually ended.
* he has knowledge of when the step actually ended.
*/
public static void close() {
synchronizationManager.close();
manager.close();
}
/**
* A convenient "deep" close operation. Call this instead of {@link #close()} if the job execution for the current
* context is ending.
* Delegates to {@link JobContext#close()} and then ensures that {@link #close()} is also called in a finally block.
* A convenient "deep" close operation. Call this instead of
* {@link #close()} if the step execution for the current context is ending.
* Delegates to {@link JobContext#close()} and then ensures that
* {@link #close()} is also called in a finally block.
*/
public static void release() {
synchronizationManager.release();
manager.release();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* Copyright 2006-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,24 +32,27 @@ import org.springframework.batch.core.jsr.configuration.support.BatchPropertyCon
*/
public class StepSynchronizationManager {
private static final SynchronizationManagerSupport<StepExecution, StepContext> synchronizationManager =
private static final SynchronizationManagerSupport<StepExecution, StepContext> manager =
new SynchronizationManagerSupport<StepExecution, StepContext>() {
@Override
protected StepContext createNewContext(StepExecution execution) {
return new StepContext(execution);
}
@Override
protected StepContext createNewContext(StepExecution execution, BatchPropertyContext propertyContext) {
StepContext context;
@Override
protected void close(StepContext context) {
context.close();
}
};
if(propertyContext != null) {
context = new StepContext(execution, propertyContext);
} else {
context = new StepContext(execution);
}
/*
* We have to deal with single and multi-threaded execution, with a single
* and with multiple step execution instances. That's 2x2 = 4 scenarios.
*/
return context;
}
@Override
protected void close(StepContext context) {
context.close();
}
};
/**
* Getter for the current context if there is one, otherwise returns null.
@@ -58,7 +61,7 @@ public class StepSynchronizationManager {
* has not been registered for this thread).
*/
public static StepContext getContext() {
return synchronizationManager.getContext();
return manager.getContext();
}
/**
@@ -71,7 +74,7 @@ public class StepSynchronizationManager {
* {@link StepExecution}
*/
public static StepContext register(StepExecution stepExecution) {
return synchronizationManager.register(stepExecution);
return manager.register(stepExecution);
}
/**
@@ -84,20 +87,7 @@ public class StepSynchronizationManager {
* {@link StepExecution}
*/
public static StepContext register(StepExecution stepExecution, BatchPropertyContext propertyContext) {
if (stepExecution == null) {
return null;
}
getCurrent().push(stepExecution);
StepContext context;
synchronized (contexts) {
context = contexts.get(stepExecution);
if (context == null) {
context = new StepContext(stepExecution, propertyContext);
contexts.put(stepExecution, context);
}
}
increment();
return context;
return manager.register(stepExecution, propertyContext);
}
/**
@@ -109,7 +99,7 @@ public class StepSynchronizationManager {
* he has knowledge of when the step actually ended.
*/
public static void close() {
synchronizationManager.close();
manager.close();
}
/**
@@ -119,7 +109,6 @@ public class StepSynchronizationManager {
* {@link #close()} is also called in a finally block.
*/
public static void release() {
synchronizationManager.release();
manager.release();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2007 the original author or authors.
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,12 +20,16 @@ import java.util.Map;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext;
/**
* Central convenience class for framework use in managing the scope
* context.
*
*
* @author Dave Syer
* @author Jimmy Praet
* @since 3.0
*/
public abstract class SynchronizationManagerSupport<E, C> {
@@ -58,7 +62,7 @@ public abstract class SynchronizationManagerSupport<E, C> {
/**
* Getter for the current context if there is one, otherwise returns null.
*
*
* @return the current context or null if there is none (if one
* has not been registered for this thread).
*/
@@ -75,7 +79,7 @@ public abstract class SynchronizationManagerSupport<E, C> {
* Register a context with the current thread - always put a matching {@link #close()} call in a finally block to
* ensure that the correct
* context is available in the enclosing block.
*
*
* @param execution the execution to register
* @return a new context or the current one if it has the same
* execution
@@ -89,7 +93,33 @@ public abstract class SynchronizationManagerSupport<E, C> {
synchronized (contexts) {
context = contexts.get(execution);
if (context == null) {
context = createNewContext(execution);
context = createNewContext(execution, null);
contexts.put(execution, context);
}
}
increment();
return context;
}
/**
* Register a context with the current thread - always put a matching {@link #close()} call in a finally block to
* ensure that the correct
* context is available in the enclosing block.
*
* @param execution the execution to register
* @return a new context or the current one if it has the same
* execution
*/
public C register(E execution, BatchPropertyContext propertyContext) {
if (execution == null) {
return null;
}
getCurrent().push(execution);
C context;
synchronized (contexts) {
context = contexts.get(execution);
if (context == null) {
context = createNewContext(execution, propertyContext);
contexts.put(execution, context);
}
}
@@ -99,7 +129,7 @@ public abstract class SynchronizationManagerSupport<E, C> {
/**
* Method for de-registering the current context - should always and only be
* used by in conjunction with a matching {@link #register(E)} to ensure that {@link #getContext()} always returns
* used by in conjunction with a matching {@link #register(Object)} to ensure that {@link #getContext()} always returns
* the correct value.
* Does not call close on the context - that is left up to the caller
* because he has a reference to the context (having registered it) and only
@@ -126,7 +156,7 @@ public abstract class SynchronizationManagerSupport<E, C> {
}
}
private void increment() {
public void increment() {
E current = getCurrent().peek();
if (current != null) {
AtomicInteger count;
@@ -141,7 +171,7 @@ public abstract class SynchronizationManagerSupport<E, C> {
}
}
private Stack<E> getCurrent() {
public Stack<E> getCurrent() {
if (executionHolder.get() == null) {
executionHolder.set(new Stack<E>());
}
@@ -151,7 +181,7 @@ public abstract class SynchronizationManagerSupport<E, C> {
/**
* A convenient "deep" close operation. Call this instead of {@link #close()} if the execution for the current
* context is ending.
* Delegates to {@link context#close()} and then ensures that {@link #close()} is also called in a finally block.
* Delegates to {@link #close(Object)} and then ensures that {@link #close()} is also called in a finally block.
*/
public void release() {
C context = getContext();
@@ -166,6 +196,6 @@ public abstract class SynchronizationManagerSupport<E, C> {
protected abstract void close(C context);
protected abstract C createNewContext(E execution);
protected abstract C createNewContext(E execution, BatchPropertyContext propertyContext);
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.scope.util;
import org.springframework.batch.core.scope.context.JobContext;
import org.springframework.batch.core.scope.context.JobSynchronizationManager;
/**
* Implementation of {@link ContextFactory} that provides the current
* {@link JobContext} as a context object.
*
* @author Dave Syer
* @author Jimmy Praet
*/
public class JobContextFactory implements ContextFactory {
public Object getContext() {
return JobSynchronizationManager.getContext();
}
public String getContextId() {
JobContext context = JobSynchronizationManager.getContext();
return context!=null ? (String) context.getId() : "sysinit";
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2006-2009 the original author or authors.
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,10 +30,10 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
* @author Jimmy Praet
*/
public class AutoRegisteringJobScopeTests {
@Test
public void testJobElement() throws Exception {
ConfigurableApplicationContext ctx =
ConfigurableApplicationContext ctx =
new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/AutoRegisteringJobScopeForJobElementTests-context.xml");
@SuppressWarnings("unchecked")
@@ -43,7 +43,7 @@ public class AutoRegisteringJobScopeTests {
@Test
public void testStepElement() throws Exception {
ConfigurableApplicationContext ctx =
ConfigurableApplicationContext ctx =
new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/AutoRegisteringJobScopeForStepElementTests-context.xml");
@SuppressWarnings("unchecked")

View File

@@ -22,6 +22,8 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
public class JobScopeIntegrationTests {
private static final String PROXY_TO_STRING_REGEX = "class .*\\$Proxy\\d+";
@Autowired
@Qualifier("vanilla")
private Job vanilla;
@@ -64,7 +66,7 @@ public class JobScopeIntegrationTests {
assertNotNull(collaborator);
assertEquals("bar", collaborator);
assertTrue("Scoped proxy not created", ((String) TestJob.getContext().getAttribute("collaborator.class"))
.startsWith("class $Proxy"));
.matches(PROXY_TO_STRING_REGEX));
}
@Test
@@ -78,7 +80,7 @@ public class JobScopeIntegrationTests {
assertNotNull(parent);
assertEquals("bar", parent);
assertTrue("Scoped proxy not created", ((String) TestJob.getContext().getAttribute("parent.class"))
.startsWith("class $Proxy"));
.matches(PROXY_TO_STRING_REGEX));
}
@Test

View File

@@ -37,10 +37,10 @@ public class StepScopePerformanceTests implements ApplicationContextAware {
public void start() throws Exception {
int count = doTest("vanilla", "warmup");
logger.info("Item count: "+count);
StepSynchronizationManager.close();
StepSynchronizationManager.register(new StepExecution("step", new JobExecution(0L),1L));
}
@Before
@After
public void cleanup() {
StepSynchronizationManager.close();

View File

@@ -24,6 +24,7 @@ import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
@@ -37,11 +38,20 @@ import org.springframework.batch.item.ExecutionContext;
*/
public class JobContextTests {
private List<String> list = new ArrayList<String>();
private List<String> list;
private JobExecution jobExecution = new JobExecution(new JobInstance(2L, null, "job"), 1L);
private JobExecution jobExecution;
private JobContext context = new JobContext(jobExecution);
private JobContext context;
@Before
public void setUp() {
jobExecution = new JobExecution(1l);
JobInstance jobInstance = new JobInstance(2l, "job");
jobExecution.setJobInstance(jobInstance);
context = new JobContext(jobExecution);
list = new ArrayList<String>();
}
@Test
public void testGetJobExecution() {
@@ -79,6 +89,7 @@ public class JobContextTests {
public void testDestructionCallbackSunnyDay() throws Exception {
context.setAttribute("foo", "FOO");
context.registerDestructionCallback("foo", new Runnable() {
@Override
public void run() {
list.add("bar");
}
@@ -91,6 +102,7 @@ public class JobContextTests {
@Test
public void testDestructionCallbackMissingAttribute() throws Exception {
context.registerDestructionCallback("foo", new Runnable() {
@Override
public void run() {
list.add("bar");
}
@@ -106,12 +118,14 @@ public class JobContextTests {
context.setAttribute("foo", "FOO");
context.setAttribute("bar", "BAR");
context.registerDestructionCallback("bar", new Runnable() {
@Override
public void run() {
list.add("spam");
throw new RuntimeException("fail!");
}
});
context.registerDestructionCallback("foo", new Runnable() {
@Override
public void run() {
list.add("bar");
throw new RuntimeException("fail!");
@@ -152,8 +166,10 @@ public class JobContextTests {
@Test
public void testJobParameters() throws Exception {
JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters();
JobInstance jobInstance = new JobInstance(0L, jobParameters, "foo");
JobInstance jobInstance = new JobInstance(0L, "foo");
jobExecution = new JobExecution(5l, jobParameters);
jobExecution.setJobInstance(jobInstance);
context = new JobContext(jobExecution);
assertEquals("bar", context.getJobParameters().get("foo"));
}
@@ -164,9 +180,7 @@ public class JobContextTests {
@Test(expected = IllegalStateException.class)
public void testIllegalContextId() throws Exception {
JobParameters jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters();
JobInstance jobInstance = new JobInstance(0L, jobParameters, "foo");
context = new JobContext(new JobExecution(jobInstance));
context = new JobContext(new JobExecution(null));
context.getId();
}

View File

@@ -4,10 +4,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -18,11 +16,10 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.util.ReflectionUtils;
/**
* JobSynchronizationManagerTests.
*
*
* @author Jimmy Praet
*/
public class JobSynchronizationManagerTests {
@@ -49,6 +46,7 @@ public class JobSynchronizationManagerTests {
final List<String> list = new ArrayList<String>();
JobContext context = JobSynchronizationManager.register(jobExecution);
context.registerDestructionCallback("foo", new Runnable() {
@Override
public void run() {
list.add("foo");
}
@@ -56,26 +54,14 @@ public class JobSynchronizationManagerTests {
JobSynchronizationManager.close();
assertNull(JobSynchronizationManager.getContext());
assertEquals(0, list.size());
// check for possible memory leak
assertEquals(0, extractStaticMap("counts").size());
assertEquals(0, extractStaticMap("contexts").size());
}
private Map<?, ?> extractStaticMap(String name) throws IllegalAccessException {
Field field = ReflectionUtils.findField(JobSynchronizationManager.class, "synchronizationManager");
ReflectionUtils.makeAccessible(field);
SynchronizationManagerSupport<?, ?> synchronizationManager =
(SynchronizationManagerSupport<?, ?>) field.get(JobSynchronizationManager.class);
field = ReflectionUtils.findField(SynchronizationManagerSupport.class, name);
ReflectionUtils.makeAccessible(field);
Map<?, ?> map = (Map<?, ?>) field.get(synchronizationManager);
return map;
}
@Test
public void testMultithreaded() throws Exception {
JobContext context = JobSynchronizationManager.register(jobExecution);
ExecutorService executorService = Executors.newFixedThreadPool(2);
FutureTask<JobContext> task = new FutureTask<JobContext>(new Callable<JobContext>() {
@Override
public JobContext call() throws Exception {
try {
JobSynchronizationManager.register(jobExecution);
@@ -100,6 +86,7 @@ public class JobSynchronizationManagerTests {
JobContext context = JobSynchronizationManager.register(jobExecution);
final List<String> list = new ArrayList<String>();
context.registerDestructionCallback("foo", new Runnable() {
@Override
public void run() {
list.add("foo");
}
@@ -130,4 +117,4 @@ public class JobSynchronizationManagerTests {
assertNull(JobSynchronizationManager.getContext());
}
}
}

View File

@@ -19,10 +19,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -35,7 +33,6 @@ import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext;
import org.springframework.util.ReflectionUtils;
public class StepSynchronizationManagerTests {
@@ -80,20 +77,6 @@ public class StepSynchronizationManagerTests {
StepSynchronizationManager.close();
assertNull(StepSynchronizationManager.getContext());
assertEquals(0, list.size());
// check for possible memory leak
assertEquals(0, extractStaticMap("counts").size());
assertEquals(0, extractStaticMap("contexts").size());
}
private Map<?, ?> extractStaticMap(String name) throws IllegalAccessException {
Field field = ReflectionUtils.findField(StepSynchronizationManager.class, "synchronizationManager");
ReflectionUtils.makeAccessible(field);
SynchronizationManagerSupport<?, ?> synchronizationManager =
(SynchronizationManagerSupport<?, ?>) field.get(StepSynchronizationManager.class);
field = ReflectionUtils.findField(SynchronizationManagerSupport.class, name);
ReflectionUtils.makeAccessible(field);
Map<?, ?> map = (Map<?, ?>) field.get(synchronizationManager);
return map;
}
@Test

View File

@@ -1,38 +0,0 @@
package org.springframework.batch.core.scope.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import org.junit.After;
import org.junit.Test;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.scope.context.JobContext;
import org.springframework.batch.core.scope.context.JobSynchronizationManager;
public class JobContextFactoryTests {
private JobContextFactory factory = new JobContextFactory();
@After
public void cleanUp() {
JobSynchronizationManager.close();
JobSynchronizationManager.close();
}
@Test
public void testGetContext() {
JobExecution jobExecution = new JobExecution(11L);
JobContext context = JobSynchronizationManager.register(jobExecution);
assertEquals(context, factory.getContext());
}
@Test
public void testGetContextId() {
JobSynchronizationManager.register(new JobExecution(11L));
Object id1 = factory.getContextId();
JobSynchronizationManager.register(new JobExecution(12L));
Object id2 = factory.getContextId();
assertFalse(id2.equals(id1));
}
}

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/batch" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<beans:import resource="common-context.xml" />
<job id="job">
<step id="step">
<tasklet ref="tasklet"/>
</step>
</job>
<beans:bean id="tasklet" class="org.springframework.batch.core.configuration.xml.TestTasklet"/>
</beans:beans>
</beans:beans>

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd">
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd">
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"/>
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager"/>
<job id="job" xmlns="http://www.springframework.org/schema/batch">
<step id="step"><tasklet ref="reader" method="read"/></step>
</job>