diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java
index 9ef662dc0..7a644b6a7 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/support/BatchPropertyBeanPostProcessor.java
@@ -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) {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReader.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReader.java
index 25d4baac9..80f52bf52 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReader.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReader.java
@@ -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;
/**
*
- * {@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.
*
*
* @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 referenceCountMap = new HashMap();
+
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 {
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParser.java
index f2e10b089..c09561f2d 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParser.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParser.java
@@ -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;
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java
index 597857981..6932fe566 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/jsr/launch/JsrJobOperator.java
@@ -540,8 +540,6 @@ public class JsrJobOperator implements JobOperator, InitializingBean {
jobRepository.update(jobExecution);
throw new JobRestartException(e);
- } finally {
- batchContext.close();
}
return jobExecution.getId();
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReaderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReaderTests.java
index 239970779..66e6e5474 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReaderTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/JsrBeanDefinitionDocumentReaderTests.java
@@ -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 {
+
+ }
+ }
}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParserTests.java
index 56026c4d0..fde9dcb48 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParserTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/configuration/xml/ListenerParserTests.java
@@ -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());
}
}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/listener/ExitStatusSettingStepListener.java b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/listener/ExitStatusSettingStepListener.java
new file mode 100644
index 000000000..63d341b14
--- /dev/null
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/jsr/step/listener/ExitStatusSettingStepListener.java
@@ -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;
+
+/**
+ *
+ * {@link StepListener} for testing. Sets or appends the value of the
+ * testProperty field to the {@link JobContext} exit status on afterStep.
+ *
+ *
+ * @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);
+ }
+ }
+}
diff --git a/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrSpringInstanceTests.xml b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrSpringInstanceTests.xml
new file mode 100644
index 000000000..8535e3a9c
--- /dev/null
+++ b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrSpringInstanceTests.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrUniqueInstanceTests.xml b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrUniqueInstanceTests.xml
new file mode 100644
index 000000000..aab71ad34
--- /dev/null
+++ b/spring-batch-core/src/test/resources/META-INF/batch-jobs/jsrUniqueInstanceTests.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-batch-core/src/test/resources/META-INF/batch.xml b/spring-batch-core/src/test/resources/META-INF/batch.xml
index 14ac315ab..347468b4c 100644
--- a/spring-batch-core/src/test/resources/META-INF/batch.xml
+++ b/spring-batch-core/src/test/resources/META-INF/batch.xml
@@ -2,4 +2,5 @@
+