diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java index 1df5beef0..e6e02a1e7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractStepParser.java @@ -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 taskletElements = (List) 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") diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.java new file mode 100644 index 000000000..8dfaf699c --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespacePostProcessor.java @@ -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. + * + * + * @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; + } +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java index 8c9267d8f..10e51c0b4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java @@ -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. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/FlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/FlowParser.java index 4fb70ba93..ceca94da5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/FlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/FlowParser.java @@ -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 stateTransitions = new ArrayList(); - 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 <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 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 createTransition(FlowExecutionStatus status, String on, String next, String exitCode, BeanDefinition stateDef, ParserContext parserContext, boolean abandon) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java index 732e60252..ef5a28a0a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/InlineStepParser.java @@ -45,18 +45,18 @@ public class InlineStepParser extends AbstractStepParser { * * @param element the <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 parse(Element element, ParserContext parserContext, String jobRepositoryRef) { + public Collection 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); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java index c839a5a69..3c3b43640 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParser.java @@ -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 getBeanClass(Element element) { - return FlowJob.class; + protected Class getBeanClass(Element element) { + return JobParserJobFactoryBean.class; } /** * Create a bean definition for a - * {@link org.springframework.batch.core.job.flow.FlowJob}. The - * jobRepository 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); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.java new file mode 100644 index 000000000..cac420889 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/JobParserJobFactoryBean.java @@ -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 <job/>. + * + * @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 getObjectType() { + return FlowJob.class; + } + + public boolean isSingleton() { + return false; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SplitParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SplitParser.java index b2602ec87..f0303f4ea 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SplitParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/SplitParser.java @@ -30,9 +30,9 @@ import org.w3c.dom.Element; /** * Internal parser for the <split/> 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 <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 <next on="pattern" * to="stepName"/>. 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 <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 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 flowElements = (List) DomUtils.getChildElementsByTagName(element, "flow"); @@ -81,21 +82,21 @@ public class SplitParser { if (flowElements.size() < 2) { parserContext.getReaderContext().error("A must contain at least two 'flow' elements.", element); } - + Collection flows = new ArrayList(); 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); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java index 2816e31a5..22dd069f1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java @@ -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 implements FactoryBean, BeanNameAware { * @see FactoryBean#getObject() */ public final Object getObject() throws Exception { + AbstractStep step; if (hasChunkElement) { Assert.isNull(tasklet, "Step [" + name + "] has both a element and a 'ref' attribute referencing a Tasklet."); @@ -146,24 +148,26 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware { FaultTolerantStepFactoryBean fb = new FaultTolerantStepFactoryBean(); configureSimple(fb); configureFaultTolerant(fb); - return fb.getObject(); + step = (AbstractStep) fb.getObject(); } else { validateSimpleStep(); SimpleStepFactoryBean fb = new SimpleStepFactoryBean(); 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 element nor a 'ref' attribute referencing a Tasklet."); } + + return step; } private void configureSimple(SimpleStepFactoryBean fb) { @@ -391,6 +395,13 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware { } + /** + * @return jobRepository + */ + public JobRepository getJobRepository() { + return jobRepository; + } + /** * Public setter for {@link JobRepository}. * @@ -418,6 +429,13 @@ class StepParserStepFactoryBean 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 implements FactoryBean, BeanNameAware { * {@link MapRetryContextCache}.
* * @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 implements FactoryBean, BeanNameAware { public void setHasChunkElement(boolean hasChunkElement) { this.hasChunkElement = hasChunkElement; } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java index 4b2c8b3e3..d43be0157 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/TopLevelStepParser.java @@ -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); - + } } diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd index 792a59f4c..81307ed1e 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd @@ -153,7 +153,22 @@ - + + + + + + + + + + - + + + + + + + + + + @@ -733,7 +763,7 @@ - + - - - - - - - - - - - - - @@ -810,4 +821,4 @@ - \ No newline at end of file + diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java new file mode 100644 index 000000000..23b575be1 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyJobRepository.java @@ -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) { + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyPlatformTransactionManager.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyPlatformTransactionManager.java new file mode 100644 index 000000000..9d8b33832 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/DummyPlatformTransactionManager.java @@ -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; + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserTests.java index c858fa885..4cdff6d02 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/JobParserTests.java @@ -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 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; + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java index f9f563156..b3bb5163a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java @@ -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 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 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 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; } } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml index 2b52350ac..b21a9f156 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/JobParserParentAttributeTests-context.xml @@ -7,9 +7,7 @@ - - - + @@ -17,9 +15,7 @@ - - - + @@ -27,15 +23,31 @@ - - - + + + + + + + + + + + + + + + + + + + @@ -52,4 +64,6 @@ - \ No newline at end of file + + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserParentAttributeTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserParentAttributeTests-context.xml index 5d1707de3..3e1cbc634 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserParentAttributeTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StepParserParentAttributeTests-context.xml @@ -82,5 +82,47 @@ - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml index b0bf021f6..e277fa9dc 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/common-context.xml @@ -5,6 +5,7 @@ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> + @@ -39,4 +40,4 @@ - \ No newline at end of file + diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/RestartFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/RestartFunctionalTests.java index 9d4a167e3..13c7c71a7 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/RestartFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/RestartFunctionalTests.java @@ -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"))); } }