From 6521a3a8b4c3de044289cbb2d973f87302f929dd Mon Sep 17 00:00:00 2001 From: aek Date: Thu, 29 Apr 2021 21:38:48 +0200 Subject: [PATCH] base architecture --- .../tutorial/batch/BatchConfiguration.java | 96 ++++++++++++++++++ .../tutorial/batch/EmployeeItemReader.java | 54 ++++++++++ .../tutorial/batch/EmployeeJobLauncher.java | 59 +++++++++++ .../listeners/JobCompletionListener.java | 42 ++++++++ .../batch/mappers/EmployeeItemRowMapper.java | 23 +++++ .../processors/EmployeeItemProcessor.java | 45 ++++++++ .../EmployeeJobParametersValidator.java | 19 ++++ .../batch/writers/EmployeeItemWriter.java | 41 ++++++++ .../excel/tutorial/domain/Employee.java | 4 + .../excel/tutorial/support/package-info.java | 1 + .../{ => support}/poi/AbstractExcelPoi.java | 4 +- .../{ => support}/poi/CellFactory.java | 5 +- .../{ => support}/poi/ExcelFileException.java | 2 +- .../tutorial/{ => support}/poi/RowMapper.java | 2 +- .../poi/UnknownCellTypeException.java | 2 +- .../{ => support}/poi/package-info.java | 2 +- src/main/resources/application.yml | 4 + src/main/resources/employee.xlsx | Bin 0 -> 14851 bytes 18 files changed, 399 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/springbatch/excel/tutorial/batch/EmployeeItemReader.java create mode 100644 src/main/java/com/springbatch/excel/tutorial/batch/EmployeeJobLauncher.java create mode 100644 src/main/java/com/springbatch/excel/tutorial/batch/listeners/JobCompletionListener.java create mode 100644 src/main/java/com/springbatch/excel/tutorial/batch/mappers/EmployeeItemRowMapper.java create mode 100644 src/main/java/com/springbatch/excel/tutorial/batch/processors/EmployeeItemProcessor.java create mode 100644 src/main/java/com/springbatch/excel/tutorial/batch/validators/EmployeeJobParametersValidator.java create mode 100644 src/main/java/com/springbatch/excel/tutorial/batch/writers/EmployeeItemWriter.java create mode 100644 src/main/java/com/springbatch/excel/tutorial/support/package-info.java rename src/main/java/com/springbatch/excel/tutorial/{ => support}/poi/AbstractExcelPoi.java (96%) rename src/main/java/com/springbatch/excel/tutorial/{ => support}/poi/CellFactory.java (91%) rename src/main/java/com/springbatch/excel/tutorial/{ => support}/poi/ExcelFileException.java (83%) rename src/main/java/com/springbatch/excel/tutorial/{ => support}/poi/RowMapper.java (87%) rename src/main/java/com/springbatch/excel/tutorial/{ => support}/poi/UnknownCellTypeException.java (86%) rename src/main/java/com/springbatch/excel/tutorial/{ => support}/poi/package-info.java (56%) create mode 100644 src/main/resources/employee.xlsx diff --git a/src/main/java/com/springbatch/excel/tutorial/batch/BatchConfiguration.java b/src/main/java/com/springbatch/excel/tutorial/batch/BatchConfiguration.java index 31db883..c5e53a7 100644 --- a/src/main/java/com/springbatch/excel/tutorial/batch/BatchConfiguration.java +++ b/src/main/java/com/springbatch/excel/tutorial/batch/BatchConfiguration.java @@ -1,4 +1,100 @@ package com.springbatch.excel.tutorial.batch; +import com.springbatch.excel.tutorial.batch.listeners.JobCompletionListener; +import com.springbatch.excel.tutorial.batch.processors.EmployeeItemProcessor; +import com.springbatch.excel.tutorial.batch.validators.EmployeeJobParametersValidator; +import com.springbatch.excel.tutorial.batch.writers.EmployeeItemWriter; +import com.springbatch.excel.tutorial.domain.Employee; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobParametersValidator; +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.job.CompositeJobParametersValidator; +import org.springframework.batch.core.launch.support.RunIdIncrementer; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ItemWriter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Collections; + +/** + * Configuration for batch + */ +@EnableBatchProcessing +@Configuration public class BatchConfiguration { + + public final JobBuilderFactory jobBuilderFactory; + + public final StepBuilderFactory stepBuilderFactory; + + public BatchConfiguration(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory) { + this.jobBuilderFactory = jobBuilderFactory; + this.stepBuilderFactory = stepBuilderFactory; + } + + @Bean + public JobParametersValidator jobParametersValidator() { + return new EmployeeJobParametersValidator(); + } + + @Bean + public JobParametersValidator compositeJobParametersValidator() { + CompositeJobParametersValidator bean = new CompositeJobParametersValidator(); + bean.setValidators(Collections.singletonList(jobParametersValidator())); + return bean; + } + + @Bean + public ItemProcessor itemProcessor() { + return new EmployeeItemProcessor(); + } + + @Bean + @StepScope + public ItemReader itemReader(@Value("#{jobParameters[excelPath]}") String pathToFile) { + return new EmployeeItemReader(pathToFile); + } + + @Bean + public ItemWriter itemWriter() { + return new EmployeeItemWriter(); + } + + /** + * Declaration step + * @return {@link Step} + */ + @Bean + public Step employeeStep() { + return stepBuilderFactory.get("employeeStep") + .chunk(1) + .reader(itemReader(null)) + .processor(itemProcessor()) + .writer(itemWriter()) + .build(); + } + + /** + * Declaration job + * @param listener {@link JobCompletionListener} + * @return {@link Job} + */ + @Bean + public Job employeeJob(JobCompletionListener listener) { + return jobBuilderFactory.get("employeeJob") + .incrementer(new RunIdIncrementer()) + .listener(listener) + .flow(employeeStep()) + .end() + .validator(compositeJobParametersValidator()) + .build(); + } + } diff --git a/src/main/java/com/springbatch/excel/tutorial/batch/EmployeeItemReader.java b/src/main/java/com/springbatch/excel/tutorial/batch/EmployeeItemReader.java new file mode 100644 index 0000000..8802fcb --- /dev/null +++ b/src/main/java/com/springbatch/excel/tutorial/batch/EmployeeItemReader.java @@ -0,0 +1,54 @@ +package com.springbatch.excel.tutorial.batch; + +import com.springbatch.excel.tutorial.batch.mappers.EmployeeItemRowMapper; +import com.springbatch.excel.tutorial.domain.Employee; +import com.springbatch.excel.tutorial.support.poi.AbstractExcelPoi; +import org.springframework.batch.core.configuration.annotation.StepScope; +import org.springframework.batch.item.ItemReader; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@StepScope +public class EmployeeItemReader extends AbstractExcelPoi implements ItemReader { + + private int nextBeneficiaryIndex = 0; + + private final String filePath; + + public EmployeeItemReader(String filePath) { + super(); + this.filePath = filePath; + } + + /** + * {@inheritDoc} + */ + @Override + public Employee read() { + + List beneficiaryDossierDatas; + Employee nextBeneficiary = null; + + // read data in file + beneficiaryDossierDatas = read(filePath, new EmployeeItemRowMapper()); + + if(!beneficiaryDossierDatas.isEmpty()) { + + if (nextBeneficiaryIndex < beneficiaryDossierDatas.size()) { + nextBeneficiary = beneficiaryDossierDatas.get(nextBeneficiaryIndex); + nextBeneficiaryIndex++; + } else { + nextBeneficiaryIndex = 0; + } + } + + return nextBeneficiary; + } + + @Override + public void write(String filePath , List aList) { + + } +} diff --git a/src/main/java/com/springbatch/excel/tutorial/batch/EmployeeJobLauncher.java b/src/main/java/com/springbatch/excel/tutorial/batch/EmployeeJobLauncher.java new file mode 100644 index 0000000..630ca38 --- /dev/null +++ b/src/main/java/com/springbatch/excel/tutorial/batch/EmployeeJobLauncher.java @@ -0,0 +1,59 @@ +package com.springbatch.excel.tutorial.batch; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @author aek + */ +@Component +public class EmployeeJobLauncher { + + private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeJobLauncher.class); + + private final Job job; + + private final JobLauncher jobLauncher; + + @Value("${employee.excel.path}") + private String excelPath; + + EmployeeJobLauncher(Job job, JobLauncher jobLauncher) { + this.job = job; + this.jobLauncher = jobLauncher; + } + + @Scheduled(cron = "*/2 * * * *") + void launchFileToJob() throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException, JobRestartException { + LOGGER.info("Starting job"); + + jobLauncher.run(job, jobParameters()); + + LOGGER.info("Stopping job"); + } + + private JobParameters jobParameters() { + Map parameters = new HashMap<>(); + + parameters.put("currentTime", new JobParameter(new Date())); + parameters.put("excelPath", new JobParameter(excelPath)); + + return new JobParameters(parameters); + } + +} diff --git a/src/main/java/com/springbatch/excel/tutorial/batch/listeners/JobCompletionListener.java b/src/main/java/com/springbatch/excel/tutorial/batch/listeners/JobCompletionListener.java new file mode 100644 index 0000000..8b896f2 --- /dev/null +++ b/src/main/java/com/springbatch/excel/tutorial/batch/listeners/JobCompletionListener.java @@ -0,0 +1,42 @@ +package com.springbatch.excel.tutorial.batch.listeners; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.listener.JobExecutionListenerSupport; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component +public class JobCompletionListener extends JobExecutionListenerSupport { + + private static final Logger LOGGER = LoggerFactory.getLogger(JobCompletionListener.class); + + public JobCompletionListener() { + } + + @Override + public void afterJob(JobExecution jobExecution) { + + String pathToExtractionFile = jobExecution.getJobParameters().getString("pathToFile"); + String jobId = jobExecution.getJobParameters().getString("jobId"); + + // get job's start time + Date start = jobExecution.getCreateTime(); + // get job's end time + Date end = jobExecution.getEndTime(); + + if(jobExecution.getStatus() == BatchStatus.COMPLETED) { + + LOGGER.trace("===========================JOB FINISHED================================================"); + LOGGER.trace("JobId : {}",jobId); + LOGGER.trace("Path file : {}", pathToExtractionFile); + LOGGER.trace("Date: {}", end); + LOGGER.trace("======================================================================================="); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/springbatch/excel/tutorial/batch/mappers/EmployeeItemRowMapper.java b/src/main/java/com/springbatch/excel/tutorial/batch/mappers/EmployeeItemRowMapper.java new file mode 100644 index 0000000..5929ff0 --- /dev/null +++ b/src/main/java/com/springbatch/excel/tutorial/batch/mappers/EmployeeItemRowMapper.java @@ -0,0 +1,23 @@ +package com.springbatch.excel.tutorial.batch.mappers; + +import com.springbatch.excel.tutorial.domain.Employee; +import com.springbatch.excel.tutorial.support.poi.CellFactory; +import com.springbatch.excel.tutorial.support.poi.RowMapper; +import org.apache.poi.ss.usermodel.Row; + + +public class EmployeeItemRowMapper extends CellFactory implements RowMapper { + + @Override + public Employee transformerRow(Row row) { + Employee employee = new Employee(); + + employee.setFirstName((String) getCellValue(row.getCell(0))); + employee.setLastName((String) getCellValue(row.getCell(1))); + employee.setEmail((String) getCellValue(row.getCell(2))); + employee.setDepartment((String) getCellValue(row.getCell(3))); + employee.setSalary((Double) getCellValue(row.getCell(4))); + + return employee; + } +} diff --git a/src/main/java/com/springbatch/excel/tutorial/batch/processors/EmployeeItemProcessor.java b/src/main/java/com/springbatch/excel/tutorial/batch/processors/EmployeeItemProcessor.java new file mode 100644 index 0000000..64c2c84 --- /dev/null +++ b/src/main/java/com/springbatch/excel/tutorial/batch/processors/EmployeeItemProcessor.java @@ -0,0 +1,45 @@ +package com.springbatch.excel.tutorial.batch.processors; + +import com.springbatch.excel.tutorial.domain.Employee; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.item.ItemProcessor; + + +/** + * @author Eric KOUAME + */ +public class EmployeeItemProcessor implements ItemProcessor, StepExecutionListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeItemProcessor.class); + + + public EmployeeItemProcessor() { + super(); + } + + + /** + * {@inheritDoc} + */ + @Override + public Employee process(Employee item) throws Exception { + + return null; + } + + @Override + public void beforeStep(StepExecution stepExecution) { + /* Nothing to do before */ + } + + @Override + public ExitStatus afterStep(StepExecution stepExecution) { + + return null; + } + +} diff --git a/src/main/java/com/springbatch/excel/tutorial/batch/validators/EmployeeJobParametersValidator.java b/src/main/java/com/springbatch/excel/tutorial/batch/validators/EmployeeJobParametersValidator.java new file mode 100644 index 0000000..399f3b6 --- /dev/null +++ b/src/main/java/com/springbatch/excel/tutorial/batch/validators/EmployeeJobParametersValidator.java @@ -0,0 +1,19 @@ +package com.springbatch.excel.tutorial.batch.validators; + +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.JobParametersValidator; +import org.springframework.util.StringUtils; + +public class EmployeeJobParametersValidator implements org.springframework.batch.core.JobParametersValidator { + + @Override + public void validate(final JobParameters jobParameters) throws JobParametersInvalidException { + String fileName = jobParameters != null ? jobParameters.getString("excelPath") : null; + + if (fileName !=null && !StringUtils.endsWithIgnoreCase(fileName, "xlsx")) { + throw new JobParametersInvalidException("The file type must be in xlsx format"); + } + } + +} diff --git a/src/main/java/com/springbatch/excel/tutorial/batch/writers/EmployeeItemWriter.java b/src/main/java/com/springbatch/excel/tutorial/batch/writers/EmployeeItemWriter.java new file mode 100644 index 0000000..ecd77d7 --- /dev/null +++ b/src/main/java/com/springbatch/excel/tutorial/batch/writers/EmployeeItemWriter.java @@ -0,0 +1,41 @@ +package com.springbatch.excel.tutorial.batch.writers; + +import com.springbatch.excel.tutorial.domain.Employee; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.item.ItemWriter; + +import java.util.List; + +public class EmployeeItemWriter implements ItemWriter, StepExecutionListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeItemWriter.class); + + public EmployeeItemWriter() { + super(); + } + + @Override + public void write(List items) throws Exception { + + } + + @Override + public void beforeStep(StepExecution stepExecution) { + /* Nothing to do before */ + } + + @Override + public ExitStatus afterStep(StepExecution stepExecution) { + + if(stepExecution.getStatus() == BatchStatus.COMPLETED) { + + } + return null; + } + +} diff --git a/src/main/java/com/springbatch/excel/tutorial/domain/Employee.java b/src/main/java/com/springbatch/excel/tutorial/domain/Employee.java index 1af2367..db48db1 100644 --- a/src/main/java/com/springbatch/excel/tutorial/domain/Employee.java +++ b/src/main/java/com/springbatch/excel/tutorial/domain/Employee.java @@ -23,4 +23,8 @@ public class Employee { private String lastName; private String email; + + private String department; + + private double salary; } \ No newline at end of file diff --git a/src/main/java/com/springbatch/excel/tutorial/support/package-info.java b/src/main/java/com/springbatch/excel/tutorial/support/package-info.java new file mode 100644 index 0000000..c689ca3 --- /dev/null +++ b/src/main/java/com/springbatch/excel/tutorial/support/package-info.java @@ -0,0 +1 @@ +package com.springbatch.excel.tutorial.support; \ No newline at end of file diff --git a/src/main/java/com/springbatch/excel/tutorial/poi/AbstractExcelPoi.java b/src/main/java/com/springbatch/excel/tutorial/support/poi/AbstractExcelPoi.java similarity index 96% rename from src/main/java/com/springbatch/excel/tutorial/poi/AbstractExcelPoi.java rename to src/main/java/com/springbatch/excel/tutorial/support/poi/AbstractExcelPoi.java index b15ea42..8027d1a 100644 --- a/src/main/java/com/springbatch/excel/tutorial/poi/AbstractExcelPoi.java +++ b/src/main/java/com/springbatch/excel/tutorial/support/poi/AbstractExcelPoi.java @@ -1,4 +1,4 @@ -package com.springbatch.excel.tutorial.poi; +package com.springbatch.excel.tutorial.support.poi; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; @@ -97,6 +97,8 @@ public void createHeaderRow(Sheet sheet, List headers, CellStyle rowStyl cellId.setCellStyle(rowStyle); } cellId.setCellValue(headers.get(i)); + + sheet.autoSizeColumn(i); } } diff --git a/src/main/java/com/springbatch/excel/tutorial/poi/CellFactory.java b/src/main/java/com/springbatch/excel/tutorial/support/poi/CellFactory.java similarity index 91% rename from src/main/java/com/springbatch/excel/tutorial/poi/CellFactory.java rename to src/main/java/com/springbatch/excel/tutorial/support/poi/CellFactory.java index 75d8f10..584f69a 100644 --- a/src/main/java/com/springbatch/excel/tutorial/poi/CellFactory.java +++ b/src/main/java/com/springbatch/excel/tutorial/support/poi/CellFactory.java @@ -1,4 +1,4 @@ -package com.springbatch.excel.tutorial.poi; +package com.springbatch.excel.tutorial.support.poi; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; @@ -38,6 +38,9 @@ public class CellFactory { */ public Object getCellValue(Cell cell) { try{ + if(cell == null){ + return null; + } return EXCEL_CELL_VALUE.get(cell.getCellType()).apply(cell); }catch (Exception e){ throw new UnknownCellTypeException(String.format("Unknown cell type ( %s )",cell.getCellType())); diff --git a/src/main/java/com/springbatch/excel/tutorial/poi/ExcelFileException.java b/src/main/java/com/springbatch/excel/tutorial/support/poi/ExcelFileException.java similarity index 83% rename from src/main/java/com/springbatch/excel/tutorial/poi/ExcelFileException.java rename to src/main/java/com/springbatch/excel/tutorial/support/poi/ExcelFileException.java index 66d7582..cf3d3a2 100644 --- a/src/main/java/com/springbatch/excel/tutorial/poi/ExcelFileException.java +++ b/src/main/java/com/springbatch/excel/tutorial/support/poi/ExcelFileException.java @@ -1,4 +1,4 @@ -package com.springbatch.excel.tutorial.poi; +package com.springbatch.excel.tutorial.support.poi; public class ExcelFileException extends RuntimeException { diff --git a/src/main/java/com/springbatch/excel/tutorial/poi/RowMapper.java b/src/main/java/com/springbatch/excel/tutorial/support/poi/RowMapper.java similarity index 87% rename from src/main/java/com/springbatch/excel/tutorial/poi/RowMapper.java rename to src/main/java/com/springbatch/excel/tutorial/support/poi/RowMapper.java index 4aaf6a6..e33d255 100644 --- a/src/main/java/com/springbatch/excel/tutorial/poi/RowMapper.java +++ b/src/main/java/com/springbatch/excel/tutorial/support/poi/RowMapper.java @@ -1,4 +1,4 @@ -package com.springbatch.excel.tutorial.poi; +package com.springbatch.excel.tutorial.support.poi; import org.apache.poi.ss.usermodel.Row; diff --git a/src/main/java/com/springbatch/excel/tutorial/poi/UnknownCellTypeException.java b/src/main/java/com/springbatch/excel/tutorial/support/poi/UnknownCellTypeException.java similarity index 86% rename from src/main/java/com/springbatch/excel/tutorial/poi/UnknownCellTypeException.java rename to src/main/java/com/springbatch/excel/tutorial/support/poi/UnknownCellTypeException.java index 8af2209..7c4da5e 100644 --- a/src/main/java/com/springbatch/excel/tutorial/poi/UnknownCellTypeException.java +++ b/src/main/java/com/springbatch/excel/tutorial/support/poi/UnknownCellTypeException.java @@ -1,4 +1,4 @@ -package com.springbatch.excel.tutorial.poi; +package com.springbatch.excel.tutorial.support.poi; public class UnknownCellTypeException extends RuntimeException { diff --git a/src/main/java/com/springbatch/excel/tutorial/poi/package-info.java b/src/main/java/com/springbatch/excel/tutorial/support/poi/package-info.java similarity index 56% rename from src/main/java/com/springbatch/excel/tutorial/poi/package-info.java rename to src/main/java/com/springbatch/excel/tutorial/support/poi/package-info.java index 634531a..4e12456 100644 --- a/src/main/java/com/springbatch/excel/tutorial/poi/package-info.java +++ b/src/main/java/com/springbatch/excel/tutorial/support/poi/package-info.java @@ -2,4 +2,4 @@ * Core package for the Apache POI library. * @author aek */ -package com.springbatch.excel.tutorial.poi; \ No newline at end of file +package com.springbatch.excel.tutorial.support.poi; \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b61e24c..1eb75df 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -17,3 +17,7 @@ spring: logging: level: org.springframework.data.mongodb.core.MongoTemplate: DEBUG + +employee: + excel: + path: employee.xlsx \ No newline at end of file diff --git a/src/main/resources/employee.xlsx b/src/main/resources/employee.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8283a9c9292b378fe13d589df38264ac1651f178 GIT binary patch literal 14851 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