[FINERACT-1678] Exclude loan IDs from execution context

This commit is contained in:
taskain7
2023-04-12 15:03:12 +02:00
committed by Adam Saghy
parent 63fa315f15
commit ace0786024
23 changed files with 370 additions and 249 deletions
@@ -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.
*/
package org.apache.fineract.cob.data;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@Getter
@NoArgsConstructor
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public class LoanCOBParameter {
private Long minLoanId;
private Long maxLoanId;
}
@@ -37,7 +37,7 @@ import org.springframework.batch.item.ItemReader;
@RequiredArgsConstructor
public abstract class AbstractLoanItemReader implements ItemReader<Loan> {
private final LoanRepository loanRepository;
protected final LoanRepository loanRepository;
@Setter(AccessLevel.PROTECTED)
private List<Long> alreadyLockedOrProcessedAccounts;
@@ -20,17 +20,23 @@ package org.apache.fineract.cob.loan;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.data.LoanCOBParameter;
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.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.jetbrains.annotations.NotNull;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
@@ -46,11 +52,21 @@ public class ApplyLoanLockTasklet implements Tasklet {
private final LoanAccountLockRepository accountLockRepository;
private final FineractProperties fineractProperties;
private final JdbcTemplate jdbcTemplate;
private final LoanRepository loanRepository;
@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);
LoanCOBParameter loanCOBParameter = (LoanCOBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER);
List<Long> loanIds;
if (Objects.isNull(loanCOBParameter)
|| (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))
|| (loanCOBParameter.getMinLoanId().equals(0L) && loanCOBParameter.getMaxLoanId().equals(0L))) {
loanIds = Collections.emptyList();
} else {
loanIds = new ArrayList<>(loanRepository.findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(loanCOBParameter.getMinLoanId(),
loanCOBParameter.getMaxLoanId(), ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE)));
}
List<List<Long>> loanIdPartitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit());
List<LoanAccountLock> accountLocks = new ArrayList<>();
loanIdPartitions.forEach(loanIdPartition -> accountLocks.addAll(accountLockRepository.findAllByLoanIdIn(loanIdPartition)));
@@ -1,112 +0,0 @@
/**
* 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 com.google.common.collect.Lists;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
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.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
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;
import org.springframework.jdbc.core.JdbcTemplate;
@Slf4j
@RequiredArgsConstructor
public class FetchAndLockLoanTasklet implements Tasklet {
private static final Long NUMBER_OF_DAYS_BEHIND = 1L;
private final LoanAccountLockRepository loanAccountLockRepository;
private final RetrieveLoanIdService retrieveLoanIdService;
private final FineractProperties fineractProperties;
private final JdbcTemplate jdbcTemplate;
@Override
public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception {
String businessDateParameter = (String) contribution.getStepExecution().getJobExecution().getExecutionContext()
.get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
LocalDate businessDate = LocalDate.parse(Objects.requireNonNull(businessDateParameter));
List<Long> allNonClosedLoanIds = retrieveLoanIdService.retrieveLoanIdsNDaysBehind(NUMBER_OF_DAYS_BEHIND, businessDate);
if (allNonClosedLoanIds.isEmpty()) {
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, Collections.emptyList());
return RepeatStatus.FINISHED;
}
List<Long> remainingIds = new ArrayList<>(allNonClosedLoanIds);
List<List<Long>> remainingIdPartitions = Lists.partition(remainingIds, getInClauseParameterSizeLimit());
List<LoanAccountLock> loanAccountLocks = new ArrayList<>();
remainingIdPartitions.forEach(
remainingIdPartition -> loanAccountLocks.addAll(loanAccountLockRepository.findAllByLoanIdIn(remainingIdPartition)));
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> accountsToLock) {
LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
jdbcTemplate.batchUpdate("""
INSERT INTO m_loan_account_locks (loan_id, version, lock_owner, lock_placed_on, lock_placed_on_cob_business_date)
VALUES (?, ?, ?, ?, ?)
""", accountsToLock, getInClauseParameterSizeLimit(), (ps, id) -> {
ps.setLong(1, id);
ps.setLong(2, 1);
ps.setString(3, LockOwner.LOAN_COB_PARTITIONING.name());
ps.setObject(4, DateUtils.getOffsetDateTimeOfTenant());
ps.setObject(5, cobBusinessDate);
});
}
private int getInClauseParameterSizeLimit() {
return fineractProperties.getQuery().getInClauseParameterSizeLimit();
}
}
@@ -36,7 +36,7 @@ public class InlineCOBLoanItemReader extends AbstractLoanItemReader {
@SuppressWarnings({ "unchecked" })
public void beforeStep(@NotNull StepExecution stepExecution) {
ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER);
setRemainingData(new ArrayList<>(loanIds));
}
}
@@ -58,7 +58,8 @@ public class InlineLoanCOBBuildExecutionContextTasklet implements Tasklet {
ThreadLocalContextUtil.setActionContext(ActionContext.COB);
Set<BusinessStepNameAndOrder> cobBusinessSteps = cobBusinessStepService.getCOBBusinessSteps(LoanCOBBusinessStep.class,
LoanCOBConstant.LOAN_COB_JOB_NAME);
contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, getLoanIdsFromJobParameters(chunkContext));
contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
getLoanIdsFromJobParameters(chunkContext));
contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_STEPS, cobBusinessSteps);
String businessDateString = getBusinessDateFromJobParameters(chunkContext);
contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME, businessDateString);
@@ -23,11 +23,10 @@ public final class LoanCOBConstant {
public static final String JOB_NAME = "LOAN_COB";
public static final String JOB_HUMAN_READABLE_NAME = "Loan COB";
public static final String LOAN_COB_JOB_NAME = "LOAN_CLOSE_OF_BUSINESS";
public static final String LOAN_IDS = "loanIds";
public static final String LOAN_COB_PARAMETER = "loanCobParameter";
public static final String BUSINESS_STEPS = "businessSteps";
public static final String LOAN_COB_WORKER_STEP = "loanCOBWorkerStep";
public static final String ALREADY_LOCKED_BY_INLINE_COB_OR_PROCESSED_LOAN_IDS = "alreadyLockedOrProcessedLoanIds";
public static final String INLINE_LOAN_COB_JOB_NAME = "INLINE_LOAN_COB";
public static final String BUSINESS_DATE_PARAMETER_NAME = "BusinessDate";
public static final String IS_CATCH_UP_PARAMETER_NAME = "IS_CATCH_UP";
@@ -18,12 +18,10 @@
*/
package org.apache.fineract.cob.loan;
import java.util.List;
import org.apache.fineract.cob.COBBusinessStepService;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.listener.COBExecutionListenerRunner;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.apache.fineract.infrastructure.springbatch.PropertyService;
@@ -74,23 +72,19 @@ public class LoanCOBManagerConfiguration {
@Autowired
private RetrieveLoanIdService retrieveLoanIdService;
@Autowired
private LoanAccountLockRepository accountLockRepository;
@Autowired
private BusinessEventNotifierService businessEventNotifierService;
@Autowired
private CustomJobParameterResolver customJobParameterResolver;
@Autowired
private LoanRepository loanRepository;
@Autowired
private FineractProperties fineractProperties;
@Autowired
private JdbcTemplate jdbcTemplate;
@Bean
@JobScope
public LoanCOBPartitioner partitioner(@Value("#{jobExecutionContext['loanIds']}") List<Long> loanIds) {
return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, loanIds);
public LoanCOBPartitioner partitioner(@Value("#{jobExecutionContext['loanIds']}") LoanCOBParameter minAndMaxLoanId) {
return new LoanCOBPartitioner(propertyService, cobBusinessStepService, jobOperator, jobExplorer, minAndMaxLoanId);
}
@Bean
@@ -100,8 +94,13 @@ public class LoanCOBManagerConfiguration {
}
@Bean
public Step fetchAndLockStep() {
return localStepBuilderFactory.get("Fetch and Lock loan accounts - Step").tasklet(fetchAndLockLoanTasklet()).build();
public Step loanIdParameterStep() {
return localStepBuilderFactory.get("Set loan ID parameter - Step").tasklet(loanIdParameterTasklet()).build();
}
@Bean
public Step lockStep() {
return localStepBuilderFactory.get("Lock loan accounts - Step").tasklet(lockLoanTasklet()).build();
}
@Bean
@@ -117,8 +116,14 @@ public class LoanCOBManagerConfiguration {
@Bean
@JobScope
public FetchAndLockLoanTasklet fetchAndLockLoanTasklet() {
return new FetchAndLockLoanTasklet(accountLockRepository, retrieveLoanIdService, fineractProperties, jdbcTemplate);
public LoanIdParameterTasklet loanIdParameterTasklet() {
return new LoanIdParameterTasklet(retrieveLoanIdService);
}
@Bean
@JobScope
public LockLoanTasklet lockLoanTasklet() {
return new LockLoanTasklet(jdbcTemplate);
}
@Bean
@@ -138,7 +143,7 @@ public class LoanCOBManagerConfiguration {
return jobBuilderFactory.get(JobName.LOAN_COB.name()) //
.listener(new COBExecutionListenerRunner(applicationContext, JobName.LOAN_COB.name())) //
.start(resolveCustomJobParametersStep()) //
.next(fetchAndLockStep()).next(loanCOBStep()).next(stayedLockedStep()) //
.next(loanIdParameterStep()).next(lockStep()).next(loanCOBStep()).next(stayedLockedStep()) //
.incrementer(new RunIdIncrementer()) //
.build();
}
@@ -18,15 +18,15 @@
*/
package org.apache.fineract.cob.loan;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.COBBusinessStepService;
import org.apache.fineract.cob.data.BusinessStepNameAndOrder;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.apache.fineract.infrastructure.springbatch.PropertyService;
import org.jetbrains.annotations.NotNull;
@@ -49,7 +49,7 @@ public class LoanCOBPartitioner implements Partitioner {
private final JobOperator jobOperator;
private final JobExplorer jobExplorer;
private final List<Long> loanIds;
private final LoanCOBParameter minAndMaxLoanId;
@NotNull
@Override
@@ -67,19 +67,25 @@ public class LoanCOBPartitioner implements Partitioner {
return Map.of();
}
int partitionIndex = 1;
int remainingSpace = 0;
createNewPartition(partitions, partitionIndex, cobBusinessSteps);
for (Long loanId : loanIds) {
if (remainingSpace == partitionSize) {
partitionIndex++;
createNewPartition(partitions, partitionIndex, cobBusinessSteps);
remainingSpace = 0;
}
String key = PARTITION_PREFIX + partitionIndex;
ExecutionContext executionContext = partitions.get(key);
List<Long> data = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
data.add(loanId);
remainingSpace++;
if (!Objects.isNull(minAndMaxLoanId)) {
long remainingLoanIdCount = minAndMaxLoanId.getMaxLoanId() - minAndMaxLoanId.getMinLoanId() + 1;
long startLoanId = minAndMaxLoanId.getMinLoanId();
long endLoanId;
do {
String key = PARTITION_PREFIX + partitionIndex;
ExecutionContext executionContext = partitions.get(key);
if (remainingLoanIdCount > partitionSize) {
endLoanId = startLoanId + partitionSize - 1;
partitionIndex++;
createNewPartition(partitions, partitionIndex, cobBusinessSteps);
} else {
endLoanId = minAndMaxLoanId.getMaxLoanId();
}
executionContext.put(LoanCOBConstant.LOAN_COB_PARAMETER, new LoanCOBParameter(startLoanId, endLoanId));
startLoanId = startLoanId + partitionSize;
remainingLoanIdCount = minAndMaxLoanId.getMaxLoanId() - endLoanId;
} while (remainingLoanIdCount > 0);
}
return partitions;
}
@@ -87,7 +93,6 @@ public class LoanCOBPartitioner implements Partitioner {
private void createNewPartition(Map<String, ExecutionContext> partitions, int partitionIndex,
Set<BusinessStepNameAndOrder> cobBusinessSteps) {
ExecutionContext executionContext = new ExecutionContext();
executionContext.put(LoanCOBConstant.LOAN_IDS, new ArrayList<Long>());
executionContext.put(LoanCOBConstant.BUSINESS_STEPS, cobBusinessSteps);
executionContext.put("partition", PARTITION_PREFIX + partitionIndex);
partitions.put(PARTITION_PREFIX + partitionIndex, executionContext);
@@ -123,7 +123,7 @@ public class LoanCOBWorkerConfiguration {
@Bean
public ApplyLoanLockTasklet applyLock() {
return new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate);
return new ApplyLoanLockTasklet(accountLockRepository, fineractProperties, jdbcTemplate, loanRepository);
}
@Bean
@@ -0,0 +1,56 @@
/**
* 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.time.LocalDate;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.data.LoanCOBParameter;
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 LoanIdParameterTasklet implements Tasklet {
private static final Long NUMBER_OF_DAYS_BEHIND = 1L;
private final RetrieveLoanIdService retrieveLoanIdService;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
String businessDateParameter = (String) contribution.getStepExecution().getJobExecution().getExecutionContext()
.get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
LocalDate businessDate = LocalDate.parse(Objects.requireNonNull(businessDateParameter));
LoanCOBParameter minAndMaxLoanId = retrieveLoanIdService.retrieveMinAndMaxLoanIdsNDaysBehind(NUMBER_OF_DAYS_BEHIND, businessDate);
if (Objects.isNull(minAndMaxLoanId)
|| (Objects.isNull(minAndMaxLoanId.getMinLoanId()) && Objects.isNull(minAndMaxLoanId.getMaxLoanId()))) {
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER,
new LoanCOBParameter(0L, 0L));
return RepeatStatus.FINISHED;
}
contribution.getStepExecution().getJobExecution().getExecutionContext().put(LoanCOBConstant.LOAN_COB_PARAMETER, minAndMaxLoanId);
return RepeatStatus.FINISHED;
}
}
@@ -121,8 +121,8 @@ public class LoanInlineCOBConfig {
@Bean
public ExecutionContextPromotionListener inlineCobPromotionListener() {
ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener();
listener.setKeys(
new String[] { LoanCOBConstant.LOAN_IDS, LoanCOBConstant.BUSINESS_STEPS, LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME });
listener.setKeys(new String[] { LoanCOBConstant.LOAN_COB_PARAMETER, LoanCOBConstant.BUSINESS_STEPS,
LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME });
return listener;
}
}
@@ -19,7 +19,12 @@
package org.apache.fineract.cob.loan;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.jetbrains.annotations.NotNull;
import org.springframework.batch.core.StepExecution;
@@ -35,8 +40,17 @@ public class LoanItemReader extends AbstractLoanItemReader {
@BeforeStep
@SuppressWarnings({ "unchecked" })
public void beforeStep(@NotNull StepExecution stepExecution) {
ExecutionContext executionContext = stepExecution.getExecutionContext();
List<Long> loanIds = (List<Long>) executionContext.get(LoanCOBConstant.LOAN_IDS);
ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext();
LoanCOBParameter loanCOBParameter = (LoanCOBParameter) executionContext.get(LoanCOBConstant.LOAN_COB_PARAMETER);
List<Long> loanIds;
if (Objects.isNull(loanCOBParameter)
|| (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))
|| (loanCOBParameter.getMinLoanId().equals(0L) && loanCOBParameter.getMaxLoanId().equals(0L))) {
loanIds = Collections.emptyList();
} else {
loanIds = loanRepository.findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(loanCOBParameter.getMinLoanId(),
loanCOBParameter.getMaxLoanId(), ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE));
}
setRemainingData(new ArrayList<>(loanIds));
}
}
@@ -0,0 +1,78 @@
/**
* 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.time.LocalDate;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.domain.LockOwner;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
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;
import org.springframework.jdbc.core.JdbcTemplate;
@Slf4j
@RequiredArgsConstructor
public class LockLoanTasklet implements Tasklet {
private final JdbcTemplate jdbcTemplate;
@Override
public RepeatStatus execute(@NotNull StepContribution contribution, @NotNull ChunkContext chunkContext) throws Exception {
String businessDateParameter = (String) contribution.getStepExecution().getJobExecution().getExecutionContext()
.get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME);
LocalDate businessDate = LocalDate.parse(Objects.requireNonNull(businessDateParameter));
LoanCOBParameter loanCOBParameter = (LoanCOBParameter) contribution.getStepExecution().getJobExecution().getExecutionContext()
.get(LoanCOBConstant.LOAN_COB_PARAMETER);
if (Objects.isNull(loanCOBParameter)
|| (Objects.isNull(loanCOBParameter.getMinLoanId()) && Objects.isNull(loanCOBParameter.getMaxLoanId()))) {
loanCOBParameter = new LoanCOBParameter(0L, 0L);
}
applySoftLock(businessDate, loanCOBParameter);
return RepeatStatus.FINISHED;
}
private void applySoftLock(LocalDate businessDate, LoanCOBParameter loanCOBParameter) {
LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
jdbcTemplate.update("""
INSERT INTO m_loan_account_locks (loan_id, version, lock_owner, lock_placed_on, lock_placed_on_cob_business_date)
SELECT loan.id, ?, ?, ?, ? FROM m_loan loan
WHERE loan.id NOT IN (SELECT loan_id FROM m_loan_account_locks)
AND loan.id BETWEEN ? AND ?
AND loan.loan_status_id IN (100,200,300,303,304)
AND (? = loan.last_closed_business_date OR loan.last_closed_business_date IS NULL)
""", ps -> {
ps.setLong(1, 1);
ps.setString(2, LockOwner.LOAN_COB_PARTITIONING.name());
ps.setObject(3, DateUtils.getOffsetDateTimeOfTenant());
ps.setObject(4, cobBusinessDate);
ps.setObject(5, loanCOBParameter.getMinLoanId());
ps.setObject(6, loanCOBParameter.getMaxLoanId());
ps.setObject(7, businessDate);
});
}
}
@@ -21,6 +21,7 @@ package org.apache.fineract.cob.loan;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
@@ -30,8 +31,8 @@ public class RetrieveAllNonClosedLoanIdServiceImpl implements RetrieveLoanIdServ
private final LoanRepository loanRepository;
@Override
public List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate) {
return loanRepository.findAllNonClosedLoanIdsByLastClosedBusinessDate(businessDate.minusDays(numberOfDays));
public LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate) {
return loanRepository.findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(businessDate.minusDays(numberOfDays));
}
@Override
@@ -20,11 +20,12 @@ package org.apache.fineract.cob.loan;
import java.time.LocalDate;
import java.util.List;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
public interface RetrieveLoanIdService {
List<Long> retrieveLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate);
LoanCOBParameter retrieveMinAndMaxLoanIdsNDaysBehind(Long numberOfDays, LocalDate businessDate);
List<LoanIdAndLastClosedBusinessDate> retrieveLoanIdsBehindDateOrNull(LocalDate businessDate, List<Long> loanIds);
@@ -53,7 +53,8 @@ public class StayedLockedLoansTasklet implements Tasklet {
private LoanAccountsStayedLockedData buildLoanAccountData() {
LocalDate cobBusinessDate = ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE);
List<LoanIdAndExternalIdAndAccountNo> stayedLockedLoanAccounts = loanRepository.findAllStayedLockedByLoanIds(cobBusinessDate);
List<LoanIdAndExternalIdAndAccountNo> stayedLockedLoanAccounts = loanRepository
.findAllStayedLockedByCobBusinessDate(cobBusinessDate);
List<LoanAccountStayedLockedData> loanAccounts = new ArrayList<>();
stayedLockedLoanAccounts.forEach(loanAccount -> {
loanAccounts.add(new LoanAccountStayedLockedData(loanAccount.getId(), loanAccount.getExternalId(), loanAccount.getAccountNo()));
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.domain;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
import org.apache.fineract.cob.data.LoanCOBParameter;
import org.apache.fineract.cob.data.LoanIdAndExternalIdAndAccountNo;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
@@ -74,7 +75,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
String FIND_ALL_NON_CLOSED = "select loan.id from Loan loan where loan.loanStatus in (100,200,300,303,304)";
String FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE = "select loan.id from Loan loan where loan.loanStatus in (100,200,300,303,304) and (:businessDate = loan.lastClosedBusinessDate or loan.lastClosedBusinessDate is NULL)";
String FIND_MIN_AND_MAX_NON_CLOSED_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE = "select new org.apache.fineract.cob.data.LoanCOBParameter(min(loan.id), max(loan.id)) from Loan loan where loan.loanStatus in (100,200,300,303,304) "
+ "and (:businessDate = loan.lastClosedBusinessDate or loan.lastClosedBusinessDate is NULL)";
String FIND_NON_CLOSED_LOAN_THAT_BELONGS_TO_CLIENT = "select loan from Loan loan where loan.id = :loanId and loan.loanStatus = 300 and loan.client.id = :clientId";
@@ -89,9 +91,12 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
String FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_BY_LOAN_IDS = "select loan.id, loan.lastClosedBusinessDate from Loan loan where loan.id IN :loanIds and loan.loanStatus in (100,200,300,303,304) and (loan.lastClosedBusinessDate < :cobBusinessDate or "
+ "loan.lastClosedBusinessDate is null)";
String FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_BY_MIN_AND_MAX_LOAN_ID = "select loan.id from Loan loan where loan.id BETWEEN :minLoanId and :maxLoanId and loan.loanStatus in (100,200,300,303,304) and (loan.lastClosedBusinessDate < "
+ ":cobBusinessDate or loan.lastClosedBusinessDate is null)";
String FIND_ALL_NON_CLOSED_LOANS_BEHIND_BY_LOAN_IDS = "select loan.id, loan.lastClosedBusinessDate from Loan loan where loan.id IN :loanIds and loan.loanStatus in (100,200,300,303,304) and loan.lastClosedBusinessDate < :cobBusinessDate";
String FIND_ALL_STAYED_LOCKED_BY_LOAN_IDS = "select loan.id, loan.externalId, loan.accountNumber from LoanAccountLock lock left join Loan loan on lock.loanId = loan.id where lock.lockPlacedOnCobBusinessDate = :cobBusinessDate";
String FIND_ALL_STAYED_LOCKED_BY_COB_BUSINESS_DATE = "select loan.id, loan.externalId, loan.accountNumber from LoanAccountLock lock left join Loan loan on lock.loanId = loan.id where lock.lockPlacedOnCobBusinessDate = :cobBusinessDate";
@Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") LocalDate disbursementDate, @Param("groupId") Long groupId,
@@ -189,8 +194,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
@Query(FIND_ID_BY_EXTERNAL_ID)
Long findIdByExternalId(@Param("externalId") ExternalId externalId);
@Query(FIND_ALL_NON_CLOSED_LOANS_BY_LAST_CLOSED_BUSINESS_DATE)
List<Long> findAllNonClosedLoanIdsByLastClosedBusinessDate(@Param("businessDate") LocalDate businessDate);
@Query(FIND_MIN_AND_MAX_NON_CLOSED_LOAN_IDS_BY_LAST_CLOSED_BUSINESS_DATE)
LoanCOBParameter findMinAndMaxNonClosedLoanIdsByLastClosedBusinessDate(@Param("businessDate") LocalDate businessDate);
@Query(FIND_ALL_NON_CLOSED_LOANS_BEHIND_BY_LOAN_IDS)
List<LoanIdAndLastClosedBusinessDate> findAllNonClosedLoansBehindByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate,
@@ -200,10 +205,14 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
List<LoanIdAndLastClosedBusinessDate> findAllNonClosedLoansBehindOrNullByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate,
@Param("loanIds") List<Long> loanIds);
@Query(FIND_ALL_NON_CLOSED_LOANS_BEHIND_OR_NULL_BY_MIN_AND_MAX_LOAN_ID)
List<Long> findAllNonClosedLoansBehindOrNullByMinAndMaxLoanId(@Param("minLoanId") Long minLoanId, @Param("maxLoanId") Long maxLoanId,
@Param("cobBusinessDate") LocalDate cobBusinessDate);
@Query(FIND_OLDEST_COB_PROCESSED_LOAN)
List<LoanIdAndLastClosedBusinessDate> findOldestCOBProcessedLoan(@Param("cobBusinessDate") LocalDate cobBusinessDate);
@Query(FIND_ALL_STAYED_LOCKED_BY_LOAN_IDS)
List<LoanIdAndExternalIdAndAccountNo> findAllStayedLockedByLoanIds(@Param("cobBusinessDate") LocalDate cobBusinessDate);
@Query(FIND_ALL_STAYED_LOCKED_BY_COB_BUSINESS_DATE)
List<LoanIdAndExternalIdAndAccountNo> findAllStayedLockedByCobBusinessDate(@Param("cobBusinessDate") LocalDate cobBusinessDate);
}