2

I am trying to create an application that uses the spring-batch-excel extension to be able to read Excel files uploaded through a web interface by it's users in order to parse the Excel file for addresses.

When the code runs, there is no error, but all I get is the following in my log. Even though I have log/syso throughout my Processor and Writer (these are never being called, and all I can imagine is it's not properly reading the file, and returning no data to process/write). And yes, the file has data, several thousand records in fact.

Job: [FlowJob: [name=excelFileJob]] launched with the following parameters: [{file=Book1.xlsx}]
Executing step: [excelFileStep]
Job: [FlowJob: [name=excelFileJob]] completed with the following parameters: [{file=Book1.xlsx}] and the following status: [COMPLETED]

Below is my JobConfig

@Configuration
@EnableBatchProcessing
public class AddressExcelJobConfig {

    @Bean
    public BatchConfigurer configurer(EntityManagerFactory entityManagerFactory) {
        return new CustomBatchConfigurer(entityManagerFactory);
    }

    @Bean
    Step excelFileStep(ItemReader<AddressExcel> excelAddressReader,
                       ItemProcessor<AddressExcel, AddressExcel> excelAddressProcessor,
                       ItemWriter<AddressExcel> excelAddressWriter, 
                       StepBuilderFactory stepBuilderFactory) {
        return stepBuilderFactory.get("excelFileStep")
                .<AddressExcel, AddressExcel>chunk(1)
                .reader(excelAddressReader)
                .processor(excelAddressProcessor)
                .writer(excelAddressWriter)
                .build();
    }

    @Bean
    Job excelFileJob(JobBuilderFactory jobBuilderFactory, 
                     @Qualifier("excelFileStep") Step excelAddressStep) {
        return jobBuilderFactory.get("excelFileJob")
                .incrementer(new RunIdIncrementer())
                .flow(excelAddressStep)
                .end()
                .build();
    }
}

Below is my AddressExcelReader The late binding works fine, there is no error. I have tried loading the resource given the file name, in addition to creating a new ClassPathResource and FileSystemResource. All are giving me the same results.

@Component
@StepScope
public class AddressExcelReader implements ItemReader<AddressExcel> {

    private PoiItemReader<AddressExcel> itemReader = new PoiItemReader<AddressExcel>();

    @Override
    public AddressExcel read()
            throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
        return itemReader.read();
    }

    public AddressExcelReader(@Value("#{jobParameters['file']}") String file, StorageService storageService) {
        //Resource resource = storageService.loadAsResource(file);
        //Resource testResource = new FileSystemResource("upload-dir/Book1.xlsx");
        itemReader.setResource(new ClassPathResource("/upload-dir/Book1.xlsx"));
        itemReader.setLinesToSkip(1);
        itemReader.setStrict(true);
        itemReader.setRowMapper(excelRowMapper());
    }

    public RowMapper<AddressExcel> excelRowMapper() {
        BeanWrapperRowMapper<AddressExcel> rowMapper = new BeanWrapperRowMapper<>();
        rowMapper.setTargetType(AddressExcel.class);
        return rowMapper;
    }

}

Below is my AddressExcelProcessor

@Component
public class AddressExcelProcessor implements ItemProcessor<AddressExcel, AddressExcel> {

    private static final Logger log = LoggerFactory.getLogger(AddressExcelProcessor.class);

    @Override
    public AddressExcel process(AddressExcel item) throws Exception {
        System.out.println("Converting " + item);
        log.info("Convert {}", item);
        return item;
    }

}

Again, this is never coming into play (no logs generated). And if it matters, this is how I'm launching my job from a FileUploadController from a @PostMapping("/") to handle the file upload, which first stores the file, then runs the job:

@PostMapping("/")
public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {

    storageService.store(file);

    try {
        JobParameters jobParameters = new JobParametersBuilder()
                .addString("file", file.getOriginalFilename().toString()).toJobParameters();
        jobLauncher.run(job, jobParameters);
    } catch (JobExecutionAlreadyRunningException | JobRestartException | JobInstanceAlreadyCompleteException
            | JobParametersInvalidException e) {
        e.printStackTrace();
    }

    redirectAttributes.addFlashAttribute("message",
            "You successfully uploaded " + file.getOriginalFilename() + "!");

    return "redirect:/";
}

And last by not least

Here is my AddressExcel POJO

import lombok.Data;

@Data
public class AddressExcel {

    private String address1;
    private String address2;
    private String city;
    private String state;
    private String zip;

    public AddressExcel() {}

}

UPDATE (10/13/2016) From Nghia Do's comments, I also created my own RowMapper instead of using the BeanWrapper to see if that was the issue. Still the same results.

public class AddressExcelRowMapper implements RowMapper<AddressExcel> {

    @Override
    public AddressExcel mapRow(RowSet rs) throws Exception {
        AddressExcel temp = new AddressExcel();

        temp.setAddress1(rs.getColumnValue(0));
        temp.setAddress2(rs.getColumnValue(1));
        temp.setCity(rs.getColumnValue(2));
        temp.setState(rs.getColumnValue(3));
        temp.setZip(rs.getColumnValue(4));

        return temp;
    }

}
  • For troubleshooting, instead of using BeanWrapperRowMapper, could you have a simple class which implement org.springframework.batch.item.excel.RowMapper to see if Reader actually read data and come to your RowMapper. – Nghia Do Oct 12 '16 at 20:29
  • 1
    are you sure, that 'itemReader.setResource(new ClassPathResource("/upload-dir/Book1.xlsx"));' is correct? You would need to have the directory that contains the ubdirectory "upload-dir" in your classpath. – Hansjoerg Wingeier Oct 13 '16 at 05:06
  • Did you debug your reader to check if file is found and opened correctly? – Luca Basso Ricci Oct 13 '16 at 05:08
  • @NghiaDo I created a simple class implementing RowMapper and overriding the mapRow function. Very simple, created a temp AddressExcel object, populated values, and returned the object. That didn't do anything different. – LiveFreeOrCode Oct 13 '16 at 14:33
  • @HansjoergWingeier Yes, the file is correct. Changing the file name even slightly results in an error. I have tested by logging the resource.exists(), which comes back as true. – LiveFreeOrCode Oct 13 '16 at 14:34
  • @LucaBassoRicci I'm not sure how else to debug it. As mentioned above I have tried a manual RowMapper, and I've also confirmed that the resource does in fact exist. – LiveFreeOrCode Oct 13 '16 at 14:35
  • Your `AddressExcelReader` should extends `ItemStreamReader` to allow SB manage delegate reader lifecycle – Luca Basso Ricci Oct 13 '16 at 14:47
  • Could you put a breakpoint at class which implements RowMapper to see if it comes to that or not? – Nghia Do Oct 13 '16 at 15:00
  • I run with Spring Boot, so I don't have much of an answer for your current issue. I can tell you that you can't use `FileSystemResource` - it isn't supported mark/reset. You can, however, use `UrlResource` when you get to that point. – Andy Sampson Nov 02 '16 at 02:18

1 Answers1

0

All it seems I needed was to add the following to my ItemReader:

itemReader.afterPropertiesSet();
itemReader.open(new ExecutionContext());
  • This is only a partial solution to problem. You need to register delegate as stream or follow my comment on question – Luca Basso Ricci Oct 13 '16 at 21:29
  • I didn't have to implement ItemStreamReader for it to work. What benefit would that give me? Sorry, I just don't know is why I'm asking. – LiveFreeOrCode Oct 14 '16 at 15:47
  • @ThirstyForJava : Whenever I tried to do as mentioned above, I got this exception: `org.springframework.batch.item.ItemStreamException: Failed to initialize the reader` and `Caused by: java.lang.IllegalStateException: InputStream MUST either support mark/reset, or be wrapped as a PushbackInputStream` What should i do? Is there any other alternative? – Naveen Kumar Feb 12 '18 at 09:42