[FINERACT-1678] Configuration for enabling/disabling Loan COB

This commit is contained in:
taskain7
2023-08-07 14:32:39 +02:00
parent a04c8da659
commit 7559e90ce2
27 changed files with 535 additions and 18 deletions
@@ -380,6 +380,7 @@ public class FineractProperties {
public static class FineractJobProperties {
private int stuckRetryThreshold;
private boolean loanCobEnabled;
}
@Getter
@@ -177,6 +177,17 @@ public class ApiGlobalErrorResponse {
return globalErrorResponse;
}
public static ApiGlobalErrorResponse jobIsDisabled(final String globalisationMessageCode, final String defaultUserMessage) {
final ApiGlobalErrorResponse globalErrorResponse = new ApiGlobalErrorResponse();
globalErrorResponse.setHttpStatusCode(Status.FORBIDDEN.toString());
globalErrorResponse.setDeveloperMessage(defaultUserMessage);
globalErrorResponse.setUserMessageGlobalisationCode(globalisationMessageCode);
globalErrorResponse.setDefaultUserMessage(defaultUserMessage);
return globalErrorResponse;
}
public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage) {
return badClientRequest(globalisationMessageCode, defaultUserMessage, Collections.emptyList());
}
@@ -0,0 +1,30 @@
/**
* 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.exception;
public class JobIsNotFoundOrNotEnabledException extends AbstractPlatformException {
public JobIsNotFoundOrNotEnabledException(Exception e, String jobName) {
super("error.msg.job.disabled", "Job " + jobName + " is not found or it is disabled", e);
}
public JobIsNotFoundOrNotEnabledException(String jobName) {
super("error.msg.job.disabled", "Job " + jobName + " is not found or it is disabled");
}
}
@@ -0,0 +1,42 @@
/**
* 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.exceptionmapper;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
import org.apache.fineract.infrastructure.core.exception.JobIsNotFoundOrNotEnabledException;
import org.springframework.stereotype.Component;
@Provider
@Component
@Slf4j
public class JobIsNotFoundOrNotEnabledExceptionMapper implements ExceptionMapper<JobIsNotFoundOrNotEnabledException> {
@Override
public Response toResponse(JobIsNotFoundOrNotEnabledException exception) {
log.warn("Exception: {}, Message: {}", exception.getClass().getName(), exception.getMessage());
return Response.status(Response.Status.FORBIDDEN)
.entity(ApiGlobalErrorResponse.jobIsDisabled(exception.getGlobalisationMessageCode(), exception.getDefaultUserMessage()))
.type(MediaType.APPLICATION_JSON).build();
}
}
@@ -253,4 +253,56 @@ PUT /fineract-provider/api/v1/jobs/{jobName}/steps?tenantIdentifier={tenantId}
}
----
The business step configuration for jobs are tracked within the database in the `m_batch_business_steps` table.
The business step configuration for jobs are tracked within the database in the `m_batch_business_steps` table.
== Inline Jobs
Some jobs that work with business entities have a corresponding job that can trigger the job with a list of specified entities.
When the Inline job gets triggered then then the corresponding existing job will run in real time with the given entities as a dataset.
=== List of Inline jobs
[cols="1,1"]
|===
|*Inline Job name*
|*Corresponding Job*
|LOAN_COB
|LOAN_CLOSE_OF_BUSINESS
|===
Triggering the Inline Loan COB Job:
[source]
----
POST /fineract-provider/api/v1/jobs/LOAN_COB/inline?tenantIdentifier={tenantId}
{
"loanIds": [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
]
}
----
In this case the Loan COB job will work only with the given loan IDs.
== Global Configuration for enabling/disabling jobs
Some jobs can be enabled/disabled with global configuration.
If the job is disabled with the global configuration then it cannot be scheduled and cannot be triggered via API.
=== List of jobs with global configuration
[cols="1,1,1,1"]
|===
|*Job name*
|*Application property*
|*Environment variable*
|*Default value*
|LOAN_CLOSE_OF_BUSINESS
|fineract.job.loan-cob-enabled
|FINERACT_JOB_LOAN_COB_ENABLED
|true
|===
@@ -31,22 +31,27 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.RequiredArgsConstructor;
import java.util.Objects;
import org.apache.fineract.cob.data.IsCatchUpRunningDTO;
import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO;
import org.apache.fineract.cob.service.LoanCOBCatchUpService;
import org.apache.fineract.infrastructure.core.exception.JobIsNotFoundOrNotEnabledException;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.jobs.service.JobName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Path("/v1/loans")
@Component
@Tag(name = "Loan COB Catch Up", description = "")
@RequiredArgsConstructor
public class LoanCOBCatchUpApiResource {
private final DefaultToApiJsonSerializer<OldestCOBProcessedLoanDTO> oldestCOBProcessedLoanSerializeService;
private final DefaultToApiJsonSerializer<IsCatchUpRunningDTO> isCatchUpRunningSerializer;
private final LoanCOBCatchUpService loanCOBCatchUpService;
@Autowired
private DefaultToApiJsonSerializer<OldestCOBProcessedLoanDTO> oldestCOBProcessedLoanSerializeService;
@Autowired
private DefaultToApiJsonSerializer<IsCatchUpRunningDTO> isCatchUpRunningSerializer;
@Autowired(required = false)
private LoanCOBCatchUpService loanCOBCatchUpService;
@GET
@Path("oldest-cob-closed")
@@ -56,6 +61,9 @@ public class LoanCOBCatchUpApiResource {
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanCOBCatchUpApiResourceSwagger.GetOldestCOBProcessedLoanResponse.class))) })
public String getOldestCOBProcessedLoan() {
if (Objects.isNull(loanCOBCatchUpService)) {
throw new JobIsNotFoundOrNotEnabledException(JobName.LOAN_COB.name());
}
OldestCOBProcessedLoanDTO response = loanCOBCatchUpService.getOldestCOBProcessedLoan();
return oldestCOBProcessedLoanSerializeService.serialize(response);
}
@@ -69,6 +77,9 @@ public class LoanCOBCatchUpApiResource {
@ApiResponse(responseCode = "202", description = "Catch Up has been started"),
@ApiResponse(responseCode = "400", description = "Catch Up is already running") })
public Response executeLoanCOBCatchUp() {
if (Objects.isNull(loanCOBCatchUpService)) {
throw new JobIsNotFoundOrNotEnabledException(JobName.LOAN_COB.name());
}
if (loanCOBCatchUpService.isCatchUpRunning().isCatchUpRunning()) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
@@ -90,6 +101,9 @@ public class LoanCOBCatchUpApiResource {
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = LoanCOBCatchUpApiResourceSwagger.IsCatchUpRunningResponse.class))) })
public String isCatchUpRunning() {
if (Objects.isNull(loanCOBCatchUpService)) {
throw new JobIsNotFoundOrNotEnabledException(JobName.LOAN_COB.name());
}
IsCatchUpRunningDTO response = loanCOBCatchUpService.isCatchUpRunning();
return isCatchUpRunningSerializer.serialize(response);
@@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.conditions;
import org.apache.fineract.infrastructure.core.condition.PropertiesCondition;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
public class LoanCOBEnabledCondition extends PropertiesCondition {
@Override
protected boolean matches(FineractProperties properties) {
return properties.getJob().isLoanCobEnabled();
}
}
@@ -0,0 +1,32 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.conditions;
import org.apache.fineract.infrastructure.core.condition.PropertiesCondition;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
public class LoanCOBManagerCondition extends PropertiesCondition {
@Override
protected boolean matches(FineractProperties properties) {
boolean loanCobEnabled = properties.getJob().isLoanCobEnabled();
boolean batchManagerEnabled = properties.getMode().isBatchManagerEnabled();
return loanCobEnabled && batchManagerEnabled;
}
}
@@ -0,0 +1,32 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.conditions;
import org.apache.fineract.infrastructure.core.condition.PropertiesCondition;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
public class LoanCOBWorkerCondition extends PropertiesCondition {
@Override
protected boolean matches(FineractProperties properties) {
boolean loanCobEnabled = properties.getJob().isLoanCobEnabled();
boolean batchWorkerEnabled = properties.getMode().isBatchWorkerEnabled();
return loanCobEnabled && batchWorkerEnabled;
}
}
@@ -20,6 +20,7 @@ package org.apache.fineract.cob.loan;
import org.apache.fineract.cob.COBBusinessStepService;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.conditions.LoanCOBManagerCondition;
import org.apache.fineract.cob.listener.COBExecutionListenerRunner;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.jobs.service.JobName;
@@ -37,16 +38,16 @@ import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.integration.config.annotation.EnableBatchIntegration;
import org.springframework.batch.integration.partition.RemotePartitioningManagerStepBuilderFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
@EnableBatchIntegration
@ConditionalOnProperty(value = "fineract.mode.batch-manager-enabled", havingValue = "true")
@Conditional(LoanCOBManagerCondition.class)
public class LoanCOBManagerConfiguration {
@Autowired
@@ -22,6 +22,7 @@ import org.apache.fineract.cob.COBBusinessStepService;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.common.InitialisationTasklet;
import org.apache.fineract.cob.common.ResetContextTasklet;
import org.apache.fineract.cob.conditions.LoanCOBWorkerCondition;
import org.apache.fineract.cob.listener.ChunkProcessingLoanItemListener;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.jobs.service.JobName;
@@ -39,8 +40,8 @@ import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.integration.partition.RemotePartitioningWorkerStepBuilderFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
@@ -50,7 +51,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
@Configuration
@ConditionalOnProperty(value = "fineract.mode.batch-worker-enabled", havingValue = "true")
@Conditional(LoanCOBWorkerCondition.class)
public class LoanCOBWorkerConfiguration {
@Autowired
@@ -21,6 +21,7 @@ package org.apache.fineract.cob.loan;
import org.apache.fineract.cob.COBBusinessStepService;
import org.apache.fineract.cob.common.CustomJobParameterResolver;
import org.apache.fineract.cob.common.ResetContextTasklet;
import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
import org.apache.fineract.cob.listener.InlineCOBLoanItemListener;
import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository;
@@ -38,12 +39,14 @@ import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.integration.config.annotation.EnableBatchIntegration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
@Configuration
@EnableBatchIntegration
@Conditional(LoanCOBEnabledCondition.class)
public class LoanInlineCOBConfig {
@Autowired
@@ -25,6 +25,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.loan.LoanCOBConstant;
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
@@ -45,11 +46,13 @@ import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.context.annotation.Conditional;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
@Conditional(LoanCOBEnabledCondition.class)
public class AsyncLoanCOBExecutorServiceImpl implements AsyncLoanCOBExecutorService {
private final JobLocator jobLocator;
@@ -35,6 +35,7 @@ import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.domain.LoanAccountLock;
import org.apache.fineract.cob.domain.LoanAccountLockRepository;
@@ -68,6 +69,7 @@ import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
@@ -78,6 +80,7 @@ import org.springframework.transaction.support.TransactionTemplate;
@Service
@Slf4j
@RequiredArgsConstructor
@Conditional(LoanCOBEnabledCondition.class)
public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService<Long> {
private static final String JOB_EXECUTION_FAILED_MESSAGE = "Job execution failed for job with name: ";
@@ -22,6 +22,7 @@ import com.google.gson.Gson;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.cob.data.IsCatchUpRunningDTO;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.data.OldestCOBProcessedLoanDTO;
@@ -34,10 +35,12 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository;
import org.apache.fineract.infrastructure.jobs.domain.JobExecutionRepository;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
@Conditional(LoanCOBEnabledCondition.class)
public class LoanCOBCatchUpServiceImpl implements LoanCOBCatchUpService {
private final AsyncLoanCOBExecutorService asyncLoanCOBExecutorService;
@@ -24,6 +24,7 @@ import static org.springframework.security.authorization.AuthorityAuthorizationM
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;
import java.util.Objects;
import org.apache.fineract.commands.domain.CommandSourceRepository;
import org.apache.fineract.commands.service.CommandSourceService;
import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
@@ -107,7 +108,7 @@ public class SecurityConfig {
@Autowired
private FineractRequestContextHolder fineractRequestContextHolder;
@Autowired
@Autowired(required = false)
private LoanCOBFilterHelper loanCOBFilterHelper;
@Autowired
private PlatformSecurityContext context;
@@ -136,9 +137,13 @@ public class SecurityConfig {
.addFilterAfter(requestResponseFilter(), ExceptionTranslationFilter.class) //
.addFilterAfter(correlationHeaderFilter(), RequestResponseFilter.class) //
.addFilterAfter(responseCorsFilter(), CorrelationHeaderFilter.class) //
.addFilterAfter(fineractInstanceModeApiFilter(), ResponseCorsFilter.class) //
.addFilterAfter(loanCOBApiFilter(), FineractInstanceModeApiFilter.class) //
.addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); //
.addFilterAfter(fineractInstanceModeApiFilter(), ResponseCorsFilter.class); //
if (!Objects.isNull(loanCOBFilterHelper)) {
http.addFilterAfter(loanCOBApiFilter(), FineractInstanceModeApiFilter.class) //
.addFilterAfter(idempotencyStoreFilter(), LoanCOBApiFilter.class); //
} else {
http.addFilterAfter(idempotencyStoreFilter(), FineractInstanceModeApiFilter.class); //
}
if (fineractProperties.getSecurity().getTwoFactor().isEnabled()) {
http.addFilterAfter(twoFactorAuthenticationFilter(), ResponseCorsFilter.class);
@@ -25,13 +25,16 @@ import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.cob.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse;
import org.apache.fineract.infrastructure.jobs.exception.LoanIdsHardLockedException;
import org.apache.fineract.useradministration.exception.UnAuthenticatedUserException;
import org.apache.http.HttpStatus;
import org.springframework.context.annotation.Conditional;
import org.springframework.web.filter.OncePerRequestFilter;
@RequiredArgsConstructor
@Conditional(LoanCOBEnabledCondition.class)
public class LoanCOBApiFilter extends OncePerRequestFilter {
private final LoanCOBFilterHelper helper;
@@ -24,8 +24,10 @@ 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.cob.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.infrastructure.core.filters.BatchRequestPreprocessor;
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
@@ -33,6 +35,7 @@ import org.springframework.transaction.support.TransactionTemplate;
@Component
@RequiredArgsConstructor
@Conditional(LoanCOBEnabledCondition.class)
public class LoanCOBBatchPreprocessor implements BatchRequestPreprocessor {
private final LoanCOBFilterHelper helper;
@@ -28,6 +28,7 @@ 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.conditions.LoanCOBEnabledCondition;
import org.apache.fineract.cob.data.LoanIdAndLastClosedBusinessDate;
import org.apache.fineract.cob.loan.RetrieveLoanIdService;
import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl;
@@ -43,11 +44,13 @@ import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonit
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.context.annotation.Conditional;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Component
@Conditional(LoanCOBEnabledCondition.class)
public class LoanCOBFilterHelper {
private final GLIMAccountInfoRepository glimAccountInfoRepository;
@@ -23,6 +23,8 @@ import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.exception.JobIsNotFoundOrNotEnabledException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
@@ -36,7 +38,11 @@ public class InlineJobExecuteHandler implements NewCommandSourceHandler {
@Override
public CommandProcessingResult processCommand(JsonCommand command) {
InlineJobType inlineJobType = InlineJobType.getInlineJobType(command.getJobName());
InlineExecutorService inlineJobExecutorService = applicationContext.getBean(inlineJobType.getExecutorServiceClass());
return inlineJobExecutorService.executeInlineJob(command, inlineJobType.getInlineJobName());
try {
InlineExecutorService inlineJobExecutorService = applicationContext.getBean(inlineJobType.getExecutorServiceClass());
return inlineJobExecutorService.executeInlineJob(command, inlineJobType.getInlineJobName());
} catch (NoSuchBeanDefinitionException e) {
throw new JobIsNotFoundOrNotEnabledException(e, inlineJobType.getInlineJobName());
}
}
}
@@ -29,6 +29,7 @@ import java.util.TimeZone;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.exception.JobIsNotFoundOrNotEnabledException;
import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
@@ -48,6 +49,7 @@ import org.quartz.Trigger;
import org.quartz.TriggerListener;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.configuration.JobLocator;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
@@ -118,6 +120,10 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
scheduler.addJob(jobDetail, true);
scheduler.triggerJob(jobKey, jobDataMap);
}
} catch (JobIsNotFoundOrNotEnabledException e) {
final String msg = "Job is not found or it is disabled with job ID: " + scheduledJobDetail.getId();
log.error("{}", msg, e);
throw e;
} catch (final Exception e) {
final String msg = "Job execution failed for job with id:" + scheduledJobDetail.getId();
log.error("{}", msg, e);
@@ -327,7 +333,12 @@ public class JobRegisterServiceImpl implements JobRegisterService, ApplicationLi
final FineractPlatformTenant tenant = ThreadLocalContextUtil.getTenant();
JobNameData jobName = jobNameService.getJobByHumanReadableName(scheduledJobDetail.getJobName());
Job job = jobLocator.getJob(jobName.getEnumStyleName());
Job job;
try {
job = jobLocator.getJob(jobName.getEnumStyleName());
} catch (NoSuchJobException e) {
throw new JobIsNotFoundOrNotEnabledException(e, jobName.getEnumStyleName());
}
final MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean();
jobDetailFactoryBean.setName(scheduledJobDetail.getJobName() + "JobDetail" + tenant.getId());
@@ -28,6 +28,7 @@ import org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadP
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.exception.JobIsNotFoundOrNotEnabledException;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
@@ -63,7 +64,11 @@ public class JobSchedulerServiceImpl implements ApplicationListener<ContextRefre
final List<ScheduledJobDetail> scheduledJobDetails = schedularWritePlatformService
.retrieveAllJobs(fineractProperties.getNodeId());
for (final ScheduledJobDetail jobDetails : scheduledJobDetails) {
jobRegisterService.scheduleJob(jobDetails);
try {
jobRegisterService.scheduleJob(jobDetails);
} catch (JobIsNotFoundOrNotEnabledException e) {
log.warn("{}", e.getMessage());
}
jobDetails.setTriggerMisfired(false);
schedularWritePlatformService.saveOrUpdate(jobDetails);
}
@@ -60,6 +60,7 @@ fineract.correlation.enabled=${FINERACT_LOGGING_HTTP_CORRELATION_ID_ENABLED:fals
fineract.correlation.header-name=${FINERACT_LOGGING_HTTP_CORRELATION_ID_HEADER_NAME:X-Correlation-ID}
fineract.job.stuck-retry-threshold=${FINERACT_JOB_STUCK_RETRY_THRESHOLD:5}
fineract.job.loan-cob-enabled=${FINERACT_JOB_LOAN_COB_ENABLED:true}
fineract.partitioned-job.partitioned-job-properties[0].job-name=LOAN_COB
fineract.partitioned-job.partitioned-job-properties[0].chunk-size=${LOAN_COB_CHUNK_SIZE:100}
@@ -0,0 +1,67 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.conditions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.mock.env.MockEnvironment;
@ExtendWith(MockitoExtension.class)
class LoanCOBEnabledConditionTest {
@Mock
private ConditionContext conditionContext;
@Mock
private AnnotatedTypeMetadata metadata;
private MockEnvironment environment;
@InjectMocks
private LoanCOBEnabledCondition testObj = new LoanCOBEnabledCondition();
@BeforeEach
public void setUp() {
environment = new MockEnvironment();
given(conditionContext.getEnvironment()).willReturn(environment);
}
@Test
public void testMatchesShouldReturnFalseWhenLoanCobIsDisabled() {
environment.withProperty("fineract.job.loan-cob-enabled", "false");
boolean result = testObj.matches(conditionContext, metadata);
assertThat(result).isFalse();
}
@Test
public void testMatchesShouldReturnTrueWhenLoanCobIsEnabled() {
environment.withProperty("fineract.job.loan-cob-enabled", "true");
boolean result = testObj.matches(conditionContext, metadata);
assertThat(result).isTrue();
}
}
@@ -0,0 +1,77 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.conditions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.mock.env.MockEnvironment;
@ExtendWith(MockitoExtension.class)
class LoanCOBManagerConditionTest {
@Mock
private ConditionContext conditionContext;
@Mock
private AnnotatedTypeMetadata metadata;
private MockEnvironment environment;
@InjectMocks
private LoanCOBManagerCondition testObj = new LoanCOBManagerCondition();
@BeforeEach
public void setUp() {
environment = new MockEnvironment();
given(conditionContext.getEnvironment()).willReturn(environment);
}
@Test
public void testMatchesShouldReturnFalseWhenLoanCobIsDisabledAndManager() {
environment.withProperty("fineract.job.loan-cob-enabled", "false");
environment.withProperty("fineract.mode.batch-manager-enabled", "true");
boolean result = testObj.matches(conditionContext, metadata);
assertThat(result).isFalse();
}
@Test
public void testMatchesShouldReturnFalseWhenLoanCobIsEnabledAndNotManager() {
environment.withProperty("fineract.job.loan-cob-enabled", "true");
environment.withProperty("fineract.mode.batch-manager-enabled", "false");
boolean result = testObj.matches(conditionContext, metadata);
assertThat(result).isFalse();
}
@Test
public void testMatchesShouldReturnTrueWhenLoanCobIsEnabledAndManager() {
environment.withProperty("fineract.job.loan-cob-enabled", "true");
environment.withProperty("fineract.mode.batch-manager-enabled", "true");
boolean result = testObj.matches(conditionContext, metadata);
assertThat(result).isTrue();
}
}
@@ -0,0 +1,77 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.cob.conditions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.mock.env.MockEnvironment;
@ExtendWith(MockitoExtension.class)
class LoanCOBWorkerConditionTest {
@Mock
private ConditionContext conditionContext;
@Mock
private AnnotatedTypeMetadata metadata;
private MockEnvironment environment;
@InjectMocks
private LoanCOBWorkerCondition testObj = new LoanCOBWorkerCondition();
@BeforeEach
public void setUp() {
environment = new MockEnvironment();
given(conditionContext.getEnvironment()).willReturn(environment);
}
@Test
public void testMatchesShouldReturnFalseWhenLoanCobIsDisabledAndWorker() {
environment.withProperty("fineract.job.loan-cob-enabled", "false");
environment.withProperty("fineract.mode.batch-worker-enabled", "true");
boolean result = testObj.matches(conditionContext, metadata);
assertThat(result).isFalse();
}
@Test
public void testMatchesShouldReturnFalseWhenLoanCobIsEnabledAndNotWorker() {
environment.withProperty("fineract.job.loan-cob-enabled", "true");
environment.withProperty("fineract.mode.batch-worker-enabled", "false");
boolean result = testObj.matches(conditionContext, metadata);
assertThat(result).isFalse();
}
@Test
public void testMatchesShouldReturnTrueWhenLoanCobIsEnabledAndWorker() {
environment.withProperty("fineract.job.loan-cob-enabled", "true");
environment.withProperty("fineract.mode.batch-worker-enabled", "true");
boolean result = testObj.matches(conditionContext, metadata);
assertThat(result).isTrue();
}
}
@@ -87,6 +87,7 @@ fineract.report.export.s3.enabled=${FINERACT_REPORT_EXPORT_S3_ENABLED:false}
fineract.jpa.statementLoggingEnabled=${FINERACT_STATEMENT_LOGGING_ENABLED:false}
fineract.job.loan-cob-enabled=${FINERACT_JOB_LOAN_COB_ENABLED:true}
fineract.sampling.enabled=false
fineract.sampling.sampledClasses=