FINERACT-1678: Soft-lock Loan COB relevant loan accounts upon startup
This commit is contained in:
committed by
Arnold Gálovics
parent
f258aa258c
commit
44db1d7b94
+1
-1
@@ -67,7 +67,7 @@ public class ApplyLoanLockTasklet implements Tasklet {
|
|||||||
accountLockRepository.save(loanAccountLock);
|
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;
|
return RepeatStatus.FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+79
@@ -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<Long> allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIds();
|
||||||
|
if (allNonClosedLoanIds.isEmpty()) {
|
||||||
|
return RepeatStatus.FINISHED;
|
||||||
|
}
|
||||||
|
List<Long> remainingIds = new ArrayList<>(allNonClosedLoanIds);
|
||||||
|
|
||||||
|
List<LoanAccountLock> loanAccountLocks = loanAccountLockRepository.findAllByLoanIdIn(remainingIds);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Remove already hard locked accounts
|
||||||
|
remainingIds.removeAll(alreadyMarkedForChunkProcessingLockedAccounts);
|
||||||
|
remainingIds.removeAll(alreadyMarkedForInlineCOBLockedAccounts);
|
||||||
|
|
||||||
|
List<Long> 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<Long> alreadySoftLockedAccounts) {
|
||||||
|
for (Long loanId : alreadySoftLockedAccounts) {
|
||||||
|
LoanAccountLock loanAccountLock = new LoanAccountLock(loanId, LockOwner.LOAN_COB_PARTITIONING);
|
||||||
|
loanAccountLockRepository.save(loanAccountLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,8 @@ public final class LoanCOBConstant {
|
|||||||
public static final String BUSINESS_STEP_MAP = "businessStepMap";
|
public static final String BUSINESS_STEP_MAP = "businessStepMap";
|
||||||
public static final String LOAN_COB_WORKER_STEP = "loanCOBWorkerStep";
|
public static final String LOAN_COB_WORKER_STEP = "loanCOBWorkerStep";
|
||||||
|
|
||||||
|
public static final String ALREADY_LOCKED_LOAN_IDS = "alreadyLockedLoanIds";
|
||||||
|
|
||||||
private LoanCOBConstant() {
|
private LoanCOBConstant() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-6
@@ -18,19 +18,24 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.cob.loan;
|
package org.apache.fineract.cob.loan;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import org.apache.fineract.cob.COBBusinessStepService;
|
import org.apache.fineract.cob.COBBusinessStepService;
|
||||||
|
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
|
||||||
import org.apache.fineract.cob.listener.COBExecutionListenerRunner;
|
import org.apache.fineract.cob.listener.COBExecutionListenerRunner;
|
||||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||||
import org.apache.fineract.infrastructure.springbatch.PropertyService;
|
import org.apache.fineract.infrastructure.springbatch.PropertyService;
|
||||||
import org.springframework.batch.core.Job;
|
import org.springframework.batch.core.Job;
|
||||||
import org.springframework.batch.core.Step;
|
import org.springframework.batch.core.Step;
|
||||||
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
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.explore.JobExplorer;
|
||||||
import org.springframework.batch.core.launch.JobOperator;
|
import org.springframework.batch.core.launch.JobOperator;
|
||||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||||
import org.springframework.batch.integration.config.annotation.EnableBatchIntegration;
|
import org.springframework.batch.integration.config.annotation.EnableBatchIntegration;
|
||||||
import org.springframework.batch.integration.partition.RemotePartitioningManagerStepBuilderFactory;
|
import org.springframework.batch.integration.partition.RemotePartitioningManagerStepBuilderFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -47,6 +52,8 @@ public class LoanCOBManagerConfiguration {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RemotePartitioningManagerStepBuilderFactory stepBuilderFactory;
|
private RemotePartitioningManagerStepBuilderFactory stepBuilderFactory;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private StepBuilderFactory localStepBuilderFactory;
|
||||||
|
@Autowired
|
||||||
private PropertyService propertyService;
|
private PropertyService propertyService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private DirectChannel outboundRequests;
|
private DirectChannel outboundRequests;
|
||||||
@@ -58,27 +65,40 @@ public class LoanCOBManagerConfiguration {
|
|||||||
private JobExplorer jobExplorer;
|
private JobExplorer jobExplorer;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ApplicationContext applicationContext;
|
private ApplicationContext applicationContext;
|
||||||
@Autowired(required = false)
|
@Autowired
|
||||||
private RetrieveLoanIdService retrieveLoanIdService;
|
private RetrieveLoanIdService retrieveLoanIdService;
|
||||||
|
@Autowired
|
||||||
|
private LoanAccountLockRepository accountLockRepository;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public LoanCOBPartitioner partitioner() {
|
@JobScope
|
||||||
return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, retrieveLoanIdService);
|
public LoanCOBPartitioner partitioner(@Value("#{jobExecutionContext['loanIds']}") List<Long> loanIds) {
|
||||||
|
return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, loanIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Step loanCOBStep() {
|
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();
|
.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")
|
@Bean(name = "loanCOBJob")
|
||||||
public Job loanCOBJob() {
|
public Job loanCOBJob() {
|
||||||
return jobBuilderFactory.get(JobName.LOAN_COB.name()) //
|
return jobBuilderFactory.get(JobName.LOAN_COB.name()) //
|
||||||
.listener(new COBExecutionListenerRunner(applicationContext, JobName.LOAN_COB.name())) //
|
.listener(new COBExecutionListenerRunner(applicationContext, JobName.LOAN_COB.name())) //
|
||||||
.start(loanCOBStep()) //
|
.start(fetchAndLockStep()).next(loanCOBStep()) //
|
||||||
.incrementer(new RunIdIncrementer()) //
|
.incrementer(new RunIdIncrementer()) //
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-5
@@ -37,6 +37,7 @@ import org.springframework.batch.core.launch.JobOperator;
|
|||||||
import org.springframework.batch.core.launch.NoSuchJobExecutionException;
|
import org.springframework.batch.core.launch.NoSuchJobExecutionException;
|
||||||
import org.springframework.batch.core.partition.support.Partitioner;
|
import org.springframework.batch.core.partition.support.Partitioner;
|
||||||
import org.springframework.batch.item.ExecutionContext;
|
import org.springframework.batch.item.ExecutionContext;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -48,7 +49,8 @@ public class LoanCOBPartitioner implements Partitioner {
|
|||||||
private final COBBusinessStepService cobBusinessStepService;
|
private final COBBusinessStepService cobBusinessStepService;
|
||||||
private final JobOperator jobOperator;
|
private final JobOperator jobOperator;
|
||||||
private final JobExplorer jobExplorer;
|
private final JobExplorer jobExplorer;
|
||||||
private final RetrieveLoanIdService retrieveLoanIdService;
|
|
||||||
|
private final List<Long> loanIds;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
@@ -65,15 +67,15 @@ public class LoanCOBPartitioner implements Partitioner {
|
|||||||
|
|
||||||
private Map<String, ExecutionContext> getPartitions(int partitionSize, TreeMap<Long, String> cobBusinessStepMap) {
|
private Map<String, ExecutionContext> getPartitions(int partitionSize, TreeMap<Long, String> cobBusinessStepMap) {
|
||||||
Map<String, ExecutionContext> partitions = new HashMap<>();
|
Map<String, ExecutionContext> partitions = new HashMap<>();
|
||||||
List<Long> allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIds();
|
|
||||||
if (allNonClosedLoanIds.isEmpty()) {
|
if (CollectionUtils.isEmpty(loanIds)) {
|
||||||
stopJobExecution();
|
stopJobExecution();
|
||||||
return Map.of();
|
return Map.of();
|
||||||
}
|
}
|
||||||
int partitionIndex = 1;
|
int partitionIndex = 1;
|
||||||
int remainingSpace = 0;
|
int remainingSpace = 0;
|
||||||
createNewPartition(partitions, partitionIndex, cobBusinessStepMap);
|
createNewPartition(partitions, partitionIndex, cobBusinessStepMap);
|
||||||
for (Long allNonClosedLoanId : allNonClosedLoanIds) {
|
for (Long loanId : loanIds) {
|
||||||
if (remainingSpace == partitionSize) {
|
if (remainingSpace == partitionSize) {
|
||||||
partitionIndex++;
|
partitionIndex++;
|
||||||
createNewPartition(partitions, partitionIndex, cobBusinessStepMap);
|
createNewPartition(partitions, partitionIndex, cobBusinessStepMap);
|
||||||
@@ -82,7 +84,7 @@ public class LoanCOBPartitioner implements Partitioner {
|
|||||||
String key = PARTITION_PREFIX + partitionIndex;
|
String key = PARTITION_PREFIX + partitionIndex;
|
||||||
ExecutionContext executionContext = partitions.get(key);
|
ExecutionContext executionContext = partitions.get(key);
|
||||||
List<Long> data = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
|
List<Long> data = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
|
||||||
data.add(allNonClosedLoanId);
|
data.add(loanId);
|
||||||
remainingSpace++;
|
remainingSpace++;
|
||||||
}
|
}
|
||||||
return partitions;
|
return partitions;
|
||||||
@@ -93,6 +95,7 @@ public class LoanCOBPartitioner implements Partitioner {
|
|||||||
ExecutionContext executionContext = new ExecutionContext();
|
ExecutionContext executionContext = new ExecutionContext();
|
||||||
executionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList<Long>());
|
executionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList<Long>());
|
||||||
executionContext.put(LoanCOBConstant.BUSINESS_STEP_MAP, cobBusinessStepMap);
|
executionContext.put(LoanCOBConstant.BUSINESS_STEP_MAP, cobBusinessStepMap);
|
||||||
|
executionContext.put("partition", PARTITION_PREFIX + partitionIndex);
|
||||||
partitions.put(PARTITION_PREFIX + partitionIndex, executionContext);
|
partitions.put(PARTITION_PREFIX + partitionIndex, executionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-12
@@ -28,12 +28,14 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||||
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
|
import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
|
||||||
import org.springframework.batch.core.Step;
|
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.configuration.annotation.StepScope;
|
||||||
import org.springframework.batch.core.job.builder.FlowBuilder;
|
import org.springframework.batch.core.job.builder.FlowBuilder;
|
||||||
import org.springframework.batch.core.job.flow.Flow;
|
import org.springframework.batch.core.job.flow.Flow;
|
||||||
import org.springframework.batch.core.listener.ExecutionContextPromotionListener;
|
import org.springframework.batch.core.listener.ExecutionContextPromotionListener;
|
||||||
import org.springframework.batch.integration.partition.RemotePartitioningWorkerStepBuilderFactory;
|
import org.springframework.batch.integration.partition.RemotePartitioningWorkerStepBuilderFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
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")
|
@ConditionalOnProperty(value = "fineract.mode.batch-worker-enabled", havingValue = "true")
|
||||||
public class LoanCOBWorkerConfiguration {
|
public class LoanCOBWorkerConfiguration {
|
||||||
|
|
||||||
public static final String ALREADY_LOCKED_LOAN_IDS = "alreadyLockedLoanIds";
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RemotePartitioningWorkerStepBuilderFactory stepBuilderFactory;
|
private RemotePartitioningWorkerStepBuilderFactory stepBuilderFactory;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StepBuilderFactory localStepBuilderFactory;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PropertyService propertyService;
|
private PropertyService propertyService;
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -69,17 +73,20 @@ public class LoanCOBWorkerConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Flow flow() {
|
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
|
@Bean
|
||||||
public Step initialisationStep() {
|
@StepScope
|
||||||
return stepBuilderFactory.get("Initialisation - Step").inputChannel(inboundRequests).tasklet(initialiseContext()).build();
|
public Step initialisationStep(@Value("#{stepExecutionContext['partition']}") String partitionName) {
|
||||||
|
return localStepBuilderFactory.get("Initialisation - Step:" + partitionName).tasklet(initialiseContext()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Step loanBusinessStep() {
|
@StepScope
|
||||||
return stepBuilderFactory.get("Loan COB worker - Step").inputChannel(inboundRequests)
|
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())
|
.<Loan, Loan>chunk(propertyService.getChunkSize(JobName.LOAN_COB.name())).reader(cobWorkerItemReader())
|
||||||
.processor(cobWorkerItemProcessor()).writer(cobWorkerItemWriter()).faultTolerant().skip(Exception.class)
|
.processor(cobWorkerItemProcessor()).writer(cobWorkerItemWriter()).faultTolerant().skip(Exception.class)
|
||||||
.skipLimit(propertyService.getChunkSize(JobName.LOAN_COB.name()) + 1).listener(loanItemListener())
|
.skipLimit(propertyService.getChunkSize(JobName.LOAN_COB.name()) + 1).listener(loanItemListener())
|
||||||
@@ -87,13 +94,12 @@ public class LoanCOBWorkerConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Step applyLockStep() {
|
@StepScope
|
||||||
return stepBuilderFactory.get("Apply lock - Step").inputChannel(inboundRequests).tasklet(applyLock()).listener(promotionListener())
|
public Step applyLockStep(@Value("#{stepExecutionContext['partition']}") String partitionName) {
|
||||||
.build();
|
return localStepBuilderFactory.get("Apply lock - Step:" + partitionName).tasklet(applyLock()).listener(promotionListener()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@StepScope
|
|
||||||
public InitialisationTasklet initialiseContext() {
|
public InitialisationTasklet initialiseContext() {
|
||||||
return new InitialisationTasklet(userRepository);
|
return new InitialisationTasklet(userRepository);
|
||||||
}
|
}
|
||||||
@@ -104,7 +110,6 @@ public class LoanCOBWorkerConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@StepScope
|
|
||||||
public ApplyLoanLockTasklet applyLock() {
|
public ApplyLoanLockTasklet applyLock() {
|
||||||
return new ApplyLoanLockTasklet(accountLockRepository);
|
return new ApplyLoanLockTasklet(accountLockRepository);
|
||||||
}
|
}
|
||||||
@@ -132,7 +137,7 @@ public class LoanCOBWorkerConfiguration {
|
|||||||
@Bean
|
@Bean
|
||||||
public ExecutionContextPromotionListener promotionListener() {
|
public ExecutionContextPromotionListener promotionListener() {
|
||||||
ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
|
ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
|
||||||
listener.setKeys(new String[] { ALREADY_LOCKED_LOAN_IDS });
|
listener.setKeys(new String[] { LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS });
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public class LoanItemReader implements ItemReader<Loan> {
|
|||||||
ExecutionContext executionContext = stepExecution.getExecutionContext();
|
ExecutionContext executionContext = stepExecution.getExecutionContext();
|
||||||
ExecutionContext jobExecutionContext = stepExecution.getJobExecution().getExecutionContext();
|
ExecutionContext jobExecutionContext = stepExecution.getJobExecution().getExecutionContext();
|
||||||
List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
|
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);
|
remainingData = new ArrayList<>(loanIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -73,8 +73,8 @@ public class ApplyLoanLockTaskletStepDefinitions implements En {
|
|||||||
|
|
||||||
Then("ApplyLoanLockTasklet.execute result should match", () -> {
|
Then("ApplyLoanLockTasklet.execute result should match", () -> {
|
||||||
assertEquals(RepeatStatus.FINISHED, resultItem);
|
assertEquals(RepeatStatus.FINISHED, resultItem);
|
||||||
assertEquals(3L, ((List) stepContribution.getStepExecution().getExecutionContext()
|
assertEquals(3L,
|
||||||
.get(LoanCOBWorkerConfiguration.ALREADY_LOCKED_LOAN_IDS)).get(0));
|
((List) stepContribution.getStepExecution().getExecutionContext().get(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS)).get(0));
|
||||||
verify(this.accountLockRepository, Mockito.times(2)).save(valueCaptor.capture());
|
verify(this.accountLockRepository, Mockito.times(2)).save(valueCaptor.capture());
|
||||||
List<LoanAccountLock> values = valueCaptor.getAllValues();
|
List<LoanAccountLock> values = valueCaptor.getAllValues();
|
||||||
assertEquals(2L, values.get(0).getLoanId());
|
assertEquals(2L, values.get(0).getLoanId());
|
||||||
|
|||||||
+146
@@ -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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-6
@@ -26,7 +26,7 @@ import static org.mockito.Mockito.mock;
|
|||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import io.cucumber.java8.En;
|
import io.cucumber.java8.En;
|
||||||
import java.util.Collections;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -46,9 +46,9 @@ public class LoanCOBPartitionerStepDefinitions implements En {
|
|||||||
COBBusinessStepService cobBusinessStepService = mock(COBBusinessStepService.class);
|
COBBusinessStepService cobBusinessStepService = mock(COBBusinessStepService.class);
|
||||||
JobOperator jobOperator = mock(JobOperator.class);
|
JobOperator jobOperator = mock(JobOperator.class);
|
||||||
JobExplorer jobExplorer = mock(JobExplorer.class);
|
JobExplorer jobExplorer = mock(JobExplorer.class);
|
||||||
RetrieveLoanIdService retrieveLoanIdService = mock(RetrieveLoanIdService.class);
|
|
||||||
private LoanCOBPartitioner loanCOBPartitioner = new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator,
|
List<Long> loanIds;
|
||||||
jobExplorer, retrieveLoanIdService);
|
private LoanCOBPartitioner loanCOBPartitioner;
|
||||||
|
|
||||||
private TreeMap<Long, String> cobBusinessMap = new TreeMap<>();
|
private TreeMap<Long, String> cobBusinessMap = new TreeMap<>();
|
||||||
|
|
||||||
@@ -69,14 +69,15 @@ public class LoanCOBPartitionerStepDefinitions implements En {
|
|||||||
cobBusinessMap.put(1L, "Business step");
|
cobBusinessMap.put(1L, "Business step");
|
||||||
lenient().when(cobBusinessStepService.getCOBBusinessStepMap(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME))
|
lenient().when(cobBusinessStepService.getCOBBusinessStepMap(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME))
|
||||||
.thenReturn(cobBusinessMap);
|
.thenReturn(cobBusinessMap);
|
||||||
lenient().when(retrieveLoanIdService.retrieveLoanIds()).thenReturn(Collections.emptyList());
|
loanIds = new ArrayList<>();
|
||||||
lenient().when(jobExplorer.findRunningJobExecutions(JobName.LOAN_COB.name())).thenThrow(new RuntimeException("fail"));
|
lenient().when(jobExplorer.findRunningJobExecutions(JobName.LOAN_COB.name())).thenThrow(new RuntimeException("fail"));
|
||||||
} else if ("good".equals(action)) {
|
} else if ("good".equals(action)) {
|
||||||
cobBusinessMap.put(1L, "Business step");
|
cobBusinessMap.put(1L, "Business step");
|
||||||
lenient().when(cobBusinessStepService.getCOBBusinessStepMap(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME))
|
lenient().when(cobBusinessStepService.getCOBBusinessStepMap(LoanCOBBusinessStep.class, LoanCOBConstant.LOAN_COB_JOB_NAME))
|
||||||
.thenReturn(cobBusinessMap);
|
.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", () -> {
|
When("LoanCOBPartitioner.partition method executed", () -> {
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ public class LoanItemReaderStepDefinitions implements En {
|
|||||||
List<String> splitLockedAccountsStr = Splitter.on(',').splitToList(lockedAccounts);
|
List<String> splitLockedAccountsStr = Splitter.on(',').splitToList(lockedAccounts);
|
||||||
splitLockedAccounts = splitLockedAccountsStr.stream().map(Long::parseLong).toList();
|
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);
|
jobExecution.setExecutionContext(jobExecutionContext);
|
||||||
StepExecution stepExecution = new StepExecution("test", jobExecution);
|
StepExecution stepExecution = new StepExecution("test", jobExecution);
|
||||||
ExecutionContext stepExecutionContext = new ExecutionContext();
|
ExecutionContext stepExecutionContext = new ExecutionContext();
|
||||||
|
|||||||
+1
-2
@@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* 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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
@@ -28,7 +28,6 @@ import java.time.ZoneId;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
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.AbstractAuditableCustom;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||||
@@ -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 <action>
|
||||||
|
When FetchAndLockLoanTasklet.execute method executed
|
||||||
|
Then FetchAndLockLoanTasklet.execute result should match
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|action|
|
||||||
|
|empty loanIds|
|
||||||
|
|good|
|
||||||
|
|soft lock|
|
||||||
|
|inline cob|
|
||||||
|
|chunk processing|
|
||||||
Reference in New Issue
Block a user