FINERACT-1724: Further fixes
This commit is contained in:
committed by
Arnold Gálovics
parent
a7d5f834d5
commit
aa249cc16f
-1
@@ -26,7 +26,6 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
|||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import java.util.List;
|
|
||||||
import jakarta.ws.rs.Consumes;
|
import jakarta.ws.rs.Consumes;
|
||||||
import jakarta.ws.rs.GET;
|
import jakarta.ws.rs.GET;
|
||||||
import jakarta.ws.rs.POST;
|
import jakarta.ws.rs.POST;
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ import org.springframework.security.web.SecurityFilterChain;
|
|||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||||
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration
|
||||||
@ConditionalOnProperty("fineract.security.oauth.enabled")
|
@ConditionalOnProperty("fineract.security.oauth.enabled")
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
public class OAuth2SecurityConfig {
|
public class OAuth2SecurityConfig {
|
||||||
|
|||||||
+10
-22
@@ -23,8 +23,6 @@ import static org.springframework.security.authorization.AuthenticatedAuthorizat
|
|||||||
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
|
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
|
||||||
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
|
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
|
||||||
|
|
||||||
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
|
|
||||||
import org.apache.fineract.cob.service.LoanAccountLockService;
|
|
||||||
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||||
import org.apache.fineract.commands.service.CommandSourceService;
|
import org.apache.fineract.commands.service.CommandSourceService;
|
||||||
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
|
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
|
||||||
@@ -33,12 +31,14 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
|
|||||||
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
|
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
|
||||||
import org.apache.fineract.infrastructure.core.filters.CorrelationHeaderFilter;
|
import org.apache.fineract.infrastructure.core.filters.CorrelationHeaderFilter;
|
||||||
import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreFilter;
|
import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreFilter;
|
||||||
|
import org.apache.fineract.infrastructure.core.filters.IdempotencyStoreHelper;
|
||||||
import org.apache.fineract.infrastructure.core.filters.RequestResponseFilter;
|
import org.apache.fineract.infrastructure.core.filters.RequestResponseFilter;
|
||||||
import org.apache.fineract.infrastructure.core.filters.ResponseCorsFilter;
|
import org.apache.fineract.infrastructure.core.filters.ResponseCorsFilter;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||||
import org.apache.fineract.infrastructure.core.service.MDCWrapper;
|
import org.apache.fineract.infrastructure.core.service.MDCWrapper;
|
||||||
import org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter;
|
import org.apache.fineract.infrastructure.instancemode.filter.FineractInstanceModeApiFilter;
|
||||||
import org.apache.fineract.infrastructure.jobs.filter.LoanCOBApiFilter;
|
import org.apache.fineract.infrastructure.jobs.filter.LoanCOBApiFilter;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.filter.LoanCOBFilterHelper;
|
||||||
import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
|
import org.apache.fineract.infrastructure.security.data.PlatformRequestLog;
|
||||||
import org.apache.fineract.infrastructure.security.filter.InsecureTwoFactorAuthenticationFilter;
|
import org.apache.fineract.infrastructure.security.filter.InsecureTwoFactorAuthenticationFilter;
|
||||||
import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter;
|
import org.apache.fineract.infrastructure.security.filter.TenantAwareBasicAuthenticationFilter;
|
||||||
@@ -48,9 +48,6 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte
|
|||||||
import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService;
|
import org.apache.fineract.infrastructure.security.service.TenantAwareJpaPlatformUserDetailsService;
|
||||||
import org.apache.fineract.infrastructure.security.service.TwoFactorService;
|
import org.apache.fineract.infrastructure.security.service.TwoFactorService;
|
||||||
import org.apache.fineract.notification.service.UserNotificationService;
|
import org.apache.fineract.notification.service.UserNotificationService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||||
@@ -69,10 +66,10 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration
|
||||||
@ConditionalOnProperty("fineract.security.basicauth.enabled")
|
@ConditionalOnProperty("fineract.security.basicauth.enabled")
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
@@ -110,19 +107,11 @@ public class SecurityConfig {
|
|||||||
private FineractRequestContextHolder fineractRequestContextHolder;
|
private FineractRequestContextHolder fineractRequestContextHolder;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private GLIMAccountInfoRepository glimAccountInfoRepository;
|
private LoanCOBFilterHelper loanCOBFilterHelper;
|
||||||
@Autowired
|
|
||||||
private LoanAccountLockService loanAccountLockService;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PlatformSecurityContext context;
|
private PlatformSecurityContext context;
|
||||||
@Autowired
|
@Autowired
|
||||||
private InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService;
|
private IdempotencyStoreHelper idempotencyStoreHelper;
|
||||||
@Autowired
|
|
||||||
private LoanRepository loanRepository;
|
|
||||||
@Autowired
|
|
||||||
private LoanRescheduleRequestRepository loanRescheduleRequestRepository;
|
|
||||||
@Autowired
|
|
||||||
private PlatformTransactionManager transactionManager;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception {
|
||||||
@@ -152,14 +141,13 @@ public class SecurityConfig {
|
|||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http //
|
http //
|
||||||
.csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients
|
.csrf((csrf) -> csrf.disable()) // NOSONAR only creating a service that is used by non-browser clients
|
||||||
.securityContext((securityContext) -> securityContext.requireExplicitSave(false))
|
|
||||||
.sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //
|
.sessionManagement((smc) -> smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //
|
||||||
|
.addFilterBefore(tenantAwareBasicAuthenticationFilter(), SecurityContextHolderFilter.class)
|
||||||
.addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class)
|
.addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class)
|
||||||
.addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class)
|
.addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class)
|
||||||
.addFilterAfter(responseCorsFilter(), CorrelationHeaderFilter.class) //
|
.addFilterAfter(responseCorsFilter(), CorrelationHeaderFilter.class) //
|
||||||
.addFilterAfter(fineractInstanceModeApiFilter(), ResponseCorsFilter.class) //
|
.addFilterAfter(fineractInstanceModeApiFilter(), ResponseCorsFilter.class) //
|
||||||
.addFilterAfter(tenantAwareBasicAuthenticationFilter(), FineractInstanceModeApiFilter.class) //
|
.addFilterAfter(loanCOBApiFilter(), FineractInstanceModeApiFilter.class) //
|
||||||
.addFilterAfter(loanCOBApiFilter(), TenantAwareBasicAuthenticationFilter.class) //
|
|
||||||
.addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); //
|
.addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); //
|
||||||
|
|
||||||
if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
|
if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
|
||||||
@@ -176,7 +164,7 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public LoanCOBApiFilter loanCOBApiFilter() {
|
public LoanCOBApiFilter loanCOBApiFilter() {
|
||||||
return new LoanCOBApiFilter(glimAccountInfoRepository, loanAccountLockService, context, inlineLoanCOBExecutorService, loanRepository, fineractProperties, loanRescheduleRequestRepository, transactionManager);
|
return new LoanCOBApiFilter(loanCOBFilterHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() {
|
public TwoFactorAuthenticationFilter twoFactorAuthenticationFilter() {
|
||||||
@@ -193,7 +181,7 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IdempotencyStoreFilter idempotencyStoreFilter() {
|
public IdempotencyStoreFilter idempotencyStoreFilter() {
|
||||||
return new IdempotencyStoreFilter(commandSourceRepository, commandSourceService, fineractProperties, fineractRequestContextHolder);
|
return new IdempotencyStoreFilter(fineractRequestContextHolder, idempotencyStoreHelper, fineractProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CorrelationHeaderFilter correlationHeaderFilter() {
|
public CorrelationHeaderFilter correlationHeaderFilter() {
|
||||||
|
|||||||
+9
-1
@@ -22,9 +22,11 @@ package org.apache.fineract.infrastructure.core.config;
|
|||||||
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
|
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.DependsOn;
|
||||||
import org.springframework.context.event.SimpleApplicationEventMulticaster;
|
import org.springframework.context.event.SimpleApplicationEventMulticaster;
|
||||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SpringConfig {
|
public class SpringConfig {
|
||||||
@@ -39,11 +41,17 @@ public class SpringConfig {
|
|||||||
// The application events (for importing) rely on the inheritable thread local security context strategy
|
// The application events (for importing) rely on the inheritable thread local security context strategy
|
||||||
// This is NOT compatible with threadpools so if we use threadpools the below will need to be reworked
|
// This is NOT compatible with threadpools so if we use threadpools the below will need to be reworked
|
||||||
@Bean
|
@Bean
|
||||||
public MethodInvokingFactoryBean methodInvokingFactoryBean() {
|
public MethodInvokingFactoryBean overrideSecurityContextHolderStrategy() {
|
||||||
MethodInvokingFactoryBean mifb = new MethodInvokingFactoryBean();
|
MethodInvokingFactoryBean mifb = new MethodInvokingFactoryBean();
|
||||||
mifb.setTargetClass(SecurityContextHolder.class);
|
mifb.setTargetClass(SecurityContextHolder.class);
|
||||||
mifb.setTargetMethod("setStrategyName");
|
mifb.setTargetMethod("setStrategyName");
|
||||||
mifb.setArguments("MODE_INHERITABLETHREADLOCAL");
|
mifb.setArguments("MODE_INHERITABLETHREADLOCAL");
|
||||||
return mifb;
|
return mifb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@DependsOn("overrideSecurityContextHolderStrategy")
|
||||||
|
public SecurityContextHolderStrategy securityContextHolderStrategy() {
|
||||||
|
return SecurityContextHolder.getContextHolderStrategy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.core.filters;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.batch.domain.BatchRequest;
|
||||||
|
import org.apache.fineract.batch.domain.BatchResponse;
|
||||||
|
import org.apache.fineract.batch.domain.Header;
|
||||||
|
import org.apache.fineract.commands.service.SynchronousCommandProcessingService;
|
||||||
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class IdempotencyStoreBatchFilter implements BatchFilter {
|
||||||
|
|
||||||
|
private final FineractRequestContextHolder fineractRequestContextHolder;
|
||||||
|
private final IdempotencyStoreHelper helper;
|
||||||
|
private final FineractProperties fineractProperties;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BatchResponse doFilter(BatchRequest batchRequest, UriInfo uriInfo, BatchFilterChain chain) {
|
||||||
|
extractIdempotentKeyFromBatchRequest(batchRequest).ifPresent(idempotentKey -> fineractRequestContextHolder
|
||||||
|
.setAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_ATTRIBUTE, idempotentKey));
|
||||||
|
BatchResponse result = chain.serviceCall(batchRequest, uriInfo);
|
||||||
|
Optional<Long> commandId = helper.getCommandId(null);
|
||||||
|
boolean isSuccessWithoutStored = helper.isStoreIdempotencyKey(null) && commandId.isPresent();
|
||||||
|
if (isSuccessWithoutStored) {
|
||||||
|
helper.storeCommandResult(true, result.getStatusCode(), result.getBody(), commandId);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<String> extractIdempotentKeyFromBatchRequest(BatchRequest request) {
|
||||||
|
if (request.getHeaders() == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return request.getHeaders() //
|
||||||
|
.stream().filter(header -> header.getName().equals(fineractProperties.getIdempotencyKeyHeaderName())) //
|
||||||
|
.map(Header::getValue) //
|
||||||
|
.findAny(); //
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-75
@@ -22,7 +22,6 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.ws.rs.core.UriInfo;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -30,11 +29,6 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.mutable.Mutable;
|
import org.apache.commons.lang3.mutable.Mutable;
|
||||||
import org.apache.commons.lang3.mutable.MutableObject;
|
import org.apache.commons.lang3.mutable.MutableObject;
|
||||||
import org.apache.fineract.batch.domain.BatchRequest;
|
|
||||||
import org.apache.fineract.batch.domain.BatchResponse;
|
|
||||||
import org.apache.fineract.batch.domain.Header;
|
|
||||||
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
|
||||||
import org.apache.fineract.commands.service.CommandSourceService;
|
|
||||||
import org.apache.fineract.commands.service.SynchronousCommandProcessingService;
|
import org.apache.fineract.commands.service.SynchronousCommandProcessingService;
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
|
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
|
||||||
@@ -44,31 +38,28 @@ import org.springframework.web.util.ContentCachingResponseWrapper;
|
|||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class IdempotencyStoreFilter extends OncePerRequestFilter implements BatchFilter {
|
public class IdempotencyStoreFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final CommandSourceRepository commandSourceRepository;
|
|
||||||
private final CommandSourceService commandSourceService;
|
|
||||||
|
|
||||||
private final FineractProperties fineractProperties;
|
|
||||||
|
|
||||||
private final FineractRequestContextHolder fineractRequestContextHolder;
|
private final FineractRequestContextHolder fineractRequestContextHolder;
|
||||||
|
private final IdempotencyStoreHelper helper;
|
||||||
|
private final FineractProperties fineractProperties;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
|
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,
|
||||||
@NotNull FilterChain filterChain) throws ServletException, IOException {
|
@NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||||
Mutable<ContentCachingResponseWrapper> wrapper = new MutableObject<>();
|
Mutable<ContentCachingResponseWrapper> wrapper = new MutableObject<>();
|
||||||
if (isAllowedContentTypeRequest(request)) {
|
if (helper.isAllowedContentTypeRequest(request)) {
|
||||||
wrapper.setValue(new ContentCachingResponseWrapper(response));
|
wrapper.setValue(new ContentCachingResponseWrapper(response));
|
||||||
}
|
}
|
||||||
extractIdempotentKeyFromHttpServletRequest(request).ifPresent(idempotentKey -> fineractRequestContextHolder
|
extractIdempotentKeyFromHttpServletRequest(request).ifPresent(idempotentKey -> fineractRequestContextHolder
|
||||||
.setAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_ATTRIBUTE, idempotentKey, request));
|
.setAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_ATTRIBUTE, idempotentKey, request));
|
||||||
|
|
||||||
filterChain.doFilter(request, wrapper.getValue() != null ? wrapper.getValue() : response);
|
filterChain.doFilter(request, wrapper.getValue() != null ? wrapper.getValue() : response);
|
||||||
Optional<Long> commandId = getCommandId(request);
|
Optional<Long> commandId = helper.getCommandId(request);
|
||||||
boolean isSuccessWithoutStored = isStoreIdempotencyKey(request) && commandId.isPresent() && isAllowedContentTypeResponse(response)
|
boolean isSuccessWithoutStored = helper.isStoreIdempotencyKey(request) && commandId.isPresent() && helper.isAllowedContentTypeResponse(response)
|
||||||
&& wrapper.getValue() != null;
|
&& wrapper.getValue() != null;
|
||||||
if (isSuccessWithoutStored) {
|
if (isSuccessWithoutStored) {
|
||||||
storeCommandResult(false, response.getStatus(), Optional.ofNullable(wrapper.getValue())
|
helper.storeCommandResult(false, response.getStatus(), Optional.ofNullable(wrapper.getValue())
|
||||||
.map(ContentCachingResponseWrapper::getContentAsByteArray).map(s -> new String(s, StandardCharsets.UTF_8)).orElse(null),
|
.map(ContentCachingResponseWrapper::getContentAsByteArray).map(s -> new String(s, StandardCharsets.UTF_8)).orElse(null),
|
||||||
commandId);
|
commandId);
|
||||||
}
|
}
|
||||||
@@ -77,66 +68,7 @@ public class IdempotencyStoreFilter extends OncePerRequestFilter implements Batc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeCommandResult(boolean batch, int response, String body, Optional<Long> commandId) {
|
|
||||||
commandSourceRepository.findById(commandId.get()).ifPresent(commandSource -> {
|
|
||||||
commandSource.setResultStatusCode(response);
|
|
||||||
commandSource.setResult(body);
|
|
||||||
if (batch) {
|
|
||||||
commandSourceService.saveResultNoTransaction(commandSource);
|
|
||||||
} else {
|
|
||||||
commandSourceService.saveResult(commandSource);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<String> extractIdempotentKeyFromHttpServletRequest(HttpServletRequest request) {
|
private Optional<String> extractIdempotentKeyFromHttpServletRequest(HttpServletRequest request) {
|
||||||
return Optional.ofNullable(request.getHeader(fineractProperties.getIdempotencyKeyHeaderName()));
|
return Optional.ofNullable(request.getHeader(fineractProperties.getIdempotencyKeyHeaderName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAllowedContentTypeResponse(HttpServletResponse response) {
|
|
||||||
return Optional.ofNullable(response.getContentType()).map(String::toLowerCase).map(ct -> ct.contains("application/json"))
|
|
||||||
.orElse(false) || (response.getStatus() > 200 && response.getStatus() < 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isAllowedContentTypeRequest(HttpServletRequest request) {
|
|
||||||
return Optional.ofNullable(request.getContentType()).map(String::toLowerCase).map(ct -> ct.contains("application/json"))
|
|
||||||
.orElse(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isStoreIdempotencyKey(HttpServletRequest request) {
|
|
||||||
return Optional
|
|
||||||
.ofNullable(
|
|
||||||
fineractRequestContextHolder.getAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_STORE_FLAG, request))
|
|
||||||
.filter(Boolean.class::isInstance).map(Boolean.class::cast).orElse(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Long> getCommandId(HttpServletRequest request) {
|
|
||||||
return Optional
|
|
||||||
.ofNullable(fineractRequestContextHolder.getAttribute(SynchronousCommandProcessingService.COMMAND_SOURCE_ID, request))
|
|
||||||
.filter(Long.class::isInstance).map(Long.class::cast);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<String> extractIdempotentKeyFromBatchRequest(BatchRequest request) {
|
|
||||||
if (request.getHeaders() == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return request.getHeaders() //
|
|
||||||
.stream().filter(header -> header.getName().equals(fineractProperties.getIdempotencyKeyHeaderName())) //
|
|
||||||
.map(Header::getValue) //
|
|
||||||
.findAny(); //
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BatchResponse doFilter(BatchRequest batchRequest, UriInfo uriInfo, BatchFilterChain chain) {
|
|
||||||
extractIdempotentKeyFromBatchRequest(batchRequest).ifPresent(idempotentKey -> fineractRequestContextHolder
|
|
||||||
.setAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_ATTRIBUTE, idempotentKey));
|
|
||||||
BatchResponse result = chain.serviceCall(batchRequest, uriInfo);
|
|
||||||
Optional<Long> commandId = getCommandId(null);
|
|
||||||
boolean isSuccessWithoutStored = isStoreIdempotencyKey(null) && commandId.isPresent();
|
|
||||||
if (isSuccessWithoutStored) {
|
|
||||||
storeCommandResult(true, result.getStatusCode(), result.getBody(), commandId);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.core.filters;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Optional;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.commands.domain.CommandSourceRepository;
|
||||||
|
import org.apache.fineract.commands.service.CommandSourceService;
|
||||||
|
import org.apache.fineract.commands.service.SynchronousCommandProcessingService;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.FineractRequestContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class IdempotencyStoreHelper {
|
||||||
|
|
||||||
|
private final CommandSourceRepository commandSourceRepository;
|
||||||
|
private final CommandSourceService commandSourceService;
|
||||||
|
private final FineractRequestContextHolder fineractRequestContextHolder;
|
||||||
|
|
||||||
|
public void storeCommandResult(boolean batch, int response, String body, Optional<Long> commandId) {
|
||||||
|
commandSourceRepository.findById(commandId.get()).ifPresent(commandSource -> {
|
||||||
|
commandSource.setResultStatusCode(response);
|
||||||
|
commandSource.setResult(body);
|
||||||
|
if (batch) {
|
||||||
|
commandSourceService.saveResultNoTransaction(commandSource);
|
||||||
|
} else {
|
||||||
|
commandSourceService.saveResult(commandSource);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowedContentTypeResponse(HttpServletResponse response) {
|
||||||
|
return Optional.ofNullable(response.getContentType()).map(String::toLowerCase).map(ct -> ct.contains("application/json"))
|
||||||
|
.orElse(false) || (response.getStatus() > 200 && response.getStatus() < 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowedContentTypeRequest(HttpServletRequest request) {
|
||||||
|
return Optional.ofNullable(request.getContentType()).map(String::toLowerCase).map(ct -> ct.contains("application/json"))
|
||||||
|
.orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStoreIdempotencyKey(HttpServletRequest request) {
|
||||||
|
return Optional
|
||||||
|
.ofNullable(
|
||||||
|
fineractRequestContextHolder.getAttribute(SynchronousCommandProcessingService.IDEMPOTENCY_KEY_STORE_FLAG, request))
|
||||||
|
.filter(Boolean.class::isInstance).map(Boolean.class::cast).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Long> getCommandId(HttpServletRequest request) {
|
||||||
|
return Optional
|
||||||
|
.ofNullable(fineractRequestContextHolder.getAttribute(SynchronousCommandProcessingService.COMMAND_SOURCE_ID, request))
|
||||||
|
.filter(Long.class::isInstance).map(Long.class::cast);
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -160,6 +160,17 @@ public class DatabaseSpecificSQLGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String castBigInt(String sql) {
|
||||||
|
if (databaseTypeResolver.isMySQL()) {
|
||||||
|
return format("CAST(%s AS BIGINT)", sql);
|
||||||
|
} else if (databaseTypeResolver.isPostgreSQL()) {
|
||||||
|
return format("%s::BIGINT", sql);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Database type is not supported for casting to bigint " + databaseTypeResolver.databaseType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String currentSchema() {
|
public String currentSchema() {
|
||||||
if (databaseTypeResolver.isMySQL()) {
|
if (databaseTypeResolver.isMySQL()) {
|
||||||
return "SCHEMA()";
|
return "SCHEMA()";
|
||||||
|
|||||||
+3
-1
@@ -30,6 +30,7 @@ import java.util.Map;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
|
import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
|
||||||
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
|
import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
|
||||||
import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
|
import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
@@ -43,6 +44,7 @@ public class JobExecutionRepository implements InitializingBean {
|
|||||||
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
|
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
|
||||||
private final FineractProperties fineractProperties;
|
private final FineractProperties fineractProperties;
|
||||||
private final DatabaseTypeResolver databaseTypeResolver;
|
private final DatabaseTypeResolver databaseTypeResolver;
|
||||||
|
private final DatabaseSpecificSQLGenerator sqlGenerator;
|
||||||
private final GoogleGsonSerializerHelper gsonFactory;
|
private final GoogleGsonSerializerHelper gsonFactory;
|
||||||
private Gson gson;
|
private Gson gson;
|
||||||
|
|
||||||
@@ -169,7 +171,7 @@ public class JobExecutionRepository implements InitializingBean {
|
|||||||
String jsonString = gson.toJson(new JobParameterDTO(parameterKeyName, parameterValue));
|
String jsonString = gson.toJson(new JobParameterDTO(parameterKeyName, parameterValue));
|
||||||
sqlStatementBuilder.append(
|
sqlStatementBuilder.append(
|
||||||
"SELECT bje.JOB_EXECUTION_ID FROM BATCH_JOB_INSTANCE bji INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID INNER JOIN BATCH_JOB_EXECUTION_PARAMS bjep ON bje.JOB_EXECUTION_ID = bjep.JOB_EXECUTION_ID"
|
"SELECT bje.JOB_EXECUTION_ID FROM BATCH_JOB_INSTANCE bji INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID INNER JOIN BATCH_JOB_EXECUTION_PARAMS bjep ON bje.JOB_EXECUTION_ID = bjep.JOB_EXECUTION_ID"
|
||||||
+ " WHERE bje.STATUS IN (:statuses) AND bji.JOB_NAME = :jobName AND bjep.KEY_NAME = :jobCustomParamKeyName AND bjep.LONG_VAL IN ("
|
+ " WHERE bje.STATUS IN (:statuses) AND bji.JOB_NAME = :jobName AND bjep.PARAMETER_NAME = :jobCustomParamKeyName AND " + sqlGenerator.castBigInt("bjep.PARAMETER_VALUE") + " IN ("
|
||||||
+ getSubQueryForCustomJobParameters()
|
+ getSubQueryForCustomJobParameters()
|
||||||
+ ") AND bje.JOB_INSTANCE_ID NOT IN (SELECT bje.JOB_INSTANCE_ID FROM BATCH_JOB_INSTANCE bji INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID"
|
+ ") AND bje.JOB_INSTANCE_ID NOT IN (SELECT bje.JOB_INSTANCE_ID FROM BATCH_JOB_INSTANCE bji INNER JOIN BATCH_JOB_EXECUTION bje ON bji.JOB_INSTANCE_ID = bje.JOB_INSTANCE_ID"
|
||||||
+ " WHERE bje.STATUS = :completedStatus AND bji.JOB_NAME = :jobName)");
|
+ " WHERE bje.STATUS = :completedStatus AND bji.JOB_NAME = :jobName)");
|
||||||
|
|||||||
+7
-179
@@ -18,73 +18,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.jobs.filter;
|
package org.apache.fineract.infrastructure.jobs.filter;
|
||||||
|
|
||||||
import static org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeUrlVersioned;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import io.github.resilience4j.core.functions.Either;
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.fineract.batch.domain.BatchRequest;
|
|
||||||
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
|
|
||||||
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
|
|
||||||
import org.apache.fineract.cob.service.LoanAccountLockService;
|
|
||||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
|
||||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
|
||||||
import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
|
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
|
||||||
import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
|
import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
|
||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
|
|
||||||
import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException;
|
import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
|
||||||
import org.springframework.transaction.TransactionDefinition;
|
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchRequestPreprocessor {
|
public class LoanCOBApiFilter extends OncePerRequestFilter {
|
||||||
|
private final LoanCOBFilterHelper helper;
|
||||||
private final GLIMAccountInfoRepository glimAccountInfoRepository;
|
|
||||||
private final LoanAccountLockService loanAccountLockService;
|
|
||||||
private final PlatformSecurityContext context;
|
|
||||||
private final InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService;
|
|
||||||
private final LoanRepository loanRepository;
|
|
||||||
private final FineractProperties fineractProperties;
|
|
||||||
|
|
||||||
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
|
|
||||||
|
|
||||||
private static final List<HttpMethod> HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE);
|
|
||||||
|
|
||||||
public static final Pattern IGNORE_LOAN_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/loans/catch-up");
|
|
||||||
public static final Pattern LOAN_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/(?:reschedule)?loans/(?:external-id/)?([^/?]+).*");
|
|
||||||
|
|
||||||
public static final Pattern LOAN_GLIMACCOUNT_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/loans/glimAccount/(\\d+).*");
|
|
||||||
private static final Predicate<String> URL_FUNCTION = s -> LOAN_PATH_PATTERN.matcher(s).find()
|
|
||||||
|| LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find();
|
|
||||||
private static final String JOB_NAME = "INLINE_LOAN_COB";
|
|
||||||
|
|
||||||
private final PlatformTransactionManager transactionManager;
|
|
||||||
|
|
||||||
private static class Reject {
|
private static class Reject {
|
||||||
|
|
||||||
@@ -109,18 +58,18 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchReque
|
|||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
if (!isOnApiList(request.getPathInfo(), request.getMethod())) {
|
if (!helper.isOnApiList(request.getPathInfo(), request.getMethod())) {
|
||||||
proceed(filterChain, request, response);
|
proceed(filterChain, request, response);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
boolean bypassUser = isBypassUser();
|
boolean bypassUser = helper.isBypassUser();
|
||||||
if (bypassUser) {
|
if (bypassUser) {
|
||||||
proceed(filterChain, request, response);
|
proceed(filterChain, request, response);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
List<Long> loanIds = calculateRelevantLoanIds(request.getPathInfo());
|
List<Long> loanIds = helper.calculateRelevantLoanIds(request.getPathInfo());
|
||||||
if (!loanIds.isEmpty() && isLoanBehind(loanIds)) {
|
if (!loanIds.isEmpty() && helper.isLoanBehind(loanIds)) {
|
||||||
executeInlineCob(loanIds);
|
helper.executeInlineCob(loanIds);
|
||||||
}
|
}
|
||||||
proceed(filterChain, request, response);
|
proceed(filterChain, request, response);
|
||||||
} catch (LoanIdsHardLockedException e) {
|
} catch (LoanIdsHardLockedException e) {
|
||||||
@@ -133,130 +82,9 @@ public class LoanCOBApiFilter extends OncePerRequestFilter implements BatchReque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLoanBehind(List<Long> loanIds) {
|
|
||||||
List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates = new ArrayList<>();
|
|
||||||
List<List<Long>> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit());
|
|
||||||
partitions.forEach(partition -> loanIdAndLastClosedBusinessDates.addAll(loanRepository
|
|
||||||
.findAllNonClosedLoansBehindByLoanIds(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition)));
|
|
||||||
return CollectionUtils.isNotEmpty(loanIdAndLastClosedBusinessDates);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Long> calculateRelevantLoanIds(String pathInfo) {
|
|
||||||
|
|
||||||
List<Long> loanIds = getLoanIdList(pathInfo);
|
|
||||||
if (isLoanHardLocked(loanIds)) {
|
|
||||||
throw new LoanIdsHardLockedException(loanIds.get(0));
|
|
||||||
} else {
|
|
||||||
return loanIds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Long> getLoanIdList(String pathInfo) {
|
|
||||||
boolean isGlim = isGlim(pathInfo);
|
|
||||||
Long loanIdFromRequest = getLoanId(isGlim, pathInfo);
|
|
||||||
if (loanIdFromRequest == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
if (isGlim) {
|
|
||||||
return getGlimChildLoanIds(loanIdFromRequest);
|
|
||||||
} else {
|
|
||||||
return Collections.singletonList(loanIdFromRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeInlineCob(List<Long> loanIds) {
|
|
||||||
inlineLoanCOBExecutorService.execute(loanIds, JOB_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isBypassUser() {
|
|
||||||
return context.authenticatedUser().isBypassUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isLoanHardLocked(List<Long> loanIds) {
|
|
||||||
return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void proceed(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response)
|
private void proceed(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long getLoanId(boolean isGlim, String pathInfo) {
|
|
||||||
if (!isGlim) {
|
|
||||||
String id = LOAN_PATH_PATTERN.matcher(pathInfo).replaceAll("$1");
|
|
||||||
if (isExternal(pathInfo)) {
|
|
||||||
String externalId = id;
|
|
||||||
return loanRepository.findIdByExternalId(new ExternalId(externalId));
|
|
||||||
} else if (isRescheduleLoans(pathInfo)) {
|
|
||||||
return loanRescheduleRequestRepository.getLoanIdByRescheduleRequestId(Long.valueOf(id)).orElse(null);
|
|
||||||
} else if (StringUtils.isNumeric(id)) {
|
|
||||||
return Long.valueOf(id);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Long.valueOf(LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).replaceAll("$1"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isExternal(String pathInfo) {
|
|
||||||
return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("external-id");
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isRescheduleLoans(String pathInfo) {
|
|
||||||
return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("/v1/rescheduleloans/");
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isOnApiList(String pathInfo, String method) {
|
|
||||||
if (StringUtils.isBlank(pathInfo)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && !IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find()
|
|
||||||
&& URL_FUNCTION.test(pathInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isGlim(String pathInfo) {
|
|
||||||
return LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Either<RuntimeException, BatchRequest> preprocess(BatchRequest batchRequest) {
|
|
||||||
TransactionTemplate tr = new TransactionTemplate(transactionManager);
|
|
||||||
tr.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
|
|
||||||
return tr.execute(status -> {
|
|
||||||
try {
|
|
||||||
String method = batchRequest.getMethod();
|
|
||||||
String relativeUrl = "/" + batchRequest.getRelativeUrl();
|
|
||||||
if (!isRelativeUrlVersioned(batchRequest.getRelativeUrl())) {
|
|
||||||
// to support pre-versioned relative paths
|
|
||||||
relativeUrl = "/v1/" + batchRequest.getRelativeUrl();
|
|
||||||
}
|
|
||||||
if (isOnApiList(relativeUrl, method)) {
|
|
||||||
boolean bypassUser = isBypassUser();
|
|
||||||
if (!bypassUser) {
|
|
||||||
List<Long> result = calculateRelevantLoanIds(relativeUrl);
|
|
||||||
if (!result.isEmpty() && isLoanBehind(result)) {
|
|
||||||
executeInlineCob(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (LoanNotFoundException e) {
|
|
||||||
return Either.right(batchRequest);
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
return Either.left(e);
|
|
||||||
}
|
|
||||||
return Either.right(batchRequest);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.jobs.filter;
|
||||||
|
|
||||||
|
import static org.apache.fineract.batch.command.CommandStrategyUtils.isRelativeUrlVersioned;
|
||||||
|
|
||||||
|
import io.github.resilience4j.core.functions.Either;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.batch.domain.BatchRequest;
|
||||||
|
import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.TransactionDefinition;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LoanCOBBatchPreprocessor implements BatchRequestPreprocessor {
|
||||||
|
private final LoanCOBFilterHelper helper;
|
||||||
|
|
||||||
|
private final PlatformTransactionManager transactionManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Either<RuntimeException, BatchRequest> preprocess(BatchRequest batchRequest) {
|
||||||
|
TransactionTemplate tr = new TransactionTemplate(transactionManager);
|
||||||
|
tr.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
|
||||||
|
return tr.execute(status -> {
|
||||||
|
try {
|
||||||
|
String method = batchRequest.getMethod();
|
||||||
|
String relativeUrl = "/" + batchRequest.getRelativeUrl();
|
||||||
|
if (!isRelativeUrlVersioned(batchRequest.getRelativeUrl())) {
|
||||||
|
// to support pre-versioned relative paths
|
||||||
|
relativeUrl = "/v1/" + batchRequest.getRelativeUrl();
|
||||||
|
}
|
||||||
|
if (helper.isOnApiList(relativeUrl, method)) {
|
||||||
|
boolean bypassUser = helper.isBypassUser();
|
||||||
|
if (!bypassUser) {
|
||||||
|
List<Long> result = helper.calculateRelevantLoanIds(relativeUrl);
|
||||||
|
if (!result.isEmpty() && helper.isLoanBehind(result)) {
|
||||||
|
helper.executeInlineCob(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (LoanNotFoundException e) {
|
||||||
|
return Either.right(batchRequest);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
return Either.left(e);
|
||||||
|
}
|
||||||
|
return Either.right(batchRequest);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+160
@@ -0,0 +1,160 @@
|
|||||||
|
/**
|
||||||
|
* 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.infrastructure.jobs.filter;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
|
||||||
|
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
|
||||||
|
import org.apache.fineract.cob.service.LoanAccountLockService;
|
||||||
|
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||||
|
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||||
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
|
import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
|
||||||
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Component
|
||||||
|
public class LoanCOBFilterHelper {
|
||||||
|
private final GLIMAccountInfoRepository glimAccountInfoRepository;
|
||||||
|
private final LoanAccountLockService loanAccountLockService;
|
||||||
|
private final PlatformSecurityContext context;
|
||||||
|
private final InlineLoanCOBExecutorServiceImpl inlineLoanCOBExecutorService;
|
||||||
|
private final LoanRepository loanRepository;
|
||||||
|
private final FineractProperties fineractProperties;
|
||||||
|
|
||||||
|
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
|
||||||
|
|
||||||
|
private static final List<HttpMethod> HTTP_METHODS = List.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE);
|
||||||
|
|
||||||
|
public static final Pattern IGNORE_LOAN_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/loans/catch-up");
|
||||||
|
public static final Pattern LOAN_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/(?:reschedule)?loans/(?:external-id/)?([^/?]+).*");
|
||||||
|
|
||||||
|
public static final Pattern LOAN_GLIMACCOUNT_PATH_PATTERN = Pattern.compile("/v[1-9][0-9]*/loans/glimAccount/(\\d+).*");
|
||||||
|
private static final Predicate<String> URL_FUNCTION = s -> LOAN_PATH_PATTERN.matcher(s).find()
|
||||||
|
|| LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(s).find();
|
||||||
|
|
||||||
|
private static final String JOB_NAME = "INLINE_LOAN_COB";
|
||||||
|
|
||||||
|
private Long getLoanId(boolean isGlim, String pathInfo) {
|
||||||
|
if (!isGlim) {
|
||||||
|
String id = LOAN_PATH_PATTERN.matcher(pathInfo).replaceAll("$1");
|
||||||
|
if (isExternal(pathInfo)) {
|
||||||
|
String externalId = id;
|
||||||
|
return loanRepository.findIdByExternalId(new ExternalId(externalId));
|
||||||
|
} else if (isRescheduleLoans(pathInfo)) {
|
||||||
|
return loanRescheduleRequestRepository.getLoanIdByRescheduleRequestId(Long.valueOf(id)).orElse(null);
|
||||||
|
} else if (StringUtils.isNumeric(id)) {
|
||||||
|
return Long.valueOf(id);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Long.valueOf(LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).replaceAll("$1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isExternal(String pathInfo) {
|
||||||
|
return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("external-id");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRescheduleLoans(String pathInfo) {
|
||||||
|
return LOAN_PATH_PATTERN.matcher(pathInfo).matches() && pathInfo.contains("/v1/rescheduleloans/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOnApiList(String pathInfo, String method) {
|
||||||
|
if (StringUtils.isBlank(pathInfo)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return HTTP_METHODS.contains(HttpMethod.valueOf(method)) && !IGNORE_LOAN_PATH_PATTERN.matcher(pathInfo).find()
|
||||||
|
&& URL_FUNCTION.test(pathInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isGlim(String pathInfo) {
|
||||||
|
return LOAN_GLIMACCOUNT_PATH_PATTERN.matcher(pathInfo).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBypassUser() {
|
||||||
|
return context.authenticatedUser().isBypassUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLoanHardLocked(List<Long> loanIds) {
|
||||||
|
return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked);
|
||||||
|
}
|
||||||
|
public boolean isLoanBehind(List<Long> loanIds) {
|
||||||
|
List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates = new ArrayList<>();
|
||||||
|
List<List<Long>> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit());
|
||||||
|
partitions.forEach(partition -> loanIdAndLastClosedBusinessDates.addAll(loanRepository
|
||||||
|
.findAllNonClosedLoansBehindByLoanIds(ThreadLocalContextUtil.getBusinessDateByType(BusinessDateType.COB_DATE), partition)));
|
||||||
|
return CollectionUtils.isNotEmpty(loanIdAndLastClosedBusinessDates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> calculateRelevantLoanIds(String pathInfo) {
|
||||||
|
|
||||||
|
List<Long> loanIds = getLoanIdList(pathInfo);
|
||||||
|
if (isLoanHardLocked(loanIds)) {
|
||||||
|
throw new LoanIdsHardLockedException(loanIds.get(0));
|
||||||
|
} else {
|
||||||
|
return loanIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Long> getLoanIdList(String pathInfo) {
|
||||||
|
boolean isGlim = isGlim(pathInfo);
|
||||||
|
Long loanIdFromRequest = getLoanId(isGlim, pathInfo);
|
||||||
|
if (loanIdFromRequest == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
if (isGlim) {
|
||||||
|
return getGlimChildLoanIds(loanIdFromRequest);
|
||||||
|
} else {
|
||||||
|
return Collections.singletonList(loanIdFromRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeInlineCob(List<Long> loanIds) {
|
||||||
|
inlineLoanCOBExecutorService.execute(loanIds, JOB_NAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,8 +44,10 @@ import org.mockito.quality.Strictness;
|
|||||||
import org.springframework.batch.core.configuration.ListableJobLocator;
|
import org.springframework.batch.core.configuration.ListableJobLocator;
|
||||||
import org.springframework.batch.core.explore.JobExplorer;
|
import org.springframework.batch.core.explore.JobExplorer;
|
||||||
import org.springframework.batch.core.launch.JobLauncher;
|
import org.springframework.batch.core.launch.JobLauncher;
|
||||||
|
import org.springframework.batch.core.launch.JobOperator;
|
||||||
import org.springframework.batch.core.repository.JobRepository;
|
import org.springframework.batch.core.repository.JobRepository;
|
||||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
|
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
|
||||||
@@ -72,7 +74,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
|||||||
@Configuration
|
@Configuration
|
||||||
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
|
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
|
||||||
DataSourceTransactionManagerAutoConfiguration.class, GsonAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
|
DataSourceTransactionManagerAutoConfiguration.class, GsonAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
|
||||||
LiquibaseAutoConfiguration.class })
|
LiquibaseAutoConfiguration.class, BatchAutoConfiguration.class })
|
||||||
@EnableTransactionManagement
|
@EnableTransactionManagement
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableConfigurationProperties({ FineractProperties.class, LiquibaseProperties.class })
|
@EnableConfigurationProperties({ FineractProperties.class, LiquibaseProperties.class })
|
||||||
@@ -180,4 +182,10 @@ public class TestConfiguration {
|
|||||||
public JobRepository jobRepository() {
|
public JobRepository jobRepository() {
|
||||||
return mock(JobRepository.class, RETURNS_MOCKS);
|
return mock(JobRepository.class, RETURNS_MOCKS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Primary
|
||||||
|
@Bean
|
||||||
|
public JobOperator jobOperator() {
|
||||||
|
return mock(JobOperator.class, RETURNS_MOCKS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-22
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.infrastructure.jobs.filter;
|
package org.apache.fineract.infrastructure.jobs.filter;
|
||||||
|
|
||||||
|
import static org.apache.fineract.infrastructure.jobs.filter.LoanCOBFilterHelper.LOAN_GLIMACCOUNT_PATH_PATTERN;
|
||||||
|
import static org.apache.fineract.infrastructure.jobs.filter.LoanCOBFilterHelper.LOAN_PATH_PATTERN;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyList;
|
import static org.mockito.ArgumentMatchers.anyList;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
@@ -27,6 +29,8 @@ import static org.mockito.Mockito.times;
|
|||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import com.sun.research.ws.wadl.HTTPMethods;
|
import com.sun.research.ws.wadl.HTTPMethods;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -36,8 +40,6 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
|
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
|
||||||
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
|
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
|
||||||
import org.apache.fineract.cob.service.LoanAccountLockService;
|
import org.apache.fineract.cob.service.LoanAccountLockService;
|
||||||
@@ -55,6 +57,7 @@ import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanResch
|
|||||||
import org.apache.fineract.useradministration.domain.AppUser;
|
import org.apache.fineract.useradministration.domain.AppUser;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
@@ -69,8 +72,9 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
|||||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||||
class LoanCOBApiFilterTest {
|
class LoanCOBApiFilterTest {
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
private LoanCOBApiFilter testObj;
|
private LoanCOBApiFilter testObj;
|
||||||
|
@InjectMocks
|
||||||
|
private LoanCOBFilterHelper helper;
|
||||||
@Mock
|
@Mock
|
||||||
private LoanAccountLockService loanAccountLockService;
|
private LoanAccountLockService loanAccountLockService;
|
||||||
@Mock
|
@Mock
|
||||||
@@ -85,41 +89,45 @@ class LoanCOBApiFilterTest {
|
|||||||
private FineractProperties fineractProperties;
|
private FineractProperties fineractProperties;
|
||||||
@Mock
|
@Mock
|
||||||
private FineractProperties.FineractQueryProperties fineractQueryProperties;
|
private FineractProperties.FineractQueryProperties fineractQueryProperties;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private LoanRescheduleRequestRepository loanRescheduleRequestRepository;
|
private LoanRescheduleRequestRepository loanRescheduleRequestRepository;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
testObj = new LoanCOBApiFilter(helper);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldLoanAndExternalMatchToo() {
|
void shouldLoanAndExternalMatchToo() {
|
||||||
String externalId = UUID.randomUUID().toString();
|
String externalId = UUID.randomUUID().toString();
|
||||||
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/12").matches());
|
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/loans/12").matches());
|
||||||
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").matches());
|
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").matches());
|
||||||
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").matches());
|
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").matches());
|
||||||
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12").matches());
|
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12").matches());
|
||||||
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").matches());
|
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").matches());
|
||||||
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").matches());
|
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").matches());
|
||||||
Assertions.assertTrue(LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + externalId).matches());
|
Assertions.assertTrue(LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + externalId).matches());
|
||||||
Assertions.assertTrue(
|
Assertions.assertTrue(
|
||||||
LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + externalId + "?additional=parameter").matches());
|
LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + externalId + "?additional=parameter").matches());
|
||||||
Assertions.assertEquals("12", LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/12").replaceAll("$1"));
|
Assertions.assertEquals("12", LOAN_PATH_PATTERN.matcher("/v1/loans/12").replaceAll("$1"));
|
||||||
Assertions.assertEquals("12", LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").replaceAll("$1"));
|
Assertions.assertEquals("12", LOAN_PATH_PATTERN.matcher("/v1/loans/12?correct=parameter").replaceAll("$1"));
|
||||||
Assertions.assertEquals("12", LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12").replaceAll("$1"));
|
Assertions.assertEquals("12", LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12").replaceAll("$1"));
|
||||||
Assertions.assertEquals("12",
|
Assertions.assertEquals("12",
|
||||||
LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").replaceAll("$1"));
|
LOAN_PATH_PATTERN.matcher("/v1/rescheduleloans/12?correct=parameter").replaceAll("$1"));
|
||||||
Assertions.assertEquals(externalId,
|
Assertions.assertEquals(externalId,
|
||||||
LoanCOBApiFilter.LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + externalId).replaceAll("$1"));
|
LOAN_PATH_PATTERN.matcher("/v1/loans/external-id/" + externalId).replaceAll("$1"));
|
||||||
Assertions.assertEquals(externalId, LoanCOBApiFilter.LOAN_PATH_PATTERN
|
Assertions.assertEquals(externalId, LOAN_PATH_PATTERN
|
||||||
.matcher("/v1/loans/external-id/" + externalId + "?additional=parameter").replaceAll("$1"));
|
.matcher("/v1/loans/external-id/" + externalId + "?additional=parameter").replaceAll("$1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldGlimAccountMatch() {
|
void shouldGlimAccountMatch() {
|
||||||
Assertions.assertTrue(LoanCOBApiFilter.LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12").matches());
|
Assertions.assertTrue(LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12").matches());
|
||||||
Assertions.assertTrue(
|
Assertions.assertTrue(
|
||||||
LoanCOBApiFilter.LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12?additional=parameter").matches());
|
LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12?additional=parameter").matches());
|
||||||
Assertions.assertEquals("12", LoanCOBApiFilter.LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12").replaceAll("$1"));
|
Assertions.assertEquals("12", LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12").replaceAll("$1"));
|
||||||
Assertions.assertEquals("12",
|
Assertions.assertEquals("12",
|
||||||
LoanCOBApiFilter.LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12?additional=parameter").replaceAll("$1"));
|
LOAN_GLIMACCOUNT_PATH_PATTERN.matcher("/v1/loans/glimAccount/12?additional=parameter").replaceAll("$1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -53,5 +53,5 @@ dependencies {
|
|||||||
testImplementation 'org.mapstruct:mapstruct'
|
testImplementation 'org.mapstruct:mapstruct'
|
||||||
testAnnotationProcessor 'org.mapstruct:mapstruct-processor'
|
testAnnotationProcessor 'org.mapstruct:mapstruct-processor'
|
||||||
|
|
||||||
testImplementation 'com.github.tomakehurst:wiremock-jre8:2.35.0'
|
testImplementation 'com.github.tomakehurst:wiremock:3.0.0-beta-8'
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user