I am currently setting up a file import service for an application, which would allow a user to upload a csv file via REST. The import of the job will be done via Spring Batch as these jobs may be long running, based on future processing requirements and checks.
According to me the below setup is correct for Spring Boot and Spring Batch, but the code won't compile.
The main BatchConfiguration file:
@Configuration
@EnableBatchProcessing
public class PatientBatchConfiguration {
@Autowired
private JobBuilderFactory jobBuilders;
@Autowired
private StepBuilderFactory stepBuilders;
@Autowired
private PatientFieldSetMapper fieldSetMapper;
@Autowired
private PatientItemWriter writer;
@Bean
public Job importPatientsFromUpload(){
return jobBuilders.get("importPatientsFromUpload")
.start(step())
.build();
}
@Bean
public Step step(){
return stepBuilders.get("step")
.<Patient,Patient>chunk(1)
.reader(reader(null))
.writer(writer)
.build();
}
@Bean
@StepScope
public ItemReader<Patient> reader(@Value("#{jobParameters['fileName']}") String filePath) {
FlatFileItemReader<Patient> itemReader = new FlatFileItemReader<Patient>();
itemReader.setLineMapper(lineMapper());
itemReader.setResource(new FileSystemResource(filePath));
return itemReader;
}
private LineMapper<Patient> lineMapper() {
DefaultLineMapper<Patient> lineMapper = new DefaultLineMapper<Patient>();
DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
lineTokenizer.setNames(new String[]{"name","surname","idNumber","dob", "email", "cell"});
lineMapper.setLineTokenizer(lineTokenizer);
lineMapper.setFieldSetMapper(fieldSetMapper);
return lineMapper;
}
}
FieldSetMapper code:
@Component
public class PatientFieldSetMapper implements FieldSetMapper<Patient> {
@Override
public Patient mapFieldSet(FieldSet fieldSet) throws BindException {
if(fieldSet == null){
return null;
}
Patient patient = new Patient();
patient.setName(fieldSet.readString("name"));
patient.setSurname(fieldSet.readString("surname"));
patient.setIdNo(fieldSet.readString("idNumber"));
patient.setDob(0L);
patient.setEmail(fieldSet.readString("email"));
patient.setCell(fieldSet.readString("cell"));
return patient;
}
}
PatientItemWriter code:
@Component
public class PatientItemWriter implements ItemWriter<Patient> {
@Autowired
PatientRepository patientRepository;
@Override
public void write(List<? extends Patient> list) throws Exception {
for(Patient patient: list) {
patientRepository.save(patient);
}
}
}
Stacktrace:
Caused by: java.lang.IllegalArgumentException: Path must not be null
at org.springframework.util.Assert.notNull(Assert.java:115) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.core.io.FileSystemResource.<init>(FileSystemResource.java:75) ~[spring-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at com.example.batch.patient.PatientBatchConfiguration.reader(PatientBatchConfiguration.java:59) ~[classes/:na]
at com.example.batch.patient.PatientBatchConfiguration.step(PatientBatchConfiguration.java:49) ~[classes/:na]
And finally the application.properties file
spring.batch.job.enabled=false
The reason spring.batch.job.enabled=false is located in the properties file, is that the job importPatientsFromUpload will be called from a controller after the user has upload a file, and without it, jobs will run at startup.
The problem I am having is that the FileSystemResource is failing to be created because the path can't be null. However I understood that as soon as one annotates a method with @StepScope, a proxy bean will be created, which would allow me to use the jobParameter passed through at runtime to create a new file system resource. I have seen various examples online of using jobParameters this way, but for some reason, the bean seems not to be created correctly. I am not sure whether this is related to the fact that I am using Spring Boot, or some other error.
Any help would be appreciated. Thanks in advance.
UPDATE in Reply to Gaël
PatientItemReader code:
@Component
@StepScope
public class PatientItemReader implements ItemReader<Patient> {
@Autowired
private PatientFieldSetMapper fieldSetMapper;
private FlatFileItemReader<Patient> itemReader;
@Override
public Patient read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
return itemReader.read();
}
public PatientItemReader(@Value("#{jobParameters[filePath]}") String filePath) {
itemReader = new FlatFileItemReader<Patient>();
DefaultLineMapper<Patient> lineMapper = new DefaultLineMapper<Patient>();
DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
lineTokenizer.setNames(new String[]{"name","surname","idNumber","dob", "email", "cell"});
lineMapper.setLineTokenizer(lineTokenizer);
lineMapper.setFieldSetMapper(fieldSetMapper);
itemReader.setLineMapper(lineMapper);
itemReader.setResource(new FileSystemResource(filePath));
}
}
.reader(reader(null))
with an explicit null value. – Gaël J