From 44db1d7b9484cce7761d9343254f9a87ee2b2451 Mon Sep 17 00:00:00 2001 From: Adam Saghy Date: Tue, 13 Sep 2022 12:34:17 +0200 Subject: [PATCH] FINERACT-1678: Soft-lock Loan COB relevant loan accounts upon startup --- .../cob/loan/ApplyLoanLockTasklet.java | 2 +- .../cob/loan/FetchAndLockLoanTasklet.java | 79 ++++++++++ .../fineract/cob/loan/LoanCOBConstant.java | 2 + .../cob/loan/LoanCOBManagerConfiguration.java | 32 +++- .../fineract/cob/loan/LoanCOBPartitioner.java | 13 +- .../cob/loan/LoanCOBWorkerConfiguration.java | 29 ++-- .../fineract/cob/loan/LoanItemReader.java | 2 +- .../ApplyLoanLockTaskletStepDefinitions.java | 4 +- .../loan/FetchAndLockLoanStepDefinitions.java | 146 ++++++++++++++++++ .../LoanCOBPartitionerStepDefinitions.java | 13 +- .../loan/LoanItemReaderStepDefinitions.java | 2 +- .../auditing/CustomAuditingHandlerTest.java | 3 +- ...eature => cob.loan.applylock.step.feature} | 0 .../cob/loan/cob.loan.fetch.lock.feature | 34 ++++ ...r.feature => cob.loan.partitioner.feature} | 0 15 files changed, 325 insertions(+), 36 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java rename fineract-provider/src/test/java/org/{springframework/data => apache/fineract/infrastructure/core}/auditing/CustomAuditingHandlerTest.java (98%) rename fineract-provider/src/test/resources/features/cob/loan/{cob.applylock.step.feature => cob.loan.applylock.step.feature} (100%) create mode 100644 fineract-provider/src/test/resources/features/cob/loan/cob.loan.fetch.lock.feature rename fineract-provider/src/test/resources/features/cob/loan/{cob.partitioner.feature => cob.loan.partitioner.feature} (100%) diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java index 9699d3caa..41f3b6110 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java @@ -67,7 +67,7 @@ public class ApplyLoanLockTasklet implements Tasklet { accountLockRepository.save(loanAccountLock); } - executionContext.put(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS, new ArrayList<>(alreadyUnderProcessingAccountIds)); + executionContext.put(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS, new ArrayList<>(alreadyUnderProcessingAccountIds)); return RepeatStatus.FINISHED; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java new file mode 100644 index 000000000..176823ebb --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.loan; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.LoanAccountLock; +import org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.domain.LockOwner; +import org.jetbrains.annotations.NotNull; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; + +@Slf4j +@RequiredArgsConstructor +public class FetchAndLockLoanTasklet implements Tasklet { + + private final LoanAccountLockRepository loanAccountLockRepository; + + private final RetrieveLoanIdService retrieveLoanIdService; + + @Override + public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception { + List allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIds(); + if (allNonClosedLoanIds.isEmpty()) { + return RepeatStatus.FINISHED; + } + List remainingIds = new ArrayList<>(allNonClosedLoanIds); + + List loanAccountLocks = loanAccountLockRepository.findAllByLoanIdIn(remainingIds); + + List alreadySoftLockedAccounts = loanAccountLocks.stream() + .filter(e -> LockOwner.LOAN_COB_PARTITIONING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList(); + List alreadyMarkedForInlineCOBLockedAccounts = loanAccountLocks.stream() + .filter(e -> LockOwner.LOAN_INLINE_COB_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList(); + List alreadyMarkedForChunkProcessingLockedAccounts = loanAccountLocks.stream() + .filter(e -> LockOwner.LOAN_COB_CHUNK_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList(); + + // Remove already hard locked accounts + remainingIds.removeAll(alreadyMarkedForChunkProcessingLockedAccounts); + remainingIds.removeAll(alreadyMarkedForInlineCOBLockedAccounts); + + List lockableLoanAccounts = new ArrayList<>(remainingIds); + lockableLoanAccounts.removeAll(alreadySoftLockedAccounts); + + applySoftLock(lockableLoanAccounts); + + contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, remainingIds); + + return RepeatStatus.FINISHED; + } + + private void applySoftLock(List alreadySoftLockedAccounts) { + for (Long loanId : alreadySoftLockedAccounts) { + LoanAccountLock loanAccountLock = new LoanAccountLock(loanId, LockOwner.LOAN_COB_PARTITIONING); + loanAccountLockRepository.save(loanAccountLock); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java index e9527d6c0..067eaf05f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java @@ -26,6 +26,8 @@ public final class LoanCOBConstant { public static final String BUSINESS_STEP_MAP = "businessStepMap"; public static final String LOAN_COB_WORKER_STEP = "loanCOBWorkerStep"; + public static final String ALREADY_LOCKED_LOAN_IDS = "alreadyLockedLoanIds"; + private LoanCOBConstant() { } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java index c4911703c..de3988c9b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java @@ -18,19 +18,24 @@ */ package org.apache.fineract.cob.loan; +import java.util.List; import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.domain.LoanAccountLockRepository; import org.apache.fineract.cob.listener.COBExecutionListenerRunner; import org.apache.fineract.infrastructure.jobs.service.JobName; import org.apache.fineract.infrastructure.springbatch.PropertyService; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.launch.support.RunIdIncrementer; import org.springframework.batch.integration.config.annotation.EnableBatchIntegration; import org.springframework.batch.integration.partition.RemotePartitioningManagerStepBuilderFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -47,6 +52,8 @@ public class LoanCOBManagerConfiguration { @Autowired private RemotePartitioningManagerStepBuilderFactory stepBuilderFactory; @Autowired + private StepBuilderFactory localStepBuilderFactory; + @Autowired private PropertyService propertyService; @Autowired private DirectChannel outboundRequests; @@ -58,27 +65,40 @@ public class LoanCOBManagerConfiguration { private JobExplorer jobExplorer; @Autowired private ApplicationContext applicationContext; - @Autowired(required = false) + @Autowired private RetrieveLoanIdService retrieveLoanIdService; + @Autowired + private LoanAccountLockRepository accountLockRepository; @Bean - public LoanCOBPartitioner partitioner() { - return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, retrieveLoanIdService); + @JobScope + public LoanCOBPartitioner partitioner(@Value("#{jobExecutionContext['loanIds']}") List loanIds) { + return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, loanIds); } @Bean public Step loanCOBStep() { - return stepBuilderFactory.get(JobName.LOAN_COB.name()).partitioner(LoanCOBConstant.LOAN_COB_WORKER_STEP, partitioner()) + return stepBuilderFactory.get("Loan COB partition - Step").partitioner(LoanCOBConstant.LOAN_COB_WORKER_STEP, partitioner(null)) .outputChannel(outboundRequests).build(); } + @Bean + public Step fetchAndLockStep() { + return localStepBuilderFactory.get("Fetch and Lock loan accounts - Step").tasklet(fetchAndLockLoanTasklet()).build(); + } + + @Bean + @JobScope + public FetchAndLockLoanTasklet fetchAndLockLoanTasklet() { + return new FetchAndLockLoanTasklet(accountLockRepository, retrieveLoanIdService); + } + @Bean(name = "loanCOBJob") public Job loanCOBJob() { return jobBuilderFactory.get(JobName.LOAN_COB.name()) // .listener(new COBExecutionListenerRunner(applicationContext, JobName.LOAN_COB.name())) // - .start(loanCOBStep()) // + .start(fetchAndLockStep()).next(loanCOBStep()) // .incrementer(new RunIdIncrementer()) // .build(); } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java index aa675a91e..83abe7dd5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java @@ -37,6 +37,7 @@ import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.launch.NoSuchJobExecutionException; import org.springframework.batch.core.partition.support.Partitioner; import org.springframework.batch.item.ExecutionContext; +import org.springframework.util.CollectionUtils; @Slf4j @RequiredArgsConstructor @@ -48,7 +49,8 @@ public class LoanCOBPartitioner implements Partitioner { private final COBBusinessStepService cobBusinessStepService; private final JobOperator jobOperator; private final JobExplorer jobExplorer; - private final RetrieveLoanIdService retrieveLoanIdService; + + private final List loanIds; @NotNull @Override @@ -65,15 +67,15 @@ public class LoanCOBPartitioner implements Partitioner { private Map getPartitions(int partitionSize, TreeMap cobBusinessStepMap) { Map partitions = new HashMap<>(); - List allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIds(); - if (allNonClosedLoanIds.isEmpty()) { + + if (CollectionUtils.isEmpty(loanIds)) { stopJobExecution(); return Map.of(); } int partitionIndex = 1; int remainingSpace = 0; createNewPartition(partitions, partitionIndex, cobBusinessStepMap); - for (Long allNonClosedLoanId : allNonClosedLoanIds) { + for (Long loanId : loanIds) { if (remainingSpace == partitionSize) { partitionIndex++; createNewPartition(partitions, partitionIndex, cobBusinessStepMap); @@ -82,7 +84,7 @@ public class LoanCOBPartitioner implements Partitioner { String key = PARTITION_PREFIX + partitionIndex; ExecutionContext executionContext = partitions.get(key); List data = (List) executionContext.get(LoanCOBConstant.LOAN_IDS); - data.add(allNonClosedLoanId); + data.add(loanId); remainingSpace++; } return partitions; @@ -93,6 +95,7 @@ public class LoanCOBPartitioner implements Partitioner { ExecutionContext executionContext = new ExecutionContext(); executionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList()); executionContext.put(LoanCOBConstant.BUSINESS_STEP_MAP, cobBusinessStepMap); + executionContext.put("partition", PARTITION_PREFIX + partitionIndex); partitions.put(PARTITION_PREFIX + partitionIndex, executionContext); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java index b666dc33b..c74006872 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java @@ -28,12 +28,14 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper; import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.FlowBuilder; import org.springframework.batch.core.job.flow.Flow; import org.springframework.batch.core.listener.ExecutionContextPromotionListener; import org.springframework.batch.integration.partition.RemotePartitioningWorkerStepBuilderFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -44,9 +46,11 @@ import org.springframework.transaction.support.TransactionTemplate; @ConditionalOnProperty(value = "fineract.mode.batch-worker-enabled", havingValue = "true") public class LoanCOBWorkerConfiguration { - public static final String ALREADY_LOCKED_LOAN_IDS = "alreadyLockedLoanIds"; @Autowired private RemotePartitioningWorkerStepBuilderFactory stepBuilderFactory; + + @Autowired + private StepBuilderFactory localStepBuilderFactory; @Autowired private PropertyService propertyService; @Autowired @@ -69,17 +73,20 @@ public class LoanCOBWorkerConfiguration { @Bean public Flow flow() { - return new FlowBuilder("cobFlow").start(initialisationStep()).next(applyLockStep()).next(loanBusinessStep()).build(); + return new FlowBuilder("cobFlow").start(initialisationStep(null)).next(applyLockStep(null)).next(loanBusinessStep(null)) + .build(); } @Bean - public Step initialisationStep() { - return stepBuilderFactory.get("Initialisation - Step").inputChannel(inboundRequests).tasklet(initialiseContext()).build(); + @StepScope + public Step initialisationStep(@Value("#{stepExecutionContext['partition']}") String partitionName) { + return localStepBuilderFactory.get("Initialisation - Step:" + partitionName).tasklet(initialiseContext()).build(); } @Bean - public Step loanBusinessStep() { - return stepBuilderFactory.get("Loan COB worker - Step").inputChannel(inboundRequests) + @StepScope + public Step loanBusinessStep(@Value("#{stepExecutionContext['partition']}") String partitionName) { + return localStepBuilderFactory.get("Loan Business - Step:" + partitionName) .chunk(propertyService.getChunkSize(JobName.LOAN_COB.name())).reader(cobWorkerItemReader()) .processor(cobWorkerItemProcessor()).writer(cobWorkerItemWriter()).faultTolerant().skip(Exception.class) .skipLimit(propertyService.getChunkSize(JobName.LOAN_COB.name()) + 1).listener(loanItemListener()) @@ -87,13 +94,12 @@ public class LoanCOBWorkerConfiguration { } @Bean - public Step applyLockStep() { - return stepBuilderFactory.get("Apply lock - Step").inputChannel(inboundRequests).tasklet(applyLock()).listener(promotionListener()) - .build(); + @StepScope + public Step applyLockStep(@Value("#{stepExecutionContext['partition']}") String partitionName) { + return localStepBuilderFactory.get("Apply lock - Step:" + partitionName).tasklet(applyLock()).listener(promotionListener()).build(); } @Bean - @StepScope public InitialisationTasklet initialiseContext() { return new InitialisationTasklet(userRepository); } @@ -104,7 +110,6 @@ public class LoanCOBWorkerConfiguration { } @Bean - @StepScope public ApplyLoanLockTasklet applyLock() { return new ApplyLoanLockTasklet(accountLockRepository); } @@ -132,7 +137,7 @@ public class LoanCOBWorkerConfiguration { @Bean public ExecutionContextPromotionListener promotionListener() { ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); - listener.setKeys(new String[] { ALREADY_LOCKED_LOAN_IDS }); + listener.setKeys(new String[] { LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS }); return listener; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java index 15726e713..e9336f00c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java @@ -49,7 +49,7 @@ public class LoanItemReader implements ItemReader { ExecutionContext executionContext = stepExecution.getExecutionContext(); ExecutionContext jobExecutionContext = stepExecution.getJobExecution().getExecutionContext(); List loanIds = (List) executionContext.get(LoanCOBConstant.LOAN_IDS); - alreadyLockedAccounts = (List) jobExecutionContext.get(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS); + alreadyLockedAccounts = (List) jobExecutionContext.get(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS); remainingData = new ArrayList<>(loanIds); } diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java index 3b5ffa113..aa0b8dce4 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/ApplyLoanLockTaskletStepDefinitions.java @@ -73,8 +73,8 @@ public class ApplyLoanLockTaskletStepDefinitions implements En { Then("ApplyLoanLockTasklet.execute result should match", () -> { assertEquals(RepeatStatus.FINISHED, resultItem); - assertEquals(3L, ((List) stepContribution.getStepExecution().getExecutionContext() - .get(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS)).get(0)); + assertEquals(3L, + ((List) stepContribution.getStepExecution().getExecutionContext().get(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS)).get(0)); verify(this.accountLockRepository, Mockito.times(2)).save(valueCaptor.capture()); List values = valueCaptor.getAllValues(); assertEquals(2L, values.get(0).getLoanId()); diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java new file mode 100644 index 000000000..44ab858c5 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/FetchAndLockLoanStepDefinitions.java @@ -0,0 +1,146 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.loan; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import io.cucumber.java8.En; +import java.util.Collections; +import java.util.List; +import org.apache.fineract.cob.domain.LoanAccountLock; +import org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.mockito.Mockito; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.repeat.RepeatStatus; + +public class FetchAndLockLoanStepDefinitions implements En { + + private final LoanAccountLockRepository loanAccountLockRepository = mock(LoanAccountLockRepository.class); + + private final RetrieveLoanIdService retrieveLoanIdService = mock(RetrieveLoanIdService.class); + StepContribution contribution; + private FetchAndLockLoanTasklet fetchAndLockLoanTasklet; + private String action; + private RepeatStatus result; + + public FetchAndLockLoanStepDefinitions() { + Given("/^The FetchAndLockLoanTasklet.execute method with action (.*)$/", (String action) -> { + ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); + this.action = action; + + if ("empty loanIds".equals(action)) { + lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(Collections.emptyList()); + } else if ("good".equals(action)) { + lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(List.of(1L, 2L, 3L)); + lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())).thenReturn(Collections.emptyList()); + } else if ("soft lock".equals(action)) { + lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(List.of(1L, 2L, 3L)); + lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())) + .thenReturn(List.of(new LoanAccountLock(1L, LockOwner.LOAN_COB_PARTITIONING))); + } else if ("inline cob".equals(action)) { + lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(List.of(1L, 2L, 3L)); + lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())) + .thenReturn(List.of(new LoanAccountLock(2L, LockOwner.LOAN_INLINE_COB_PROCESSING))); + } else if ("chunk processing".equals(action)) { + lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(List.of(1L, 2L, 3L)); + lenient().when(loanAccountLockRepository.findAllByLoanIdIn(Mockito.anyList())) + .thenReturn(List.of(new LoanAccountLock(3L, LockOwner.LOAN_COB_CHUNK_PROCESSING))); + } + + JobExecution jobExecution = new JobExecution(1L); + StepExecution stepExecution = new StepExecution("step", jobExecution); + contribution = new StepContribution(stepExecution); + + fetchAndLockLoanTasklet = new FetchAndLockLoanTasklet(loanAccountLockRepository, retrieveLoanIdService); + }); + + When("FetchAndLockLoanTasklet.execute method executed", () -> { + result = this.fetchAndLockLoanTasklet.execute(contribution, null); + }); + + Then("FetchAndLockLoanTasklet.execute result should match", () -> { + if ("empty steps".equals(action)) { + assertEquals(RepeatStatus.FINISHED, result); + } else if ("good".equals(action)) { + verify(loanAccountLockRepository, Mockito.times(3)).save(Mockito.any()); + assertEquals(RepeatStatus.FINISHED, result); + assertEquals(3, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .size()); + assertEquals(1L, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .get(0)); + assertEquals(2L, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .get(1)); + assertEquals(3L, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .get(2)); + } else if ("soft lock".equals(action)) { + verify(loanAccountLockRepository, Mockito.times(2)).save(Mockito.any()); + assertEquals(RepeatStatus.FINISHED, result); + assertEquals(3, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .size()); + assertEquals(1L, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .get(0)); + assertEquals(2L, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .get(1)); + assertEquals(3L, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .get(2)); + } else if ("inline cob".equals(action)) { + verify(loanAccountLockRepository, Mockito.times(2)).save(Mockito.any()); + assertEquals(RepeatStatus.FINISHED, result); + assertEquals(2, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .size()); + assertEquals(1L, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .get(0)); + assertEquals(3L, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .get(1)); + } else if ("chunk processing".equals(action)) { + verify(loanAccountLockRepository, Mockito.times(2)).save(Mockito.any()); + assertEquals(RepeatStatus.FINISHED, result); + assertEquals(2, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .size()); + assertEquals(1L, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .get(0)); + assertEquals(2L, + ((List) contribution.getStepExecution().getJobExecution().getExecutionContext().get(LoanCOBConstant.LOAN_IDS)) + .get(1)); + } + }); + + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java index 926564c96..d3892497e 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanCOBPartitionerStepDefinitions.java @@ -26,7 +26,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import io.cucumber.java8.En; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -46,9 +46,9 @@ public class LoanCOBPartitionerStepDefinitions implements En { COBBusinessStepService cobBusinessStepService = mock(COBBusinessStepService.class); JobOperator jobOperator = mock(JobOperator.class); JobExplorer jobExplorer = mock(JobExplorer.class); - RetrieveLoanIdService retrieveLoanIdService = mock(RetrieveLoanIdService.class); - private LoanCOBPartitioner loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, - jobExplorer, retrieveLoanIdService); + + List loanIds; + private LoanCOBPartitioner loanCOBPartitioner; private TreeMap cobBusinessMap = new TreeMap<>(); @@ -69,14 +69,15 @@ public class LoanCOBPartitionerStepDefinitions implements En { cobBusinessMap.put(1L, "Business step"); lenient().when(cobBusinessStepService.getCOBBusinessStepMap(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME)) .thenReturn(cobBusinessMap); - lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(Collections.emptyList()); + loanIds = new ArrayList<>(); lenient().when(jobExplorer.findRunningJobExecutions(JobName.LOAN_COB.name())).thenThrow(new RuntimeException("fail")); } else if ("good".equals(action)) { cobBusinessMap.put(1L, "Business step"); lenient().when(cobBusinessStepService.getCOBBusinessStepMap(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME)) .thenReturn(cobBusinessMap); - lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(List.of(1L, 2L, 3L)); + loanIds = List.of(1L, 2L, 3L); } + loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, loanIds); }); When("LoanCOBPartitioner.partition method executed", () -> { diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java index b519ee32f..1d30d048d 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/loan/LoanItemReaderStepDefinitions.java @@ -57,7 +57,7 @@ public class LoanItemReaderStepDefinitions implements En { List splitLockedAccountsStr = Splitter.on(',').splitToList(lockedAccounts); splitLockedAccounts = splitLockedAccountsStr.stream().map(Long::parseLong).toList(); } - jobExecutionContext.put(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS, new ArrayList<>(splitLockedAccounts)); + jobExecutionContext.put(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS, new ArrayList<>(splitLockedAccounts)); jobExecution.setExecutionContext(jobExecutionContext); StepExecution stepExecution = new StepExecution("test", jobExecution); ExecutionContext stepExecutionContext = new ExecutionContext(); diff --git a/fineract-provider/src/test/java/org/springframework/data/auditing/CustomAuditingHandlerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandlerTest.java similarity index 98% rename from fineract-provider/src/test/java/org/springframework/data/auditing/CustomAuditingHandlerTest.java rename to fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandlerTest.java index 39f37c40e..5fc70c283 100644 --- a/fineract-provider/src/test/java/org/springframework/data/auditing/CustomAuditingHandlerTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/auditing/CustomAuditingHandlerTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.springframework.data.auditing; +package org.apache.fineract.infrastructure.core.auditing; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -28,7 +28,6 @@ import java.time.ZoneId; import java.util.HashMap; import java.util.Map; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; -import org.apache.fineract.infrastructure.core.auditing.CustomAuditingHandler; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; diff --git a/fineract-provider/src/test/resources/features/cob/loan/cob.applylock.step.feature b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.applylock.step.feature similarity index 100% rename from fineract-provider/src/test/resources/features/cob/loan/cob.applylock.step.feature rename to fineract-provider/src/test/resources/features/cob/loan/cob.loan.applylock.step.feature diff --git a/fineract-provider/src/test/resources/features/cob/loan/cob.loan.fetch.lock.feature b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.fetch.lock.feature new file mode 100644 index 000000000..9df96a0e6 --- /dev/null +++ b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.fetch.lock.feature @@ -0,0 +1,34 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +Feature: COB Loan fetch and (soft) lock Step + + @cob + Scenario Outline: FetchAndLockLoanTasklet - run test + Given The FetchAndLockLoanTasklet.execute method with action + When FetchAndLockLoanTasklet.execute method executed + Then FetchAndLockLoanTasklet.execute result should match + + Examples: + |action| + |empty loanIds| + |good| + |soft lock| + |inline cob| + |chunk processing| \ No newline at end of file diff --git a/fineract-provider/src/test/resources/features/cob/loan/cob.partitioner.feature b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.partitioner.feature similarity index 100% rename from fineract-provider/src/test/resources/features/cob/loan/cob.partitioner.feature rename to fineract-provider/src/test/resources/features/cob/loan/cob.loan.partitioner.feature