diff --git a/batch/file-ingest/checkstyle.xml b/batch/file-ingest/checkstyle.xml
new file mode 100644
index 0000000..5421283
--- /dev/null
+++ b/batch/file-ingest/checkstyle.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/batch/file-ingest/pom.xml b/batch/file-ingest/pom.xml
new file mode 100644
index 0000000..1e74ed0
--- /dev/null
+++ b/batch/file-ingest/pom.xml
@@ -0,0 +1,97 @@
+
+
+ 4.0.0
+ org.springframework
+ ingest
+ 1.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.0.0.M5
+
+
+ 1.8
+ 3.7.0
+ 1.2.2.RELEASE
+ 2.0.0.M5
+ checkstyle.xml
+ 2.17
+
+
+
+ org.springframework.boot
+ spring-boot-starter-batch
+
+
+ com.h2database
+ h2
+
+
+ org.springframework.cloud
+ spring-cloud-task-core
+ ${spring.cloud.task.version}
+
+
+ org.springframework.cloud
+ spring-cloud-task-batch
+ ${spring.cloud.task.version}
+
+
+ junit
+ junit
+ test
+
+
+ org.hsqldb
+ hsqldb
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven.compiler.plugin.version}
+
+ ${java.version}
+ ${java.version}
+ ${java.version}
+ ${java.version}
+ -Xlint:all
+
+
+
+
+
+
+ repository.spring.milestone
+ Spring Milestone Repository
+ http://repo.spring.io/milestone
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${checkstyle.plugin.version}
+
+
+
+
diff --git a/batch/file-ingest/src/main/java/org/springframework/ingest/Application.java b/batch/file-ingest/src/main/java/org/springframework/ingest/Application.java
new file mode 100644
index 0000000..1cbaf9d
--- /dev/null
+++ b/batch/file-ingest/src/main/java/org/springframework/ingest/Application.java
@@ -0,0 +1,18 @@
+package org.springframework.ingest;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.task.configuration.EnableTask;
+
+/**
+ * Main entry point for the ingest sample application.
+ *
+ * @author Chris Schaefer
+ */
+@EnableTask
+@SpringBootApplication
+public class Application {
+ public static void main(String[] args) throws Exception {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/batch/file-ingest/src/main/java/org/springframework/ingest/config/BatchConfiguration.java b/batch/file-ingest/src/main/java/org/springframework/ingest/config/BatchConfiguration.java
new file mode 100644
index 0000000..5e03ec0
--- /dev/null
+++ b/batch/file-ingest/src/main/java/org/springframework/ingest/config/BatchConfiguration.java
@@ -0,0 +1,95 @@
+package org.springframework.ingest.config;
+
+import javax.sql.DataSource;
+
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
+import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
+import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
+import org.springframework.batch.core.configuration.annotation.StepScope;
+import org.springframework.batch.core.launch.support.RunIdIncrementer;
+import org.springframework.batch.item.ItemProcessor;
+import org.springframework.batch.item.ItemStreamReader;
+import org.springframework.batch.item.ItemWriter;
+import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
+import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ResourceLoader;
+
+import org.springframework.ingest.domain.Person;
+import org.springframework.ingest.mapper.fieldset.PersonFieldSetMapper;
+import org.springframework.ingest.processor.PersonItemProcessor;
+
+/**
+ * Class used to configure the batch job related beans.
+ *
+ * @author Chris Schaefer
+ */
+@Configuration
+@EnableBatchProcessing
+public class BatchConfiguration {
+ private final DataSource dataSource;
+ private final ResourceLoader resourceLoader;
+ private final JobBuilderFactory jobBuilderFactory;
+ private final StepBuilderFactory stepBuilderFactory;
+
+ @Autowired
+ public BatchConfiguration(final DataSource dataSource, final JobBuilderFactory jobBuilderFactory,
+ final StepBuilderFactory stepBuilderFactory,
+ final ResourceLoader resourceLoader) {
+ this.dataSource = dataSource;
+ this.resourceLoader = resourceLoader;
+ this.jobBuilderFactory = jobBuilderFactory;
+ this.stepBuilderFactory = stepBuilderFactory;
+ }
+
+ @Bean
+ @StepScope
+ public ItemStreamReader reader(@Value("#{jobParameters['filePath']}") String filePath) throws Exception {
+ return new FlatFileItemReaderBuilder()
+ .name("reader")
+ .resource(resourceLoader.getResource(filePath))
+ .delimited()
+ .names(new String[] {"firstName", "lastName"})
+ .fieldSetMapper(new PersonFieldSetMapper())
+ .build();
+ }
+
+ @Bean
+ public ItemProcessor processor() {
+ return new PersonItemProcessor();
+ }
+
+ @Bean
+ public ItemWriter writer() {
+ return new JdbcBatchItemWriterBuilder()
+ .beanMapped()
+ .dataSource(this.dataSource)
+ .sql("INSERT INTO people (first_name, last_name) VALUES (:firstName, :lastName)")
+ .build();
+ }
+
+ @Bean
+ public Job ingestJob() throws Exception {
+ return jobBuilderFactory.get("ingestJob")
+ .incrementer(new RunIdIncrementer())
+ .flow(step1())
+ .end()
+ .build();
+ }
+
+ @Bean
+ public Step step1() throws Exception {
+ return stepBuilderFactory.get("ingest")
+ .chunk(10)
+ .reader(reader(null))
+ .processor(processor())
+ .writer(writer())
+ .build();
+ }
+}
diff --git a/batch/file-ingest/src/main/java/org/springframework/ingest/domain/Person.java b/batch/file-ingest/src/main/java/org/springframework/ingest/domain/Person.java
new file mode 100644
index 0000000..edf03d9
--- /dev/null
+++ b/batch/file-ingest/src/main/java/org/springframework/ingest/domain/Person.java
@@ -0,0 +1,29 @@
+package org.springframework.ingest.domain;
+
+/**
+ * Domain object representing data about a Person.
+ *
+ * @author Chris Schaefer
+ */
+public class Person {
+ private final String firstName;
+ private final String lastName;
+
+ public Person(final String firstName, final String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ @Override
+ public String toString() {
+ return "First name: " + firstName + " , last name: " + lastName;
+ }
+}
diff --git a/batch/file-ingest/src/main/java/org/springframework/ingest/mapper/fieldset/PersonFieldSetMapper.java b/batch/file-ingest/src/main/java/org/springframework/ingest/mapper/fieldset/PersonFieldSetMapper.java
new file mode 100644
index 0000000..4b3f6cf
--- /dev/null
+++ b/batch/file-ingest/src/main/java/org/springframework/ingest/mapper/fieldset/PersonFieldSetMapper.java
@@ -0,0 +1,21 @@
+package org.springframework.ingest.mapper.fieldset;
+
+import org.springframework.batch.item.file.mapping.FieldSetMapper;
+import org.springframework.batch.item.file.transform.FieldSet;
+
+import org.springframework.ingest.domain.Person;
+
+/**
+ * Maps the provided FieldSet into a Person object.
+ *
+ * @author Chris Schaefer
+ */
+public class PersonFieldSetMapper implements FieldSetMapper {
+ @Override
+ public Person mapFieldSet(FieldSet fieldSet) {
+ String firstName = fieldSet.readString(0);
+ String lastName = fieldSet.readString(1);
+
+ return new Person(firstName, lastName);
+ }
+}
diff --git a/batch/file-ingest/src/main/java/org/springframework/ingest/processor/PersonItemProcessor.java b/batch/file-ingest/src/main/java/org/springframework/ingest/processor/PersonItemProcessor.java
new file mode 100644
index 0000000..6d2802c
--- /dev/null
+++ b/batch/file-ingest/src/main/java/org/springframework/ingest/processor/PersonItemProcessor.java
@@ -0,0 +1,29 @@
+package org.springframework.ingest.processor;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.springframework.batch.item.ItemProcessor;
+import org.springframework.ingest.domain.Person;
+
+/**
+ * Processes the providing record, transforming the data into
+ * uppercase characters.
+ *
+ * @author Chris Schaefer
+ */
+public class PersonItemProcessor implements ItemProcessor {
+ private static final Logger LOGGER = LoggerFactory.getLogger(PersonItemProcessor.class);
+
+ @Override
+ public Person process(Person person) throws Exception {
+ String firstName = person.getFirstName().toUpperCase();
+ String lastName = person.getLastName().toUpperCase();
+
+ Person processedPerson = new Person(firstName, lastName);
+
+ LOGGER.info("Processed: " + person + " into: " + processedPerson);
+
+ return processedPerson;
+ }
+}
diff --git a/batch/file-ingest/src/main/resources/application.properties b/batch/file-ingest/src/main/resources/application.properties
new file mode 100644
index 0000000..f5e011e
--- /dev/null
+++ b/batch/file-ingest/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.application.name=fileIngest
diff --git a/batch/file-ingest/src/main/resources/data.csv b/batch/file-ingest/src/main/resources/data.csv
new file mode 100644
index 0000000..cead90f
--- /dev/null
+++ b/batch/file-ingest/src/main/resources/data.csv
@@ -0,0 +1,5 @@
+Jill,Doe
+Joe,Doe
+Justin,Doe
+Jane,Doe
+John,Doe
diff --git a/batch/file-ingest/src/main/resources/schema-all.sql b/batch/file-ingest/src/main/resources/schema-all.sql
new file mode 100644
index 0000000..e472ce1
--- /dev/null
+++ b/batch/file-ingest/src/main/resources/schema-all.sql
@@ -0,0 +1,7 @@
+DROP TABLE people IF EXISTS;
+
+CREATE TABLE people (
+ person_id BIGINT IDENTITY NOT NULL PRIMARY KEY,
+ first_name VARCHAR(20),
+ last_name VARCHAR(20)
+);
diff --git a/batch/file-ingest/src/test/java/org/springframework/ingest/config/BatchConfigurationTests.java b/batch/file-ingest/src/test/java/org/springframework/ingest/config/BatchConfigurationTests.java
new file mode 100644
index 0000000..1577977
--- /dev/null
+++ b/batch/file-ingest/src/test/java/org/springframework/ingest/config/BatchConfigurationTests.java
@@ -0,0 +1,120 @@
+package org.springframework.ingest.config;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.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.Step;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory;
+import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import org.springframework.util.ClassUtils;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * BatchConfiguration test cases
+ *
+ * @author Chris Schaefer
+ */
+public class BatchConfigurationTests {
+ private static AnnotationConfigApplicationContext context;
+
+ @Before
+ public void createContext() {
+ context = new AnnotationConfigApplicationContext(new Class[] {
+ BatchConfiguration.class, BatchConfigurationTests.DataSourceConfiguration.class });
+ }
+
+ @After
+ public void closeContext() {
+ context.close();
+ }
+
+ @Test
+ public void testBatchConfigurationSuccess() throws Exception {
+ JobExecution jobExecution = testJob("classpath:data.csv");
+
+ assertEquals("Incorrect batch status", BatchStatus.COMPLETED, jobExecution.getStatus());
+ assertEquals("Invalid number of step executions", 1, jobExecution.getStepExecutions().size());
+ }
+
+ @Test
+ public void testBatchConfigurationFail() throws Exception {
+ JobExecution jobExecution = testJob("classpath:missing-data-file.csv");
+
+ assertEquals("Incorrect batch status", BatchStatus.FAILED, jobExecution.getStatus());
+ }
+
+ @Test
+ public void testBatchDataProcessing() throws Exception {
+ JobExecution jobExecution = testJob("classpath:data.csv");
+
+ assertEquals("Incorrect batch status", BatchStatus.COMPLETED, jobExecution.getStatus());
+ assertEquals("Invalid number of step executions", 1, jobExecution.getStepExecutions().size());
+
+ JdbcTemplate jdbcTemplate = new JdbcTemplate(context.getBean(DataSource.class));
+ List