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);
     }