diff --git a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle index 1a21f4ce1..01e25361f 100644 --- a/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle +++ b/buildSrc/src/main/groovy/org.apache.fineract.dependencies.gradle @@ -247,5 +247,7 @@ dependencyManagement { dependency 'io.cucumber:cucumber-java8:7.20.1' dependency 'io.cucumber:cucumber-junit-platform-engine:7.20.1' dependency 'io.cucumber:cucumber-spring:7.20.1' + + dependency 'org.reflections:reflections:0.10.2' } } diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index 961d5a582..69de79327 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.core.config; +import java.time.Duration; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -81,6 +82,8 @@ public class FineractProperties { private FineractSqlValidationProperties sqlValidation; + private FineractCache cache; + @Getter @Setter public static class FineractTenantProperties { @@ -578,4 +581,20 @@ public class FineractProperties { private String name; private String pattern; } + + @Getter + @Setter + public static class FineractCache { + + private FineractCacheDetails defaultTemplate; + private Map customTemplates = new HashMap<>(); + } + + @Getter + @Setter + public static class FineractCacheDetails { + + private Duration ttl; + private Integer maximumEntries; + } } diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 7af3a5998..19569b215 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -113,6 +113,7 @@ dependencies { 'org.mapstruct:mapstruct', 'io.github.resilience4j:resilience4j-spring-boot3', + 'org.reflections:reflections', ) implementation 'software.amazon.msk:aws-msk-iam-auth' diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java index 3f90d0646..392e87829 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java @@ -19,23 +19,38 @@ package org.apache.fineract.infrastructure.core.config.cache; +import java.lang.reflect.Method; import java.time.Duration; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import javax.cache.CacheManager; import javax.cache.Caching; import javax.cache.spi.CachingProvider; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.ehcache.config.builders.CacheConfigurationBuilder; import org.ehcache.config.builders.ExpiryPolicyBuilder; import org.ehcache.config.builders.ResourcePoolsBuilder; import org.ehcache.jsr107.Eh107Configuration; +import org.reflections.Reflections; +import org.reflections.scanners.Scanners; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.jcache.JCacheCacheManager; import org.springframework.cache.support.NoOpCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration +@Slf4j public class CacheConfig { public static final String CONFIG_BY_NAME_CACHE_NAME = "configByName"; + @Autowired + private FineractProperties fineractProperties; @Bean public TransactionBoundCacheManager defaultCacheManager(JCacheCacheManager ehCacheManager) { @@ -56,63 +71,52 @@ public class CacheConfig { private CacheManager getInternalEhCacheManager() { CachingProvider provider = Caching.getCachingProvider(); CacheManager cacheManager = provider.getCacheManager(); - - javax.cache.configuration.Configuration defaultTemplate = Eh107Configuration.fromEhcacheCacheConfiguration( - CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(10000)) - .withExpiry(ExpiryPolicyBuilder.noExpiration()).build()); - - if (cacheManager.getCache("users") == null) { - cacheManager.createCache("users", defaultTemplate); + // Default cache configuration template + Duration defaultTimeToLive = fineractProperties.getCache().getDefaultTemplate().getTtl(); + Integer defaultMaxEntries = fineractProperties.getCache().getDefaultTemplate().getMaximumEntries(); + javax.cache.configuration.Configuration defaultTemplate = generateCacheConfiguration(defaultMaxEntries, + defaultTimeToLive); + // Scan all packages (entire classpath) + Reflections reflections = new Reflections( + new org.reflections.util.ConfigurationBuilder().forPackages("").addScanners(Scanners.MethodsAnnotated)); + // Find all methods annotated with @Cacheable + Set annotatedMethods = reflections.getMethodsAnnotatedWith(Cacheable.class); + Set cacheNames = annotatedMethods.stream().map(method -> method.getAnnotation(Cacheable.class)) + .flatMap(annotation -> Arrays.stream(annotation.value())).collect(Collectors.toSet()); + // Register the caches into the cache manager + cacheNames.forEach(cacheName -> { + if (cacheManager.getCache(cacheName) == null) { + javax.cache.configuration.Configuration configurationTemplate = generateCustomCacheConfiguration(cacheName, + defaultTemplate, defaultTimeToLive, defaultMaxEntries); + cacheManager.createCache(cacheName, configurationTemplate); + } + }); + Set incorrectConfigurations = new HashSet<>(fineractProperties.getCache().getCustomTemplates().keySet()); + incorrectConfigurations.removeAll(cacheNames); + if (!incorrectConfigurations.isEmpty()) { + log.warn("The following cache configurations are defined but cache does not exists: {}", incorrectConfigurations); } - if (cacheManager.getCache("usersByUsername") == null) { - cacheManager.createCache("usersByUsername", defaultTemplate); - } - if (cacheManager.getCache("tenantsById") == null) { - cacheManager.createCache("tenantsById", defaultTemplate); - } - if (cacheManager.getCache("offices") == null) { - cacheManager.createCache("offices", defaultTemplate); - } - if (cacheManager.getCache("officesForDropdown") == null) { - cacheManager.createCache("officesForDropdown", defaultTemplate); - } - if (cacheManager.getCache("officesById") == null) { - cacheManager.createCache("officesById", defaultTemplate); - } - if (cacheManager.getCache("charges") == null) { - cacheManager.createCache("charges", defaultTemplate); - } - if (cacheManager.getCache("funds") == null) { - cacheManager.createCache("funds", defaultTemplate); - } - if (cacheManager.getCache("code_values") == null) { - cacheManager.createCache("code_values", defaultTemplate); - } - if (cacheManager.getCache("codes") == null) { - cacheManager.createCache("codes", defaultTemplate); - } - if (cacheManager.getCache("hooks") == null) { - cacheManager.createCache("hooks", defaultTemplate); - } - if (cacheManager.getCache("tfConfig") == null) { - cacheManager.createCache("tfConfig", defaultTemplate); - } - if (cacheManager.getCache(CONFIG_BY_NAME_CACHE_NAME) == null) { - cacheManager.createCache(CONFIG_BY_NAME_CACHE_NAME, defaultTemplate); - } - - javax.cache.configuration.Configuration accessTokenTemplate = Eh107Configuration.fromEhcacheCacheConfiguration( - CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(10000)) - .withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofHours(2))).build()); - - if (cacheManager.getCache("userTFAccessToken") == null) { - cacheManager.createCache("userTFAccessToken", accessTokenTemplate); - } - - if (cacheManager.getCache("payment_types") == null) { - cacheManager.createCache("payment_types", defaultTemplate); - } - return cacheManager; } + + private javax.cache.configuration.Configuration generateCustomCacheConfiguration(String cacheIdentifier, + javax.cache.configuration.Configuration defaultTemplate, Duration defaultTimeToLive, + Integer defaultMaxEntries) { + javax.cache.configuration.Configuration configurationTemplate = defaultTemplate; + if (fineractProperties.getCache().getCustomTemplates().containsKey(cacheIdentifier)) { + Duration timeToLiveExpiration = Objects.requireNonNullElse( + fineractProperties.getCache().getCustomTemplates().get(cacheIdentifier).getTtl(), defaultTimeToLive); + Integer maxEntries = Objects.requireNonNullElse( + fineractProperties.getCache().getCustomTemplates().get(cacheIdentifier).getMaximumEntries(), defaultMaxEntries); + configurationTemplate = generateCacheConfiguration(maxEntries, timeToLiveExpiration); + } + return configurationTemplate; + } + + private static javax.cache.configuration.Configuration generateCacheConfiguration(Integer defaultMaxEntries, + Duration defaultTimeToLive) { + return Eh107Configuration.fromEhcacheCacheConfiguration(CacheConfigurationBuilder + .newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(defaultMaxEntries)) + .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(defaultTimeToLive)).build()); + } } diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 9bb89cf38..dbc2274ed 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -284,6 +284,13 @@ fineract.sql-validation.profiles[2].patternRefs[6].name=inject-comment fineract.sql-validation.profiles[2].patternRefs[6].order=6 fineract.sql-validation.profiles[2].enabled=true +#Cache - Default +fineract.cache.default-template.ttl=1m +fineract.cache.default-template.maximum-entries=1000 +#Cache - Customization +fineract.cache.custom-templates.userTFAccessToken.ttl=2h +fineract.cache.custom-templates.userTFAccessToken.maximum-entries=10000 + # Logging pattern for the console logging.pattern.console=${CONSOLE_LOG_PATTERN:%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(%replace([%X{correlationId}]){'\\[\\]', ''}) [%15.15tenantId] %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}} logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}] diff --git a/fineract-provider/src/test/resources/application-test.properties b/fineract-provider/src/test/resources/application-test.properties index 6a6435058..f9e52bc98 100644 --- a/fineract-provider/src/test/resources/application-test.properties +++ b/fineract-provider/src/test/resources/application-test.properties @@ -202,6 +202,10 @@ fineract.sql-validation.profiles[2].patternRefs[7].name=inject-comment fineract.sql-validation.profiles[2].patternRefs[7].order=7 fineract.sql-validation.profiles[2].enabled=true +#Cache - Default +fineract.cache.default-template.ttl=1m +fineract.cache.default-template.maximum-entries=1000 + management.health.jms.enabled=false # FINERACT 1296