diff --git a/kanban-server/build.gradle b/kanban-server/build.gradle index 5cbd60c..87cd1b1 100644 --- a/kanban-server/build.gradle +++ b/kanban-server/build.gradle @@ -62,6 +62,8 @@ dependencies { compile 'org.springframework.security:spring-security-config:5.0.7.RELEASE' compile 'org.springframework:spring-context-support:5.0.7.RELEASE' compile 'net.sf.ehcache:ehcache-core:2.6.11' + compile("org.springframework.boot:spring-boot-starter-batch") + implementation('org.springframework.boot:spring-boot-starter-data-mongodb:2.0.3.RELEASE') // compile 'org.liquibase:liquibase-core:3.6.2' diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/KanbanApplication.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/KanbanApplication.java index 4f098ff..e6605b3 100644 --- a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/KanbanApplication.java +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/KanbanApplication.java @@ -1,10 +1,21 @@ package ru.otus.spring.hw.kanban; +import org.springframework.batch.core.Job; +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.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; import org.springframework.security.crypto.password.PasswordEncoder; +import ru.otus.spring.hw.kanban.conversion.ToMongoBatchConfig; import ru.otus.spring.hw.kanban.security.UserAccount; import ru.otus.spring.hw.kanban.security.UserAccountRepository; @@ -13,6 +24,10 @@ @SpringBootApplication(scanBasePackages = "ru.otus.spring.hw.kanban") @EnableJpaRepositories(basePackages = {"ru.otus.spring.hw.kanban.repository", "ru.otus.spring.hw.kanban.security"}) +@EnableMongoRepositories(basePackages = {"ru.otus.spring.hw.kanban.repository.mongo"}) + +@Configuration +@Import(ToMongoBatchConfig.class) public class KanbanApplication { public static void main(String[] args) { @@ -26,6 +41,17 @@ public static void main(String[] args) { @Autowired PasswordEncoder encoder; + + @Autowired + private JobLauncher jobLauncher; + + @Autowired + private Job convertBoardsJob; + + public void start() throws JobParametersInvalidException, JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException { + jobLauncher.run(convertBoardsJob, new JobParameters()); + } + @PostConstruct public void initAdmin() { if (repository.findByUsername("admin") == null) { @@ -35,5 +61,16 @@ public void initAdmin() { .build(); repository.save(acc); } + try { + start(); + } catch (JobParametersInvalidException e) { + e.printStackTrace(); + } catch (JobExecutionAlreadyRunningException e) { + e.printStackTrace(); + } catch (JobRestartException e) { + e.printStackTrace(); + } catch (JobInstanceAlreadyCompleteException e) { + e.printStackTrace(); + } } } diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/conversion/BoardItemProcessor.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/conversion/BoardItemProcessor.java new file mode 100644 index 0000000..b0ae0d6 --- /dev/null +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/conversion/BoardItemProcessor.java @@ -0,0 +1,51 @@ +package ru.otus.spring.hw.kanban.conversion; + +import org.springframework.batch.item.ItemProcessor; +import org.springframework.stereotype.Service; +import ru.otus.spring.hw.kanban.domain.Board; +import ru.otus.spring.hw.kanban.domain.Stage; +import ru.otus.spring.hw.kanban.domain.Task; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +public class BoardItemProcessor implements ItemProcessor { + + @Override + public ru.otus.spring.hw.kanban.domain.mongo.Board process(Board board) throws Exception { + ru.otus.spring.hw.kanban.domain.mongo.Board mongoBoard = new ru.otus.spring.hw.kanban.domain.mongo.Board(); + mongoBoard.setName(board.getName()); + + Set mongoStages = new HashSet<>(); + + Function convert = (task) -> { + ru.otus.spring.hw.kanban.domain.mongo.Task mongoTask = new ru.otus.spring.hw.kanban.domain.mongo.Task(); + mongoTask.setDescription(task.getDescription()); + mongoTask.setExecutor(task.getExecutor()); + mongoTask.setName(task.getName()); + mongoTask.setPriority(task.getPriority()); + return mongoTask; + }; + + for (Stage stage : board.getStages()) { + ru.otus.spring.hw.kanban.domain.mongo.Stage mongoStage = new ru.otus.spring.hw.kanban.domain.mongo.Stage(); + mongoStage.setDescription(stage.getDescription()); + mongoStage.setName(stage.getName()); + mongoStage.setTasks( + stage.getTasks() + .stream() + .map(task -> convert.apply(task)) + .collect(Collectors.toSet()) + ); + mongoStages.add(mongoStage); + + } + mongoBoard.setStages(mongoStages); + return mongoBoard; + + + } +} diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/conversion/NoPersistenceBatchConfigurer.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/conversion/NoPersistenceBatchConfigurer.java new file mode 100644 index 0000000..c85a277 --- /dev/null +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/conversion/NoPersistenceBatchConfigurer.java @@ -0,0 +1,16 @@ +package ru.otus.spring.hw.kanban.conversion; + +//public class NoPersistenceBatchConfigurer { +//} + +import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; + +@Component +public class NoPersistenceBatchConfigurer extends DefaultBatchConfigurer { + @Override + public void setDataSource(DataSource dataSource) { + } +} diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/conversion/ToMongoBatchConfig.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/conversion/ToMongoBatchConfig.java new file mode 100644 index 0000000..36420da --- /dev/null +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/conversion/ToMongoBatchConfig.java @@ -0,0 +1,178 @@ +package ru.otus.spring.hw.kanban.conversion; + +import com.mongodb.MongoClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.batch.core.*; +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.scope.context.ChunkContext; +import org.springframework.batch.item.*; +import org.springframework.batch.item.data.MongoItemWriter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoDbFactory; +import ru.otus.spring.hw.kanban.domain.Board; +import ru.otus.spring.hw.kanban.domain.Stage; +import ru.otus.spring.hw.kanban.repository.BoardRepository; +import ru.otus.spring.hw.kanban.repository.StageRepository; +import ru.otus.spring.hw.kanban.repository.TaskRepository; +import ru.otus.spring.hw.kanban.repository.mongo.BoardMongoRepository; + +import java.util.*; +import java.util.stream.Collectors; + +@EnableBatchProcessing +public class ToMongoBatchConfig { + + private final Logger logger = LoggerFactory.getLogger("Batch"); + + @Autowired + public JobBuilderFactory jobBuilderFactory; + + @Autowired + public StepBuilderFactory stepBuilderFactory; + + + @Autowired + BoardItemProcessor boardItemProcessor; + + @Autowired + BoardRepository boardRepository; + + + @Autowired + BoardMongoRepository boardMongoRepository; + + @Autowired + StageRepository stageRepository; + + @Autowired + TaskRepository taskRepository; + + @Bean + public ItemReader reader() { + return new ItemReader() { + + private Integer currentId = 0; + + @Override + public Board read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException { + Board board = boardRepository.findFirstByIdGreaterThan(currentId).orElse(null); + if (board != null) { + Set stagesSet = new HashSet<>(); + List stages = stageRepository.findStagesByBoard(board); + + for (Stage stage : stages) { + stage.setTasks(taskRepository.findAllByStage(stage) + .stream() + .collect(Collectors.toSet())); + stagesSet.add(stage); + } + board.setStages(stagesSet); + currentId = board.getId(); + } + + return board; + } + }; + } + + + @Bean + public ItemWriter writer() { + MongoItemWriter writer = new MongoItemWriter<>(); + try { + writer.setTemplate(mongoTemplate()); + } catch (Exception e) { + logger.error(e.toString()); + } + writer.setCollection("board"); + return writer; + } + + + @Bean + public MongoDbFactory mongoDbFactory() throws Exception { + return new SimpleMongoDbFactory(new MongoClient(), "kanban"); + } + + @Bean + public MongoTemplate mongoTemplate() throws Exception { + MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory()); + return mongoTemplate; + } + + + @Bean + public Step step1() { + return stepBuilderFactory.get("step1") + .chunk(5) + .reader(reader()) + .processor(boardItemProcessor) + .writer(writer()) + .listener(new ItemReadListener() { + public void beforeRead() { + logger.info("Начало чтения"); + } + + public void afterRead(Board o) { + logger.info("Конец чтения " + o.toString()); + } + + public void onReadError(Exception e) { + logger.info("Ошибка чтения"); + } + }) + .listener(new ItemWriteListener() { + public void beforeWrite(List list) { + logger.info("Начало записи"); + } + + public void afterWrite(List list) { + logger.info("Конец записи"); + } + + public void onWriteError(Exception e, List list) { + logger.info("Ошибка записи"); + } + }) + .listener(new ItemProcessListener() { + public void beforeProcess(Object o) { + logger.info("Начало обработки"); + } + + public void afterProcess(Object o, Object o2) { + logger.info("Конец обработки"); + } + + public void onProcessError(Object o, Exception e) { + logger.info("Ошбка обработки"); + } + }) + .listener(new ChunkListener() { + public void beforeChunk(ChunkContext chunkContext) { + logger.info("Начало пачки"); + } + + public void afterChunk(ChunkContext chunkContext) { + logger.info("Конец пачки"); + } + + public void afterChunkError(ChunkContext chunkContext) { + logger.info("Ошибка пачки"); + } + }) + .build(); + } + + @Bean + public Job convertBoardsJob() { + return jobBuilderFactory.get("convertBoardsJob") + .start(step1()) + .build(); + } +} diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/Board.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/Board.java index ac3acd7..9163c42 100644 --- a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/Board.java +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/Board.java @@ -1,10 +1,22 @@ package ru.otus.spring.hw.kanban.domain; import javax.persistence.*; +import java.util.HashSet; import java.util.Set; @Entity public class Board { + public Set getStages() { + if (stages == null) { + stages = new HashSet<>(); + } + return stages; + } + + public void setStages(Set stages) { + this.stages = stages; + } + @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY) private Set stages; @Column(length = 500) diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/Stage.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/Stage.java index e1c2bb8..4ab1d86 100644 --- a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/Stage.java +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/Stage.java @@ -1,6 +1,8 @@ package ru.otus.spring.hw.kanban.domain; import javax.persistence.*; +import java.util.HashSet; +import java.util.Set; @Entity public class Stage { @@ -16,6 +18,21 @@ public class Stage { @ManyToOne(fetch = FetchType.LAZY) private Board board; + public Set getTasks() { + if (tasks == null) { + tasks = new HashSet<>(); + } + return tasks; + } + + public void setTasks(Set tasks) { + this.tasks = tasks; + } + + @OneToMany(fetch = FetchType.LAZY) + private Set tasks; + + public Stage(String name, String description, Board board) { this.name = name; this.description = description; diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/mongo/Board.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/mongo/Board.java new file mode 100644 index 0000000..70d6a29 --- /dev/null +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/mongo/Board.java @@ -0,0 +1,78 @@ +package ru.otus.spring.hw.kanban.domain.mongo; + +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@Document +public class Board { + + private Set stages; + private String name; + private String id; + + + public Board(String name, Set stages) { + this.stages = stages; + this.name = name; + } + + public Board(String name) { + this.name = name; + } + + public Board() { + } + + public Set getStages() { + if (stages == null) { + stages = new HashSet<>(); + } + + return stages; + } + + public void addStage(Stage stage) { + UUID uuid = UUID.randomUUID(); + stage.setId(uuid.toString()); + stage.setOrder(this.getStages().size()); + this.getStages().add(stage); + } + + + public void setStages(Set stages) { + this.stages = stages; +// this.stages.forEach(stage -> stage.setId(UUID.randomUUID().toString())); + } + + @Override + public String toString() { + return "Board{" + + "stages=" + stages + + ", name='" + name + '\'' + + ", id=" + id + + '}'; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public void clearStages() { + this.getStages().clear(); + } +} diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/mongo/Stage.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/mongo/Stage.java new file mode 100644 index 0000000..dbd3147 --- /dev/null +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/mongo/Stage.java @@ -0,0 +1,79 @@ +package ru.otus.spring.hw.kanban.domain.mongo; + +import org.springframework.data.annotation.Id; + +import java.util.HashSet; +import java.util.Set; + +public class Stage { + + private String id; + private String name; + private String description; + private Set tasks; + private int order; + + public Stage(String name, String description, Board board) { + this.name = name; + this.description = description; + } + + public Stage() { + } + + public Stage(String name, String description) { + this.name = name; + this.description = description; + } + + public Set getTasks() { + if (tasks != null) + return tasks; + return new HashSet<>(); + } + + public void setTasks(Set tasks) { + this.tasks = tasks; + } + + @Override + public String toString() { + return "Stage{" + + "id=" + id + + ", name='" + name + '\'' + + ", description='" + description + '\'' + + '}'; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } +} diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/mongo/Task.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/mongo/Task.java new file mode 100644 index 0000000..dcfbf14 --- /dev/null +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/domain/mongo/Task.java @@ -0,0 +1,76 @@ +package ru.otus.spring.hw.kanban.domain.mongo; + +public class Task { + + private String id; + + private String name; + + private String description; + + private String executor; + + private int priority; + + @Override + public String toString() { + return "Task{" + + "id=" + id + + ", name='" + name + '\'' + + ", description='" + description + '\'' + + ", executor='" + executor + '\'' + + ", priority=" + priority + + '}'; + } + + public Task() { + } + + public Task(String name, String description, String executor, int priority) { + this.name = name; + this.description = description; + this.executor = executor; + this.priority = priority; + } + + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getExecutor() { + return executor; + } + + public void setExecutor(String executor) { + this.executor = executor; + } +} diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/repository/BoardRepository.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/repository/BoardRepository.java index 0b5c3bb..3653387 100644 --- a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/repository/BoardRepository.java +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/repository/BoardRepository.java @@ -4,9 +4,12 @@ import ru.otus.spring.hw.kanban.domain.Board; import java.util.List; +import java.util.Optional; public interface BoardRepository extends CrudRepository { List findBoardsByNameLike(String name); List findAll(); + Optional findFirstByIdGreaterThan(Integer id); + } diff --git a/kanban-server/src/main/java/ru/otus/spring/hw/kanban/repository/mongo/BoardMongoRepository.java b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/repository/mongo/BoardMongoRepository.java new file mode 100644 index 0000000..cffcd84 --- /dev/null +++ b/kanban-server/src/main/java/ru/otus/spring/hw/kanban/repository/mongo/BoardMongoRepository.java @@ -0,0 +1,15 @@ +package ru.otus.spring.hw.kanban.repository.mongo; + + +import org.springframework.data.mongodb.repository.MongoRepository; +import ru.otus.spring.hw.kanban.domain.mongo.Board; + +import java.util.List; + +public interface BoardMongoRepository extends MongoRepository { + List findBoardsByNameLike(String name); + + List findAll(); + + +} diff --git a/kanban-server/src/main/resources/application.properties b/kanban-server/src/main/resources/application.properties index c9e7a13..4bdaf2f 100644 --- a/kanban-server/src/main/resources/application.properties +++ b/kanban-server/src/main/resources/application.properties @@ -9,4 +9,11 @@ spring.datasource.username=sa spring.datasource.password= spring.jpa.hibernate.ddl-auto=none logging.level.org.springframework.security=DEBUG -#server.port=8090 \ No newline at end of file +#server.port=8090 + + +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.port=27017 +spring.data.mongodb.host=localhost +spring.data.mongodb.database=kanban +