* During pre-parsing of XML look for references to the same bean and if multiple

matches found, create new bean definitions and update the reference in the preprocessed
XML to allow for unique instances.

* Use job scope for job level listeners

* Remove call to application context close in JsrJobOperator.restart

Fixes TCK tests:
--
testTransitionElementOnAttrValuesWithRestartJobParamOverrides
testChunkArtifactInstanceUniqueness
testOneArtifactIsJobAndStepListener
This commit is contained in:
Chris Schaefer
2014-02-10 09:46:28 -05:00
parent 1a6ba98fc4
commit a1ee097dd4
10 changed files with 313 additions and 18 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 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.
@@ -94,18 +94,19 @@ public class BatchPropertyBeanPostProcessor implements BeanPostProcessor, BeanFa
}
private Properties getArtifactProperties(String artifactName) {
String originalArtifactName = artifactName;
if(originalArtifactName.startsWith(SCOPED_TARGET_BEAN_PREFIX)) {
originalArtifactName = artifactName.substring(SCOPED_TARGET_BEAN_PREFIX.length());
}
StepContext stepContext = StepSynchronizationManager.getContext();
if (stepContext != null) {
String originalArtifactName = artifactName;
if(originalArtifactName.startsWith(SCOPED_TARGET_BEAN_PREFIX)) {
originalArtifactName = artifactName.substring(SCOPED_TARGET_BEAN_PREFIX.length());
}
return batchPropertyContext.getStepArtifactProperties(stepContext.getStepName(), originalArtifactName);
}
return batchPropertyContext.getArtifactProperties(artifactName);
return batchPropertyContext.getArtifactProperties(originalArtifactName);
}
private void injectBatchProperties(final Object artifact, final Properties artifactProperties) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 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.
@@ -24,6 +24,8 @@ 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.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader;
import org.w3c.dom.Element;
@@ -37,11 +39,12 @@ import org.w3c.dom.traversal.NodeIterator;
/**
* <p>
* {@link DefaultBeanDefinitionDocumentReader} extension to hook into the pre/post processing of the provided
* {@link DefaultBeanDefinitionDocumentReader} extension to hook into the pre 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.
* operators can be used on any element. This document reader will also look for references to artifacts by
* the same name and create new bean definitions to provide the ability to create new instances.
* </p>
*
* @author Chris Schaefer
@@ -214,17 +217,51 @@ public class JsrBeanDefinitionDocumentReader extends DefaultBeanDefinitionDocume
DocumentTraversal traversal = (DocumentTraversal) root.getOwnerDocument();
NodeIterator iterator = traversal.createNodeIterator(root, NodeFilter.SHOW_ELEMENT, null, true);
BeanDefinitionRegistry registry = getBeanDefinitionRegistry();
Map<String, Integer> referenceCountMap = new HashMap<String, Integer>();
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 nodeName = node.getNodeName();
String nodeValue = node.getNodeValue();
String resolvedValue = resolveValue(nodeValue);
String newNodeValue = resolvedValue;
if(!nodeValue.equals(resolvedValue)) {
node.setNodeValue(resolvedValue);
if("ref".equals(nodeName)) {
if(!referenceCountMap.containsKey(resolvedValue)) {
referenceCountMap.put(resolvedValue, 0);
}
// possibly fully qualified class name in ref tag in the jobXML
if(!registry.containsBeanDefinition(resolvedValue)) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(resolvedValue)
.getBeanDefinition();
beanDefinition.setScope("step");
registry.registerBeanDefinition(resolvedValue, beanDefinition);
newNodeValue = resolvedValue;
}
if (referenceCountMap.containsKey(resolvedValue)) {
Integer referenceCount = referenceCountMap.get(resolvedValue);
referenceCount++;
referenceCountMap.put(resolvedValue, referenceCount);
newNodeValue = resolvedValue + referenceCount;
if(registry.containsBeanDefinition(resolvedValue)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(resolvedValue);
registry.registerBeanDefinition(newNodeValue, beanDefinition);
}
}
}
if(!nodeValue.equals(newNodeValue)) {
node.setNodeValue(newNodeValue);
}
}
} else {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 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.
@@ -43,6 +43,7 @@ public class ListenerParser {
private static final String LISTENER_ELEMENT = "listener";
private static final String LISTENERS_ELEMENT = "listeners";
private static final String SCOPE_STEP = "step";
private static final String SCOPE_JOB = "job";
@SuppressWarnings("rawtypes")
private Class listenerType;
@@ -128,7 +129,7 @@ public class ListenerParser {
private String getListenerScope() {
if (listenerType == JobListenerFactoryBean.class) {
return BeanDefinition.SCOPE_SINGLETON;
return SCOPE_JOB;
}
return SCOPE_STEP;

View File

@@ -540,8 +540,6 @@ public class JsrJobOperator implements JobOperator, InitializingBean {
jobRepository.update(jobExecution);
throw new JobRestartException(e);
} finally {
batchContext.close();
}
return jobExecution.getId();

View File

@@ -6,15 +6,21 @@ import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.springframework.batch.core.jsr.JsrTestUtils;
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.StringUtils;
import org.springframework.util.xml.SimpleSaxErrorHandler;
import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import javax.batch.api.Batchlet;
import javax.batch.runtime.JobExecution;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
@@ -124,6 +130,107 @@ public class JsrBeanDefinitionDocumentReaderTests {
assertEquals("myfile.txt", resolvedProperties.getProperty("jobProperty3"));
}
@Test
public void testGenerationOfBeanDefinitionsForMultipleReferences() throws Exception {
JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(new Properties());
applicationContext.setValidating(false);
applicationContext.load(new ClassPathResource("baseContext.xml"),
new ClassPathResource("/META-INF/batch.xml"),
new ClassPathResource("/META-INF/batch-jobs/jsrUniqueInstanceTests.xml"));
applicationContext.refresh();
assertTrue("exitStatusSettingStepListener bean definition not found", applicationContext.containsBeanDefinition("exitStatusSettingStepListener"));
assertTrue("exitStatusSettingStepListener1 bean definition not found", applicationContext.containsBeanDefinition("exitStatusSettingStepListener1"));
assertTrue("exitStatusSettingStepListener2 bean definition not found", applicationContext.containsBeanDefinition("exitStatusSettingStepListener2"));
assertTrue("exitStatusSettingStepListener3 bean definition not found", applicationContext.containsBeanDefinition("exitStatusSettingStepListener3"));
assertTrue("exitStatusSettingStepListenerClassBeanDefinition bean definition not found", applicationContext.containsBeanDefinition("org.springframework.batch.core.jsr.step.listener.ExitStatusSettingStepListener"));
assertTrue("exitStatusSettingStepListener1ClassBeanDefinition bean definition not found", applicationContext.containsBeanDefinition("org.springframework.batch.core.jsr.step.listener.ExitStatusSettingStepListener1"));
assertTrue("exitStatusSettingStepListener2ClassBeanDefinition bean definition not found", applicationContext.containsBeanDefinition("org.springframework.batch.core.jsr.step.listener.ExitStatusSettingStepListener2"));
assertTrue("exitStatusSettingStepListener3ClassBeanDefinition bean definition not found", applicationContext.containsBeanDefinition("org.springframework.batch.core.jsr.step.listener.ExitStatusSettingStepListener3"));
assertTrue("testBatchlet bean definition not found", applicationContext.containsBeanDefinition("testBatchlet"));
assertTrue("testBatchlet1 bean definition not found", applicationContext.containsBeanDefinition("testBatchlet1"));
assertTrue("testBatchlet2 bean definition not found", applicationContext.containsBeanDefinition("testBatchlet2"));
}
@Test
public void testArtifactUniqueness() throws Exception {
JobExecution jobExecution = JsrTestUtils.runJob("jsrUniqueInstanceTests", new Properties(), 10000L);
String exitStatus = jobExecution.getExitStatus();
assertTrue("Exit status must contain listener3", exitStatus.contains("listener3"));
exitStatus = exitStatus.replace("listener3", "");
assertTrue("Exit status must contain listener2", exitStatus.contains("listener2"));
exitStatus = exitStatus.replace("listener2", "");
assertTrue("Exit status must contain listener1", exitStatus.contains("listener1"));
exitStatus = exitStatus.replace("listener1", "");
assertTrue("Exit status must contain listener0", exitStatus.contains("listener0"));
exitStatus = exitStatus.replace("listener0", "");
assertTrue("Exit status must contain listener7", exitStatus.contains("listener7"));
exitStatus = exitStatus.replace("listener7", "");
assertTrue("Exit status must contain listener6", exitStatus.contains("listener6"));
exitStatus = exitStatus.replace("listener6", "");
assertTrue("Exit status must contain listener5", exitStatus.contains("listener5"));
exitStatus = exitStatus.replace("listener5", "");
assertTrue("Exit status must contain listener4", exitStatus.contains("listener4"));
exitStatus = exitStatus.replace("listener4", "");
assertTrue("exitStatus must be empty", "".equals(exitStatus));
}
@Test
public void testGenerationOfSpringBeanDefinitionsForMultipleReferences() {
JsrXmlApplicationContext applicationContext = new JsrXmlApplicationContext(new Properties());
applicationContext.setValidating(false);
applicationContext.load(new ClassPathResource("baseContext.xml"),
new ClassPathResource("/META-INF/batch-jobs/jsrSpringInstanceTests.xml"));
applicationContext.refresh();
assertTrue("exitStatusSettingStepListener bean definition not found", applicationContext.containsBeanDefinition("exitStatusSettingStepListener"));
assertTrue("scopedTarget.exitStatusSettingStepListener bean definition not found", applicationContext.containsBeanDefinition("scopedTarget.exitStatusSettingStepListener"));
BeanDefinition exitStatusSettingStepListenerBeanDefinition = applicationContext.getBeanDefinition("scopedTarget.exitStatusSettingStepListener");
assertTrue("step".equals(exitStatusSettingStepListenerBeanDefinition.getScope()));
assertTrue("Should not contain bean definition for exitStatusSettingStepListener1", !applicationContext.containsBeanDefinition("exitStatusSettingStepListener1"));
assertTrue("Should not contain bean definition for exitStatusSettingStepListener2", !applicationContext.containsBeanDefinition("exitStatusSettingStepListener2"));
assertTrue("Should not contain bean definition for exitStatusSettingStepListener3", !applicationContext.containsBeanDefinition("exitStatusSettingStepListener3"));
assertTrue("Should not contain bean definition for testBatchlet1", !applicationContext.containsBeanDefinition("testBatchlet1"));
assertTrue("Should not contain bean definition for testBatchlet2", !applicationContext.containsBeanDefinition("testBatchlet2"));
assertTrue("testBatchlet bean definition not found", applicationContext.containsBeanDefinition("testBatchlet"));
BeanDefinition testBatchletBeanDefinition = applicationContext.getBeanDefinition("testBatchlet");
assertTrue("singleton".equals(testBatchletBeanDefinition.getScope()));
}
@Test
public void testSpringArtifactUniqueness() throws Exception {
JobExecution jobExecution = JsrTestUtils.runJob("jsrSpringInstanceTests", new Properties(), 10000L);
String exitStatus = jobExecution.getExitStatus();
assertEquals("listener1listener1listener4listener4", exitStatus);
assertTrue("Exit status must contain listener1", exitStatus.contains("listener1"));
assertTrue("exitStatus must contain 2 listener1 values", StringUtils.countOccurrencesOf(exitStatus, "listener1") == 2);
exitStatus = exitStatus.replace("listener1", "");
assertTrue("Exit status must contain listener4", exitStatus.contains("listener4"));
assertTrue("exitStatus must contain 2 listener4 values", StringUtils.countOccurrencesOf(exitStatus, "listener4") == 2);
exitStatus = exitStatus.replace("listener4", "");
assertTrue("exitStatus must be empty", "".equals(exitStatus));
}
private Document getDocument(String location) {
InputStream inputStream = ClassLoader.class.getResourceAsStream(location);
@@ -138,4 +245,16 @@ public class JsrBeanDefinitionDocumentReaderTests {
} catch (IOException e) { }
}
}
public static class TestBatchlet implements Batchlet {
@Override
public String process() throws Exception {
return null;
}
@Override
public void stop() throws Exception {
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 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.
@@ -60,6 +60,6 @@ public class ListenerParserTests {
listenerParser.applyListenerScope("jobListener", applicationContext);
BeanDefinition beanDefinition = applicationContext.getBeanDefinition("jobListener");
assertEquals("singleton", beanDefinition.getScope());
assertEquals("job", beanDefinition.getScope());
}
}

View File

@@ -0,0 +1,40 @@
package org.springframework.batch.core.jsr.step.listener;
import javax.batch.api.BatchProperty;
import javax.batch.api.listener.StepListener;
import javax.batch.runtime.context.JobContext;
import javax.inject.Inject;
/**
* <p>
* {@link StepListener} for testing. Sets or appends the value of the
* testProperty field to the {@link JobContext} exit status on afterStep.
* </p>
*
* @author Chris Schaefer
* @since 3.0
*/
public class ExitStatusSettingStepListener implements StepListener {
@Inject
@BatchProperty
private String testProperty;
@Inject
private JobContext jobContext;
@Override
public void beforeStep() throws Exception {
}
@Override
public void afterStep() throws Exception {
String exitStatus = jobContext.getExitStatus();
if("".equals(exitStatus) || exitStatus == null) {
jobContext.setExitStatus(testProperty);
} else {
jobContext.setExitStatus(exitStatus + testProperty);
}
}
}

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd">
<job id="jsrUniqueInstanceTests" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
<step id="step1" next="step2">
<listeners>
<listener ref="exitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener0"/>
</properties>
</listener>
<listener ref="exitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener1"/>
</properties>
</listener>
</listeners>
<batchlet ref="testBatchlet"/>
</step>
<step id="step2">
<listeners>
<listener ref="exitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener3"/>
</properties>
</listener>
<listener ref="exitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener4"/>
</properties>
</listener>
</listeners>
<batchlet ref="testBatchlet"/>
</step>
</job>
<bean id="exitStatusSettingStepListener"
class="org.springframework.batch.core.jsr.step.listener.ExitStatusSettingStepListener" scope="step"/>
<bean id="testBatchlet"
class="org.springframework.batch.core.jsr.configuration.xml.JsrBeanDefinitionDocumentReaderTests$TestBatchlet"
scope="singleton"/>
</beans>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<job id="jsrUniqueInstanceTests" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
<step id="step1" next="step2">
<listeners>
<listener ref="exitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener0" />
</properties>
</listener>
<listener ref="exitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener1" />
</properties>
</listener>
<listener ref="org.springframework.batch.core.jsr.step.listener.ExitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener2" />
</properties>
</listener>
<listener ref="org.springframework.batch.core.jsr.step.listener.ExitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener3" />
</properties>
</listener>
</listeners>
<batchlet ref="testBatchlet"/>
</step>
<step id="step2">
<listeners>
<listener ref="exitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener4" />
</properties>
</listener>
<listener ref="exitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener5" />
</properties>
</listener>
<listener ref="org.springframework.batch.core.jsr.step.listener.ExitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener6" />
</properties>
</listener>
<listener ref="org.springframework.batch.core.jsr.step.listener.ExitStatusSettingStepListener">
<properties>
<property name="testProperty" value="listener7" />
</properties>
</listener>
</listeners>
<batchlet ref="testBatchlet"/>
</step>
</job>

View File

@@ -2,4 +2,5 @@
<ref id="testBatchlet" class="org.springframework.batch.core.jsr.step.batchlet.BatchletSupport" />
<ref id="restartBatchlet" class="org.springframework.batch.core.jsr.step.batchlet.RestartBatchlet" />
<ref id="failingBatchlet" class="org.springframework.batch.core.jsr.step.batchlet.FailingBatchlet" />
<ref id="exitStatusSettingStepListener" class="org.springframework.batch.core.jsr.step.listener.ExitStatusSettingStepListener" />
</batch-artifacts>