BATCH-1213: Defaults in xsd override parent attributes

*Introduced CoreNamespacePostProcessor to handle injection of JobRepository and TransactionManager onto jobs and steps defined using the namespace.
 *Introduced JobParserJobFactoryBean used to instantiate a FlowJob via the namespace.
 *Modified defaulting behavior of jobRepository attribute.  Job's jobRepository now always overrides the Step's.
This commit is contained in:
dhgarrette
2009-05-18 23:54:18 +00:00
parent d7d3be4223
commit 9bdfdcbc43
19 changed files with 762 additions and 170 deletions

View File

@@ -70,11 +70,10 @@ public abstract class AbstractStepParser {
/**
* @param stepElement The <step/> element
* @param parserContext
* @param jobRepositoryRef The name of the bean defining the JobRepository.
* Use 'null' if the job-repository is specified on the
* <tasklet/> element; this method will look it up.
* @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean}
* from the enclosing tag. Use 'null' if unknown.
*/
protected AbstractBeanDefinition parseStep(Element stepElement, ParserContext parserContext, String jobRepositoryRef) {
protected AbstractBeanDefinition parseStep(Element stepElement, ParserContext parserContext, String jobFactoryRef) {
AbstractBeanDefinition bd = new GenericBeanDefinition();
@@ -82,7 +81,7 @@ public abstract class AbstractStepParser {
List<Element> taskletElements = (List<Element>) DomUtils.getChildElementsByTagName(stepElement, TASKLET_ELE);
if (taskletElements.size() == 1) {
boolean stepUnderspecified = CoreNamespaceUtils.isUnderspecified(stepElement);
parseTasklet(taskletElements.get(0), bd, parserContext, jobRepositoryRef, stepUnderspecified);
parseTasklet(taskletElements.get(0), bd, parserContext, stepUnderspecified);
}
else if (taskletElements.size() > 1) {
parserContext.getReaderContext().error(
@@ -100,12 +99,16 @@ public abstract class AbstractStepParser {
bd.setAbstract(Boolean.valueOf(isAbstract));
}
if (StringUtils.hasText(jobFactoryRef)) {
bd.getPropertyValues().addPropertyValue("jobParserJobFactoryBeanRef", jobFactoryRef);
}
return bd;
}
private void parseTasklet(Element taskletElement, AbstractBeanDefinition bd, ParserContext parserContext,
String jobRepositoryRef, boolean stepUnderspecified) {
boolean stepUnderspecified) {
bd.setBeanClass(StepParserStepFactoryBean.class);
@@ -130,8 +133,7 @@ public abstract class AbstractStepParser {
+ "/> element nor a '" + TASKLET_REF_ATTR + "' attribute.", taskletElement);
}
setUpBeanDefinitionForTaskletStep(taskletElement, bd, parserContext, jobRepositoryRef);
setUpBeanDefinitionForTaskletStep(taskletElement, bd, parserContext);
}
private void parseTaskletRef(String taskletRef, MutablePropertyValues propertyValues) {
@@ -142,18 +144,23 @@ public abstract class AbstractStepParser {
}
private void setUpBeanDefinitionForTaskletStep(Element taskletElement, AbstractBeanDefinition bd,
ParserContext parserContext, String jobRepositoryRef) {
ParserContext parserContext) {
MutablePropertyValues propertyValues = bd.getPropertyValues();
checkStepAttributes(taskletElement, propertyValues);
propertyValues.addPropertyValue("jobRepository", resolveJobRepositoryRef(taskletElement, parserContext,
jobRepositoryRef));
String jobRepositoryRef = taskletElement.getAttribute(JOB_REPO_ATTR);
if (StringUtils.hasText(jobRepositoryRef)) {
RuntimeBeanReference jobRepositoryBeanRef = new RuntimeBeanReference(jobRepositoryRef);
propertyValues.addPropertyValue("jobRepository", jobRepositoryBeanRef);
}
String transactionManagerRef = taskletElement.getAttribute("transaction-manager");
RuntimeBeanReference transactionManagerBeanRef = new RuntimeBeanReference(transactionManagerRef);
propertyValues.addPropertyValue("transactionManager", transactionManagerBeanRef);
if (StringUtils.hasText(transactionManagerRef)) {
RuntimeBeanReference transactionManagerBeanRef = new RuntimeBeanReference(transactionManagerRef);
propertyValues.addPropertyValue("transactionManager", transactionManagerBeanRef);
}
handleTransactionAttributesElement(taskletElement, propertyValues, parserContext);
@@ -168,20 +175,6 @@ public abstract class AbstractStepParser {
}
private RuntimeBeanReference resolveJobRepositoryRef(Element taskletElement, ParserContext parserContext,
String jobRepositoryRef) {
if (!StringUtils.hasText(jobRepositoryRef)) {
jobRepositoryRef = taskletElement.getAttribute(JOB_REPO_ATTR);
if (!StringUtils.hasText(jobRepositoryRef)) {
parserContext.getReaderContext().error(
"The '" + JOB_REPO_ATTR + "' attribute may exist on an <" + taskletElement.getNodeName()
+ "/> element.", taskletElement);
}
}
RuntimeBeanReference jobRepositoryBeanRef = new RuntimeBeanReference(jobRepositoryRef);
return jobRepositoryBeanRef;
}
private void handleTransactionAttributesElement(Element stepElement, MutablePropertyValues propertyValues,
ParserContext parserContext) {
@SuppressWarnings("unchecked")

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2006-2009 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.configuration.xml;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.StringUtils;
/**
* Post-process jobs and steps defined using the batch namespace to inject
* dependencies.
*
* @author Dan Garrette
* @since 2.0.1
*/
public class CoreNamespacePostProcessor implements BeanPostProcessor, BeanFactoryPostProcessor, ApplicationContextAware {
private static final String DEFAULT_JOB_REPOSITORY_NAME = "jobRepository";
private static final String DEFAULT_TRANSACTION_MANAGER_NAME = "transactionManager";
private static final String JOB_FACTORY_PROPERTY_NAME = "jobParserJobFactoryBeanRef";
private static final String JOB_REPOSITORY_PROPERTY_NAME = "jobRepository";
private ApplicationContext applicationContext;
/**
* Inject job-repository from a job into its steps.
*
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory)
*/
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
MutablePropertyValues pvs = (MutablePropertyValues) bd.getPropertyValues();
if (pvs.contains(JOB_FACTORY_PROPERTY_NAME)) {
String jobName = (String) pvs.getPropertyValue(JOB_FACTORY_PROPERTY_NAME).getValue();
PropertyValue jobRepository = getJobRepository(beanFactory, jobName);
if (jobRepository != null) {
pvs.addPropertyValue(jobRepository);
}
else {
RuntimeBeanReference jobRepositoryBeanRef = new RuntimeBeanReference(DEFAULT_JOB_REPOSITORY_NAME);
pvs.addPropertyValue(JOB_REPOSITORY_PROPERTY_NAME, jobRepositoryBeanRef);
}
pvs.removePropertyValue(JOB_FACTORY_PROPERTY_NAME);
}
}
}
private PropertyValue getJobRepository(ConfigurableListableBeanFactory beanFactory, String jobName) {
BeanDefinition jobDef = beanFactory.getBeanDefinition(jobName);
PropertyValues jobDefPvs = jobDef.getPropertyValues();
if (jobDefPvs.contains(JOB_REPOSITORY_PROPERTY_NAME)) {
return jobDefPvs.getPropertyValue(JOB_REPOSITORY_PROPERTY_NAME);
}
else {
String parentName = jobDef.getParentName();
if (StringUtils.hasText(parentName)) {
return getJobRepository(beanFactory, parentName);
}
else {
return null;
}
}
}
/**
* Inject defaults into factory beans.
* <ul>
* <li>Inject "jobRepository" into any {@link JobParserJobFactoryBean}
* without a jobRepository.
* <li>Inject "transactionManager" into any
* {@link StepParserStepFactoryBean} without a transactionManager.
* </ul>
*
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object,
* java.lang.String)
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof JobParserJobFactoryBean) {
JobParserJobFactoryBean fb = (JobParserJobFactoryBean) bean;
JobRepository jobRepository = fb.getJobRepository();
if (jobRepository == null) {
fb.setJobRepository((JobRepository) applicationContext.getBean(DEFAULT_JOB_REPOSITORY_NAME));
}
}
else if (bean instanceof StepParserStepFactoryBean) {
StepParserStepFactoryBean<?, ?> fb = (StepParserStepFactoryBean<?, ?>) bean;
JobRepository jobRepository = fb.getJobRepository();
if (jobRepository == null) {
fb.setJobRepository((JobRepository) applicationContext.getBean(DEFAULT_JOB_REPOSITORY_NAME));
}
PlatformTransactionManager transactionManager = fb.getTransactionManager();
if (transactionManager == null) {
fb.setTransactionManager((PlatformTransactionManager) applicationContext
.getBean(DEFAULT_TRANSACTION_MANAGER_NAME));
}
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}

View File

@@ -29,7 +29,7 @@ import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* utility methods used in parsing of the batch core namespace
* Utility methods used in parsing of the batch core namespace
*
* @author Thomas Risberg
*/
@@ -45,8 +45,15 @@ public class CoreNamespaceUtils {
private static final String RANGE_ARRAY_EDITOR_CLASS_NAME = "org.springframework.batch.item.file.transform.RangeArrayPropertyEditor";
protected static void checkForStepScope(ParserContext parserContext, Object source) {
private static final String CORE_NAMESPACE_POST_PROCESSOR_CLASS_NAME = "org.springframework.batch.core.configuration.xml.CoreNamespacePostProcessor";
protected static void autoregisterBeansForNamespace(ParserContext parserContext, Object source) {
checkForStepScope(parserContext, source);
addRangePropertyEditor(parserContext);
addCoreNamespacePostProcessor(parserContext);
}
private static void checkForStepScope(ParserContext parserContext, Object source) {
boolean foundStepScope = false;
String[] beanNames = parserContext.getRegistry().getBeanDefinitionNames();
for (String beanName : beanNames) {
@@ -72,10 +79,9 @@ public class CoreNamespaceUtils {
* @param parserContext
*/
@SuppressWarnings("unchecked")
protected static void addRangePropertyEditor(ParserContext parserContext) {
private static void addRangePropertyEditor(ParserContext parserContext) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
if (!rangeArrayEditorAlreadyDefined(registry)) {
AbstractBeanDefinition customEditorConfigurer = BeanDefinitionBuilder.genericBeanDefinition(
CUSTOM_EDITOR_CONFIGURER_CLASS_NAME).getBeanDefinition();
customEditorConfigurer.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
@@ -83,7 +89,6 @@ public class CoreNamespaceUtils {
editors.put(RANGE_ARRAY_CLASS_NAME, RANGE_ARRAY_EDITOR_CLASS_NAME);
customEditorConfigurer.getPropertyValues().addPropertyValue("customEditors", editors);
registry.registerBeanDefinition(CUSTOM_EDITOR_CONFIGURER_CLASS_NAME, customEditorConfigurer);
}
}
@@ -110,6 +115,29 @@ public class CoreNamespaceUtils {
return false;
}
/**
* @param parserContext
*/
private static void addCoreNamespacePostProcessor(ParserContext parserContext) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
if (!coreNamespaceBeanPostProcessorAlreadyDefined(registry)) {
AbstractBeanDefinition postProcessorBeanDef = BeanDefinitionBuilder.genericBeanDefinition(
CORE_NAMESPACE_POST_PROCESSOR_CLASS_NAME).getBeanDefinition();
postProcessorBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(CORE_NAMESPACE_POST_PROCESSOR_CLASS_NAME, postProcessorBeanDef);
}
}
private static boolean coreNamespaceBeanPostProcessorAlreadyDefined(BeanDefinitionRegistry registry) {
for (String beanName : registry.getBeanDefinitionNames()) {
BeanDefinition bd = registry.getBeanDefinition(beanName);
if (CORE_NAMESPACE_POST_PROCESSOR_CLASS_NAME.equals(bd.getBeanClassName())) {
return true;
}
}
return false;
}
/**
* Should this element be treated as incomplete? If it has a parent or is
* abstract, then it may not have all properties.

View File

@@ -63,24 +63,28 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
private static final String EXIT_CODE_ATTR = "exit-code";
private static final InlineStepParser stepParser = new InlineStepParser();
private static final DecisionParser decisionParser = new DecisionParser();
// For generating unique state names for end transitions
private static int endCounter = 0;
private final String flowName;
private final String jobRepositoryRef;
private final String jobFactoryRef;
/**
* Construct a {@link FlowParser} with the specified name and using the
* provided job repository ref.
*
* @param flowName the name of the flow
* @param jobRepositoryRef the reference to the jobRepository from the
* enclosing tag
* @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean}
* from the enclosing tag
*/
public FlowParser(String flowName, String jobRepositoryRef) {
public FlowParser(String flowName, String jobFactoryRef) {
this.flowName = flowName;
this.jobRepositoryRef = jobRepositoryRef;
this.jobFactoryRef = jobFactoryRef;
}
@@ -102,9 +106,7 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
List<BeanDefinition> stateTransitions = new ArrayList<BeanDefinition>();
InlineStepParser stepParser = new InlineStepParser();
DecisionParser decisionParser = new DecisionParser();
SplitParser splitParser = new SplitParser(jobRepositoryRef);
SplitParser splitParser = new SplitParser(jobFactoryRef);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
parserContext.extractSource(element));
parserContext.pushContainingComponent(compositeDef);
@@ -116,7 +118,7 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
if (node instanceof Element) {
String nodeName = node.getLocalName();
if (nodeName.equals(STEP_ELE)) {
stateTransitions.addAll(stepParser.parse((Element) node, parserContext, jobRepositoryRef));
stateTransitions.addAll(stepParser.parse((Element) node, parserContext, jobFactoryRef));
stepExists = true;
}
else if (nodeName.equals(DECISION_ELE)) {
@@ -131,7 +133,8 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
}
if (!stepExists && !CoreNamespaceUtils.isUnderspecified(element)) {
parserContext.getReaderContext().error("A flow must contain at least one step", element);
parserContext.getReaderContext().error("The flow [" + flowName + "] must contain at least one step",
element);
}
builder.addConstructorArgValue(flowName);
@@ -162,7 +165,7 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
/**
* @param parserContext the parser context for the bean factory
* @param stepId the id of the current state if it is a step state, null
* otherwise
* otherwise
* @param stateDef The bean definition for the current state
* @param element the &lt;step/gt; element to parse
* @return a collection of
@@ -231,8 +234,8 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
* @param stateDef The bean definition for the current state
* @param parserContext the parser context for the bean factory
* @param a collection of
* {@link org.springframework.batch.core.job.flow.support.StateTransition}
* references
* {@link org.springframework.batch.core.job.flow.support.StateTransition}
* references
*/
private static Collection<BeanDefinition> parseTransitionElement(Element transitionElement, String stateId,
BeanDefinition stateDef, ParserContext parserContext) {
@@ -252,18 +255,18 @@ public class FlowParser extends AbstractSingleBeanDefinitionParser {
/**
* @param status The batch status that this transition will set. Use
* BatchStatus.UNKNOWN if not applicable.
* BatchStatus.UNKNOWN if not applicable.
* @param on The pattern that this transition should match. Use null for
* "no restriction" (same as "*").
* "no restriction" (same as "*").
* @param next The state to which this transition should go. Use null if not
* applicable.
* applicable.
* @param exitCode The exit code that this transition will set. Use null to
* default to batchStatus.
* default to batchStatus.
* @param stateDef The bean definition for the current state
* @param parserContext the parser context for the bean factory
* @param a collection of
* {@link org.springframework.batch.core.job.flow.support.StateTransition}
* references
* {@link org.springframework.batch.core.job.flow.support.StateTransition}
* references
*/
private static Collection<BeanDefinition> createTransition(FlowExecutionStatus status, String on, String next,
String exitCode, BeanDefinition stateDef, ParserContext parserContext, boolean abandon) {

View File

@@ -45,18 +45,18 @@ public class InlineStepParser extends AbstractStepParser {
*
* @param element the &lt;step/gt; element to parse
* @param parserContext the parser context for the bean factory
* @param jobRepositoryRef the reference to the jobRepository from the
* enclosing tag
* @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean}
* from the enclosing tag
* @return a collection of bean definitions for
* {@link org.springframework.batch.core.job.flow.support.StateTransition}
* instances objects
*/
public Collection<BeanDefinition> parse(Element element, ParserContext parserContext, String jobRepositoryRef) {
public Collection<BeanDefinition> parse(Element element, ParserContext parserContext, String jobFactoryRef) {
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder.genericBeanDefinition(StepState.class);
String stepId = element.getAttribute(ID_ATTR);
AbstractBeanDefinition bd = parseStep(element, parserContext, jobRepositoryRef);
AbstractBeanDefinition bd = parseStep(element, parserContext, jobFactoryRef);
parserContext.registerBeanComponent(new BeanComponentDefinition(bd, stepId));
stateBuilder.addConstructorArgReference(stepId);

View File

@@ -17,7 +17,6 @@ package org.springframework.batch.core.configuration.xml;
import java.util.List;
import org.springframework.batch.core.job.flow.FlowJob;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -40,17 +39,14 @@ public class JobParser extends AbstractSingleBeanDefinitionParser {
private static final JobExecutionListenerParser jobListenerParser = new JobExecutionListenerParser();
@Override
protected Class<FlowJob> getBeanClass(Element element) {
return FlowJob.class;
protected Class<JobParserJobFactoryBean> getBeanClass(Element element) {
return JobParserJobFactoryBean.class;
}
/**
* Create a bean definition for a
* {@link org.springframework.batch.core.job.flow.FlowJob}. The
* <code>jobRepository</code> attribute is a reference to a
* {@link org.springframework.batch.core.repository.JobRepository} and
* defaults to "jobRepository". Nested step elements are delegated to an
* {@link InlineStepParser}.
* {@link org.springframework.batch.core.job.flow.FlowJob}. Nested step
* elements are delegated to an {@link InlineStepParser}.
*
* @see AbstractSingleBeanDefinitionParser#doParse(Element, ParserContext,
* BeanDefinitionBuilder)
@@ -59,8 +55,7 @@ public class JobParser extends AbstractSingleBeanDefinitionParser {
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
CoreNamespaceUtils.checkForStepScope(parserContext, parserContext.extractSource(element));
CoreNamespaceUtils.addRangePropertyEditor(parserContext);
CoreNamespaceUtils.autoregisterBeansForNamespace(parserContext, parserContext.extractSource(element));
String jobName = element.getAttribute("id");
builder.addConstructorArgValue(jobName);
@@ -76,10 +71,9 @@ public class JobParser extends AbstractSingleBeanDefinitionParser {
}
String repositoryAttribute = element.getAttribute("job-repository");
if (!StringUtils.hasText(repositoryAttribute)) {
repositoryAttribute = "jobRepository";
if (StringUtils.hasText(repositoryAttribute)) {
builder.addPropertyReference("jobRepository", repositoryAttribute);
}
builder.addPropertyReference("jobRepository", repositoryAttribute);
String restartableAttribute = element.getAttribute("restartable");
if (StringUtils.hasText(restartableAttribute)) {
@@ -91,7 +85,7 @@ public class JobParser extends AbstractSingleBeanDefinitionParser {
builder.addPropertyReference("jobParametersIncrementer", incrementer);
}
FlowParser flowParser = new FlowParser(jobName, repositoryAttribute);
FlowParser flowParser = new FlowParser(jobName, jobName);
BeanDefinition flowDef = flowParser.parse(element, parserContext);
builder.addPropertyValue("flow", flowDef);

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2006-2009 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.configuration.xml;
import org.apache.commons.lang.StringUtils;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.JobParametersIncrementer;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.job.flow.FlowJob;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.util.Assert;
/**
* This {@link FactoryBean} is used by the batch namespace parser to create
* {@link FlowJob} objects. It stores all of the properties that are
* configurable on the &lt;job/&gt;.
*
* @author Dan Garrette
* @since 2.0.1
*/
class JobParserJobFactoryBean implements FactoryBean {
private String name;
private Boolean restartable;
private JobRepository jobRepository;
private JobExecutionListener[] jobExecutionListeners;
private JobParametersIncrementer jobParametersIncrementer;
private Flow flow;
public JobParserJobFactoryBean(String name) {
this.name = name;
}
public final Object getObject() throws Exception {
Assert.isTrue(!StringUtils.isBlank(name), "The job must have an 'id'.");
FlowJob flowJob = new FlowJob(name);
if (restartable != null) {
flowJob.setRestartable(restartable);
}
if (jobRepository != null) {
flowJob.setJobRepository(jobRepository);
}
if (jobExecutionListeners != null) {
flowJob.setJobExecutionListeners(jobExecutionListeners);
}
if (jobParametersIncrementer != null) {
flowJob.setJobParametersIncrementer(jobParametersIncrementer);
}
if (flow != null) {
flowJob.setFlow(flow);
}
flowJob.afterPropertiesSet();
return flowJob;
}
public void setRestartable(Boolean restartable) {
this.restartable = restartable;
}
public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
public JobRepository getJobRepository() {
return this.jobRepository;
}
public void setJobExecutionListeners(JobExecutionListener[] jobExecutionListeners) {
this.jobExecutionListeners = jobExecutionListeners;
}
public void setJobParametersIncrementer(JobParametersIncrementer jobParametersIncrementer) {
this.jobParametersIncrementer = jobParametersIncrementer;
}
public void setFlow(Flow flow) {
this.flow = flow;
}
public Class<FlowJob> getObjectType() {
return FlowJob.class;
}
public boolean isSingleton() {
return false;
}
}

View File

@@ -30,9 +30,9 @@ import org.w3c.dom.Element;
/**
* Internal parser for the &lt;split/&gt; elements inside a job. A split element
* references a bean definition for a
* {@link org.springframework.batch.core.job.flow.JobExecutionDecider} and goes on to
* list a set of transitions to other states with &lt;next on="pattern"
* references a bean definition for a
* {@link org.springframework.batch.core.job.flow.JobExecutionDecider} and goes
* on to list a set of transitions to other states with &lt;next on="pattern"
* to="stepName"/&gt;. Used by the {@link JobParser}.
*
* @see JobParser
@@ -42,38 +42,39 @@ import org.w3c.dom.Element;
*/
public class SplitParser {
private final String jobRepositoryRef;
private final String jobFactoryRef;
/**
* Construct a {@link FlowParser} using the provided job repository ref.
* @param jobRepositoryRef the reference to the jobRepository from the enclosing tag
*
* @param jobFactoryRef the reference to the {@link JobParserJobFactoryBean}
* from the enclosing tag
*/
public SplitParser(String jobRepositoryRef) {
this.jobRepositoryRef = jobRepositoryRef;
public SplitParser(String jobFactoryRef) {
this.jobFactoryRef = jobFactoryRef;
}
/**
* Parse the split and turn it into a list of transitions.
*
* @param element the &lt;split/gt; element to parse
* @param parserContext the parser context for the bean factory
* @return a collection of bean definitions for
* {@link org.springframework.batch.core.job.flow.support.StateTransition}
* instances objects
* @return a collection of bean definitions for
* {@link org.springframework.batch.core.job.flow.support.StateTransition}
* instances objects
*/
public Collection<BeanDefinition> parse(Element element, ParserContext parserContext) {
String idAttribute = element.getAttribute("id");
BeanDefinitionBuilder stateBuilder =
BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.SplitState");
BeanDefinitionBuilder stateBuilder = BeanDefinitionBuilder
.genericBeanDefinition("org.springframework.batch.core.job.flow.support.state.SplitState");
String taskExecutorBeanId = element.getAttribute("task-executor");
if (StringUtils.hasText(taskExecutorBeanId)) {
RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId);
stateBuilder.addPropertyValue("taskExecutor", taskExecutorRef);
}
String taskExecutorBeanId = element.getAttribute("task-executor");
if (StringUtils.hasText(taskExecutorBeanId)) {
RuntimeBeanReference taskExecutorRef = new RuntimeBeanReference(taskExecutorBeanId);
stateBuilder.addPropertyValue("taskExecutor", taskExecutorRef);
}
@SuppressWarnings("unchecked")
List<Element> flowElements = (List<Element>) DomUtils.getChildElementsByTagName(element, "flow");
@@ -81,21 +82,21 @@ public class SplitParser {
if (flowElements.size() < 2) {
parserContext.getReaderContext().error("A <split/> must contain at least two 'flow' elements.", element);
}
Collection<BeanDefinition> flows = new ArrayList<BeanDefinition>();
int i = 0;
for (Element nextElement : flowElements) {
FlowParser flowParser = new FlowParser(idAttribute+"#"+i, jobRepositoryRef);
FlowParser flowParser = new FlowParser(idAttribute + "#" + i, jobFactoryRef);
flows.add(flowParser.parse(nextElement, parserContext));
i++;
}
}
ManagedList managedList = new ManagedList();
@SuppressWarnings( { "unchecked", "unused" })
boolean dummy = managedList.addAll(flows);
stateBuilder.addConstructorArgValue(managedList);
stateBuilder.addConstructorArgValue(idAttribute);
return FlowParser.getNextElements(parserContext, stateBuilder.getBeanDefinition(), element);
}

View File

@@ -24,6 +24,7 @@ import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean;
import org.springframework.batch.core.step.item.SimpleStepFactoryBean;
import org.springframework.batch.core.step.tasklet.Tasklet;
@@ -138,6 +139,7 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
* @see FactoryBean#getObject()
*/
public final Object getObject() throws Exception {
AbstractStep step;
if (hasChunkElement) {
Assert.isNull(tasklet, "Step [" + name
+ "] has both a <chunk/> element and a 'ref' attribute referencing a Tasklet.");
@@ -146,24 +148,26 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
FaultTolerantStepFactoryBean<I, O> fb = new FaultTolerantStepFactoryBean<I, O>();
configureSimple(fb);
configureFaultTolerant(fb);
return fb.getObject();
step = (AbstractStep) fb.getObject();
}
else {
validateSimpleStep();
SimpleStepFactoryBean<I, O> fb = new SimpleStepFactoryBean<I, O>();
configureSimple(fb);
return fb.getObject();
step = (AbstractStep) fb.getObject();
}
}
else if (tasklet != null) {
TaskletStep ts = new TaskletStep();
configureTaskletStep(ts);
return ts;
step = (AbstractStep) ts;
}
else {
throw new IllegalStateException("Step [" + name
+ "] has neither a <chunk/> element nor a 'ref' attribute referencing a Tasklet.");
}
return step;
}
private void configureSimple(SimpleStepFactoryBean<I, O> fb) {
@@ -391,6 +395,13 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
}
/**
* @return jobRepository
*/
public JobRepository getJobRepository() {
return jobRepository;
}
/**
* Public setter for {@link JobRepository}.
*
@@ -418,6 +429,13 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
this.tasklet = tasklet;
}
/**
* @return transactionManager
*/
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}
/**
* @param transactionManager the transaction manager to set
*/
@@ -487,7 +505,7 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
* {@link MapRetryContextCache}.<br/>
*
* @param cacheCapacity the cache capacity to set (greater than 0 else
* ignored)
* ignored)
*/
public void setCacheCapacity(int cacheCapacity) {
this.cacheCapacity = cacheCapacity;
@@ -644,4 +662,5 @@ class StepParserStepFactoryBean<I, O> implements FactoryBean, BeanNameAware {
public void setHasChunkElement(boolean hasChunkElement) {
this.hasChunkElement = hasChunkElement;
}
}

View File

@@ -21,23 +21,24 @@ import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* Parser for the lt;step/gt; top level element in the Batch namespace. Sets up and returns
* a bean definition for a {@link org.springframework.batch.core.Step}.
* Parser for the lt;step/gt; top level element in the Batch namespace. Sets up
* and returns a bean definition for a
* {@link org.springframework.batch.core.Step}.
*
* @author Thomas Risberg
*
*/
public class TopLevelStepParser extends AbstractBeanDefinitionParser {
private static final StandaloneStepParser stepParser = new StandaloneStepParser();
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
CoreNamespaceUtils.checkForStepScope(parserContext, parserContext.extractSource(element));
CoreNamespaceUtils.addRangePropertyEditor(parserContext);
CoreNamespaceUtils.autoregisterBeansForNamespace(parserContext, parserContext.extractSource(element));
return stepParser.parse(element, parserContext);
}
}

View File

@@ -153,7 +153,22 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="transactionManagerAttribute" />
<xsd:attribute name="transaction-manager" type="xsd:string" default="transactionManager">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.transaction.PlatformTransactionManager"><![CDATA[
The bean name of the TransactionManager that is to be used. This attribute
is not required, and only needs to be specified explicitly
if the bean name of the desired TransactionManager is not 'transactionManager'.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.transaction.PlatformTransactionManager" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="isolation-level-for-create" default="SERIALIZABLE" type="isolationType">
<xsd:annotation>
<xsd:documentation><![CDATA[
@@ -312,7 +327,22 @@
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="transactionManagerAttribute"/>
<xsd:attribute name="transaction-manager" type="xsd:string">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.transaction.PlatformTransactionManager"><![CDATA[
The bean name of the TransactionManager that is to be used. This attribute
is not required, and only needs to be specified explicitly
if the bean name of the desired TransactionManager is not 'transactionManager'.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.transaction.PlatformTransactionManager" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="transaction-attributesType">
@@ -733,7 +763,7 @@
</xsd:group>
<xsd:attributeGroup name="jobRepositoryAttribute">
<xsd:attribute name="job-repository" type="xsd:string" default="jobRepository">
<xsd:attribute name="job-repository" type="xsd:string">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.batch.core.repository.JobRepository"><![CDATA[
@@ -751,25 +781,6 @@
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="transactionManagerAttribute">
<xsd:attribute name="transaction-manager" type="xsd:string" default="transactionManager">
<xsd:annotation>
<xsd:documentation
source="java:org.springframework.transaction.PlatformTransactionManager"><![CDATA[
The bean name of the TransactionManager that is to be used. This attribute
is not required, and only needs to be specified explicitly
if the bean name of the desired TransactionManager is not 'transactionManager'.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type
type="org.springframework.transaction.PlatformTransactionManager" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="parentAttribute">
<xsd:attribute name="parent" type="xsd:string" use="optional">
<xsd:annotation>
@@ -810,4 +821,4 @@
</xsd:attribute>
</xsd:attributeGroup>
</xsd:schema>
</xsd:schema>

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2006-2009 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.configuration.xml;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.BeanNameAware;
/**
* @author Dan Garrette
* @since 2.0.1
*/
public class DummyJobRepository implements JobRepository, BeanNameAware {
private String name;
public String getName() {
return name;
}
public void setBeanName(String name) {
this.name = name;
}
public void add(StepExecution stepExecution) {
}
public JobExecution createJobExecution(String jobName, JobParameters jobParameters)
throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
return null;
}
public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) {
return null;
}
public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
return null;
}
public int getStepExecutionCount(JobInstance jobInstance, String stepName) {
return 0;
}
public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) {
return false;
}
public void update(JobExecution jobExecution) {
}
public void update(StepExecution stepExecution) {
}
public void updateExecutionContext(StepExecution stepExecution) {
}
public void updateExecutionContext(JobExecution jobExecution) {
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2006-2009 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.configuration.xml;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
/**
* @author Dan Garrette
* @since 2.0.1
*/
public class DummyPlatformTransactionManager implements PlatformTransactionManager, BeanNameAware {
private String name;
public String getName() {
return name;
}
public void setBeanName(String name) {
this.name = name;
}
public void commit(TransactionStatus status) throws TransactionException {
}
public void rollback(TransactionStatus status) throws TransactionException {
}
public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
return null;
}
}

View File

@@ -20,7 +20,6 @@ import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.aop.framework.Advised;
@@ -28,6 +27,8 @@ import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.job.AbstractJob;
import org.springframework.batch.core.listener.JobExecutionListenerSupport;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.SimpleJobRepository;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@@ -39,11 +40,12 @@ import org.springframework.test.util.ReflectionTestUtils;
*/
public class JobParserTests {
ConfigurableApplicationContext jobParserParentAttributeTestsCtx = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml");
@Test
public void testInheritListeners() throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml");
List<?> job1Listeners = getListeners("job1", ctx);
List<?> job1Listeners = getListeners("job1", jobParserParentAttributeTestsCtx);
assertEquals(2, job1Listeners.size());
boolean a = false;
boolean b = false;
@@ -61,9 +63,7 @@ public class JobParserTests {
@Test
public void testInheritListeners_NoMerge() throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml");
List<?> job2Listeners = getListeners("job2", ctx);
List<?> job2Listeners = getListeners("job2", jobParserParentAttributeTestsCtx);
assertEquals(1, job2Listeners.size());
boolean c = false;
for (Object l : job2Listeners) {
@@ -76,9 +76,7 @@ public class JobParserTests {
@Test
public void testStandaloneListener() throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml");
List<?> jobListeners = getListeners("job3", ctx);
List<?> jobListeners = getListeners("job3", jobParserParentAttributeTestsCtx);
assertEquals(2, jobListeners.size());
boolean a = false;
boolean b = false;
@@ -96,8 +94,7 @@ public class JobParserTests {
@SuppressWarnings("unchecked")
private List<?> getListeners(String jobName, ApplicationContext ctx) throws Exception {
Map<String, Object> beans = ctx.getBeansOfType(Job.class);
assertTrue(beans.containsKey(jobName));
assertTrue(ctx.containsBean(jobName));
Job job = (Job) ctx.getBean(jobName);
assertTrue(job instanceof AbstractJob);
@@ -114,4 +111,29 @@ public class JobParserTests {
}
return listeners;
}
@Test
public void testJobRepositoryDefaults() throws Exception {
ApplicationContext ctx = jobParserParentAttributeTestsCtx;
assertTrue(getJobRepository("defaultRepoJob", ctx) instanceof SimpleJobRepository);
assertTrue(getJobRepository("specifiedRepoJob", ctx) instanceof DummyJobRepository);
assertTrue(getJobRepository("inheritSpecifiedRepoJob", ctx) instanceof DummyJobRepository);
assertTrue(getJobRepository("overrideInheritedRepoJob", ctx) instanceof SimpleJobRepository);
}
private JobRepository getJobRepository(String jobName, ApplicationContext ctx) throws Exception {
assertTrue(ctx.containsBean(jobName));
Job job = (Job) ctx.getBean(jobName);
assertTrue(job instanceof AbstractJob);
Object jobRepository = ReflectionTestUtils.getField(job, "jobRepository");
while (jobRepository instanceof Advised) {
jobRepository = ((Advised) jobRepository).getTargetSource().getTarget();
}
assertTrue(jobRepository instanceof JobRepository);
return (JobRepository) jobRepository;
}
}

View File

@@ -25,11 +25,16 @@ import org.junit.Test;
import org.springframework.aop.framework.Advised;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.job.AbstractJob;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.SimpleJobRepository;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.batch.repeat.CompletionPolicy;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
@@ -37,11 +42,13 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
/**
* @author Thomas Risberg
* @author Dan Garrette
*/
public class StepParserTests {
@@ -182,8 +189,7 @@ public class StepParserTests {
@SuppressWarnings("unchecked")
private StepExecutionListener getListener(String stepName, ApplicationContext ctx) throws Exception {
Map<String, Object> beans = ctx.getBeansOfType(Step.class);
assertTrue(beans.containsKey(stepName));
assertTrue(ctx.containsBean(stepName));
Step step = (Step) ctx.getBean(stepName);
assertTrue(step instanceof TaskletStep);
Object compositeListener = ReflectionTestUtils.getField(step, "stepExecutionListener");
@@ -199,34 +205,107 @@ public class StepParserTests {
return listener;
}
@SuppressWarnings("unchecked")
private DefaultTransactionAttribute getTransactionAttribute(ApplicationContext ctx, String stepName) {
Map<String, Object> beans = ctx.getBeansOfType(Step.class);
assertTrue(beans.containsKey(stepName));
assertTrue(ctx.containsBean(stepName));
Step step = (Step) ctx.getBean(stepName);
assertTrue(step instanceof TaskletStep);
Object transactionAttribute = ReflectionTestUtils.getField(step, "transactionAttribute");
DefaultTransactionAttribute txa = (DefaultTransactionAttribute) transactionAttribute;
return txa;
return (DefaultTransactionAttribute) transactionAttribute;
}
@Test
public void testInheritFromBean() throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/StepParserParentAttributeTests-context.xml");
assertTrue(getTasklet("s9", ctx) instanceof DummyTasklet);
assertTrue(getTasklet("s10", ctx) instanceof DummyTasklet);
}
@SuppressWarnings("unchecked")
private Tasklet getTasklet(String stepName, ApplicationContext ctx) {
Map<String, Object> beans = ctx.getBeansOfType(Step.class);
assertTrue(beans.containsKey(stepName));
assertTrue(ctx.containsBean(stepName));
Step step = (Step) ctx.getBean(stepName);
assertTrue(step instanceof TaskletStep);
Object tasklet = ReflectionTestUtils.getField(step, "tasklet");
assertTrue(tasklet instanceof Tasklet);
return (Tasklet)tasklet;
return (Tasklet) tasklet;
}
@Test
public void testJobRepositoryDefaults() throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/StepParserParentAttributeTests-context.xml");
assertTrue(getJobRepository("defaultRepoStep", ctx) instanceof SimpleJobRepository);
assertTrue(getJobRepository("defaultRepoStepWithParent", ctx) instanceof SimpleJobRepository);
assertTrue(getJobRepository("overrideRepoStep", ctx) instanceof SimpleJobRepository);
assertDummyJobRepository("injectedRepoStep", "dummyJobRepository", ctx);
assertDummyJobRepository("injectedRepoStepWithParent", "dummyJobRepository", ctx);
assertDummyJobRepository("injectedOverrideRepoStep", "dummyJobRepository", ctx);
assertDummyJobRepository("injectedRepoFromParentStep", "dummyJobRepository2", ctx);
assertDummyJobRepository("injectedRepoFromParentStepWithParent", "dummyJobRepository2", ctx);
assertDummyJobRepository("injectedOverrideRepoFromParentStep", "dummyJobRepository2", ctx);
assertTrue(getJobRepository("defaultRepoStandaloneStep", ctx) instanceof SimpleJobRepository);
assertDummyJobRepository("specifiedRepoStandaloneStep", "dummyJobRepository2", ctx);
}
@Test
public void testTransactionManagerDefaults() throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"org/springframework/batch/core/configuration/xml/StepParserParentAttributeTests-context.xml");
assertTrue(getTransactionManager("defaultTxMgrStep", ctx) instanceof ResourcelessTransactionManager);
assertDummyTransactionManager("specifiedTxMgrStep", "dummyTxMgr", ctx);
assertDummyTransactionManager("defaultTxMgrWithParentStep", "dummyTxMgr", ctx);
assertDummyTransactionManager("overrideTxMgrOnParentStep", "dummyTxMgr2", ctx);
}
private void assertDummyJobRepository(String beanName, String jobRepoName, ApplicationContext ctx) throws Exception {
JobRepository jobRepository = getJobRepository(beanName, ctx);
assertTrue(jobRepository instanceof DummyJobRepository);
assertEquals(jobRepoName, ((DummyJobRepository) jobRepository).getName());
}
private void assertDummyTransactionManager(String beanName, String txMgrName, ApplicationContext ctx)
throws Exception {
PlatformTransactionManager txMgr = getTransactionManager(beanName, ctx);
assertTrue(txMgr instanceof DummyPlatformTransactionManager);
assertEquals(txMgrName, ((DummyPlatformTransactionManager) txMgr).getName());
}
private JobRepository getJobRepository(String beanName, ApplicationContext ctx) throws Exception {
Object jobRepository = getFieldFromBean(beanName, "jobRepository", ctx);
assertTrue(jobRepository instanceof JobRepository);
return (JobRepository) jobRepository;
}
private PlatformTransactionManager getTransactionManager(String beanName, ApplicationContext ctx) throws Exception {
Object jobRepository = getFieldFromBean(beanName, "transactionManager", ctx);
assertTrue(jobRepository instanceof PlatformTransactionManager);
return (PlatformTransactionManager) jobRepository;
}
private Object getFieldFromBean(String beanName, String field, ApplicationContext ctx) throws Exception {
assertTrue(ctx.containsBean(beanName));
Object bean = ctx.getBean(beanName);
assertTrue(bean instanceof AbstractStep || bean instanceof AbstractJob);
Object property = ReflectionTestUtils.getField(bean, field);
while (property instanceof Advised) {
property = ((Advised) property).getTargetSource().getTarget();
}
return property;
}
}

View File

@@ -7,9 +7,7 @@
<beans:import resource="common-context.xml" />
<job id="job1" parent="baseJob">
<step id="s1">
<tasklet ref="dummyTasklet"/>
</step>
<step id="s1"><tasklet ref="dummyTasklet"/></step>
<listeners merge="true">
<listener class="org.springframework.batch.core.listener.JobExecutionListenerSupport"/>
@@ -17,9 +15,7 @@
</job>
<job id="job2" parent="baseJob">
<step id="s2">
<tasklet ref="dummyTasklet"/>
</step>
<step id="s2"><tasklet ref="dummyTasklet"/></step>
<listeners>
<listener class="org.springframework.batch.core.listener.JobExecutionListenerSupport"/>
@@ -27,15 +23,31 @@
</job>
<job id="job3" parent="baseJob3">
<step id="s3">
<tasklet ref="dummyTasklet"/>
</step>
<step id="s3"><tasklet ref="dummyTasklet"/></step>
<listeners merge="true">
<listener ref="listener1"/>
</listeners>
</job>
<job id="defaultRepoJob">
<step id="s4"><tasklet ref="dummyTasklet"/></step>
</job>
<job id="specifiedRepoJob" job-repository="dummyJobRepository">
<step id="s5"><tasklet ref="dummyTasklet"/></step>
</job>
<job id="inheritSpecifiedRepoJob" parent="baseSpecifiedRepoJob">
<step id="s6"><tasklet ref="dummyTasklet"/></step>
</job>
<job id="overrideInheritedRepoJob" parent="baseSpecifiedRepoJob" job-repository="jobRepository">
<step id="s7"><tasklet ref="dummyTasklet"/></step>
</job>
<job id="baseSpecifiedRepoJob" abstract="true" job-repository="dummyJobRepository"/>
<job id="baseJob" abstract="true">
<listeners>
<listener class="org.springframework.batch.core.configuration.xml.DummyAnnotationJobExecutionListener"/>
@@ -52,4 +64,6 @@
</beans:property>
</beans:bean>
</beans:beans>
<beans:bean id="dummyJobRepository" class="org.springframework.batch.core.configuration.xml.DummyJobRepository"/>
</beans:beans>

View File

@@ -82,5 +82,47 @@
<listener class="org.springframework.batch.core.listener.StepExecutionListenerSupport" />
</listeners>
</tasklet>
</step>
</beans:beans>
</step>
<job id="jobWithoutRepo">
<step id="defaultRepoStep"><tasklet ref="dummyTasklet"/></step>
<step id="defaultRepoStepWithParent" parent="defaultRepoStandaloneStep"><tasklet ref="dummyTasklet"/></step>
<step id="overrideRepoStep" parent="specifiedRepoStandaloneStep"><tasklet ref="dummyTasklet"/></step>
</job>
<job id="jobWithRepo" job-repository="dummyJobRepository">
<step id="injectedRepoStep"><tasklet ref="dummyTasklet"/></step>
<step id="injectedRepoStepWithParent" parent="defaultRepoStandaloneStep"><tasklet ref="dummyTasklet"/></step>
<step id="injectedOverrideRepoStep" parent="specifiedRepoStandaloneStep"><tasklet ref="dummyTasklet"/></step>
</job>
<job id="jobWithRepoOnParent" parent="baseJobWithRepo">
<step id="injectedRepoFromParentStep"><tasklet ref="dummyTasklet"/></step>
<step id="injectedRepoFromParentStepWithParent" parent="defaultRepoStandaloneStep"><tasklet ref="dummyTasklet"/></step>
<step id="injectedOverrideRepoFromParentStep" parent="specifiedRepoStandaloneStep"><tasklet ref="dummyTasklet"/></step>
</job>
<step id="defaultRepoStandaloneStep">
<tasklet ref="dummyTasklet"/>
</step>
<step id="specifiedRepoStandaloneStep">
<tasklet job-repository="dummyJobRepository2" transaction-manager="dummyTxMgr" ref="dummyTasklet"/>
</step>
<job id="baseJobWithRepo" job-repository="dummyJobRepository2" abstract="true"/>
<beans:bean id="dummyJobRepository" class="org.springframework.batch.core.configuration.xml.DummyJobRepository"/>
<beans:bean id="dummyJobRepository2" class="org.springframework.batch.core.configuration.xml.DummyJobRepository"/>
<job id="defaultTxMgrTestJob">
<step id="defaultTxMgrStep"><tasklet ref="dummyTasklet"/></step>
<step id="specifiedTxMgrStep"><tasklet ref="dummyTasklet" transaction-manager="dummyTxMgr"/></step>
<step id="defaultTxMgrWithParentStep" parent="specifiedRepoStandaloneStep"/>
<step id="overrideTxMgrOnParentStep" parent="specifiedRepoStandaloneStep"><tasklet transaction-manager="dummyTxMgr2"/></step>
</job>
<beans:bean id="dummyTxMgr" class="org.springframework.batch.core.configuration.xml.DummyPlatformTransactionManager"/>
<beans:bean id="dummyTxMgr2" class="org.springframework.batch.core.configuration.xml.DummyPlatformTransactionManager"/>
</beans:beans>

View File

@@ -5,6 +5,7 @@
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager" />
</bean>
@@ -39,4 +40,4 @@
<bean id="dummyTasklet" class="org.springframework.batch.core.configuration.xml.DummyTasklet"/>
</beans>
</beans>

View File

@@ -61,20 +61,23 @@ public class RestartFunctionalTests extends AbstractBatchLauncherTests {
* finish successfully, because it continues execution where the previous
* run stopped (module throws exception after fixed number of processed
* records).
* @throws Throwable
*
* @throws Exception
*/
@Test
public void testRestart() throws Throwable {
public void testLaunchJob() throws Exception {
int before = simpleJdbcTemplate.queryForInt("SELECT COUNT(*) FROM TRADE");
JobExecution jobExecution = runJobForRestartTest();
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
Throwable expected = jobExecution.getAllFailureExceptions().get(0);
if(expected.getMessage().toLowerCase().indexOf(
"planned") < 0) {
throw expected;
Throwable ex = jobExecution.getAllFailureExceptions().get(0);
if (ex.getMessage().toLowerCase().indexOf("planned") < 0) {
if (ex instanceof Exception) {
throw (Exception) ex;
}
throw new RuntimeException(ex);
}
int medium = simpleJdbcTemplate.queryForInt("SELECT COUNT(*) FROM TRADE");
@@ -91,8 +94,12 @@ public class RestartFunctionalTests extends AbstractBatchLauncherTests {
// load the application context and launch the job
private JobExecution runJobForRestartTest() throws Exception {
return getLauncher().run(getJob(), new DefaultJobParametersConverter().getJobParameters(PropertiesConverter
.stringToProperties("run.id(long)=1,parameter=true,run.date=20070122,input.file=classpath:data/fixedLengthImportJob/input/20070122.teststream.ImportTradeDataStep.txt")));
return getLauncher()
.run(
getJob(),
new DefaultJobParametersConverter()
.getJobParameters(PropertiesConverter
.stringToProperties("run.id(long)=1,parameter=true,run.date=20070122,input.file=classpath:data/fixedLengthImportJob/input/20070122.teststream.ImportTradeDataStep.txt")));
}
}