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:
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
* <bean id="..." class="..." scope="job">
|
||||
* <property name="name" value="#{jobParameters[input]}" />
|
||||
* </bean>
|
||||
*
|
||||
*
|
||||
* <bean id="..." class="..." scope="job">
|
||||
* <property name="name" value="#{jobExecutionContext['input.stem']}.txt" />
|
||||
* </bean>
|
||||
* </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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
* <aop-auto-proxy/> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <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 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>
|
||||
* <bean id="..." class="..." scope="step">
|
||||
* <property name="parent" ref="#{stepExecutionContext[helper]}" />
|
||||
* </bean>
|
||||
*
|
||||
*
|
||||
* <bean id="..." class="..." scope="step">
|
||||
* <property name="name" value="#{stepExecutionContext['input.name']}" />
|
||||
* </bean>
|
||||
*
|
||||
*
|
||||
* <bean id="..." class="..." scope="step">
|
||||
* <property name="name" value="#{jobParameters[input]}" />
|
||||
* </bean>
|
||||
*
|
||||
*
|
||||
* <bean id="..." class="..." scope="step">
|
||||
* <property name="name" value="#{jobExecutionContext['input.stem']}.txt" />
|
||||
* </bean>
|
||||
* </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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user