You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/09/13 14:47:17 UTC
[fineract] branch develop updated: FINERACT-1678: Soft-lock Loan COB relevant loan accounts upon startup
This is an automated email from the ASF dual-hosted git repository.
arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 44db1d7b9 FINERACT-1678: Soft-lock Loan COB relevant loan accounts upon startup
44db1d7b9 is described below
commit 44db1d7b9484cce7761d9343254f9a87ee2b2451
Author: Adam Saghy <ad...@gmail.com>
AuthorDate: Tue Sep 13 12:34:17 2022 +0200
FINERACT-1678: Soft-lock Loan COB relevant loan accounts upon startup
---
.../fineract/cob/loan/ApplyLoanLockTasklet.java | 2 +-
...ckTasklet.java => FetchAndLockLoanTasklet.java} | 58 ++++----
.../apache/fineract/cob/loan/LoanCOBConstant.java | 2 +
.../cob/loan/LoanCOBManagerConfiguration.java | 32 ++++-
.../fineract/cob/loan/LoanCOBPartitioner.java | 13 +-
.../cob/loan/LoanCOBWorkerConfiguration.java | 29 ++--
.../apache/fineract/cob/loan/LoanItemReader.java | 2 +-
.../loan/ApplyLoanLockTaskletStepDefinitions.java | 4 +-
.../cob/loan/FetchAndLockLoanStepDefinitions.java | 146 +++++++++++++++++++++
.../loan/LoanCOBPartitionerStepDefinitions.java | 13 +-
.../cob/loan/LoanItemReaderStepDefinitions.java | 2 +-
.../core}/auditing/CustomAuditingHandlerTest.java | 3 +-
...tep.feature => cob.loan.applylock.step.feature} | 0
...ck.step.feature => cob.loan.fetch.lock.feature} | 23 ++--
...tioner.feature => cob.loan.partitioner.feature} | 0
15 files changed, 247 insertions(+), 82 deletions(-)
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/ApplyLoanLockTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java
similarity index 50%
copy from fineract-provider/src/main/java/org/apache/fineract/cob/loan/ApplyLoanLockTasklet.java
copy to fineract-provider/src/main/java/org/apache/fineract/cob/loan/FetchAndLockLoanTasklet.java
index 9699d3caa..176823ebb 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/FetchAndLockLoanTasklet.java
@@ -20,9 +20,6 @@ package org.apache.fineract.cob.loan;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.domain.LoanAccountLock;
@@ -32,54 +29,51 @@ 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.item.ExecutionContext;
import org.springframework.batch.repeat.RepeatStatus;
@Slf4j
@RequiredArgsConstructor
-public class ApplyLoanLockTasklet implements Tasklet {
+public class FetchAndLockLoanTasklet implements Tasklet {
- private final LoanAccountLockRepository accountLockRepository;
+ private final LoanAccountLockRepository loanAccountLockRepository;
+
+ private final RetrieveLoanIdService retrieveLoanIdService;
@Override
public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception {
- ExecutionContext executionContext = contribution.getStepExecution().getExecutionContext();
- List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
- List<Long> remainingLoanIds = new ArrayList<>(loanIds);
+ List<Long> allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIds();
+ if (allNonClosedLoanIds.isEmpty()) {
+ return RepeatStatus.FINISHED;
+ }
+ List<Long> remainingIds = new ArrayList<>(allNonClosedLoanIds);
- List<LoanAccountLock> accountLocks = accountLockRepository.findAllByLoanIdIn(remainingLoanIds);
+ List<LoanAccountLock> loanAccountLocks = loanAccountLockRepository.findAllByLoanIdIn(remainingIds);
- List<Long> alreadyHardLockedAccountIds = accountLocks.stream()
+ List<Long> alreadySoftLockedAccounts = loanAccountLocks.stream()
+ .filter(e -> LockOwner.LOAN_COB_PARTITIONING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList();
+ List<Long> alreadyMarkedForInlineCOBLockedAccounts = loanAccountLocks.stream()
+ .filter(e -> LockOwner.LOAN_INLINE_COB_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList();
+ List<Long> alreadyMarkedForChunkProcessingLockedAccounts = loanAccountLocks.stream()
.filter(e -> LockOwner.LOAN_COB_CHUNK_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList();
- List<Long> alreadyUnderProcessingAccountIds = accountLocks.stream()
- .filter(e -> LockOwner.LOAN_INLINE_COB_PROCESSING.equals(e.getLockOwner())).map(LoanAccountLock::getLoanId).toList();
+ // Remove already hard locked accounts
+ remainingIds.removeAll(alreadyMarkedForChunkProcessingLockedAccounts);
+ remainingIds.removeAll(alreadyMarkedForInlineCOBLockedAccounts);
- Map<Long, LoanAccountLock> alreadySoftLockedAccountsMap = accountLocks.stream()
- .filter(e -> LockOwner.LOAN_COB_PARTITIONING.equals(e.getLockOwner()))
- .collect(Collectors.toMap(LoanAccountLock::getLoanId, Function.identity()));
+ List<Long> lockableLoanAccounts = new ArrayList<>(remainingIds);
+ lockableLoanAccounts.removeAll(alreadySoftLockedAccounts);
- remainingLoanIds.removeAll(alreadyHardLockedAccountIds);
- remainingLoanIds.removeAll(alreadyUnderProcessingAccountIds);
+ applySoftLock(lockableLoanAccounts);
- for (Long loanId : remainingLoanIds) {
- LoanAccountLock loanAccountLock = addLock(loanId, alreadySoftLockedAccountsMap);
- accountLockRepository.save(loanAccountLock);
- }
+ contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, remainingIds);
- executionContext.put(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS, new ArrayList<>(alreadyUnderProcessingAccountIds));
return RepeatStatus.FINISHED;
}
- private LoanAccountLock addLock(Long loanId, Map<Long, LoanAccountLock> alreadySoftLockedAccountsMap) {
- LoanAccountLock loanAccountLock;
- if (alreadySoftLockedAccountsMap.containsKey(loanId)) {
- // Upgrade lock
- loanAccountLock = alreadySoftLockedAccountsMap.get(loanId);
- loanAccountLock.setNewLockOwner(LockOwner.LOAN_COB_CHUNK_PROCESSING);
- } else {
- loanAccountLock = new LoanAccountLock(loanId, LockOwner.LOAN_COB_CHUNK_PROCESSING);
+ private void applySoftLock(List<Long> alreadySoftLockedAccounts) {
+ for (Long loanId : alreadySoftLockedAccounts) {
+ LoanAccountLock loanAccountLock = new LoanAccountLock(loanId, LockOwner.LOAN_COB_PARTITIONING);
+ loanAccountLockRepository.save(loanAccountLock);
}
- return 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<Long> 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<Long> loanIds;
@NotNull
@Override
@@ -65,15 +67,15 @@ public class LoanCOBPartitioner implements Partitioner {
private Map<String, ExecutionContext> getPartitions(int partitionSize, TreeMap<Long, String> cobBusinessStepMap) {
Map<String, ExecutionContext> partitions = new HashMap<>();
- List<Long> 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<Long> data = (List<Long>) 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<Long>());
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<Flow>("cobFlow").start(initialisationStep()).next(applyLockStep()).next(loanBusinessStep()).build();
+ return new FlowBuilder<Flow>("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)
.<Loan, Loan>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<Loan> {
ExecutionContext executionContext = stepExecution.getExecutionContext();
ExecutionContext jobExecutionContext = stepExecution.getJobExecution().getExecutionContext();
List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
- alreadyLockedAccounts = (List<Long>) jobExecutionContext.get(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS);
+ alreadyLockedAccounts = (List<Long>) 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<LoanAccountLock> 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<Long> loanIds;
+ private LoanCOBPartitioner loanCOBPartitioner;
private TreeMap<Long, String> 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<String> 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%
copy from fineract-provider/src/test/resources/features/cob/loan/cob.applylock.step.feature
copy 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.applylock.step.feature b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.fetch.lock.feature
similarity index 61%
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.fetch.lock.feature
index e0423cf61..9df96a0e6 100644
--- a/fineract-provider/src/test/resources/features/cob/loan/cob.applylock.step.feature
+++ b/fineract-provider/src/test/resources/features/cob/loan/cob.loan.fetch.lock.feature
@@ -17,23 +17,18 @@
# under the License.
#
-Feature: COB Apply Loan Lock Step
+Feature: COB Loan fetch and (soft) lock Step
@cob
- Scenario Outline: ApplyLoanLockTasklet - run test
- Given The ApplyLoanLockTasklet.execute method with action <action>
- When ApplyLoanLockTasklet.execute method executed
- Then ApplyLoanLockTasklet.execute result should match
+ Scenario Outline: FetchAndLockLoanTasklet - run test
+ Given The FetchAndLockLoanTasklet.execute method with action <action>
+ When FetchAndLockLoanTasklet.execute method executed
+ Then FetchAndLockLoanTasklet.execute result should match
Examples:
|action|
+ |empty loanIds|
|good|
-
- @cob
- Scenario Outline: ApplyLoanLockTasklet - run test: exception
- Given The ApplyLoanLockTasklet.execute method with action <action>
- Then throw exception ApplyLoanLockTasklet.execute method
-
- Examples:
- |action|
- |error|
\ No newline at end of file
+ |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