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 loanIds, LockOwner lockOwner); List findAllByLoanIdIn(List 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, 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 loanIds = dataParser.parseExecution(command); - lockLoanAccounts(loanIds); execute(loanIds, jobName); return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build(); } + @Override + public void execute(List 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 getLoanAccountLocks(List loanIds) { List loanAccountLocks = new ArrayList<>(); List alreadyLockedLoanIds = new ArrayList<>(); @@ -121,28 +144,6 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService, return loanAccountLocks; } - private void execute(List 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 getJobParametersMap(List 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 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 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 HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE); private static final Function 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,33 +70,48 @@ public class LoanCOBApiFilter extends OncePerRequestFilter { Iterable split = Splitter.on('/').split(request.getPathInfo()); Supplier> 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 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 void executeInlineCob(List loanIds) { + inlineLoanCOBExecutorService.execute(loanIds, JOB_NAME); + } + + private List getGlimChildLoanIds(Long loanIdFromRequest) { + GroupLoanIndividualMonitoringAccount glimAccount = glimAccountInfoRepository.findOneByIsAcceptingChildAndApplicationId(true, + BigDecimal.valueOf(loanIdFromRequest)); + if (glimAccount != null) { + return glimAccount.getChildLoan().stream().map(Loan::getId).toList(); + } else { + return Collections.emptyList(); + } + } + + private boolean isLoanSoftLocked(List loanIds) { + return isLoanLocked(loanIds, false); + } + + private boolean isLoanHardLocked(List loanIds) { + return isLoanLocked(loanIds, true); + } + private boolean isBypassUser() { return context.getAuthenticatedUserIfPresent().isBypassUser(); } - private boolean isLoanLocked(Long loanId, boolean isGlim) { - if (!isGlim) { - return loanAccountLockService.isLoanHardLocked(loanId); - } else { - GroupLoanIndividualMonitoringAccount glimAccount = glimAccountInfoRepository.findOneByIsAcceptingChildAndApplicationId(true, - BigDecimal.valueOf(loanId)); - if (glimAccount != null) { - Set loans = glimAccount.getChildLoan(); - List loanIds = loans.stream().map(Loan::getId).toList(); - return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked); - } else { - return false; - } - } + private boolean isLoanLocked(List loanIds, boolean isHardLock) { + return isHardLock ? loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked) + : loanIds.stream().anyMatch(loanAccountLockService::isLoanSoftLocked); } private void proceed(FilterChain filterChain, HttpServletRequest request, HttpServletResponse 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 { CommandProcessingResult executeInlineJob(JsonCommand command, String jobName); + + void execute(List 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); }