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/11/08 13:46:59 UTC
[fineract] branch develop updated: [FINERACT-1678] Introducing synchronous COB execution for loan account changing APIs
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 3951e69ad [FINERACT-1678] Introducing synchronous COB execution for loan account changing APIs
3951e69ad is described below
commit 3951e69ad7d9474a4a4985bfe15c2f61eb9ed34e
Author: taskain7 <ta...@gmail.com>
AuthorDate: Mon Nov 7 11:09:49 2022 +0100
[FINERACT-1678] Introducing synchronous COB execution for loan account changing APIs
---
.../cob/domain/LoanAccountLockRepository.java | 2 +
.../service/InlineLoanCOBExecutorServiceImpl.java | 49 ++++++++++----------
.../cob/service/LoanAccountLockService.java | 2 +
.../cob/service/LoanAccountLockServiceImpl.java | 14 +++---
.../infrastructure/core/config/SecurityConfig.java | 5 +-
.../jobs/filter/LoanCOBApiFilter.java | 54 ++++++++++++++--------
.../jobs/service/InlineExecutorService.java | 10 +++-
.../jobs/filter/LoanCOBApiFilterTest.java | 8 +++-
8 files changed, 91 insertions(+), 53 deletions(-)
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java
index e25da035a..6ba104ffb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/domain/LoanAccountLockRepository.java
@@ -30,4 +30,6 @@ public interface LoanAccountLockRepository extends JpaRepository<LoanAccountLock
void deleteByLoanIdInAndLockOwner(List<Long> loanIds, LockOwner lockOwner);
List<LoanAccountLock> findAllByLoanIdIn(List<Long> loanIds);
+
+ boolean existsByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
index 2c50ec66a..cb56e4664 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java
@@ -66,7 +66,7 @@ import org.springframework.transaction.support.TransactionTemplate;
@Service
@Slf4j
@RequiredArgsConstructor
-public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService, InitializingBean {
+public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<Long>, InitializingBean {
private static final String JOB_EXECUTION_FAILED_MESSAGE = "Job execution failed for job with name: ";
@@ -91,11 +91,34 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService,
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public CommandProcessingResult executeInlineJob(JsonCommand command, String jobName) throws LoanAccountLockCannotBeOverruledException {
List<Long> loanIds = dataParser.parseExecution(command);
- lockLoanAccounts(loanIds);
execute(loanIds, jobName);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build();
}
+ @Override
+ public void execute(List<Long> loanIds, String jobName) {
+ lockLoanAccounts(loanIds);
+ Job inlineLoanCOBJob;
+ try {
+ inlineLoanCOBJob = jobLocator.getJob(jobName);
+ } catch (NoSuchJobException e) {
+ throw new JobNotFoundException(jobName, e);
+ }
+ JobParameters jobParameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(inlineLoanCOBJob)
+ .addJobParameters(new JobParameters(getJobParametersMap(loanIds))).toJobParameters();
+ JobExecution jobExecution;
+ try {
+ jobExecution = jobLauncher.run(inlineLoanCOBJob, jobParameters);
+ } catch (Exception e) {
+ log.error("{}{}", JOB_EXECUTION_FAILED_MESSAGE, jobName, e);
+ throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", JOB_EXECUTION_FAILED_MESSAGE, jobName, e);
+ }
+ if (!BatchStatus.COMPLETED.equals(jobExecution.getStatus())) {
+ log.error("{}{}", JOB_EXECUTION_FAILED_MESSAGE, jobName);
+ throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", JOB_EXECUTION_FAILED_MESSAGE, jobName);
+ }
+ }
+
private List<LoanAccountLock> getLoanAccountLocks(List<Long> loanIds) {
List<LoanAccountLock> loanAccountLocks = new ArrayList<>();
List<Long> alreadyLockedLoanIds = new ArrayList<>();
@@ -121,28 +144,6 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService,
return loanAccountLocks;
}
- private void execute(List<Long> loanIds, String jobName) {
- Job inlineLoanCOBJob;
- try {
- inlineLoanCOBJob = jobLocator.getJob(jobName);
- } catch (NoSuchJobException e) {
- throw new JobNotFoundException(jobName, e);
- }
- JobParameters jobParameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(inlineLoanCOBJob)
- .addJobParameters(new JobParameters(getJobParametersMap(loanIds))).toJobParameters();
- JobExecution jobExecution;
- try {
- jobExecution = jobLauncher.run(inlineLoanCOBJob, jobParameters);
- } catch (Exception e) {
- log.error("{}{}", JOB_EXECUTION_FAILED_MESSAGE, jobName, e);
- throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", JOB_EXECUTION_FAILED_MESSAGE, jobName, e);
- }
- if (!BatchStatus.COMPLETED.equals(jobExecution.getStatus())) {
- log.error("{}{}", JOB_EXECUTION_FAILED_MESSAGE, jobName);
- throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", JOB_EXECUTION_FAILED_MESSAGE, jobName);
- }
- }
-
private Map<String, JobParameter> getJobParametersMap(List<Long> loanIds) {
// TODO: refactor for a more generic solution
String parameterJson = gson.toJson(loanIds);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java
index 7f584d6ba..5fd6eaeee 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockService.java
@@ -26,4 +26,6 @@ public interface LoanAccountLockService {
List<LoanAccountLock> getLockedLoanAccountByPage(int page, int limit);
boolean isLoanHardLocked(Long loanId);
+
+ boolean isLoanSoftLocked(Long loanId);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java
index 4eeb31570..29423638b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/LoanAccountLockServiceImpl.java
@@ -19,9 +19,7 @@
package org.apache.fineract.cob.service;
import java.util.List;
-import java.util.Optional;
import lombok.RequiredArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
import org.apache.fineract.cob.domain.LockOwner;
@@ -45,10 +43,12 @@ public class LoanAccountLockServiceImpl implements LoanAccountLockService {
@Override
public boolean isLoanHardLocked(Long loanId) {
- Optional<LoanAccountLock> loanAccountLockOptional = loanAccountLockRepository.findById(loanId);
- return loanAccountLockOptional //
- .filter(loanAccountLock -> LockOwner.LOAN_COB_CHUNK_PROCESSING.equals(loanAccountLock.getLockOwner()) //
- || LockOwner.LOAN_INLINE_COB_PROCESSING.equals(loanAccountLock.getLockOwner())) //
- .filter(loanAccountLock -> StringUtils.isBlank(loanAccountLock.getError())).isPresent();
+ return loanAccountLockRepository.existsByLoanIdAndLockOwner(loanId, LockOwner.LOAN_COB_CHUNK_PROCESSING) //
+ || loanAccountLockRepository.existsByLoanIdAndLockOwner(loanId, LockOwner.LOAN_INLINE_COB_PROCESSING);
+ }
+
+ @Override
+ public boolean isLoanSoftLocked(Long loanId) {
+ return loanAccountLockRepository.existsByLoanIdAndLockOwner(loanId, LockOwner.LOAN_COB_PARTITIONING);
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
index 3901e092f..05b59f2bf 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/SecurityConfig.java
@@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.core.config;
import org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter;
import org.apache.fineract.infrastructure.jobs.filter.LoanCOBApiFilter;
+import org.apache.fineract.infrastructure.security.filter.InsecureTwoFactorAuthenticationFilter;
import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter;
import org.apache.fineract.infrastructure.security.filter.TwoFactorAuthenticationFilter;
import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService;
@@ -89,8 +90,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.and() //
.addFilterAfter(fineractInstanceModeApiFilter, SecurityContextPersistenceFilter.class) //
.addFilterAfter(tenantAwareBasicAuthenticationFilter(), FineractInstanceModeApiFilter.class) //
- .addFilterAfter(loanCOBApiFilter, TenantAwareBasicAuthenticationFilter.class) //
- .addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class); //
+ .addFilterAfter(twoFactorAuthenticationFilter, BasicAuthenticationFilter.class) //
+ .addFilterAfter(loanCOBApiFilter, InsecureTwoFactorAuthenticationFilter.class);
if (serverProperties.getSsl().isEnabled()) {
http.requiresChannel(channel -> channel.antMatchers("/api/**").requiresSecure());
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
index 08e33e286..c717fc083 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilter.java
@@ -21,8 +21,8 @@ package org.apache.fineract.infrastructure.jobs.filter;
import com.google.common.base.Splitter;
import java.io.IOException;
import java.math.BigDecimal;
+import java.util.Collections;
import java.util.List;
-import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
import org.apache.fineract.cob.service.LoanAccountLockService;
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -51,12 +52,14 @@ public class LoanCOBApiFilter extends OncePerRequestFilter {
private final GLIMAccountInfoRepository glimAccountInfoRepository;
private final LoanAccountLockService loanAccountLockService;
private final PlatformSecurityContext context;
+ private final InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService;
private static final List<HttpMethod> HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE);
private static final Function<String, Boolean> URL_FUNCTION = s -> s.matches("/loans/\\d+.*") || s.matches("/loans/glimAccount/\\d+.*");
private static final Integer LOAN_ID_INDEX_IN_URL = 2;
private static final Integer GLIM_ID_INDEX_IN_URL = 3;
private static final Integer GLIM_STRING_INDEX_IN_URL = 2;
+ private static final String JOB_NAME = "INLINE_LOAN_COB";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
@@ -67,35 +70,50 @@ public class LoanCOBApiFilter extends OncePerRequestFilter {
Iterable<String> split = Splitter.on('/').split(request.getPathInfo());
Supplier<Stream<String>> streamSupplier = () -> StreamSupport.stream(split.spliterator(), false);
boolean isGlim = isGlim(streamSupplier);
- Long loanId = getLoanId(isGlim, streamSupplier);
- if (isLoanLocked(loanId, isGlim)) {
- reject(loanId, response);
+ Long loanIdFromRequest = getLoanId(isGlim, streamSupplier);
+ List<Long> loanIds = isGlim ? getGlimChildLoanIds(loanIdFromRequest) : Collections.singletonList(loanIdFromRequest);
+ if (isLoanHardLocked(loanIds)) {
+ reject(loanIdFromRequest, response);
+ } else if (isLoanSoftLocked(loanIds)) {
+ executeInlineCob(loanIds);
+ proceed(filterChain, request, response);
} else {
proceed(filterChain, request, response);
}
}
}
- private boolean isBypassUser() {
- return context.getAuthenticatedUserIfPresent().isBypassUser();
+ private void executeInlineCob(List<Long> loanIds) {
+ inlineLoanCOBExecutorService.execute(loanIds, JOB_NAME);
}
- private boolean isLoanLocked(Long loanId, boolean isGlim) {
- if (!isGlim) {
- return loanAccountLockService.isLoanHardLocked(loanId);
+ private List<Long> getGlimChildLoanIds(Long loanIdFromRequest) {
+ GroupLoanIndividualMonitoringAccount glimAccount = glimAccountInfoRepository.findOneByIsAcceptingChildAndApplicationId(true,
+ BigDecimal.valueOf(loanIdFromRequest));
+ if (glimAccount != null) {
+ return glimAccount.getChildLoan().stream().map(Loan::getId).toList();
} else {
- GroupLoanIndividualMonitoringAccount glimAccount = glimAccountInfoRepository.findOneByIsAcceptingChildAndApplicationId(true,
- BigDecimal.valueOf(loanId));
- if (glimAccount != null) {
- Set<Loan> loans = glimAccount.getChildLoan();
- List<Long> loanIds = loans.stream().map(Loan::getId).toList();
- return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked);
- } else {
- return false;
- }
+ return Collections.emptyList();
}
}
+ private boolean isLoanSoftLocked(List<Long> loanIds) {
+ return isLoanLocked(loanIds, false);
+ }
+
+ private boolean isLoanHardLocked(List<Long> loanIds) {
+ return isLoanLocked(loanIds, true);
+ }
+
+ private boolean isBypassUser() {
+ return context.getAuthenticatedUserIfPresent().isBypassUser();
+ }
+
+ private boolean isLoanLocked(List<Long> loanIds, boolean isHardLock) {
+ return isHardLock ? loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked)
+ : loanIds.stream().anyMatch(loanAccountLockService::isLoanSoftLocked);
+ }
+
private void proceed(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
filterChain.doFilter(request, response);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineExecutorService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineExecutorService.java
index 2b96a1b3a..ced0ae577 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineExecutorService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineExecutorService.java
@@ -18,10 +18,18 @@
*/
package org.apache.fineract.infrastructure.jobs.service;
+import java.util.Collections;
+import java.util.List;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-public interface InlineExecutorService {
+public interface InlineExecutorService<T> {
CommandProcessingResult executeInlineJob(JsonCommand command, String jobName);
+
+ void execute(List<T> elements, String jobName);
+
+ default void execute(T element, String jobName) {
+ execute(Collections.singletonList(element), jobName);
+ }
}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java
index 216c670a4..7176dae4c 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/jobs/filter/LoanCOBApiFilterTest.java
@@ -30,6 +30,7 @@ import java.math.BigDecimal;
import java.util.Collections;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
+import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
import org.apache.fineract.cob.service.LoanAccountLockService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
@@ -59,6 +60,8 @@ class LoanCOBApiFilterTest {
private GLIMAccountInfoRepository glimAccountInfoRepository;
@Mock
private PlatformSecurityContext context;
+ @Mock
+ private InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService;
@Test
void shouldProceedWhenUrlDoesNotMatch() throws ServletException, IOException {
@@ -99,6 +102,7 @@ class LoanCOBApiFilterTest {
given(request.getPathInfo()).willReturn("/loans/2/charges");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
+ given(loanAccountLockService.isLoanSoftLocked(2L)).willReturn(false);
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
testObj.doFilterInternal(request, response, filterChain);
@@ -106,7 +110,7 @@ class LoanCOBApiFilterTest {
}
@Test
- void shouldProceedWhenLoanIsSoftLocked() throws ServletException, IOException {
+ void shouldRunInlineCOBAndProceedWhenLoanIsSoftLocked() throws ServletException, IOException {
MockHttpServletRequest request = mock(MockHttpServletRequest.class);
MockHttpServletResponse response = mock(MockHttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
@@ -115,9 +119,11 @@ class LoanCOBApiFilterTest {
given(request.getPathInfo()).willReturn("/loans/2/charges");
given(request.getMethod()).willReturn(HTTPMethods.POST.value());
given(loanAccountLockService.isLoanHardLocked(2L)).willReturn(false);
+ given(loanAccountLockService.isLoanSoftLocked(2L)).willReturn(true);
given(context.getAuthenticatedUserIfPresent()).willReturn(appUser);
testObj.doFilterInternal(request, response, filterChain);
+ verify(inlineLoanCOBExecutorService, times(1)).execute(Collections.singletonList(2L), "INLINE_LOAN_COB");
verify(filterChain, times(1)).doFilter(request, response);
}