0

[New to Spring Batch] I have different csv of different format, there can be more csv added in future so I thought of having a common FlatFileItemReader<T> instead of defining @Bean for each csv format, I created a base configuration class then concrete class for each csv type.

Since I have defined Reader bean as @StepScope , during batch job runtime it auto-initializes bean with the first concrete class in the package, same kind of problem is discussed here but answer is not relevant to my case

How do I pass particular concrete class type of ItemReader to my step during job run?

Here is my base configuration class:

public abstract class AbstractBatchItemReader<T> {

private CsvInformation csvInformation;

protected AbstractBatchItemReader(CsvInformation csvInformation) {
    this.csvInformation = csvInformation;
}

@Bean
@StepScope
//fileName is retrieved from jobParameters during runtime
public FlatFileItemReader<T> getItemReader(@Value("#{jobParameters['input.file.name']}") String fileName) {
    return new FlatFileItemReaderBuilder<T>()
            .name("invoiceHeaderItemReader")
            .resource(new FileSystemResource(fileName))
            .linesToSkip(1)
            .delimited()
            .names(csvInformation.getHeaders().split(","))
            .fieldSetMapper(new BeanWrapperFieldSetMapper<T>() {{
                setConversionService(new StringToLocalDateConversion().convert());
                setTargetType(csvInformation.getERPClass());
            }})
            .build();

    }
 }

Here is the concrete class that extends the base config:

@Configuration
public class InvoiceHeaderReader extends AbstractBatchItemReader<ERPInvoiceHeader> {
protected InvoiceHeaderReader(InvoiceHeaderCsvInformation csvInformation) {
    super(csvInformation);
  }
}

Here is my base step config:

public abstract class AbstractBatchStep<T> {

private final AbstractBatchItemReader<T> reader;
private final AbstractBatchItemWriter<T> writer;
private final StepBuilderFactory stepBuilderFactory;

protected AbstractBatchStep(AbstractBatchItemReader<T> reader,
                            AbstractBatchItemWriter<T> writer,
                            StepBuilderFactory stepBuilderFactory) {
    this.reader = reader;
    this.writer = writer;
    this.stepBuilderFactory = stepBuilderFactory;
}

public Step getStep() {
    afterPropertiesSet();
    return stepBuilderFactory.get("Batch Step")
            .<T, T>chunk(BatchConfiguration.READER_CHUNK_SIZE)
            //fileName is passed during runtime
            .reader(reader.getItemReader(null))
            .writer(writer.getItemWriter())
            .build();
   }
 }

Here is the concrete class that extends step config:

@Configuration("invoice_header")
public class InvoiceHeaderStep extends AbstractBatchStep<ERPInvoiceHeader> {
protected InvoiceHeaderStep(InvoiceHeaderReader reader, InvoiceHeaderWriter writer, StepBuilderFactory stepBuilderFactory) {
    super(reader, writer, stepBuilderFactory);
 }
}

The whole Job cycle runs only for the first concrete class in the package if I try to run another type of csv it fails with exception.. Unexpected token required n found n exception is obviously because the reader was auto initialized by first class in package, not the one that I pass to Step

Please also suggest if this design pattern is correct of there could possibly be an easy way to achieve this.

  • Does this answer your question? [How to create a generic FlatFileItemReader to read CSV files with different headers?](https://stackoverflow.com/questions/59548732/how-to-create-a-generic-flatfileitemreader-to-read-csv-files-with-different-head) – Mahmoud Ben Hassine Jan 29 '21 at 13:04
  • Does this answer your question? [Generic Item Reader and Item Writer in spring-batch](https://stackoverflow.com/questions/16054107/generic-item-reader-and-item-writer-in-spring-batch) – Mahmoud Ben Hassine Jan 29 '21 at 13:05
  • @MahmoudBenHassine actually I referred both SO question, my problem is very different from both.. I have already created generic reader, the issue is it is not properly initialized at runtime during job run since I'm using `@StepScope` – abdur rehman Jan 29 '21 at 14:15
  • @MahmoudBenHassine Please refer your last comment in [this](https://stackoverflow.com/questions/65626409/configure-itemwriter-itemreader-step-and-job-dynamically-in-spring-batch) thread.. taking some idea from your comment now that I have successfully created generic reader. After testing I encountered the above issue – abdur rehman Jan 29 '21 at 14:30
  • Does it work without the `@StepScope` annotation? Try with a hardcoded job parameter and let me know. I want to make sure this is not related to `@StepScoped`. – Mahmoud Ben Hassine Feb 01 '21 at 08:50
  • @MahmoudBenHassine yes it works without `@StepScope` , I tried for two different types with static resource path in reader – abdur rehman Feb 01 '21 at 09:33
  • ok just to confirm, the approach I suggested [here](https://stackoverflow.com/questions/65626409/configure-itemwriter-itemreader-step-and-job-dynamically-in-spring-batch#comment116098840_65626409) worked for you except the `StepScoped` issue. Is that correct? If it's the case, have you tried creating a separate step scoped bean for the job parameter like `@Bean @StepScope public Resource resource(@Value("#{jobParameters['input.file.name']}") String fileName) {return new FileSystemResource(fileName)}` and pass that to `.resource(resource(null))` in your item reader? – Mahmoud Ben Hassine Feb 02 '21 at 08:51
  • Thanks a lot it worked, All I had to do is remove `@Bean` annotation from AbstractReader class method and define separate `@StepScope` Resource bean like you suggested. Actually the problem was with `@Bean` annotation in Abstract class, spring will try to initialize bean at start-up so by default picks first concrete class in the package, – abdur rehman Feb 02 '21 at 14:16
  • I tried providing an answer but SO is not accepting answer from my account.. LOL, I will try posting answer after few days.. meanwhile i will check how can i contribute to SO and improve my reputation Thanks – abdur rehman Feb 03 '21 at 06:52
  • @MahmoudBenHassine As Requested I have posted an answer, I dont know if you get notified if I answer my own question so commenting here to update you on this – abdur rehman Feb 05 '21 at 08:52
  • Great, thank you. I upvote it. – Mahmoud Ben Hassine Feb 05 '21 at 09:48

1 Answers1

1

I would like to post answer as a reference to others.

  1. I created an AbstractBatchItemReader<T> class which has base configuration
  2. Concrete classes that extends base config class TypeOneCsvReader extends AbstractBatchItemReader<TypeOneEntity>

3.Interface with Csv Information methods and Classes implementing interface for each Csv type

Here is the code sample:

AbstractBatchItemReader:

public abstract class AbstractBatchItemReader<T> {

private CsvInformation csvInformation;
protected AbstractBatchItemReader(CsvInformation csvInformation) {
    this.csvInformation = csvInformation;
}

 FlatFileItemReader<T> getItemReader() {
    return new FlatFileItemReaderBuilder<T>()
            .name("Batch Reader")
            .resource(resource(null))
            .linesToSkip(1)
            .delimited()
            .quoteCharacter(BatchConfiguration.READER_QUOTE_CHARACTER)
            .names(csvInformation.getHeaders().split(","))
            .fieldSetMapper(new BeanWrapperFieldSetMapper<T>() {{
                setConversionService(StringToLocalDateConversion.convert());
                setTargetType(csvInformation.getEntityClass());
            }})
            .build();

}

@Bean
@StepScope
public Resource resource(@Value("#{jobParameters['input.file.name']}") String fileName) {
    return new FileSystemResource(fileName);
}
}

Concrete Class:

@Configuration
public class TypeOneCsvReader extends AbstractBatchItemReader<TypeOneEntity> {
protected TypeOneCsvReader(TypeOneCsv csvInformation) {
    super(csvInformation);
}
}

CsvInformation Interface:

public interface CsvInformation {
String getHeaders();
Class getEntityClass();

}

Each implementation of interface has to be annotated with @Component so that Concrete Reader class picks it up via DI

Benefit of having such an approach is, it can be scaled to as many csv type as required and also the Reader logic stays at one place

Thanks