This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new e584e26c39 FINERACT-2191: ThreadLocal context handling during job
execution
e584e26c39 is described below
commit e584e26c39ab7725dbee129defdffd8552976cb0
Author: Oleksii Novikov <[email protected]>
AuthorDate: Mon Mar 24 10:35:17 2025 +0200
FINERACT-2191: ThreadLocal context handling during job execution
---
.../core/domain/FineractContext.java | 3 +
.../core/domain/FineractPlatformTenant.java | 34 +----
.../core/service/ThreadLocalContextUtil.java | 4 +-
.../service/BusinessEventNotifierServiceImpl.java | 29 ++--
.../jobs/SendAsynchronousEventsTasklet.java | 31 ++--
.../service/RecalculateInterestPoster.java | 11 +-
.../fineract/cob/common/InitialisationTasklet.java | 10 +-
.../cob/loan/ContextAwareTaskDecorator.java | 19 ++-
.../service/AsyncLoanCOBExecutorServiceImpl.java | 24 ++-
.../service/BulkImportEventListener.java | 162 ++++++++-------------
.../hooks/listener/FineractHookListener.java | 39 ++---
.../infrastructure/jobs/service/JobStarter.java | 30 ++--
.../jobs/service/SchedulerJobListener.java | 22 +--
.../jobs/service/SchedulerTriggerListener.java | 18 ++-
.../jobs/service/StuckJobListener.java | 32 ++--
.../SmsMessageScheduledJobServiceImpl.java | 8 +-
.../springbatch/InputChannelInterceptor.java | 20 ++-
.../SpringNotificationEventListener.java | 14 +-
.../PostInterestForSavingTasklet.java | 32 ++--
.../SavingsSchedularInterestPosterTask.java | 13 +-
20 files changed, 295 insertions(+), 260 deletions(-)
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractContext.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractContext.java
index b5e77a2768..cd173419bf 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractContext.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractContext.java
@@ -23,6 +23,7 @@ import java.time.LocalDate;
import java.util.HashMap;
import lombok.AllArgsConstructor;
import lombok.Builder;
+import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.extern.jackson.Jacksonized;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@@ -31,6 +32,7 @@ import
org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
@Jacksonized
@Builder
@Getter
+@EqualsAndHashCode
public class FineractContext implements Serializable {
private final String contextHolder;
@@ -38,4 +40,5 @@ public class FineractContext implements Serializable {
private final String authTokenContext;
private final HashMap<BusinessDateType, LocalDate> businessDateContext;
private final ActionContext actionContext;
+
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenant.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenant.java
index bddce4c681..ed2e81ce3c 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenant.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/domain/FineractPlatformTenant.java
@@ -20,10 +20,16 @@ package org.apache.fineract.infrastructure.core.domain;
import java.io.Serializable;
import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
import lombok.extern.jackson.Jacksonized;
@Jacksonized
@Builder
+@EqualsAndHashCode
+@RequiredArgsConstructor
+@Getter
public class FineractPlatformTenant implements Serializable {
private final Long id;
@@ -32,32 +38,4 @@ public class FineractPlatformTenant implements Serializable {
private final String timezoneId;
private final FineractPlatformTenantConnection connection;
- public FineractPlatformTenant(final Long id, final String
tenantIdentifier, final String name, final String timezoneId,
- final FineractPlatformTenantConnection connection) {
- this.id = id;
- this.tenantIdentifier = tenantIdentifier;
- this.name = name;
- this.timezoneId = timezoneId;
- this.connection = connection;
- }
-
- public Long getId() {
- return this.id;
- }
-
- public String getTenantIdentifier() {
- return this.tenantIdentifier;
- }
-
- public String getName() {
- return this.name;
- }
-
- public String getTimezoneId() {
- return this.timezoneId;
- }
-
- public FineractPlatformTenantConnection getConnection() {
- return connection;
- }
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/ThreadLocalContextUtil.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/ThreadLocalContextUtil.java
index fe3f10d54f..9eb04e491f 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/ThreadLocalContextUtil.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/ThreadLocalContextUtil.java
@@ -27,7 +27,8 @@ import
org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.springframework.util.Assert;
/**
- *
+ * A utility class for managing ThreadLocal context in the application.
Provides methods for context initialization and
+ * cleanup.
*/
public final class ThreadLocalContextUtil {
@@ -124,4 +125,5 @@ public final class ThreadLocalContextUtil {
businessDateContext.remove();
actionContext.remove();
}
+
}
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImpl.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImpl.java
index 66eb6824c6..4ae4b37023 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImpl.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/business/service/BusinessEventNotifierServiceImpl.java
@@ -36,6 +36,7 @@ import
org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
import
org.apache.fineract.infrastructure.event.business.domain.NoExternalEvent;
import
org.apache.fineract.infrastructure.event.external.service.ExternalEventService;
import org.springframework.beans.factory.InitializingBean;
+import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionExecution;
import org.springframework.transaction.TransactionExecutionListener;
@@ -198,19 +199,29 @@ public class BusinessEventNotifierServiceImpl implements
BusinessEventNotifierSe
}
@Override
- public void beforeCommit(TransactionExecution transaction) {
- List<BusinessEventWithContext> businessEventWithContexts =
transactionBusinessEvents.get().peek();
- if (!businessEventWithContexts.isEmpty()) {
- FineractContext originalContext =
ThreadLocalContextUtil.getContext();
+ public void beforeCommit(@NonNull final TransactionExecution transaction) {
+ final List<BusinessEventWithContext> businessEventWithContexts =
transactionBusinessEvents.get().peek();
+ if (businessEventWithContexts.isEmpty()) {
+ return;
+ }
+ final FineractContext originalContext =
ThreadLocalContextUtil.getContext();
+ businessEventWithContexts.forEach(businessEventWithContext -> {
+ final FineractContext currentContext =
businessEventWithContext.getFineractContext();
+ boolean swappedContext = false;
try {
- for (BusinessEventWithContext businessEventWithContext :
businessEventWithContexts) {
-
ThreadLocalContextUtil.init(businessEventWithContext.getFineractContext());
-
externalEventService.postEvent(businessEventWithContext.getEvent());
+ if (!originalContext.equals(currentContext)) {
+ swappedContext = true;
+ ThreadLocalContextUtil.init(currentContext);
}
+
externalEventService.postEvent(businessEventWithContext.getEvent());
} finally {
- ThreadLocalContextUtil.init(originalContext);
+ // Back to original context if we swapped it. We should
restore the original context rather than reset
+ // it completely
+ if (swappedContext) {
+ ThreadLocalContextUtil.init(originalContext);
+ }
}
- }
+ });
}
@Override
diff --git
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTasklet.java
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTasklet.java
index 70b514ee89..b52bb661f8 100644
---
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTasklet.java
+++
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/event/external/jobs/SendAsynchronousEventsTasklet.java
@@ -109,7 +109,7 @@ public class SendAsynchronousEventsTasklet implements
Tasklet {
eventProducer.sendEvents(partitions);
}
- private void markEventsAsSent(List<Long> eventIds) {
+ private void markEventsAsSent(final List<Long> eventIds) {
OffsetDateTime sentAt = DateUtils.getAuditOffsetDateTime();
// Partitioning dataset to avoid exception: PreparedStatement can have
at most 65,535 parameters
@@ -117,20 +117,23 @@ public class SendAsynchronousEventsTasklet implements
Tasklet {
List<List<Long>> partitions = Lists.partition(eventIds, partitionSize);
List<Future<?>> tasks = new ArrayList<>();
final FineractContext context = ThreadLocalContextUtil.getContext();
- partitions //
- .forEach(partitionedEventIds -> {
- tasks.add(threadPoolTaskExecutor.submit(() -> {
- ThreadLocalContextUtil.init(context);
- transactionTemplate.execute((status) -> {
- measure(() -> {
- repository.markEventsSent(partitionedEventIds,
sentAt);
- }, timeTaken -> {
- log.debug("Took {}ms to update {} events",
timeTaken.toMillis(), partitionedEventIds.size());
- });
- return null;
+ partitions.forEach(partitionedEventIds -> {
+ tasks.add(threadPoolTaskExecutor.submit(() -> {
+ try {
+ ThreadLocalContextUtil.init(context);
+ transactionTemplate.execute((status) -> {
+ measure(() -> {
+ repository.markEventsSent(partitionedEventIds,
sentAt);
+ }, timeTaken -> {
+ log.debug("Took {}ms to update {} events",
timeTaken.toMillis(), partitionedEventIds.size());
});
- }));
- });
+ return null;
+ });
+ } finally {
+ ThreadLocalContextUtil.reset();
+ }
+ }));
+ });
for (Future<?> task : tasks) {
try {
task.get();
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java
index a274a9a985..030e93c29e 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/RecalculateInterestPoster.java
@@ -41,9 +41,12 @@ public class RecalculateInterestPoster implements
Callable<Void> {
@Override
public Void call() throws JobExecutionException {
- ThreadLocalContextUtil.init(fineractContext);
- if (!loanIds.isEmpty()) {
- List<Throwable> errors = new ArrayList<>();
+ if (loanIds.isEmpty()) {
+ return null;
+ }
+ try {
+ ThreadLocalContextUtil.init(fineractContext);
+ final List<Throwable> errors = new ArrayList<>();
for (Long loanId : loanIds) {
log.debug("Loan ID {}", loanId);
try {
@@ -55,6 +58,8 @@ public class RecalculateInterestPoster implements
Callable<Void> {
if (!errors.isEmpty()) {
throw new JobExecutionException(errors);
}
+ } finally {
+ ThreadLocalContextUtil.reset();
}
return null;
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/common/InitialisationTasklet.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/common/InitialisationTasklet.java
index 858e999b14..b97d152ca2 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/common/InitialisationTasklet.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/common/InitialisationTasklet.java
@@ -38,6 +38,9 @@ import org.springframework.batch.repeat.RepeatStatus;
import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
+/**
+ * Tasklet to initialize the thread local context for job execution
+ */
@Slf4j
@RequiredArgsConstructor
public class InitialisationTasklet implements Tasklet {
@@ -51,14 +54,17 @@ public class InitialisationTasklet implements Tasklet {
UsernamePasswordAuthenticationToken auth = new
UsernamePasswordAuthenticationToken(user, user.getPassword(),
user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
ThreadLocalContextUtil.setActionContext(ActionContext.COB);
+
String businessDateString = Objects.requireNonNull((String)
chunkContext.getStepContext().getStepExecution().getJobExecution()
.getExecutionContext().get(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME));
LocalDate businessDate = LocalDate.parse(businessDateString,
DateTimeFormatter.ISO_DATE);
+
businessDates.put(BusinessDateType.COB_DATE, businessDate);
businessDates.put(BusinessDateType.BUSINESS_DATE,
businessDate.plusDays(1));
ThreadLocalContextUtil.setBusinessDates(businessDates);
- log.debug("Initialisation with Business Date [{}], COB Date [{}] and
Action Context [{}]", businessDate.plusDays(1), businessDate,
- ThreadLocalContextUtil.getActionContext());
+
+ log.debug("Initialized context with Business Date [{}], COB Date [{}]
and Action Context [{}]", businessDate.plusDays(1),
+ businessDate, ThreadLocalContextUtil.getActionContext());
return RepeatStatus.FINISHED;
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ContextAwareTaskDecorator.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ContextAwareTaskDecorator.java
index c6bccf0ed9..8cde28bbf3 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ContextAwareTaskDecorator.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/ContextAwareTaskDecorator.java
@@ -18,19 +18,30 @@
*/
package org.apache.fineract.cob.loan;
+import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.domain.FineractContext;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
-import org.jetbrains.annotations.NotNull;
import org.springframework.core.task.TaskDecorator;
+import org.springframework.lang.NonNull;
+/**
+ * Task decorator to ensure proper thread context propagation and cleanup
+ */
+@Slf4j
public class ContextAwareTaskDecorator implements TaskDecorator {
+ @NonNull
@Override
- public Runnable decorate(@NotNull Runnable runnable) {
+ public Runnable decorate(@NonNull final Runnable runnable) {
final FineractContext context = ThreadLocalContextUtil.getContext();
return () -> {
- ThreadLocalContextUtil.init(context);
- runnable.run();
+ try {
+ log.debug("Initializing thread context for decorated task");
+ ThreadLocalContextUtil.init(context);
+ runnable.run();
+ } finally {
+ ThreadLocalContextUtil.reset();
+ }
};
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java
index ed6c1428d2..92d9c49249 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/cob/service/AsyncLoanCOBExecutorServiceImpl.java
@@ -18,7 +18,6 @@
*/
package org.apache.fineract.cob.service;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
@@ -37,11 +36,11 @@ import
org.apache.fineract.infrastructure.core.domain.FineractContext;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.jobs.data.JobParameterDTO;
-import org.apache.fineract.infrastructure.jobs.domain.JobParameterRepository;
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetail;
import
org.apache.fineract.infrastructure.jobs.domain.ScheduledJobDetailRepository;
import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException;
import org.apache.fineract.infrastructure.jobs.service.JobStarter;
+import
org.apache.fineract.infrastructure.jobs.service.SchedulerServiceConstants;
import org.quartz.JobExecutionException;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParametersInvalidException;
@@ -63,12 +62,10 @@ public class AsyncLoanCOBExecutorServiceImpl implements
AsyncLoanCOBExecutorServ
private final JobLocator jobLocator;
private final ScheduledJobDetailRepository scheduledJobDetailRepository;
private final JobStarter jobStarter;
- private final JobParameterRepository jobParameterRepository;
private final RetrieveLoanIdService retrieveLoanIdService;
@Override
@Async(TaskExecutorConstant.LOAN_COB_CATCH_UP_TASK_EXECUTOR_BEAN_NAME)
- @SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
public void executeLoanCOBCatchUpAsync(FineractContext context) {
try {
ThreadLocalContextUtil.init(context);
@@ -80,35 +77,36 @@ public class AsyncLoanCOBExecutorServiceImpl implements
AsyncLoanCOBExecutorServ
?
loanIdAndLastClosedBusinessDate.get(0).getLastClosedBusinessDate()
: cobBusinessDate;
if (DateUtils.isBefore(oldestCOBProcessedDate, cobBusinessDate)) {
-
executeLoanCOBDayByDayUntilCOBBusinessDate(oldestCOBProcessedDate,
cobBusinessDate, context);
+
executeLoanCOBDayByDayUntilCOBBusinessDate(oldestCOBProcessedDate,
cobBusinessDate);
}
} catch (NoSuchJobException e) {
// Throwing an error here is useless as it will be swallowed hence
it is async method
- log.error("", new JobNotFoundException(LoanCOBConstant.JOB_NAME,
e));
+ log.error("Job not found: {}", LoanCOBConstant.JOB_NAME, new
JobNotFoundException(LoanCOBConstant.JOB_NAME, e));
} catch (JobInstanceAlreadyCompleteException | JobRestartException |
JobParametersInvalidException
| JobExecutionAlreadyRunningException | JobExecutionException
e) {
// Throwing an error here is useless as it will be swallowed hence
it is async method
- log.error("", e);
+ log.error("Error executing job", e);
} finally {
ThreadLocalContextUtil.reset();
}
}
- private void executeLoanCOBDayByDayUntilCOBBusinessDate(LocalDate
oldestCOBProcessedDate, LocalDate cobBusinessDate,
- FineractContext context) throws NoSuchJobException,
JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException,
+ private void executeLoanCOBDayByDayUntilCOBBusinessDate(LocalDate
oldestCOBProcessedDate, LocalDate cobBusinessDate)
+ throws NoSuchJobException, JobInstanceAlreadyCompleteException,
JobExecutionAlreadyRunningException,
JobParametersInvalidException, JobRestartException,
JobExecutionException {
Job job = jobLocator.getJob(LoanCOBConstant.JOB_NAME);
ScheduledJobDetail scheduledJobDetail =
scheduledJobDetailRepository.findByJobName(LoanCOBConstant.JOB_HUMAN_READABLE_NAME);
LocalDate executingBusinessDate = oldestCOBProcessedDate.plusDays(1);
+ String tenantIdentifier =
ThreadLocalContextUtil.getTenant().getTenantIdentifier();
+
while (!DateUtils.isAfter(executingBusinessDate, cobBusinessDate)) {
- // Need to reinitialize the thread-local tenant info because after
running the job, it resets the thread
- ThreadLocalContextUtil.init(context);
JobParameterDTO jobParameterDTO = new
JobParameterDTO(LoanCOBConstant.BUSINESS_DATE_PARAMETER_NAME,
executingBusinessDate.format(DateTimeFormatter.ISO_DATE));
JobParameterDTO jobParameterCatchUpDTO = new
JobParameterDTO(LoanCOBConstant.IS_CATCH_UP_PARAMETER_NAME, "true");
+ JobParameterDTO tenantParameterDTO = new
JobParameterDTO(SchedulerServiceConstants.TENANT_IDENTIFIER, tenantIdentifier);
Set<JobParameterDTO> jobParameters = new HashSet<>();
- Collections.addAll(jobParameters, jobParameterDTO,
jobParameterCatchUpDTO);
- jobStarter.run(job, scheduledJobDetail, jobParameters,
ThreadLocalContextUtil.getTenant().getTenantIdentifier());
+ Collections.addAll(jobParameters, jobParameterDTO,
jobParameterCatchUpDTO, tenantParameterDTO);
+ jobStarter.run(job, scheduledJobDetail, jobParameters,
tenantIdentifier);
executingBusinessDate = executingBusinessDate.plusDays(1);
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportEventListener.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportEventListener.java
index 3c13f6afd4..2597c7f783 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportEventListener.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/service/BulkImportEventListener.java
@@ -24,6 +24,8 @@ import java.io.IOException;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.bulkimport.data.BulkImportEvent;
import org.apache.fineract.infrastructure.bulkimport.data.Count;
import org.apache.fineract.infrastructure.bulkimport.data.GlobalEntityType;
@@ -33,134 +35,88 @@ import
org.apache.fineract.infrastructure.bulkimport.importhandler.ImportHandler
import
org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
-import
org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
import
org.apache.fineract.infrastructure.documentmanagement.command.DocumentCommand;
import org.apache.fineract.infrastructure.documentmanagement.domain.Document;
import
org.apache.fineract.infrastructure.documentmanagement.service.DocumentWritePlatformService;
import org.apache.poi.ss.usermodel.Workbook;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
+@Slf4j
@Service
+@RequiredArgsConstructor
public class BulkImportEventListener implements
ApplicationListener<BulkImportEvent> {
- private static final Logger LOG =
LoggerFactory.getLogger(BulkImportEventListener.class);
- private final TenantDetailsService tenantDetailsService;
private final ApplicationContext applicationContext;
private final ImportDocumentRepository importRepository;
private final DocumentWritePlatformService documentService;
- @Autowired
- public BulkImportEventListener(final TenantDetailsService
tenantDetailsService, final ApplicationContext context,
- final ImportDocumentRepository importRepository, final
DocumentWritePlatformService documentService) {
- this.tenantDetailsService = tenantDetailsService;
- this.applicationContext = context;
- this.importRepository = importRepository;
- this.documentService = documentService;
- }
-
@Override
public void onApplicationEvent(final BulkImportEvent event) {
- ThreadLocalContextUtil.init(event.getContext());
- ImportHandler importHandler = null;
- final ImportDocument importDocument =
this.importRepository.findById(event.getImportId()).orElse(null);
- final GlobalEntityType entityType =
GlobalEntityType.fromInt(importDocument.getEntityType());
-
- switch (entityType) {
- case OFFICES:
- importHandler =
this.applicationContext.getBean("officeImportHandler", ImportHandler.class);
- break;
- case CENTERS:
- importHandler =
this.applicationContext.getBean("centerImportHandler", ImportHandler.class);
- break;
- case CHART_OF_ACCOUNTS:
- importHandler =
this.applicationContext.getBean("chartOfAccountsImportHandler",
ImportHandler.class);
- break;
- case CLIENTS_ENTITY:
- importHandler =
this.applicationContext.getBean("clientEntityImportHandler",
ImportHandler.class);
- break;
- case CLIENTS_PERSON:
- importHandler =
this.applicationContext.getBean("clientPersonImportHandler",
ImportHandler.class);
- break;
- case FIXED_DEPOSIT_ACCOUNTS:
- importHandler =
this.applicationContext.getBean("fixedDepositImportHandler",
ImportHandler.class);
- break;
- case FIXED_DEPOSIT_TRANSACTIONS:
- importHandler =
this.applicationContext.getBean("fixedDepositTransactionImportHandler",
ImportHandler.class);
- break;
- case GROUPS:
- importHandler =
this.applicationContext.getBean("groupImportHandler", ImportHandler.class);
- break;
- case GUARANTORS:
- importHandler =
this.applicationContext.getBean("guarantorImportHandler", ImportHandler.class);
- break;
- case GL_JOURNAL_ENTRIES:
- importHandler =
this.applicationContext.getBean("journalEntriesImportHandler",
ImportHandler.class);
- break;
- case LOANS:
- importHandler =
this.applicationContext.getBean("loanImportHandler", ImportHandler.class);
- break;
- case LOAN_TRANSACTIONS:
- importHandler =
this.applicationContext.getBean("loanRepaymentImportHandler",
ImportHandler.class);
- break;
- case RECURRING_DEPOSIT_ACCOUNTS:
- importHandler =
this.applicationContext.getBean("recurringDepositImportHandler",
ImportHandler.class);
- break;
- case RECURRING_DEPOSIT_ACCOUNTS_TRANSACTIONS:
- importHandler =
this.applicationContext.getBean("recurringDepositTransactionImportHandler",
ImportHandler.class);
- break;
- case SAVINGS_ACCOUNT:
- importHandler =
this.applicationContext.getBean("savingsImportHandler", ImportHandler.class);
- break;
- case SAVINGS_TRANSACTIONS:
- importHandler =
this.applicationContext.getBean("savingsTransactionImportHandler",
ImportHandler.class);
- break;
- case SHARE_ACCOUNTS:
- importHandler =
this.applicationContext.getBean("sharedAccountImportHandler",
ImportHandler.class);
- break;
- case STAFF:
- importHandler =
this.applicationContext.getBean("staffImportHandler", ImportHandler.class);
- break;
- case USERS:
- importHandler =
this.applicationContext.getBean("userImportHandler", ImportHandler.class);
- break;
- default:
- throw new
GeneralPlatformDomainRuleException("error.msg.unable.to.find.resource", "Unable
to find requested resource");
+ try {
+ ThreadLocalContextUtil.init(event.getContext());
+ final ImportDocument importDocument =
this.importRepository.findById(event.getImportId()).orElse(null);
+ final GlobalEntityType entityType =
GlobalEntityType.fromInt(importDocument.getEntityType());
- }
+ final ImportHandler importHandler = switch (entityType) {
+ case OFFICES ->
this.applicationContext.getBean("officeImportHandler", ImportHandler.class);
+ case CENTERS ->
this.applicationContext.getBean("centerImportHandler", ImportHandler.class);
+ case CHART_OF_ACCOUNTS ->
this.applicationContext.getBean("chartOfAccountsImportHandler",
ImportHandler.class);
+ case CLIENTS_ENTITY ->
this.applicationContext.getBean("clientEntityImportHandler",
ImportHandler.class);
+ case CLIENTS_PERSON ->
this.applicationContext.getBean("clientPersonImportHandler",
ImportHandler.class);
+ case FIXED_DEPOSIT_ACCOUNTS ->
this.applicationContext.getBean("fixedDepositImportHandler",
ImportHandler.class);
+ case FIXED_DEPOSIT_TRANSACTIONS ->
+
this.applicationContext.getBean("fixedDepositTransactionImportHandler",
ImportHandler.class);
+ case GROUPS ->
this.applicationContext.getBean("groupImportHandler", ImportHandler.class);
+ case GUARANTORS ->
this.applicationContext.getBean("guarantorImportHandler", ImportHandler.class);
+ case GL_JOURNAL_ENTRIES ->
this.applicationContext.getBean("journalEntriesImportHandler",
ImportHandler.class);
+ case LOANS ->
this.applicationContext.getBean("loanImportHandler", ImportHandler.class);
+ case LOAN_TRANSACTIONS ->
this.applicationContext.getBean("loanRepaymentImportHandler",
ImportHandler.class);
+ case RECURRING_DEPOSIT_ACCOUNTS ->
this.applicationContext.getBean("recurringDepositImportHandler",
ImportHandler.class);
+ case RECURRING_DEPOSIT_ACCOUNTS_TRANSACTIONS ->
+
this.applicationContext.getBean("recurringDepositTransactionImportHandler",
ImportHandler.class);
+ case SAVINGS_ACCOUNT ->
this.applicationContext.getBean("savingsImportHandler", ImportHandler.class);
+ case SAVINGS_TRANSACTIONS ->
this.applicationContext.getBean("savingsTransactionImportHandler",
ImportHandler.class);
+ case SHARE_ACCOUNTS ->
this.applicationContext.getBean("sharedAccountImportHandler",
ImportHandler.class);
+ case STAFF ->
this.applicationContext.getBean("staffImportHandler", ImportHandler.class);
+ case USERS ->
this.applicationContext.getBean("userImportHandler", ImportHandler.class);
+ default ->
+ throw new
GeneralPlatformDomainRuleException("error.msg.unable.to.find.resource", "Unable
to find requested resource");
+ };
- final Workbook workbook = event.getWorkbook();
- final Count count = importHandler.process(workbook, event.getLocale(),
event.getDateFormat());
- importDocument.update(DateUtils.getLocalDateTimeOfTenant(),
count.getSuccessCount(), count.getErrorCount());
- this.importRepository.saveAndFlush(importDocument);
+ final Workbook workbook = event.getWorkbook();
+ final Count count = importHandler.process(workbook,
event.getLocale(), event.getDateFormat());
+ importDocument.update(DateUtils.getLocalDateTimeOfTenant(),
count.getSuccessCount(), count.getErrorCount());
+ this.importRepository.saveAndFlush(importDocument);
- final Set<String> modifiedParams = new HashSet<>();
- modifiedParams.add("fileName");
- modifiedParams.add("size");
- modifiedParams.add("type");
- modifiedParams.add("location");
- Document document = importDocument.getDocument();
+ final Set<String> modifiedParams = new HashSet<>();
+ modifiedParams.add("fileName");
+ modifiedParams.add("size");
+ modifiedParams.add("type");
+ modifiedParams.add("location");
+ Document document = importDocument.getDocument();
- DocumentCommand documentCommand = new DocumentCommand(modifiedParams,
document.getId(), entityType.name(), null, document.getName(),
- document.getFileName(), document.getSize(),
URLConnection.guessContentTypeFromName(document.getFileName()), null, null);
+ DocumentCommand documentCommand = new
DocumentCommand(modifiedParams, document.getId(), entityType.name(), null,
+ document.getName(), document.getFileName(),
document.getSize(),
+
URLConnection.guessContentTypeFromName(document.getFileName()), null, null);
- final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- try {
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
- workbook.write(bos);
- } finally {
- bos.close();
+ try {
+ workbook.write(bos);
+ } finally {
+ bos.close();
+ }
+ } catch (IOException io) {
+ log.error("Problem occurred in onApplicationEvent function",
io);
}
- } catch (IOException io) {
- LOG.error("Problem occurred in onApplicationEvent function", io);
+ byte[] bytes = bos.toByteArray();
+ ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ this.documentService.updateDocument(documentCommand, bis);
+ } finally {
+ ThreadLocalContextUtil.reset();
}
- byte[] bytes = bos.toByteArray();
- ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
- this.documentService.updateDocument(documentCommand, bis);
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/listener/FineractHookListener.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/listener/FineractHookListener.java
index 53e10c4dd4..2cfda6434f 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/listener/FineractHookListener.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/listener/FineractHookListener.java
@@ -42,28 +42,33 @@ public class FineractHookListener implements HookListener {
@Override
public void onApplicationEvent(final HookEvent event) {
- ThreadLocalContextUtil.init(event.getContext());
+ try {
+ ThreadLocalContextUtil.init(event.getContext());
- final AppUser appUser = event.getAppUser();
+ final AppUser appUser = event.getAppUser();
- final HookEventSource hookEventSource = (HookEventSource)
event.getSource();
- final FineractContext fineractContext = event.getContext();
- final String entityName = hookEventSource.getEntityName();
- final String actionName = hookEventSource.getActionName();
- final String payload = event.getPayload();
+ final HookEventSource hookEventSource = (HookEventSource)
event.getSource();
+ final FineractContext fineractContext = event.getContext();
+ final String entityName = hookEventSource.getEntityName();
+ final String actionName = hookEventSource.getActionName();
+ final String payload = event.getPayload();
- final List<Hook> hooks =
hookReadPlatformService.retrieveHooksByEvent(hookEventSource.getEntityName(),
- hookEventSource.getActionName());
+ final List<Hook> hooks =
hookReadPlatformService.retrieveHooksByEvent(hookEventSource.getEntityName(),
+ hookEventSource.getActionName());
- for (final Hook hook : hooks) {
- final HookProcessor processor =
hookProcessorProvider.getProcessor(hook);
- try {
- processor.process(hook, payload, entityName, actionName,
fineractContext);
- } catch (Throwable e) {
- log.error("Hook {} failed in HookProcessor {} for
tenantIdentifier/user {}/{}, entityName: {}, actionName: {}, payload {} ",
- hook.getId(), processor.getClass().getSimpleName(),
fineractContext.getTenantContext().getTenantIdentifier(),
- appUser.getDisplayName(), entityName, actionName,
payload, e);
+ for (final Hook hook : hooks) {
+ final HookProcessor processor =
hookProcessorProvider.getProcessor(hook);
+ try {
+ processor.process(hook, payload, entityName, actionName,
fineractContext);
+ } catch (Throwable e) {
+ log.error(
+ "Hook {} failed in HookProcessor {} for
tenantIdentifier/user {}/{}, entityName: {}, actionName: {}, payload {} ",
+ hook.getId(),
processor.getClass().getSimpleName(),
fineractContext.getTenantContext().getTenantIdentifier(),
+ appUser.getDisplayName(), entityName, actionName,
payload, e);
+ }
}
+ } finally {
+ ThreadLocalContextUtil.reset();
}
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobStarter.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobStarter.java
index a90fb90291..df91753658 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobStarter.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobStarter.java
@@ -78,16 +78,24 @@ public class JobStarter {
public JobExecution run(Job job, ScheduledJobDetail scheduledJobDetail,
Set<JobParameterDTO> jobParameterDTOSet,
String tenantIdentifier) throws
JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException,
JobParametersInvalidException, JobRestartException,
JobExecutionException {
+
+ boolean contextInitialized = false;
+ final FineractPlatformTenant existingTenant =
ThreadLocalContextUtil.getTenant();
+
try {
- FineractPlatformTenant tenant =
tenantDetailsService.loadTenantById(tenantIdentifier);
- ThreadLocalContextUtil.setTenant(tenant);
- AppUser user = this.userRepository.fetchSystemUser();
- UsernamePasswordAuthenticationToken auth = new
UsernamePasswordAuthenticationToken(user, user.getPassword(),
- user.getAuthorities());
- SecurityContextHolder.getContext().setAuthentication(auth);
- HashMap<BusinessDateType, LocalDate> businessDates =
businessDateReadPlatformService.getBusinessDates();
- ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
- ThreadLocalContextUtil.setBusinessDates(businessDates);
+ if (existingTenant == null) {
+ contextInitialized = true;
+ FineractPlatformTenant tenant =
tenantDetailsService.loadTenantById(tenantIdentifier);
+ ThreadLocalContextUtil.setTenant(tenant);
+ AppUser user = this.userRepository.fetchSystemUser();
+ UsernamePasswordAuthenticationToken auth = new
UsernamePasswordAuthenticationToken(user, user.getPassword(),
+ user.getAuthorities());
+ SecurityContextHolder.getContext().setAuthentication(auth);
+ HashMap<BusinessDateType, LocalDate> businessDates =
businessDateReadPlatformService.getBusinessDates();
+ ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
+ ThreadLocalContextUtil.setBusinessDates(businessDates);
+ }
+
Map<String, JobParameter<?>> jobParameterMap =
getJobParameter(scheduledJobDetail);
JobParameters jobParameters = new
JobParametersBuilder(jobExplorer).getNextJobParameters(job)
.addJobParameters(new JobParameters(jobParameterMap))
@@ -101,7 +109,9 @@ public class JobStarter {
}
return result;
} finally {
- ThreadLocalContextUtil.reset();
+ if (contextInitialized) {
+ ThreadLocalContextUtil.reset();
+ }
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java
index 00b169169e..a87fc122fb 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerJobListener.java
@@ -20,13 +20,11 @@ package org.apache.fineract.infrastructure.jobs.service;
import java.util.Date;
import lombok.RequiredArgsConstructor;
-import
org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
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;
import org.apache.fineract.infrastructure.jobs.domain.ScheduledJobRunHistory;
-import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
@@ -43,8 +41,6 @@ import org.springframework.stereotype.Component;
public class SchedulerJobListener implements JobListener {
private final SchedularWritePlatformService schedularService;
- private final AppUserRepositoryWrapper userRepository;
- private final BusinessDateReadPlatformService
businessDateReadPlatformService;
private final TenantDetailsService tenantDetailsService;
private int stackTraceLevel = 0;
@@ -61,10 +57,17 @@ public class SchedulerJobListener implements JobListener {
@Override
public void jobWasExecuted(final JobExecutionContext context, final
JobExecutionException jobException) {
+ final String tenantIdentifier =
context.getMergedJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
+ final FineractPlatformTenant existingTenant =
ThreadLocalContextUtil.getTenant();
+ boolean contextInitialized = false;
+
try {
- String tenantIdentifier =
context.getMergedJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
- FineractPlatformTenant tenant =
tenantDetailsService.loadTenantById(tenantIdentifier);
- ThreadLocalContextUtil.setTenant(tenant);
+ if (existingTenant == null ||
!existingTenant.getTenantIdentifier().equals(tenantIdentifier)) {
+ contextInitialized = true;
+ final FineractPlatformTenant tenant =
tenantDetailsService.loadTenantById(tenantIdentifier);
+ ThreadLocalContextUtil.setTenant(tenant);
+ }
+
final Trigger trigger = context.getTrigger();
final JobKey key = context.getJobDetail().getKey();
@@ -105,11 +108,12 @@ public class SchedulerJobListener implements JobListener {
final ScheduledJobRunHistory runHistory = new
ScheduledJobRunHistory().setScheduledJobDetail(scheduledJobDetails)
.setVersion(version).setStartTime(context.getFireTime()).setEndTime(new
Date()).setStatus(status)
.setErrorMessage(errorMessage).setTriggerType(triggerType).setErrorLog(errorLog);
- // scheduledJobDetails.addRunHistory(runHistory);
this.schedularService.saveOrUpdate(scheduledJobDetails,
runHistory);
} finally {
- ThreadLocalContextUtil.reset();
+ if (contextInitialized) {
+ ThreadLocalContextUtil.reset();
+ }
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java
index ead4e9ead3..c889bbd949 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerTriggerListener.java
@@ -50,14 +50,22 @@ public class SchedulerTriggerListener implements
TriggerListener {
@Override
public boolean vetoJobExecution(final Trigger trigger, final
JobExecutionContext context) {
- String tenantIdentifier =
trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
- FineractPlatformTenant tenant =
tenantDetailsService.loadTenantById(tenantIdentifier);
+ final String tenantIdentifier =
trigger.getJobDataMap().getString(SchedulerServiceConstants.TENANT_IDENTIFIER);
+ final FineractPlatformTenant existingTenant =
ThreadLocalContextUtil.getTenant();
+ boolean contextInitialized = false;
+
try {
- ThreadLocalContextUtil.setTenant(tenant);
- ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
+ if (existingTenant == null ||
!existingTenant.getTenantIdentifier().equals(tenantIdentifier)) {
+ contextInitialized = true;
+ final FineractPlatformTenant tenant =
tenantDetailsService.loadTenantById(tenantIdentifier);
+ ThreadLocalContextUtil.setTenant(tenant);
+ ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
+ }
return schedulerVetoer.veto(trigger, context);
} finally {
- ThreadLocalContextUtil.reset();
+ if (contextInitialized) {
+ ThreadLocalContextUtil.reset();
+ }
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobListener.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobListener.java
index 21051c12ba..c705a5582c 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobListener.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/StuckJobListener.java
@@ -25,7 +25,6 @@ import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import
org.apache.fineract.infrastructure.businessdate.service.BusinessDateReadPlatformService;
import org.apache.fineract.infrastructure.core.domain.ActionContext;
-import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.JdbcTemplateFactory;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import
org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
@@ -37,6 +36,7 @@ import
org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.lang.NonNull;
import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@@ -55,30 +55,32 @@ public class StuckJobListener implements
ApplicationListener<ContextRefreshedEve
private final AppUserRepositoryWrapper userRepository;
@Override
- public void onApplicationEvent(ContextRefreshedEvent event) {
- if (!jobRegistry.getJobNames().isEmpty()) {
- List<FineractPlatformTenant> allTenants =
tenantDetailsService.findAllTenants();
- allTenants.forEach(tenant -> {
+ public void onApplicationEvent(@NonNull final ContextRefreshedEvent event)
{
+ if (jobRegistry.getJobNames().isEmpty()) {
+ return;
+ }
+ tenantDetailsService.findAllTenants().forEach(tenant -> {
+ try {
ThreadLocalContextUtil.setTenant(tenant);
- NamedParameterJdbcTemplate namedParameterJdbcTemplate =
jdbcTemplateFactory.createNamedParameterJdbcTemplate(tenant);
- List<String> stuckJobNames =
jobExecutionRepository.getStuckJobNames(namedParameterJdbcTemplate);
+ final NamedParameterJdbcTemplate namedParameterJdbcTemplate =
jdbcTemplateFactory.createNamedParameterJdbcTemplate(tenant);
+ final List<String> stuckJobNames =
jobExecutionRepository.getStuckJobNames(namedParameterJdbcTemplate);
if (!stuckJobNames.isEmpty()) {
try {
- HashMap<BusinessDateType, LocalDate> businessDates =
businessDateReadPlatformService.getBusinessDates();
+ final HashMap<BusinessDateType, LocalDate>
businessDates = businessDateReadPlatformService.getBusinessDates();
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
ThreadLocalContextUtil.setBusinessDates(businessDates);
- AppUser user = userRepository.fetchSystemUser();
- UsernamePasswordAuthenticationToken auth = new
UsernamePasswordAuthenticationToken(user, user.getPassword(),
+ final AppUser user = userRepository.fetchSystemUser();
+ final UsernamePasswordAuthenticationToken auth = new
UsernamePasswordAuthenticationToken(user, user.getPassword(),
user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
stuckJobNames.forEach(stuckJobExecutorService::resumeStuckJob);
- } catch (Exception e) {
- throw new RuntimeException("Error while trying to
restart stuck jobs", e);
} finally {
- ThreadLocalContextUtil.reset();
+
SecurityContextHolder.getContext().setAuthentication(null);
}
}
- });
- }
+ } finally {
+ ThreadLocalContextUtil.reset();
+ }
+ });
}
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
index e760ccbd2d..278f4fc7b6 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/sms/scheduler/SmsMessageScheduledJobServiceImpl.java
@@ -155,8 +155,12 @@ public class SmsMessageScheduledJobServiceImpl implements
SmsMessageScheduledJob
@Override
public void run() {
- ThreadLocalContextUtil.init(context);
- connectAndSendToIntermediateServer(apiQueueResourceDatas);
+ try {
+ ThreadLocalContextUtil.init(context);
+ connectAndSendToIntermediateServer(apiQueueResourceDatas);
+ } finally {
+ ThreadLocalContextUtil.reset();
+ }
}
@Override
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/springbatch/InputChannelInterceptor.java
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/springbatch/InputChannelInterceptor.java
index 0d372435a5..52ac7be931 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/springbatch/InputChannelInterceptor.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/springbatch/InputChannelInterceptor.java
@@ -18,29 +18,43 @@
*/
package org.apache.fineract.infrastructure.springbatch;
+import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
-import org.jetbrains.annotations.NotNull;
import org.springframework.batch.integration.partition.StepExecutionRequest;
+import org.springframework.lang.NonNull;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.ExecutorChannelInterceptor;
import org.springframework.messaging.support.GenericMessage;
+/**
+ * Channel interceptor for Spring Batch message handling that ensures
ThreadLocal context is properly initialized before
+ * message handling and cleaned up afterwards
+ */
+@Slf4j
public class InputChannelInterceptor implements ExecutorChannelInterceptor {
@Override
- public Message<StepExecutionRequest> beforeHandle(Message<?> message,
@NotNull MessageChannel channel,
- @NotNull MessageHandler handler) {
+ public Message<StepExecutionRequest> beforeHandle(@NonNull final
Message<?> message, @NonNull final MessageChannel channel,
+ @NonNull final MessageHandler handler) {
return beforeHandleMessage(message);
}
+ @Override
+ public void afterMessageHandled(@NonNull final Message<?> message,
@NonNull final MessageChannel channel,
+ @NonNull final MessageHandler handler, final Exception ex) {
+ log.debug("Cleaning up ThreadLocal context after message handling");
+ ThreadLocalContextUtil.reset();
+ }
+
public Message<StepExecutionRequest> beforeHandleMessage(Message<?>
message) {
return new GenericMessage<>(beforeHandleMessage((ContextualMessage)
message.getPayload()));
}
public StepExecutionRequest beforeHandleMessage(ContextualMessage
contextualMessage) {
+ log.debug("Initializing ThreadLocal context for message handling");
ThreadLocalContextUtil.init(contextualMessage.getContext());
ThreadLocalContextUtil.setActionContext(ActionContext.COB);
return contextualMessage.getStepExecutionRequest();
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringNotificationEventListener.java
b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringNotificationEventListener.java
index 4d427725b4..b01903eaba 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringNotificationEventListener.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/SpringNotificationEventListener.java
@@ -24,6 +24,7 @@ import
org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.notification.data.NotificationData;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Profile;
+import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
@Component
@@ -35,10 +36,15 @@ public class SpringNotificationEventListener implements
ApplicationListener<Noti
private final NotificationEventListener notificationEventListener;
@Override
- public void onApplicationEvent(NotificationEvent event) {
+ public void onApplicationEvent(@NonNull final NotificationEvent event) {
log.debug("Processing Spring notification event {}", event);
- ThreadLocalContextUtil.init(event.getContext());
- NotificationData notificationData = event.getNotificationData();
- notificationEventListener.receive(notificationData);
+ try {
+ ThreadLocalContextUtil.init(event.getContext());
+ final NotificationData notificationData =
event.getNotificationData();
+ notificationEventListener.receive(notificationData);
+ } finally {
+ ThreadLocalContextUtil.reset();
+ }
}
+
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingTasklet.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingTasklet.java
index 221a8336f2..0ac4a2c358 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingTasklet.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/postinterestforsavings/PostInterestForSavingTasklet.java
@@ -117,23 +117,27 @@ public class PostInterestForSavingTasklet implements
Tasklet {
FineractContext context = ThreadLocalContextUtil.getContext();
Callable<Void> fetchData = () -> {
- ThreadLocalContextUtil.init(context);
- Long maxId = maxSavingsIdInList;
- if (!queue.isEmpty()) {
- maxId = Math.max(maxSavingsIdInList,
queue.element().get(queue.element().size() - 1).getId());
- }
+ try {
+ ThreadLocalContextUtil.init(context);
+ Long maxId = maxSavingsIdInList;
+ if (!queue.isEmpty()) {
+ maxId = Math.max(maxSavingsIdInList,
queue.element().get(queue.element().size() - 1).getId());
+ }
- while (queue.size() <= QUEUE_SIZE) {
- log.debug("Fetching while threads are running!");
- List<SavingsAccountData> savingsAccountDataList =
Collections.synchronizedList(this.savingAccountReadPlatformService
-
.retrieveAllSavingsDataForInterestPosting(backdatedTxnsAllowedTill, pageSize,
ACTIVE.getValue(), maxId));
- if (savingsAccountDataList.isEmpty()) {
- break;
+ while (queue.size() <= QUEUE_SIZE) {
+ log.debug("Fetching while threads are running!");
+ List<SavingsAccountData> savingsAccountDataList =
Collections.synchronizedList(this.savingAccountReadPlatformService
+
.retrieveAllSavingsDataForInterestPosting(backdatedTxnsAllowedTill, pageSize,
ACTIVE.getValue(), maxId));
+ if (savingsAccountDataList.isEmpty()) {
+ break;
+ }
+ maxId =
savingsAccountDataList.get(savingsAccountDataList.size() - 1).getId();
+ queue.add(savingsAccountDataList);
}
- maxId =
savingsAccountDataList.get(savingsAccountDataList.size() - 1).getId();
- queue.add(savingsAccountDataList);
+ return null;
+ } finally {
+ ThreadLocalContextUtil.reset();
}
- return null;
};
posters.add(fetchData);
diff --git
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPosterTask.java
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPosterTask.java
index 3ebda9bed2..2b8fa5a7b6 100644
---
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPosterTask.java
+++
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPosterTask.java
@@ -25,6 +25,7 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.domain.FineractContext;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
/**
@@ -40,10 +41,14 @@ public class SavingsSchedularInterestPosterTask implements
Callable<Void> {
private FineractContext context;
@Override
- public Void call() throws
org.apache.fineract.infrastructure.jobs.exception.JobExecutionException {
- ThreadLocalContextUtil.init(context);
- interestPoster.postInterest();
- return null;
+ public Void call() throws JobExecutionException {
+ try {
+ ThreadLocalContextUtil.init(context);
+ interestPoster.postInterest();
+ return null;
+ } finally {
+ ThreadLocalContextUtil.reset();
+ }
}
public void setSavingAccounts(Collection<SavingsAccountData>
savingAccounts) {