Remove property resolution through the original bean factory post processor and move resolution to

occur during XML pre-processing. JSR-352 allows for property replacement and expressions to be present
on any element and the preprocessing provides the ability to load a well formed XML document into the
context with the intended values already present.
This commit is contained in:
Chris Schaefer
2013-11-15 17:59:34 -05:00
parent 1f0d5496d1
commit e5fc0665cc
12 changed files with 732 additions and 343 deletions

View File

@@ -1,136 +0,0 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.jsr.configuration.support;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionVisitor;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.expression.StandardBeanExpressionResolver;
import org.springframework.core.PriorityOrdered;
import org.springframework.util.StringValueResolver;
/**
* <p>
* This post processor will resolve values in bean definitions that represent #{jobParameter['key']}
* expressions prior to the standard SPeL resolution if they correspond with an entry in the user
* provided {@link java.util.Properties} to the start or restart methods of the
* {@link org.springframework.batch.core.jsr.launch.JsrJobOperator}. This allows jobProperty replacements
* to occur for elements that require resolution prior to context initialization and are not step scoped.
* </p>
*
* @author Chris Schaefer
* @since 3.0
*/
public class JobParameterResolvingBeanFactoryPostProcessor implements BeanFactoryPostProcessor, PriorityOrdered {
private Properties properties;
private JobParameterResolver jobParameterResolver;
private static final Pattern JOB_PARAMETERS_KEY_PATTERN = Pattern.compile("'([^']*?)'");
private static final Pattern JOB_PARAMETERS_PATTERN = Pattern.compile("(#\\{jobParameters[^}]+\\})");
public JobParameterResolvingBeanFactoryPostProcessor(Properties properties) {
this.properties = properties;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory)
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (jobParameterResolver == null) {
this.jobParameterResolver = new JobParameterResolver(beanFactory, properties);
}
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (String curName : beanNames) {
jobParameterResolver.visitBeanDefinition(beanFactory.getBeanDefinition(curName));
}
}
/**
* Sets this {@link BeanFactoryPostProcessor} to the highest precedence so that
* it is executed as early as possible in the chain of {@link BeanFactoryPostProcessor}s
*/
@Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
protected class JobParameterResolver {
private Properties properties;
private JsrExpressionParser jsrExpressionParser;
private BeanDefinitionVisitor beanDefinitionVisitor;
public JobParameterResolver(ConfigurableListableBeanFactory beanFactory, Properties properties) {
this.properties = properties;
this.jsrExpressionParser = new JsrExpressionParser(new StandardBeanExpressionResolver(), new BeanExpressionContext(beanFactory, null));
this.beanDefinitionVisitor = new BeanDefinitionVisitor(new JobParameterStringValueResolver());
}
public void visitBeanDefinition(BeanDefinition beanDefinition) {
if (properties != null && ! properties.isEmpty() && ! "step".equals(beanDefinition.getScope())) {
beanDefinitionVisitor.visitBeanDefinition(beanDefinition);
}
}
protected class JobParameterStringValueResolver implements StringValueResolver {
private static final String NULL = "null";
@Override
public String resolveStringValue(String value) {
if (value != null && ! "".equals(value)) {
String resolvedString = resolveJobParameters(value);
if (!"".equals(resolvedString)) {
return jsrExpressionParser.parseExpression(resolvedString);
}
}
return value;
}
private String resolveJobParameters(String value) {
StringBuffer valueBuffer = new StringBuffer();
Matcher jobParameterMatcher = JOB_PARAMETERS_PATTERN.matcher(value);
while (jobParameterMatcher.find()) {
Matcher jobParameterKeyMatcher = JOB_PARAMETERS_KEY_PATTERN.matcher(jobParameterMatcher.group(1));
if (jobParameterKeyMatcher.find()) {
String extractedProperty = jobParameterKeyMatcher.group(1);
if (properties.containsKey(extractedProperty)) {
String resolvedProperty = properties.getProperty(extractedProperty);
jobParameterMatcher.appendReplacement(valueBuffer, resolvedProperty);
} else {
jobParameterMatcher.appendReplacement(valueBuffer, NULL);
}
}
}
jobParameterMatcher.appendTail(valueBuffer);
return valueBuffer.toString();
}
}
}
}

View File

@@ -31,7 +31,8 @@ import org.springframework.util.StringUtils;
* @since 3.0
*/
public class JsrExpressionParser {
public static final String QUOTE = "'";
private static final String QUOTE = "'";
private static final String NULL = "null";
private static final String ELVIS_RHS = ":";
private static final String ELVIS_LHS = "\\?";
private static final String ELVIS_OPERATOR = "?:";
@@ -43,6 +44,15 @@ public class JsrExpressionParser {
private BeanExpressionContext beanExpressionContext;
private BeanExpressionResolver beanExpressionResolver;
/**
* <p>
* Creates a new instance of this expression parser without and expression resolver. Creating
* an instance via this constructor will still parse expressions but no resolution of operators
* will occur as its expected the caller will.
* </p>
*/
public JsrExpressionParser() { }
/**
* <p>
* Creates a new instances of this expression parser with the provided expression resolver and context to evaluate
@@ -66,11 +76,13 @@ public class JsrExpressionParser {
* @return a JSR-352 transformed expression that can be evaluated by a SPeL parser
*/
public String parseExpression(String expression) {
if (StringUtils.countOccurrencesOf(expression, ELVIS_OPERATOR) > 0) {
return parseConditionalExpressions(expression);
String expressionToParse = expression;
if (StringUtils.countOccurrencesOf(expressionToParse, ELVIS_OPERATOR) > 0) {
expressionToParse = parseConditionalExpressions(expressionToParse);
}
return (String) beanExpressionResolver.evaluate(expression, beanExpressionContext);
return evaluateExpression(expressionToParse);
}
private String parseConditionalExpressions(String expression) {
@@ -84,20 +96,35 @@ public class JsrExpressionParser {
String value = conditionalExpression.split(ELVIS_LHS)[0];
String defaultValue = conditionalExpression.split(ELVIS_RHS)[1];
StringBuilder parsedExpression = new StringBuilder()
.append(EXPRESSION_PREFIX)
.append((String) beanExpressionResolver.evaluate(value, beanExpressionContext))
.append(ELVIS_OPERATOR)
.append(QUOTE)
.append((String) beanExpressionResolver.evaluate(defaultValue, beanExpressionContext))
.append(QUOTE)
.append(EXPRESSION_SUFFIX);
StringBuilder parsedExpression = new StringBuilder();
if(beanExpressionResolver != null) {
parsedExpression.append(EXPRESSION_PREFIX)
.append(evaluateExpression(value))
.append(ELVIS_OPERATOR)
.append(QUOTE)
.append(evaluateExpression(defaultValue))
.append(QUOTE)
.append(EXPRESSION_SUFFIX);
} else {
if(NULL.equals(value)) {
parsedExpression.append(defaultValue);
} else {
parsedExpression.append(value);
}
}
expressionToParse = expressionToParse.replace(conditionalExpression, parsedExpression);
}
expressionToParse = expressionToParse.replace(DEFAULT_VALUE_SEPARATOR, "");
return expressionToParse.replace(DEFAULT_VALUE_SEPARATOR, "");
}
return (String) beanExpressionResolver.evaluate(expressionToParse, beanExpressionContext);
private String evaluateExpression(String expression) {
if(beanExpressionResolver != null) {
return (String) beanExpressionResolver.evaluate(expression, beanExpressionContext);
}
return expression;
}
}

View File

@@ -0,0 +1,253 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.jsr.configuration.xml;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.jsr.configuration.support.JsrExpressionParser;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
/**
* <p>
* {@link DefaultBeanDefinitionDocumentReader} extension to hook into the pre/post processing of the provided
* XML document, ensuring any references to property operators such as jobParameters and jobProperties are
* resolved prior to loading the context. Since we know these initial values upfront, doing this transformation
* allows us to ensure values are retrieved in their resolved form prior to loading the context and property
* operators can be used on any element.
* </p>
*
* @author Chris Schaefer
* @since 3.0
*/
public class JsrBeanDefinitionDocumentReader extends DefaultBeanDefinitionDocumentReader {
private static final String NULL = "null";
private static final String ROOT_JOB_ELEMENT_NAME = "job";
private static final String JOB_PROPERTY_ELEMENT_NAME = "property";
private static final String JOB_PROPERTIES_ELEMENT_NAME = "properties";
private static final String JOB_PROPERTY_ELEMENT_NAME_ATTRIBUTE = "name";
private static final String JOB_PROPERTY_ELEMENT_VALUE_ATTRIBUTE = "value";
private static final String JOB_PROPERTIES_KEY_NAME = "jobProperties";
private static final String JOB_PARAMETERS_KEY_NAME = "jobParameters";
private static final String JOB_PARAMETERS_BEAN_DEFINITION_NAME = "jsr_jobParameters";
private static final Log LOG = LogFactory.getLog(JsrBeanDefinitionDocumentReader.class);
private static final Pattern PROPERTY_KEY_SEPERATOR = Pattern.compile("'([^']*?)'");
private static final Pattern OPERATOR_PATTERN = Pattern.compile("(#\\{(job(Properties|Parameters))[^}]+\\})");
private BeanDefinitionRegistry beanDefinitionRegistry;
private JsrExpressionParser expressionParser = new JsrExpressionParser();
private Map<String, Properties> propertyMap = new HashMap<String, Properties>();
/**
* <p>
* Creates a new {@link JsrBeanDefinitionDocumentReader} instance.
* </p>
*/
public JsrBeanDefinitionDocumentReader() { }
/**
* <p>
* Create a new {@link JsrBeanDefinitionDocumentReader} instance with the provided
* {@link BeanDefinitionRegistry}.
* </p>
*
* @param beanDefinitionRegistry the {@link BeanDefinitionRegistry} to use
*/
public JsrBeanDefinitionDocumentReader(BeanDefinitionRegistry beanDefinitionRegistry) {
this.beanDefinitionRegistry = beanDefinitionRegistry;
}
@Override
protected void preProcessXml(Element root) {
if (ROOT_JOB_ELEMENT_NAME.equals(root.getLocalName())) {
initProperties(root);
transformDocument(root);
if (LOG.isDebugEnabled()) {
LOG.debug("Transformed XML from preProcessXml: " + elementToString(root));
}
}
}
protected void initProperties(Element root) {
propertyMap.put(JOB_PARAMETERS_KEY_NAME, initJobParameters());
propertyMap.put(JOB_PROPERTIES_KEY_NAME, initJobProperties(root));
resolvePropertyValues(propertyMap.get(JOB_PARAMETERS_KEY_NAME));
resolvePropertyValues(propertyMap.get(JOB_PROPERTIES_KEY_NAME));
}
private Properties initJobParameters() {
Properties jobParameters = new Properties();
if (getBeanDefinitionRegistry().containsBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME)) {
BeanDefinition beanDefintion = getBeanDefinitionRegistry().getBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME);
Properties properties = (Properties) beanDefintion.getConstructorArgumentValues()
.getGenericArgumentValue(Properties.class)
.getValue();
if (properties == null) {
return new Properties();
}
jobParameters.putAll(properties);
}
return jobParameters;
}
private Properties initJobProperties(Element root) {
Properties properties = new Properties();
Node propertiesNode = root.getElementsByTagName(JOB_PROPERTIES_ELEMENT_NAME).item(0);
if(propertiesNode != null) {
NodeList children = propertiesNode.getChildNodes();
for(int i=0; i < children.getLength(); i++) {
Node child = children.item(i);
if(JOB_PROPERTY_ELEMENT_NAME.equals(child.getLocalName())) {
NamedNodeMap attributes = child.getAttributes();
Node name = attributes.getNamedItem(JOB_PROPERTY_ELEMENT_NAME_ATTRIBUTE);
Node value = attributes.getNamedItem(JOB_PROPERTY_ELEMENT_VALUE_ATTRIBUTE);
properties.setProperty(name.getNodeValue(), value.getNodeValue());
}
}
}
return properties;
}
private void resolvePropertyValues(Properties properties) {
for (String propertyKey : properties.stringPropertyNames()) {
String resolvedPropertyValue = resolvePropertyValue(properties.getProperty(propertyKey));
if(!properties.getProperty(propertyKey).equals(resolvedPropertyValue)) {
properties.setProperty(propertyKey, resolvedPropertyValue);
}
}
}
private String resolvePropertyValue(String propertyValue) {
String resolvedValue = resolveValue(propertyValue);
Matcher jobParameterMatcher = OPERATOR_PATTERN.matcher(resolvedValue);
while (jobParameterMatcher.find()) {
resolvedValue = resolvePropertyValue(resolvedValue);
}
return resolvedValue;
}
private String resolveValue(String value) {
StringBuffer valueBuffer = new StringBuffer();
Matcher jobParameterMatcher = OPERATOR_PATTERN.matcher(value);
while (jobParameterMatcher.find()) {
Matcher jobParameterKeyMatcher = PROPERTY_KEY_SEPERATOR.matcher(jobParameterMatcher.group(1));
if (jobParameterKeyMatcher.find()) {
String propertyType = jobParameterMatcher.group(2);
String extractedProperty = jobParameterKeyMatcher.group(1);
Properties properties = propertyMap.get(propertyType);
if(properties == null) {
throw new IllegalArgumentException("Unknown property type: " + propertyType);
}
String resolvedProperty = properties.getProperty(extractedProperty, NULL);
if (NULL.equals(resolvedProperty)) {
LOG.info(propertyType + " with key of: " + extractedProperty + " could not be resolved. Possible configuration error?");
}
jobParameterMatcher.appendReplacement(valueBuffer, resolvedProperty);
}
}
jobParameterMatcher.appendTail(valueBuffer);
String resolvedValue = valueBuffer.toString();
if (NULL.equals(resolvedValue)) {
return "";
}
return expressionParser.parseExpression(resolvedValue);
}
private BeanDefinitionRegistry getBeanDefinitionRegistry() {
return beanDefinitionRegistry != null ? beanDefinitionRegistry : getReaderContext().getRegistry();
}
private void transformDocument(Element root) {
DocumentTraversal traversal = (DocumentTraversal) root.getOwnerDocument();
NodeIterator iterator = traversal.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, null, true);
for (Node n = iterator.nextNode(); n != null; n = iterator.nextNode()) {
NamedNodeMap map = n.getAttributes();
if (map.getLength() > 0) {
for (int i = 0; i < map.getLength(); i++) {
Node node = map.item(i);
String nodeValue = node.getNodeValue();
String resolvedValue = resolveValue(nodeValue);
if(!nodeValue.equals(resolvedValue)) {
node.setNodeValue(resolvedValue);
}
}
} else {
String nodeValue = n.getTextContent();
String resolvedValue = resolveValue(nodeValue);
if(!nodeValue.equals(resolvedValue)) {
n.setTextContent(resolvedValue);
}
}
}
}
protected Properties getJobParameters() {
return propertyMap.get(JOB_PARAMETERS_KEY_NAME);
}
protected Properties getJobProperties() {
return propertyMap.get(JOB_PROPERTIES_KEY_NAME);
}
private String elementToString(Element root) {
DOMImplementationLS domImplLS = (DOMImplementationLS) root.getOwnerDocument().getImplementation();
return domImplLS.createLSSerializer().writeToString(root);
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.jsr.configuration.xml;
import java.util.Properties;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
/**
* <p>
* {@link GenericApplicationContext} implementation providing JSR-352 related context operations.
* </p>
*
* @author Chris Schaefer
* @since 3.0
*/
public class JsrXmlApplicationContext extends GenericApplicationContext {
private static final String JOB_PARAMETERS_BEAN_DEFINITION_NAME = "jsr_jobParameters";
private XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
* <p>
* Create a new context instance with no job parameters.
* </p>
*/
public JsrXmlApplicationContext() {
reader.setDocumentReaderClass(JsrBeanDefinitionDocumentReader.class);
reader.setEnvironment(this.getEnvironment());
}
/**
* <p>
* Create a new context instance using the provided {@link Properties} representing job
* parameters when pre-processing the job definition document.
* </p>
*
* @param jobParameters the {@link Properties} representing job parameters
*/
public JsrXmlApplicationContext(Properties jobParameters) {
reader.setDocumentReaderClass(JsrBeanDefinitionDocumentReader.class);
reader.setEnvironment(this.getEnvironment());
storeJobParameters(jobParameters);
}
private void storeJobParameters(Properties properties) {
BeanDefinition jobParameters = BeanDefinitionBuilder.genericBeanDefinition(Properties.class).getBeanDefinition();
jobParameters.getConstructorArgumentValues().addGenericArgumentValue(properties != null ? properties : new Properties());
reader.getRegistry().registerBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME, jobParameters);
}
protected XmlBeanDefinitionReader getReader() {
return reader;
}
/**
* Set whether to use XML validation. Default is <code>true</code>.
*/
public void setValidating(boolean validating) {
this.reader.setValidating(validating);
}
/**
* Load bean definitions from the given XML resources.
* @param resources one or more resources to load from
*/
public void load(Resource... resources) {
this.reader.loadBeanDefinitions(resources);
}
}

View File

@@ -42,6 +42,8 @@ public class ListenerParser {
private static final String REF_ATTRIBUTE = "ref";
private static final String LISTENER_ELEMENT = "listener";
private static final String LISTENERS_ELEMENT = "listeners";
private static final String SCOPE_STEP = "step";
@SuppressWarnings("rawtypes")
private Class listenerType;
private String propertyKey;
@@ -103,15 +105,35 @@ public class ListenerParser {
}
protected void applyListenerScope(String beanName, BeanDefinitionRegistry beanDefinitionRegistry) {
if (listenerType == JobListenerFactoryBean.class) {
if (beanDefinitionRegistry.containsBeanDefinition(beanName)) {
BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanName);
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
beanDefinition.setLazyInit(true);
}
BeanDefinition beanDefinition = getListenerBeanDefinition(beanName, beanDefinitionRegistry);
beanDefinition.setScope(getListenerScope());
beanDefinition.setLazyInit(isLazyInit());
if (!beanDefinitionRegistry.containsBeanDefinition(beanName)) {
beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
}
}
private BeanDefinition getListenerBeanDefinition(String beanName, BeanDefinitionRegistry beanDefinitionRegistry) {
if (beanDefinitionRegistry.containsBeanDefinition(beanName)) {
return beanDefinitionRegistry.getBeanDefinition(beanName);
}
return BeanDefinitionBuilder.genericBeanDefinition(beanName).getBeanDefinition();
}
private boolean isLazyInit() {
return listenerType == JobListenerFactoryBean.class;
}
private String getListenerScope() {
if (listenerType == JobListenerFactoryBean.class) {
return BeanDefinition.SCOPE_SINGLETON;
}
return SCOPE_STEP;
}
private BatchArtifact.BatchArtifactType getBatchArtifactType(String stepName) {
return (stepName != null && !"".equals(stepName)) ? BatchArtifact.BatchArtifactType.STEP_ARTIFACT
: BatchArtifact.BatchArtifactType.ARTIFACT;

View File

@@ -55,7 +55,7 @@ import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.jsr.JobContextFactoryBean;
import org.springframework.batch.core.jsr.JsrJobParametersConverter;
import org.springframework.batch.core.jsr.configuration.support.JobParameterResolvingBeanFactoryPostProcessor;
import org.springframework.batch.core.jsr.configuration.xml.JsrXmlApplicationContext;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
import org.springframework.batch.core.step.NoSuchStepException;
@@ -73,7 +73,6 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@@ -450,7 +449,9 @@ public class JsrJobOperator implements JobOperator, InitializingBean {
String jobName = previousJobExecution.getJobInstance().getJobName();
final GenericXmlApplicationContext batchContext = new GenericXmlApplicationContext();
Properties jobRestartProperties = getJobRestartProperties(params, previousJobExecution);
final JsrXmlApplicationContext batchContext = new JsrXmlApplicationContext(jobRestartProperties);
batchContext.setValidating(false);
Resource batchXml = new ClassPathResource("/META-INF/batch.xml");
@@ -464,8 +465,6 @@ public class JsrJobOperator implements JobOperator, InitializingBean {
batchContext.load(jobXml);
}
batchContext.addBeanFactoryPostProcessor(new JobParameterResolvingBeanFactoryPostProcessor(params));
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.jsr.JobContextFactoryBean").getBeanDefinition();
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
batchContext.registerBeanDefinition(JSR_JOB_CONTEXT_BEAN_NAME, beanDefinition);
@@ -487,7 +486,6 @@ public class JsrJobOperator implements JobOperator, InitializingBean {
final org.springframework.batch.core.JobExecution jobExecution;
try {
Properties jobRestartProperties = getJobRestartProperties(params, previousJobExecution);
JobParameters jobParameters = jobParametersConverter.getJobParameters(jobRestartProperties);
jobExecution = jobRepository.createJobExecution(previousJobExecution.getJobInstance(), jobParameters, previousJobExecution.getJobConfigurationName());
} catch (Exception e) {
@@ -579,7 +577,7 @@ public class JsrJobOperator implements JobOperator, InitializingBean {
@SuppressWarnings("resource")
public long start(String jobName, Properties params) throws JobStartException,
JobSecurityException {
final GenericXmlApplicationContext batchContext = new GenericXmlApplicationContext();
final JsrXmlApplicationContext batchContext = new JsrXmlApplicationContext(params);
batchContext.setValidating(false);
Resource batchXml = new ClassPathResource("/META-INF/batch.xml");
@@ -594,7 +592,6 @@ public class JsrJobOperator implements JobOperator, InitializingBean {
batchContext.load(jobXml);
}
batchContext.addBeanFactoryPostProcessor(new JobParameterResolvingBeanFactoryPostProcessor(params));
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.jsr.JobContextFactoryBean").getBeanDefinition();
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
batchContext.registerBeanDefinition(JSR_JOB_CONTEXT_BEAN_NAME, beanDefinition);

View File

@@ -15,10 +15,9 @@
*/
package org.springframework.batch.core.jsr.configuration.xml;
import static org.junit.Assert.assertEquals;
import java.io.Serializable;
import java.util.List;
import java.util.Properties;
import javax.batch.api.BatchProperty;
import javax.batch.api.Batchlet;
@@ -28,18 +27,15 @@ import javax.batch.api.chunk.ItemProcessor;
import javax.batch.api.chunk.ItemReader;
import javax.batch.api.chunk.ItemWriter;
import javax.batch.api.listener.StepListener;
import javax.batch.runtime.BatchStatus;
import javax.batch.runtime.JobExecution;
import javax.batch.runtime.context.JobContext;
import javax.inject.Inject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.batch.core.jsr.JsrTestUtils;
import static junit.framework.Assert.assertEquals;
/**
* <p>
@@ -47,20 +43,18 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* </p>
*
* @author Chris Schaefer
* @since 3.0
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class JobPropertyTests {
@Autowired
private Job job;
@Autowired
private JobLauncher jobLauncher;
@Test
public void testJobPropertyConfiguration() throws Exception {
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
Properties jobParameters = new Properties();
jobParameters.setProperty("allow.start.if.complete", "true");
jobParameters.setProperty("deciderName", "stepDecider");
jobParameters.setProperty("deciderNumber", "1");
JobExecution jobExecution = JsrTestUtils.runJob("jsrJobPropertyTests", jobParameters, 5000L);
assertEquals(BatchStatus.COMPLETED, jobExecution.getBatchStatus());
}
public static final class TestItemReader implements ItemReader {
@@ -74,7 +68,7 @@ public class JobPropertyTests {
@Inject @BatchProperty(name = "notDefinedAnnotationNamedProperty") String notDefinedAnnotationNamedProperty;
@Inject @BatchProperty String jobPropertyName1;
@Inject @BatchProperty String jobPropertyName2;
@Inject InjectTestObj injectAnnotatedOnlyField;
@Inject JobContext injectAnnotatedOnlyField;
@BatchProperty String batchAnnotatedOnlyField;
@Inject javax.batch.runtime.context.StepContext stepContext;
@@ -96,7 +90,7 @@ public class JobPropertyTests {
org.springframework.util.Assert.isNull(notDefinedAnnotationNamedProperty);
org.springframework.util.Assert.isNull(batchAnnotatedOnlyField);
org.springframework.util.Assert.notNull(injectAnnotatedOnlyField);
org.springframework.util.Assert.isTrue("Chris".equals(injectAnnotatedOnlyField.getName()));
org.springframework.util.Assert.isTrue("job1".equals(injectAnnotatedOnlyField.getJobName()));
org.springframework.util.Assert.isNull(readerPropertyName3);
}
@@ -272,16 +266,4 @@ public class JobPropertyTests {
public void stop() throws Exception {
}
}
public static class InjectTestObj {
private String name;
public InjectTestObj(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}

View File

@@ -0,0 +1,141 @@
package org.springframework.batch.core.jsr.configuration.xml;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.xml.DefaultDocumentLoader;
import org.springframework.beans.factory.xml.DelegatingEntityResolver;
import org.springframework.beans.factory.xml.DocumentLoader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.xml.SimpleSaxErrorHandler;
import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
/**
* <p>
* Test cases around {@link JsrBeanDefinitionDocumentReader}.
* </p>
*
* @author Chris Schaefer
*/
public class JsrBeanDefinitionDocumentReaderTests {
private static final String JOB_PARAMETERS_BEAN_DEFINITION_NAME = "jsr_jobParameters";
private Log logger = LogFactory.getLog(getClass());
private DocumentLoader documentLoader = new DefaultDocumentLoader();
private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
@Test
public void testGetJobParameters() {
Properties jobParameters = new Properties();
jobParameters.setProperty("jobParameter1", "jobParameter1Value");
jobParameters.setProperty("jobParameter2", "jobParameter2Value");
JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(jobParameters);
applicationContext.setValidating(false);
applicationContext.load(new ClassPathResource("baseContext.xml"),
new ClassPathResource("/META-INF/batch.xml"),
new ClassPathResource("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml"));
applicationContext.refresh();
BeanDefinition beanDefinition = applicationContext.getBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME);
Properties processedJobParameters = (Properties) beanDefinition.getConstructorArgumentValues().getGenericArgumentValue(Properties.class).getValue();
assertNotNull(processedJobParameters);
assertTrue("Wrong number of job parameters", processedJobParameters.size() == 2);
assertEquals("jobParameter1Value", processedJobParameters.getProperty("jobParameter1"));
assertEquals("jobParameter2Value", processedJobParameters.getProperty("jobParameter2"));
}
@Test
public void testGetJobProperties() {
Document document = getDocument("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml");
JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext();
JsrBeanDefinitionDocumentReader documentReader = new JsrBeanDefinitionDocumentReader(applicationContext);
documentReader.initProperties(document.getDocumentElement());
Properties documentJobProperties = documentReader.getJobProperties();
assertNotNull(documentJobProperties);
assertTrue("Wrong number of job properties", documentJobProperties.size() == 3);
assertEquals("jobProperty1Value", documentJobProperties.getProperty("jobProperty1"));
assertEquals("jobProperty1Value", documentJobProperties.getProperty("jobProperty2"));
assertEquals("", documentJobProperties.getProperty("jobProperty3"));
}
@Test
public void testJobParametersResolution() {
Properties jobParameters = new Properties();
jobParameters.setProperty("jobParameter1", "myfile.txt");
jobParameters.setProperty("jobParameter2", "#{jobProperties['jobProperty2']}");
jobParameters.setProperty("jobParameter3", "#{jobParameters['jobParameter1']}");
JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(jobParameters);
applicationContext.setValidating(false);
applicationContext.load(new ClassPathResource("baseContext.xml"),
new ClassPathResource("/META-INF/batch.xml"),
new ClassPathResource("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml"));
applicationContext.refresh();
Document document = getDocument("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml");
JsrBeanDefinitionDocumentReader documentReader = new JsrBeanDefinitionDocumentReader(applicationContext);
documentReader.initProperties(document.getDocumentElement());
Properties resolvedParameters = documentReader.getJobParameters();
assertNotNull(resolvedParameters);
assertTrue("Wrong number of job parameters", resolvedParameters.size() == 3);
assertEquals("myfile.txt", resolvedParameters.getProperty("jobParameter1"));
assertEquals("jobProperty1Value", resolvedParameters.getProperty("jobParameter2"));
assertEquals("myfile.txt", resolvedParameters.getProperty("jobParameter3"));
}
@Test
public void testJobPropertyResolution() {
Properties jobParameters = new Properties();
jobParameters.setProperty("file.name", "myfile.txt");
JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(jobParameters);
applicationContext.setValidating(false);
applicationContext.load(new ClassPathResource("baseContext.xml"),
new ClassPathResource("/META-INF/batch.xml"),
new ClassPathResource("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml"));
applicationContext.refresh();
Document document = getDocument("/META-INF/batch-jobs/jsrPropertyPreparseTestJob.xml");
JsrBeanDefinitionDocumentReader documentReader = new JsrBeanDefinitionDocumentReader(applicationContext);
documentReader.initProperties(document.getDocumentElement());
Properties resolvedProperties = documentReader.getJobProperties();
assertNotNull(resolvedProperties);
assertTrue("Wrong number of job properties", resolvedProperties.size() == 3);
assertEquals("jobProperty1Value", resolvedProperties.getProperty("jobProperty1"));
assertEquals("jobProperty1Value", resolvedProperties.getProperty("jobProperty2"));
assertEquals("myfile.txt", resolvedProperties.getProperty("jobProperty3"));
}
private Document getDocument(String location) {
InputStream inputStream = ClassLoader.class.getResourceAsStream(location);
try {
return documentLoader.loadDocument(new InputSource(inputStream),
new DelegatingEntityResolver(getClass().getClassLoader()), errorHandler, 0, true);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
inputStream.close();
} catch (IOException e) { }
}
}
}

View File

@@ -0,0 +1,46 @@
package org.springframework.batch.core.jsr.configuration.xml;
import java.util.Properties;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
/**
* <p>
* Test cases around {@link JsrXmlApplicationContext}.
* </p>
*
* @author Chris Schaefer
*/
public class JsrXmlApplicationContextTests {
private static final String JOB_PARAMETERS_BEAN_DEFINITION_NAME = "jsr_jobParameters";
@Test
public void testNullProperties() {
JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(null);
BeanDefinition beanDefinition = applicationContext.getBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME);
Properties properties = (Properties) beanDefinition.getConstructorArgumentValues().getGenericArgumentValue(Properties.class).getValue();
assertNotNull("Properties should not be null", properties);
assertTrue("Properties should be empty", properties.isEmpty());
}
@Test
public void testWithProperties() {
Properties properties = new Properties();
properties.put("prop1key", "prop1val");
JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(properties);
BeanDefinition beanDefinition = applicationContext.getBeanDefinition(JOB_PARAMETERS_BEAN_DEFINITION_NAME);
Properties storedProperties = (Properties) beanDefinition.getConstructorArgumentValues().getGenericArgumentValue(Properties.class).getValue();
assertNotNull("Properties should not be null", storedProperties);
assertFalse("Properties not be empty", storedProperties.isEmpty());
assertEquals("prop1val", storedProperties.getProperty("prop1key"));
}
}

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<job id="job1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
<!-- JSR Section 8.1.3 -->
<properties>
<property name="jobPropertyName1" value="jobPropertyValue1"/>
<property name="jobPropertyName2" value="jobPropertyValue2"/>
<property name="step2name" value="step2"/>
</properties>
<step id="step1" allow-start-if-complete="#{jobParameters['allow.start.if.complete']}">
<!-- JSR Section 8.2.3 -->
<properties>
<property name="step1PropertyName1" value="step1PropertyValue1"/>
<property name="step1PropertyName2" value="step1PropertyValue2"/>
</properties>
<chunk checkpoint-policy="custom">
<reader ref="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestItemReader">
<!-- JSR Section 8.2.1.1.1 -->
<properties>
<property name="readerPropertyName1" value="readerPropertyValue1"/>
<property name="readerPropertyName2" value="readerPropertyValue2"/>
<property name="readerPropertyName3" value="#{jobParameters['shouldNotBeParsedByPostProcessor']}"/>
<property name="annotationNamedReaderPropertyName" value="annotationNamedReaderPropertyValue"/>
<property name="nonexistentReaderPropertyName" value="nonexistentReaderPropertyValue"/>
</properties>
</reader>
<processor ref="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestItemProcessor">
<!-- JSR Section 8.2.1.2.1 -->
<properties>
<property name="processorPropertyName1" value="processorPropertyValue1"/>
<property name="processorPropertyName2" value="processorPropertyValue2"/>
<property name="annotationNamedProcessorPropertyName"
value="annotationNamedProcessorPropertyValue"/>
<property name="nonexistentProcessorPropertyName" value="nonexistentProcessorPropertyValue"/>
</properties>
</processor>
<writer ref="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestItemWriter">
<!-- JSR Section 8.2.1.3.1 -->
<properties>
<property name="writerPropertyName1" value="writerPropertyValue1"/>
<property name="writerPropertyName2" value="writerPropertyValue2"/>
<property name="annotationNamedWriterPropertyName" value="annotationNamedWriterPropertyValue"/>
<property name="nonexistentWriterPropertyName" value="nonexistentWriterPropertyValue"/>
</properties>
</writer>
<checkpoint-algorithm
ref="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestCheckpointAlgorithm">
<!-- JSR Section 8.2.1.5.1 -->
<properties>
<property name="algorithmPropertyName1" value="algorithmPropertyValue1"/>
<property name="algorithmPropertyName2" value="algorithmPropertyValue2"/>
<property name="annotationNamedAlgorithmPropertyName"
value="annotationNamedAlgorithmPropertyValue"/>
<property name="nonexistentAlgorithmPropertyName" value="nonexistentAlgorithmPropertyValue"/>
</properties>
</checkpoint-algorithm>
</chunk>
<next on="*" to="#{jobParameters['deciderName']}#{jobParameters['deciderNumber']}"/>
</step>
<decision id="stepDecider1" ref="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestDecider">
<properties>
<property name="deciderPropertyName1" value="deciderPropertyValue1"/>
<property name="deciderPropertyName2" value="deciderPropertyValue2"/>
<property name="annotationNamedDeciderPropertyName" value="annotationNamedDeciderPropertyValue"/>
<property name="nonexistentDeciderPropertyName" value="nonexistentDeciderPropertyValue"/>
</properties>
<next on="*" to="#{jobParameters['unresolving.prop']}?:#{jobProperties['step2name']};"/>
</decision>
<step id="step2">
<!-- JSR Section 8.2.3 -->
<properties>
<property name="step2PropertyName1" value="step2PropertyValue1"/>
<property name="step2PropertyName2" value="step2PropertyValue2"/>
</properties>
<!-- JSR Section 8.2.4.1 -->
<listeners>
<listener ref="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestStepListener">
<properties>
<property name="stepListenerPropertyName1" value="stepListenerPropertyValue1"/>
<property name="stepListenerPropertyName2" value="stepListenerPropertyValue2"/>
<property name="annotationNamedStepListenerPropertyName"
value="annotationNamedStepListenerPropertyValue"/>
<property name="nonexistentStepListenerPropertyName" value="nonexistentStepListenerPropertyValue"/>
</properties>
</listener>
</listeners>
<batchlet ref="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestBatchlet">
<!-- JSR Section 8.2.2.2 -->
<properties>
<property name="batchletPropertyName1" value="batchletPropertyValue1"/>
<property name="batchletPropertyName2" value="batchletPropertyValue2"/>
<property name="annotationNamedBatchletPropertyName" value="annotationNamedBatchletPropertyValue"/>
<property name="nonexistentBatchletPropertyName" value="nonexistentBatchletPropertyValue"/>
</properties>
</batchlet>
</step>
</job>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<job id="jsrPropertyPreparseTestJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
<properties>
<property name="jobProperty1" value="jobProperty1Value"/>
<property name="jobProperty2" value="#{jobProperties['jobProperty1']}"/>
<property name="jobProperty3" value="#{jobParameters['file.name']}"/>
</properties>
<step id="step1" >
<batchlet ref="testBatchlet">
<properties>
<property name="file" value="#{jobParameters['unresolving.prop']}?:myfile1.txt;"/>
</properties>
</batchlet>
</step>
</job>

View File

@@ -1,144 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd">
<!-- Not implemented (partition relatated): JSR 8.2.6.2, 8.2.6.3.1, 8.2.6.4.1, 8.2.6.5.1, 8.2.6.6.1 -->
<job id="job1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
<!-- JSR Section 8.1.3 -->
<properties>
<property name="jobPropertyName1" value="jobPropertyValue1"/>
<property name="jobPropertyName2" value="jobPropertyValue2"/>
<property name="step2name" value="step2"/>
</properties>
<step id="step1" allow-start-if-complete="#{jobParameters['allow.start.if.complete']}">
<!-- JSR Section 8.2.3 -->
<properties>
<property name="step1PropertyName1" value="step1PropertyValue1"/>
<property name="step1PropertyName2" value="step1PropertyValue2"/>
</properties>
<chunk checkpoint-policy="custom">
<reader ref="testReader">
<!-- JSR Section 8.2.1.1.1 -->
<properties>
<property name="readerPropertyName1" value="readerPropertyValue1"/>
<property name="readerPropertyName2" value="readerPropertyValue2"/>
<property name="readerPropertyName3" value="#{jobParameters['shouldNotBeParsedByPostProcessor']}"/>
<property name="annotationNamedReaderPropertyName" value="annotationNamedReaderPropertyValue"/>
<property name="nonexistentReaderPropertyName" value="nonexistentReaderPropertyValue"/>
</properties>
</reader>
<processor ref="testProcessor">
<!-- JSR Section 8.2.1.2.1 -->
<properties>
<property name="processorPropertyName1" value="processorPropertyValue1"/>
<property name="processorPropertyName2" value="processorPropertyValue2"/>
<property name="annotationNamedProcessorPropertyName" value="annotationNamedProcessorPropertyValue"/>
<property name="nonexistentProcessorPropertyName" value="nonexistentProcessorPropertyValue"/>
</properties>
</processor>
<writer ref="testWriter">
<!-- JSR Section 8.2.1.3.1 -->
<properties>
<property name="writerPropertyName1" value="writerPropertyValue1"/>
<property name="writerPropertyName2" value="writerPropertyValue2"/>
<property name="annotationNamedWriterPropertyName" value="annotationNamedWriterPropertyValue"/>
<property name="nonexistentWriterPropertyName" value="nonexistentWriterPropertyValue"/>
</properties>
</writer>
<checkpoint-algorithm ref="testCheckpointAlgorithm">
<!-- JSR Section 8.2.1.5.1 -->
<properties>
<property name="algorithmPropertyName1" value="algorithmPropertyValue1"/>
<property name="algorithmPropertyName2" value="algorithmPropertyValue2"/>
<property name="annotationNamedAlgorithmPropertyName" value="annotationNamedAlgorithmPropertyValue"/>
<property name="nonexistentAlgorithmPropertyName" value="nonexistentAlgorithmPropertyValue"/>
</properties>
</checkpoint-algorithm>
</chunk>
<next on="*" to="#{jobParameters['deciderName']}#{jobParameters['deciderNumber']}"/>
</step>
<decision id="stepDecider1" ref="testDecider">
<properties>
<property name="deciderPropertyName1" value="deciderPropertyValue1"/>
<property name="deciderPropertyName2" value="deciderPropertyValue2"/>
<property name="annotationNamedDeciderPropertyName" value="annotationNamedDeciderPropertyValue"/>
<property name="nonexistentDeciderPropertyName" value="nonexistentDeciderPropertyValue"/>
</properties>
<next on="*" to="#{jobParameters['unresolving.prop']}?:#{jobProperties['step2name']};"/>
</decision>
<step id="step2">
<!-- JSR Section 8.2.3 -->
<properties>
<property name="step2PropertyName1" value="step2PropertyValue1"/>
<property name="step2PropertyName2" value="step2PropertyValue2"/>
</properties>
<!-- JSR Section 8.2.4.1 -->
<listeners>
<listener ref="testStepListener">
<properties>
<property name="stepListenerPropertyName1" value="stepListenerPropertyValue1"/>
<property name="stepListenerPropertyName2" value="stepListenerPropertyValue2"/>
<property name="annotationNamedStepListenerPropertyName" value="annotationNamedStepListenerPropertyValue"/>
<property name="nonexistentStepListenerPropertyName" value="nonexistentStepListenerPropertyValue"/>
</properties>
</listener>
</listeners>
<batchlet ref="testBatchlet">
<!-- JSR Section 8.2.2.2 -->
<properties>
<property name="batchletPropertyName1" value="batchletPropertyValue1"/>
<property name="batchletPropertyName2" value="batchletPropertyValue2"/>
<property name="annotationNamedBatchletPropertyName" value="annotationNamedBatchletPropertyValue"/>
<property name="nonexistentBatchletPropertyName" value="nonexistentBatchletPropertyValue"/>
</properties>
</batchlet>
</step>
</job>
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager"/>
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository"/>
</bean>
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"/>
<bean id="testReader" class="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestItemReader"
scope="step"/>
<bean id="testProcessor" class="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestItemProcessor"
scope="step"/>
<bean id="testWriter" class="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestItemWriter"
scope="step"/>
<bean id="testCheckpointAlgorithm" class="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestCheckpointAlgorithm"
scope="step"/>
<bean id="testBatchlet" class="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestBatchlet"
scope="step"/>
<bean id="testStepListener" class="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestStepListener"
scope="step"/>
<bean id="testDecider" class="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$TestDecider"
scope="step"/>
<bean id="injectTestObj" class="org.springframework.batch.core.jsr.configuration.xml.JobPropertyTests$InjectTestObj"
c:name="Chris"/>
<bean id="jobParameterResolvingBeanFactoryPostProcessor" class="org.springframework.batch.core.jsr.configuration.support.JobParameterResolvingBeanFactoryPostProcessor">
<constructor-arg>
<props>
<prop key="allow.start.if.complete">true</prop>
<prop key="deciderName">stepDecider</prop>
<prop key="deciderNumber">1</prop>
</props>
</constructor-arg>
</bean>
</beans>