FINERACT-2181: Money helper exception handling

This commit is contained in:
Adam Saghy
2025-07-21 18:03:33 +02:00
parent 27bf6d05d5
commit 59409550a7
4 changed files with 32 additions and 76 deletions
@@ -35,7 +35,7 @@ import org.springframework.stereotype.Service;
* A JDBC implementation of {@link TenantDetailsService} for loading a tenants details by a * A JDBC implementation of {@link TenantDetailsService} for loading a tenants details by a
* <code>tenantIdentifier</code>. * <code>tenantIdentifier</code>.
*/ */
@Service @Service("tenantDetailsService")
public class JdbcTenantDetailsService implements TenantDetailsService { public class JdbcTenantDetailsService implements TenantDetailsService {
private final JdbcTemplate jdbcTemplate; private final JdbcTemplate jdbcTemplate;
@@ -33,7 +33,6 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
public final class MoneyHelper { public final class MoneyHelper {
public static final int PRECISION = 19; public static final int PRECISION = 19;
private static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_EVEN;
private static final ConcurrentHashMap<String, RoundingMode> roundingModeCache = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, RoundingMode> roundingModeCache = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, MathContext> mathContextCache = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, MathContext> mathContextCache = new ConcurrentHashMap<>();
@@ -62,7 +61,7 @@ public final class MoneyHelper {
// Clear math context cache to force recreation with new rounding mode // Clear math context cache to force recreation with new rounding mode
mathContextCache.remove(tenantIdentifier); mathContextCache.remove(tenantIdentifier);
log.info("Initialized rounding mode for tenant {}: {}", tenantIdentifier, roundingMode.name()); log.info("Initialized rounding mode for tenant `{}`: {}", tenantIdentifier, roundingMode.name());
} }
/** /**
@@ -77,9 +76,7 @@ public final class MoneyHelper {
RoundingMode roundingMode = roundingModeCache.get(tenantId); RoundingMode roundingMode = roundingModeCache.get(tenantId);
if (roundingMode == null) { if (roundingMode == null) {
// For integration tests and development, use default if not initialized throw new IllegalStateException("Rounding mode is not initialized for tenant: " + tenantId);
log.warn("Rounding mode not initialized for tenant: {}. Using default: {}", tenantId, DEFAULT_ROUNDING_MODE);
return DEFAULT_ROUNDING_MODE;
} }
return roundingMode; return roundingMode;
} }
@@ -18,7 +18,6 @@
*/ */
package org.apache.fineract.infrastructure.configuration.service; package org.apache.fineract.infrastructure.configuration.service;
import java.math.RoundingMode;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
@@ -66,9 +65,6 @@ public class MoneyHelperInitializationService {
// Initialize MoneyHelper for this tenant // Initialize MoneyHelper for this tenant
MoneyHelper.initializeTenantRoundingMode(tenantIdentifier, roundingModeValue); MoneyHelper.initializeTenantRoundingMode(tenantIdentifier, roundingModeValue);
log.info("MoneyHelper initialized for tenant '{}' with rounding mode: {}", tenantIdentifier, roundingModeValue);
} catch (Exception e) { } catch (Exception e) {
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);
@@ -90,26 +86,13 @@ public class MoneyHelperInitializationService {
} }
/** /**
* Get the rounding mode from configuration with fallback to default. This method safely handles cases where * Get the rounding mode from configuration with fallback to default.
* configuration might not be available.
* *
* @return the rounding mode value * @return the rounding mode value
*/ */
private int getRoundingModeFromConfiguration() { private int getRoundingModeFromConfiguration() {
try { GlobalConfigurationProperty roundingModeProperty = globalConfigurationRepository
GlobalConfigurationProperty roundingModeProperty = globalConfigurationRepository .findOneByNameWithNotFoundDetection(GlobalConfigurationConstants.ROUNDING_MODE);
.findOneByNameWithNotFoundDetection(GlobalConfigurationConstants.ROUNDING_MODE); return roundingModeProperty.getValue().intValue();
if (roundingModeProperty != null && roundingModeProperty.getValue() != null) {
return roundingModeProperty.getValue().intValue();
}
} catch (Exception e) {
log.warn("Failed to read rounding mode from configuration: {}", e.getMessage());
}
// Default to HALF_UP if configuration is not available
int defaultRoundingMode = RoundingMode.HALF_UP.ordinal();
log.info("Using default rounding mode: {} (HALF_UP)", defaultRoundingMode);
return defaultRoundingMode;
} }
} }
@@ -23,9 +23,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService; import org.apache.fineract.infrastructure.core.service.tenant.TenantDetailsService;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.event.EventListener; import org.springframework.context.annotation.DependsOn;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
@@ -35,7 +34,8 @@ import org.springframework.stereotype.Service;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class MoneyHelperStartupInitializationService { @DependsOn({ "tenantDetailsService", "moneyHelperInitializationService", "tenantDatabaseUpgradeService" })
public class MoneyHelperStartupInitializationService implements InitializingBean {
private final TenantDetailsService tenantDetailsService; private final TenantDetailsService tenantDetailsService;
private final MoneyHelperInitializationService moneyHelperInitializationService; private final MoneyHelperInitializationService moneyHelperInitializationService;
@@ -43,54 +43,30 @@ public class MoneyHelperStartupInitializationService {
/** /**
* Initialize MoneyHelper for all tenants after the application is ready. This method runs after * Initialize MoneyHelper for all tenants after the application is ready. This method runs after
* ApplicationReadyEvent to ensure all database migrations and tenant configurations are complete. * ApplicationReadyEvent to ensure all database migrations and tenant configurations are complete.
*
* If it fails (for any reason), it will fail the application startup!
*
*/ */
@EventListener(ApplicationReadyEvent.class) @Override
@Order(1000) // Run after other startup processes public void afterPropertiesSet() throws Exception {
public void initializeMoneyHelperForAllTenants() {
log.info("Starting MoneyHelper initialization for all tenants..."); log.info("Starting MoneyHelper initialization for all tenants...");
try { List<FineractPlatformTenant> tenants = tenantDetailsService.findAllTenants();
List<FineractPlatformTenant> tenants = tenantDetailsService.findAllTenants(); if (tenants.isEmpty()) {
log.warn("No tenants found during MoneyHelper initialization");
if (tenants.isEmpty()) { return;
log.warn("No tenants found during MoneyHelper initialization");
return;
}
int successCount = 0;
int failureCount = 0;
for (FineractPlatformTenant tenant : tenants) {
try {
String tenantIdentifier = tenant.getTenantIdentifier();
// Check if already initialized (in case of restart scenarios)
if (moneyHelperInitializationService.isTenantInitialized(tenantIdentifier)) {
log.debug("MoneyHelper already initialized for tenant: {}", tenantIdentifier);
successCount++;
continue;
}
// Initialize MoneyHelper for this tenant
moneyHelperInitializationService.initializeTenantRoundingMode(tenant);
successCount++;
} catch (Exception e) {
failureCount++;
log.error("Failed to initialize MoneyHelper for tenant '{}'", tenant.getTenantIdentifier(), e);
}
}
log.info("MoneyHelper initialization completed - Success: {}, Failures: {}, Total: {}", successCount, failureCount,
tenants.size());
if (failureCount > 0) {
log.warn("Some tenants failed MoneyHelper initialization. "
+ "These tenants may experience issues with rounding mode configuration.");
}
} catch (Exception e) {
log.error("Critical error during MoneyHelper initialization for all tenants", e);
} }
for (FineractPlatformTenant tenant : tenants) {
String tenantIdentifier = tenant.getTenantIdentifier();
// Check if already initialized (in case of restart scenarios)
if (moneyHelperInitializationService.isTenantInitialized(tenantIdentifier)) {
log.debug("MoneyHelper already initialized for tenant: {}", tenantIdentifier);
continue;
}
// Initialize MoneyHelper for this tenant
moneyHelperInitializationService.initializeTenantRoundingMode(tenant);
}
log.info("MoneyHelper initialization completed");
} }
} }