diff --git a/spring-batch-samples/README.md b/spring-batch-samples/README.md
index 969ec78dd1..42b71c0c24 100644
--- a/spring-batch-samples/README.md
+++ b/spring-batch-samples/README.md
@@ -176,6 +176,13 @@ from a fixed length file, processing it and writing it to another file.
[Fixed Length Import Job sample](./src/main/java/org/springframework/batch/sample/file/fixed/README.md)
+### XML Input Output
+
+The goal here is to show the use of XML input and output through
+streaming and Spring OXM marshallers and unmarshallers.
+
+[XML Input Output](./src/main/java/org/springframework/batch/sample/file/xml/README.md)
+
### Football Job
This is a (American) Football statistics loading job. It loads two files containing players and games
@@ -634,17 +641,6 @@ file to database. Second, the trades are read from the database and
credit on customer accounts is decreased appropriately. Last, a
report about customers is exported to a file.
-### XML Input Output
-
-The goal here is to show the use of XML input and output through
-streaming and Spring OXM marshallers and unmarshallers.
-
-The job has a single step that copies `Trade` data from one XML
-file to another. It uses XStream for the object XML conversion,
-because this is simple to configure for basic use cases like this
-one. See
-[Spring OXM documentation](https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#oxm) for details of other options.
-
### Batch metrics with Micrometer
This sample shows how to use [Micrometer](https://micrometer.io) to collect batch metrics in Spring Batch.
diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml
index 4f85457072..980bd63f63 100644
--- a/spring-batch-samples/pom.xml
+++ b/spring-batch-samples/pom.xml
@@ -174,6 +174,16 @@
slf4j-simple
${slf4j.version}
+
+ org.springframework
+ spring-oxm
+ ${spring-framework.version}
+
+
+ com.thoughtworks.xstream
+ xstream
+ ${xstream.version}
+
@@ -223,24 +233,12 @@
${spring-framework.version}
test
-
- org.springframework
- spring-oxm
- ${spring-framework.version}
- test
-
jakarta.el
jakarta.el-api
${jakarta.el-api.version}
test
-
- com.thoughtworks.xstream
- xstream
- ${xstream.version}
- test
-
org.hsqldb
hsqldb
diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/file/xml/README.md b/spring-batch-samples/src/main/java/org/springframework/batch/sample/file/xml/README.md
new file mode 100644
index 0000000000..7f07af07bb
--- /dev/null
+++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/file/xml/README.md
@@ -0,0 +1,52 @@
+### XML Input Output
+
+## About
+
+The goal here is to show the use of XML input and output through
+streaming and Spring OXM marshallers and unmarshallers.
+
+The job has a single step that copies `CustomerCredit` data from one XML
+file to another:
+
+```xml
+
+
+
+ customer1
+ 10
+
+
+ customer2
+ 20
+
+
+ customer3
+ 30
+
+
+ customer4
+ 40
+
+
+ customer5
+ 50
+
+
+```
+
+
+It uses XStream for the object XML conversion,
+because this is simple to configure for basic use cases like this
+one. See [Spring OXM documentation](https://docs.spring.io/spring-framework/reference/data-access/oxm.html) for details of other options.
+
+## Run the sample
+
+You can run the sample from the command line as following:
+
+```
+$>cd spring-batch-samples
+# Launch the sample using the XML configuration
+$>../mvnw -Dtest=XmlFunctionalTests#testLaunchJobWithXmlConfig test
+# Launch the sample using the Java configuration
+$>../mvnw -Dtest=XmlFunctionalTests#testLaunchJobWithJavaConfig test
+```
\ No newline at end of file
diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/sample/file/xml/XmlJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/sample/file/xml/XmlJobConfiguration.java
new file mode 100644
index 0000000000..907bae30e3
--- /dev/null
+++ b/spring-batch-samples/src/main/java/org/springframework/batch/sample/file/xml/XmlJobConfiguration.java
@@ -0,0 +1,97 @@
+package org.springframework.batch.sample.file.xml;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import com.thoughtworks.xstream.security.ExplicitTypePermission;
+
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
+import org.springframework.batch.core.configuration.annotation.StepScope;
+import org.springframework.batch.core.job.builder.JobBuilder;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.step.builder.StepBuilder;
+import org.springframework.batch.item.ItemReader;
+import org.springframework.batch.item.ItemWriter;
+import org.springframework.batch.item.xml.StaxEventItemReader;
+import org.springframework.batch.item.xml.StaxEventItemWriter;
+import org.springframework.batch.item.xml.builder.StaxEventItemReaderBuilder;
+import org.springframework.batch.item.xml.builder.StaxEventItemWriterBuilder;
+import org.springframework.batch.sample.domain.trade.CustomerCredit;
+import org.springframework.batch.sample.domain.trade.internal.CustomerCreditIncreaseProcessor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.WritableResource;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+import org.springframework.jdbc.support.JdbcTransactionManager;
+import org.springframework.oxm.xstream.XStreamMarshaller;
+
+@Configuration
+@EnableBatchProcessing
+public class XmlJobConfiguration {
+
+ @Bean
+ public XStreamMarshaller customerCreditMarshaller() {
+ XStreamMarshaller marshaller = new XStreamMarshaller();
+ marshaller
+ .setAliases(Map.of("customer", CustomerCredit.class, "credit", BigDecimal.class, "name", String.class));
+ marshaller.setTypePermissions(new ExplicitTypePermission(new Class[] { CustomerCredit.class }));
+ return marshaller;
+ }
+
+ @Bean
+ @StepScope
+ public StaxEventItemReader itemReader(@Value("#{jobParameters[inputFile]}") Resource resource) {
+ return new StaxEventItemReaderBuilder().name("itemReader")
+ .resource(resource)
+ .addFragmentRootElements("customer")
+ .unmarshaller(customerCreditMarshaller())
+ .build();
+ }
+
+ @Bean
+ @StepScope
+ public StaxEventItemWriter itemWriter(
+ @Value("#{jobParameters[outputFile]}") WritableResource resource) {
+ return new StaxEventItemWriterBuilder().name("itemWriter")
+ .resource(resource)
+ .marshaller(customerCreditMarshaller())
+ .rootTagName("customers")
+ .overwriteOutput(true)
+ .build();
+ }
+
+ @Bean
+ public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager,
+ ItemReader itemReader, ItemWriter itemWriter) {
+ return new JobBuilder("ioSampleJob", jobRepository)
+ .start(new StepBuilder("step1", jobRepository).chunk(2, transactionManager)
+ .reader(itemReader)
+ .processor(new CustomerCreditIncreaseProcessor())
+ .writer(itemWriter)
+ .build())
+ .build();
+ }
+
+ // Infrastructure beans
+
+ @Bean
+ public DataSource dataSource() {
+ return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL)
+ .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
+ .addScript("/org/springframework/batch/core/schema-hsqldb.sql")
+ .generateUniqueName(true)
+ .build();
+ }
+
+ @Bean
+ public JdbcTransactionManager transactionManager(DataSource dataSource) {
+ return new JdbcTransactionManager(dataSource);
+ }
+
+}
diff --git a/spring-batch-samples/src/main/resources/data/iosample/input/input.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/file/xml/data/input.xml
similarity index 100%
rename from spring-batch-samples/src/main/resources/data/iosample/input/input.xml
rename to spring-batch-samples/src/main/resources/org/springframework/batch/sample/file/xml/data/input.xml
diff --git a/spring-batch-samples/src/main/resources/jobs/iosample/xml.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/file/xml/job/xml.xml
similarity index 64%
rename from spring-batch-samples/src/main/resources/jobs/iosample/xml.xml
rename to spring-batch-samples/src/main/resources/org/springframework/batch/sample/file/xml/job/xml.xml
index 41a5f00c39..2a612dc815 100644
--- a/spring-batch-samples/src/main/resources/jobs/iosample/xml.xml
+++ b/spring-batch-samples/src/main/resources/org/springframework/batch/sample/file/xml/job/xml.xml
@@ -1,19 +1,32 @@
-
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
@@ -24,7 +37,7 @@
-
+
@@ -39,7 +52,4 @@
-
-
-
diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/file/xml/XmlFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/file/xml/XmlFunctionalTests.java
new file mode 100644
index 0000000000..e9bb777998
--- /dev/null
+++ b/spring-batch-samples/src/test/java/org/springframework/batch/sample/file/xml/XmlFunctionalTests.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2006-2023 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
+ *
+ * https://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.sample.file.xml;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.batch.core.BatchStatus;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.JobParametersBuilder;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.batch.test.JobLauncherTestUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author Dan Garrette
+ * @author Glenn Renfro
+ * @author Mahmoud Ben Hassine
+ * @since 2.0
+ */
+@SpringJUnitConfig(locations = { "/org/springframework/batch/sample/file/xml/job/xml.xml",
+ "/simple-job-launcher-context.xml", "/job-runner-context.xml" })
+class XmlFunctionalTests {
+
+ @Autowired
+ private JobLauncherTestUtils jobLauncherTestUtils;
+
+ @Test
+ void testLaunchJobWithXmlConfig() throws Exception {
+ // given
+ JobParameters jobParameters = new JobParametersBuilder()
+ .addString("inputFile", "org/springframework/batch/sample/file/xml/data/input.xml")
+ .addString("outputFile", "file:./target/test-outputs/output.xml")
+ .toJobParameters();
+
+ // when
+ JobExecution jobExecution = this.jobLauncherTestUtils.launchJob(jobParameters);
+
+ // then
+ assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
+ }
+
+ @Test
+ public void testLaunchJobWithJavaConfig() throws Exception {
+ // given
+ ApplicationContext context = new AnnotationConfigApplicationContext(XmlJobConfiguration.class);
+ JobLauncher jobLauncher = context.getBean(JobLauncher.class);
+ Job job = context.getBean(Job.class);
+ JobParameters jobParameters = new JobParametersBuilder()
+ .addString("inputFile", "org/springframework/batch/sample/file/xml/data/input.xml")
+ .addString("outputFile", "file:./target/test-outputs/output.xml")
+ .toJobParameters();
+
+ // when
+ JobExecution jobExecution = jobLauncher.run(job, jobParameters);
+
+ // then
+ assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
+ }
+
+}
diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/XmlFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/XmlFunctionalTests.java
deleted file mode 100644
index bee6b9cbf0..0000000000
--- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/XmlFunctionalTests.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2006-2022 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
- *
- * https://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.sample.iosample;
-
-import org.springframework.batch.item.ItemReader;
-import org.springframework.batch.item.xml.StaxEventItemReader;
-import org.springframework.batch.sample.domain.trade.CustomerCredit;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.core.io.Resource;
-import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
-
-/**
- * @author Dan Garrette
- * @author Glenn Renfro
- * @author Mahmoud Ben Hassine
- * @since 2.0
- */
-@SpringJUnitConfig(locations = "/jobs/iosample/xml.xml")
-class XmlFunctionalTests extends AbstractIoSampleTests {
-
- @Autowired
- private Resource outputResource;
-
- @Override
- protected void pointReaderToOutput(ItemReader reader) {
- StaxEventItemReader> xmlReader = (StaxEventItemReader>) reader;
- xmlReader.setResource(outputResource);
- }
-
-}