FINERACT-2389: Fix for rounding mode not being initialized whenever a new tenant is added to the system without a restart

This commit is contained in:
Arnold Galovics
2025-11-06 14:46:25 +01:00
committed by Arnold Gálovics
parent 63c220e5d2
commit 5079203d57
7 changed files with 34 additions and 3 deletions
@@ -26,6 +26,8 @@ import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurati
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
@@ -40,7 +42,10 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor @RequiredArgsConstructor
public class MoneyHelperInitializationService { public class MoneyHelperInitializationService {
private final GlobalConfigurationRepositoryWrapper globalConfigurationRepository; // TODO: this is preventing the circular dependency...
@Lazy
@Autowired
private GlobalConfigurationRepositoryWrapper globalConfigurationRepository;
/** /**
* Initialize MoneyHelper for a specific tenant. This method should be called during tenant setup and whenever * Initialize MoneyHelper for a specific tenant. This method should be called during tenant setup and whenever
@@ -56,6 +61,7 @@ public class MoneyHelperInitializationService {
String tenantIdentifier = tenant.getTenantIdentifier(); String tenantIdentifier = tenant.getTenantIdentifier();
FineractPlatformTenant originalTenant = ThreadLocalContextUtil.getTenant();
try { try {
// Set tenant context to read configuration // Set tenant context to read configuration
ThreadLocalContextUtil.setTenant(tenant); ThreadLocalContextUtil.setTenant(tenant);
@@ -69,8 +75,7 @@ public class MoneyHelperInitializationService {
log.error("Failed to initialize MoneyHelper for tenant '{}'", tenantIdentifier, e); log.error("Failed to initialize MoneyHelper for tenant '{}'", tenantIdentifier, e);
throw new RuntimeException("Failed to initialize MoneyHelper for tenant: " + tenantIdentifier, e); throw new RuntimeException("Failed to initialize MoneyHelper for tenant: " + tenantIdentifier, e);
} finally { } finally {
// Clear tenant context ThreadLocalContextUtil.setTenant(originalTenant);
ThreadLocalContextUtil.clearTenant();
} }
} }
@@ -85,6 +90,10 @@ public class MoneyHelperInitializationService {
return MoneyHelper.isTenantInitialized(tenantIdentifier); return MoneyHelper.isTenantInitialized(tenantIdentifier);
} }
public boolean isTenantInitialized(FineractPlatformTenant tenant) {
return isTenantInitialized(tenant.getTenantIdentifier());
}
/** /**
* Get the rounding mode from configuration with fallback to default. * Get the rounding mode from configuration with fallback to default.
* *
@@ -18,14 +18,17 @@
*/ */
package org.apache.fineract.infrastructure.core.service.database; package org.apache.fineract.infrastructure.core.service.database;
import com.google.common.collect.Sets;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import javax.sql.DataSource; import javax.sql.DataSource;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.configuration.service.MoneyHelperInitializationService;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
@@ -53,6 +56,9 @@ public class TomcatJdbcDataSourcePerTenantService implements RoutingDataSourceSe
private final DataSourcePerTenantServiceFactory dataSourcePerTenantServiceFactory; private final DataSourcePerTenantServiceFactory dataSourcePerTenantServiceFactory;
private final MoneyHelperInitializationService moneyHelperInitializationService;
private final Set<Long> tenantMoneyInitializingSet = Sets.newConcurrentHashSet();
@Override @Override
public DataSource retrieveDataSource() { public DataSource retrieveDataSource() {
// default to tenant database datasource // default to tenant database datasource
@@ -66,7 +72,23 @@ public class TomcatJdbcDataSourcePerTenantService implements RoutingDataSourceSe
// appropriate datasource for that tenant. // appropriate datasource for that tenant.
actualDataSource = TENANT_TO_DATA_SOURCE_MAP.computeIfAbsent(tenantConnectionKey, actualDataSource = TENANT_TO_DATA_SOURCE_MAP.computeIfAbsent(tenantConnectionKey,
(key) -> dataSourcePerTenantServiceFactory.createNewDataSourceFor(tenant, tenantConnection)); (key) -> dataSourcePerTenantServiceFactory.createNewDataSourceFor(tenant, tenantConnection));
}
// TODO: This is definitely not the optimal place to initialize the rounding modes
// Preferably nothing should use a statically referenced context and the initialization
// should happen within the rounding mode retrieval
if (tenant != null) {
Long connectionId = tenant.getConnection().getConnectionId();
if (!tenantMoneyInitializingSet.contains(connectionId) && !moneyHelperInitializationService.isTenantInitialized(tenant)) {
// Double check to prevent visibility and race-condition issues
synchronized (tenantMoneyInitializingSet) {
if (!tenantMoneyInitializingSet.contains(connectionId)) {
tenantMoneyInitializingSet.add(connectionId);
moneyHelperInitializationService.initializeTenantRoundingMode(tenant);
tenantMoneyInitializingSet.remove(connectionId);
}
}
}
} }
return actualDataSource; return actualDataSource;