This is an automated email from the ASF dual-hosted git repository.

arnold 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 e9e31a819 FINERACT-1724: Making global configuration caching 
transaction bound
e9e31a819 is described below

commit e9e31a819093a9f1d1fac1ee744ace465b8273c4
Author: Arnold Galovics <[email protected]>
AuthorDate: Sun Mar 12 19:14:00 2023 +0100

    FINERACT-1724: Making global configuration caching transaction bound
---
 .../infrastructure/cache/CacheApiConstants.java    |  2 +-
 .../cache/command/UpdateCacheCommandHandler.java   |  6 +-
 .../service/RuntimeDelegatingCacheManager.java     | 84 ++++++++++------------
 .../domain/ConfigurationDomainServiceJpa.java      | 33 ++-------
 .../GlobalConfigurationRepositoryWrapper.java      |  9 +++
 .../core/config/FineractProperties.java            |  7 ++
 .../core/config/{ => cache}/CacheConfig.java       | 19 ++++-
 .../SpecifiedCacheSupportingCacheManager.java      | 82 +++++++++++++++++++++
 .../config/cache/TransactionBoundCacheManager.java | 52 ++++++++++++++
 .../diagnostics/jpa/DiagnosticsEntityManager.java  | 61 ++++++++++++++++
 .../jpa/StatementLoggingCustomizer.java}           | 21 ++++--
 .../jpa/StatementLoggingCustomizerCondition.java}  | 14 ++--
 .../persistence/ExtendedJpaTransactionManager.java | 22 +++++-
 .../persistence/TransactionLifecycleCallback.java} | 12 ++--
 .../jobs/ScheduledJobRunnerConfig.java             |  6 +-
 .../src/main/resources/application.properties      |  2 +
 .../src/test/resources/application-test.properties |  2 +
 17 files changed, 331 insertions(+), 103 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
index 24e2468cc..a56acf786 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
@@ -25,6 +25,6 @@ public final class CacheApiConstants {
     }
 
     public static final String RESOURCE_NAME = "CACHE";
-    public static final String cacheTypeParameter = "cacheType";
+    public static final String CACHE_TYPE_PARAMETER = "cacheType";
 
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
index 26858cd7f..99d90afd2 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/command/UpdateCacheCommandHandler.java
@@ -48,7 +48,7 @@ import 
org.springframework.transaction.annotation.Transactional;
 public class UpdateCacheCommandHandler implements NewCommandSourceHandler {
 
     private final CacheWritePlatformService cacheService;
-    private static final Set<String> REQUEST_DATA_PARAMETERS = new 
HashSet<>(Arrays.asList(CacheApiConstants.cacheTypeParameter));
+    private static final Set<String> REQUEST_DATA_PARAMETERS = new 
HashSet<>(Arrays.asList(CacheApiConstants.CACHE_TYPE_PARAMETER));
 
     @Autowired
     public UpdateCacheCommandHandler(final CacheWritePlatformService 
cacheService) {
@@ -72,8 +72,8 @@ public class UpdateCacheCommandHandler implements 
NewCommandSourceHandler {
         final DataValidatorBuilder baseDataValidator = new 
DataValidatorBuilder(dataValidationErrors)
                 .resource(CacheApiConstants.RESOURCE_NAME.toLowerCase());
 
-        final int cacheTypeEnum = 
command.integerValueSansLocaleOfParameterNamed(CacheApiConstants.cacheTypeParameter);
-        
baseDataValidator.reset().parameter(CacheApiConstants.cacheTypeParameter).value(Integer.valueOf(cacheTypeEnum)).notNull()
+        final int cacheTypeEnum = 
command.integerValueSansLocaleOfParameterNamed(CacheApiConstants.CACHE_TYPE_PARAMETER);
+        
baseDataValidator.reset().parameter(CacheApiConstants.CACHE_TYPE_PARAMETER).value(Integer.valueOf(cacheTypeEnum)).notNull()
                 .isOneOfTheseValues(Integer.valueOf(1), Integer.valueOf(2), 
Integer.valueOf(3));
 
         if (!dataValidationErrors.isEmpty()) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java
index 74225c103..a15ae0130 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/service/RuntimeDelegatingCacheManager.java
@@ -22,17 +22,17 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.infrastructure.cache.CacheApiConstants;
 import org.apache.fineract.infrastructure.cache.CacheEnumerations;
 import org.apache.fineract.infrastructure.cache.data.CacheData;
 import org.apache.fineract.infrastructure.cache.domain.CacheType;
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.cache.Cache;
 import org.springframework.cache.CacheManager;
-import org.springframework.cache.jcache.JCacheCacheManager;
 import org.springframework.cache.support.NoOpCacheManager;
 import org.springframework.stereotype.Component;
 
@@ -43,49 +43,43 @@ import org.springframework.stereotype.Component;
  * database on startup and allow user to switch implementation through UI/API
  */
 @Component(value = "runtimeDelegatingCacheManager")
-public class RuntimeDelegatingCacheManager implements CacheManager {
-
-    private static final Logger LOG = 
LoggerFactory.getLogger(RuntimeDelegatingCacheManager.class);
-
-    private final CacheManager cacheManager;
-    private final CacheManager noOpCacheManager = new NoOpCacheManager();
+@RequiredArgsConstructor
+@Slf4j
+public class RuntimeDelegatingCacheManager implements CacheManager, 
InitializingBean {
+
+    @Qualifier("ehCacheManager")
+    private final CacheManager ehCacheManager;
+    @Qualifier("defaultCacheManager")
+    private final CacheManager defaultCacheManager;
     private CacheManager currentCacheManager;
 
-    @Autowired
-    public RuntimeDelegatingCacheManager(final JCacheCacheManager 
cacheManager) {
-        this.cacheManager = cacheManager;
-        this.currentCacheManager = this.noOpCacheManager;
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        currentCacheManager = defaultCacheManager;
     }
 
     @Override
     public Cache getCache(final String name) {
-        return this.currentCacheManager.getCache(name);
+        return currentCacheManager.getCache(name);
     }
 
     @Override
     public Collection<String> getCacheNames() {
-        return this.currentCacheManager.getCacheNames();
+        return currentCacheManager.getCacheNames();
     }
 
     public Collection<CacheData> retrieveAll() {
 
-        final boolean noCacheEnabled = this.currentCacheManager instanceof 
NoOpCacheManager;
-        final boolean ehcacheEnabled = this.currentCacheManager instanceof 
JCacheCacheManager;
-
-        // final boolean distributedCacheEnabled = false;
+        final boolean noCacheEnabled = currentCacheManager == 
defaultCacheManager;
+        final boolean ehCacheEnabled = currentCacheManager == ehCacheManager;
 
         final EnumOptionData noCacheType = 
CacheEnumerations.cacheType(CacheType.NO_CACHE);
         final EnumOptionData singleNodeCacheType = 
CacheEnumerations.cacheType(CacheType.SINGLE_NODE);
-        // final EnumOptionData multiNodeCacheType =
-        // CacheEnumerations.cacheType(CacheType.MULTI_NODE);
 
         final CacheData noCache = CacheData.instance(noCacheType, 
noCacheEnabled);
-        final CacheData singleNodeCache = 
CacheData.instance(singleNodeCacheType, ehcacheEnabled);
-        // final CacheData distributedCache =
-        // CacheData.instance(multiNodeCacheType, distributedCacheEnabled);
+        final CacheData singleNodeCache = 
CacheData.instance(singleNodeCacheType, ehCacheEnabled);
 
-        final Collection<CacheData> caches = Arrays.asList(noCache, 
singleNodeCache);
-        return caches;
+        return Arrays.asList(noCache, singleNodeCache);
     }
 
     public Map<String, Object> switchToCache(final boolean ehcacheEnabled, 
final CacheType toCacheType) {
@@ -93,42 +87,38 @@ public class RuntimeDelegatingCacheManager implements 
CacheManager {
         final Map<String, Object> changes = new HashMap<>();
 
         final boolean noCacheEnabled = !ehcacheEnabled;
-        final boolean distributedCacheEnabled = !ehcacheEnabled;
 
         switch (toCacheType) {
-            case INVALID:
-            break;
-            case NO_CACHE:
+            case INVALID -> {
+                log.warn("Invalid cache type used");
+            }
+            case NO_CACHE -> {
                 if (!noCacheEnabled) {
-                    changes.put(CacheApiConstants.cacheTypeParameter, 
toCacheType.getValue());
+                    changes.put(CacheApiConstants.CACHE_TYPE_PARAMETER, 
toCacheType.getValue());
                 }
-                this.currentCacheManager = this.noOpCacheManager;
-            break;
-            case SINGLE_NODE:
+                currentCacheManager = defaultCacheManager;
+            }
+            case SINGLE_NODE -> {
                 if (!ehcacheEnabled) {
-                    changes.put(CacheApiConstants.cacheTypeParameter, 
toCacheType.getValue());
+                    changes.put(CacheApiConstants.CACHE_TYPE_PARAMETER, 
toCacheType.getValue());
                     clearEhCache();
                 }
-                this.currentCacheManager = this.cacheManager;
+                currentCacheManager = ehCacheManager;
 
-                if (this.currentCacheManager.getCacheNames().size() == 0) {
-                    LOG.error("No caches configured for activated CacheManager 
{}", this.currentCacheManager);
-                }
-            break;
-            case MULTI_NODE:
-                if (!distributedCacheEnabled) {
-                    changes.put(CacheApiConstants.cacheTypeParameter, 
toCacheType.getValue());
+                if (currentCacheManager.getCacheNames().size() == 0) {
+                    log.error("No caches configured for activated CacheManager 
{}", currentCacheManager);
                 }
-            break;
+            }
+            case MULTI_NODE -> throw new UnsupportedOperationException("Multi 
node cache is not supported");
         }
 
         return changes;
     }
 
     private void clearEhCache() {
-        Iterable<String> cacheNames = cacheManager.getCacheNames();
+        Iterable<String> cacheNames = ehCacheManager.getCacheNames();
         for (String cacheName : cacheNames) {
-            cacheManager.getCache(cacheName).clear();
+            ehCacheManager.getCache(cacheName).clear();
         }
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
index 5088b3c38..a278c1c5c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java
@@ -19,23 +19,22 @@
 package org.apache.fineract.infrastructure.configuration.domain;
 
 import java.time.LocalDate;
-import java.util.HashMap;
-import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.infrastructure.cache.domain.CacheType;
 import org.apache.fineract.infrastructure.cache.domain.PlatformCache;
 import org.apache.fineract.infrastructure.cache.domain.PlatformCacheRepository;
 import 
org.apache.fineract.infrastructure.configuration.data.GlobalConfigurationPropertyData;
-import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
 import org.apache.fineract.useradministration.domain.Permission;
 import org.apache.fineract.useradministration.domain.PermissionRepository;
 import 
org.apache.fineract.useradministration.exception.PermissionNotFoundException;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+@Slf4j
 @Service
+@RequiredArgsConstructor
 public class ConfigurationDomainServiceJpa implements 
ConfigurationDomainService {
 
     public static final String ENABLE_BUSINESS_DATE = "enable_business_date";
@@ -55,15 +54,6 @@ public class ConfigurationDomainServiceJpa implements 
ConfigurationDomainService
     private final PermissionRepository permissionRepository;
     private final GlobalConfigurationRepositoryWrapper 
globalConfigurationRepository;
     private final PlatformCacheRepository cacheTypeRepository;
-    private static Map<String, GlobalConfigurationPropertyData> configurations 
= new HashMap<>();
-
-    @Autowired
-    public ConfigurationDomainServiceJpa(final PermissionRepository 
permissionRepository,
-            final GlobalConfigurationRepositoryWrapper 
globalConfigurationRepository, final PlatformCacheRepository 
cacheTypeRepository) {
-        this.permissionRepository = permissionRepository;
-        this.globalConfigurationRepository = globalConfigurationRepository;
-        this.cacheTypeRepository = cacheTypeRepository;
-    }
 
     @Override
     public boolean isMakerCheckerEnabledForTask(final String 
taskPermissionCode) {
@@ -321,9 +311,7 @@ public class ConfigurationDomainServiceJpa implements 
ConfigurationDomainService
 
     @Override
     public void removeGlobalConfigurationPropertyDataFromCache(final String 
propertyName) {
-        String identifier = 
ThreadLocalContextUtil.getTenant().getTenantIdentifier();
-        String key = identifier + "_" + propertyName;
-        configurations.remove(key);
+        globalConfigurationRepository.removeFromCache(propertyName);
     }
 
     @Override
@@ -389,15 +377,8 @@ public class ConfigurationDomainServiceJpa implements 
ConfigurationDomainService
         return property.getValue();
     }
 
-    @Cacheable(value = "configByName", key = 
"T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#propertyName)")
-    public GlobalConfigurationPropertyData 
getGlobalConfigurationPropertyData(final String propertyName) {
-        String identifier = 
ThreadLocalContextUtil.getTenant().getTenantIdentifier();
-        String key = identifier + "_" + propertyName;
-        if (!configurations.containsKey(key)) {
-            GlobalConfigurationProperty configuration = 
this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
-            configurations.put(key, configuration.toData());
-        }
-        return configurations.get(key);
+    private GlobalConfigurationPropertyData 
getGlobalConfigurationPropertyData(final String propertyName) {
+        return 
globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName).toData();
     }
 
     @Override
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationRepositoryWrapper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationRepositoryWrapper.java
index 7276bcd22..3d91820f7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationRepositoryWrapper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/GlobalConfigurationRepositoryWrapper.java
@@ -18,8 +18,11 @@
  */
 package org.apache.fineract.infrastructure.configuration.domain;
 
+import lombok.extern.slf4j.Slf4j;
 import 
org.apache.fineract.infrastructure.configuration.exception.GlobalConfigurationPropertyNotFoundException;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 
 /**
@@ -28,6 +31,7 @@ import org.springframework.stereotype.Service;
  * </p>
  */
 @Service
+@Slf4j
 public class GlobalConfigurationRepositoryWrapper {
 
     private final GlobalConfigurationRepository repository;
@@ -37,6 +41,7 @@ public class GlobalConfigurationRepositoryWrapper {
         this.repository = repository;
     }
 
+    @Cacheable(value = "configByName", key = 
"T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#propertyName)")
     public GlobalConfigurationProperty 
findOneByNameWithNotFoundDetection(final String propertyName) {
         final GlobalConfigurationProperty property = 
this.repository.findOneByName(propertyName);
         if (property == null) {
@@ -61,4 +66,8 @@ public class GlobalConfigurationRepositoryWrapper {
         this.repository.delete(globalConfigurationProperty);
     }
 
+    @CacheEvict(value = "configByName", key = 
"T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#propertyName)")
+    public void removeFromCache(String propertyName) {
+        log.debug("Cache entry evicted {}", propertyName);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
index c9c88fb61..2e5fc9d2a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java
@@ -53,6 +53,7 @@ public class FineractProperties {
     private FineractJobProperties job;
 
     private FineractTemplateProperties template;
+    private FineractJpaProperties jpa;
 
     @Getter
     @Setter
@@ -248,4 +249,10 @@ public class FineractProperties {
         private List<String> regexWhitelist;
     }
 
+    @Getter
+    @Setter
+    public static class FineractJpaProperties {
+
+        private boolean statementLoggingEnabled;
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CacheConfig.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java
similarity index 78%
rename from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CacheConfig.java
rename to 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java
index e6117fa68..e4202ae34 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/CacheConfig.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/CacheConfig.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.fineract.infrastructure.core.config;
+package org.apache.fineract.infrastructure.core.config.cache;
 
 import java.time.Duration;
 import javax.cache.CacheManager;
@@ -28,20 +28,32 @@ import org.ehcache.config.builders.ExpiryPolicyBuilder;
 import org.ehcache.config.builders.ResourcePoolsBuilder;
 import org.ehcache.jsr107.Eh107Configuration;
 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
 public class CacheConfig {
 
+    public static final String CONFIG_BY_NAME_CACHE_NAME = "configByName";
+
+    @Bean
+    public TransactionBoundCacheManager defaultCacheManager(JCacheCacheManager 
ehCacheManager) {
+        SpecifiedCacheSupportingCacheManager cacheManager = new 
SpecifiedCacheSupportingCacheManager();
+        cacheManager.setNoOpCacheManager(new NoOpCacheManager());
+        cacheManager.setDelegateCacheManager(ehCacheManager);
+        cacheManager.setSupportedCaches(CONFIG_BY_NAME_CACHE_NAME);
+        return new TransactionBoundCacheManager(cacheManager);
+    }
+
     @Bean
     public JCacheCacheManager ehCacheManager() {
         JCacheCacheManager jCacheCacheManager = new JCacheCacheManager();
-        jCacheCacheManager.setCacheManager(getCustomCacheManager());
+        jCacheCacheManager.setCacheManager(getInternalEhCacheManager());
         return jCacheCacheManager;
     }
 
-    private CacheManager getCustomCacheManager() {
+    private CacheManager getInternalEhCacheManager() {
         CachingProvider provider = Caching.getCachingProvider();
         CacheManager cacheManager = provider.getCacheManager();
 
@@ -61,6 +73,7 @@ public class CacheConfig {
         cacheManager.createCache("codes", defaultTemplate);
         cacheManager.createCache("hooks", defaultTemplate);
         cacheManager.createCache("tfConfig", defaultTemplate);
+        cacheManager.createCache(CONFIG_BY_NAME_CACHE_NAME, defaultTemplate);
 
         javax.cache.configuration.Configuration<Object, Object> 
accessTokenTemplate = Eh107Configuration.fromEhcacheCacheConfiguration(
                 
CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, 
Object.class, ResourcePoolsBuilder.heap(10000))
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/SpecifiedCacheSupportingCacheManager.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/SpecifiedCacheSupportingCacheManager.java
new file mode 100644
index 000000000..2baf7c0b7
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/SpecifiedCacheSupportingCacheManager.java
@@ -0,0 +1,82 @@
+/**
+ * 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.config.cache;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.jcache.JCacheCacheManager;
+import org.springframework.cache.support.NoOpCacheManager;
+import org.springframework.util.Assert;
+
+@RequiredArgsConstructor
+public class SpecifiedCacheSupportingCacheManager implements CacheManager, 
InitializingBean {
+
+    private JCacheCacheManager delegateCacheManager;
+    private NoOpCacheManager noOpCacheManager;
+
+    private final Set<String> supportedCacheNames = new LinkedHashSet<>(16);
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        Assert.notNull(delegateCacheManager, "cacheManager cannot be null");
+        Assert.notNull(noOpCacheManager, "delegate cannot be null");
+        Assert.notEmpty(supportedCacheNames, "supportedCacheNames must not be 
empty");
+        delegateCacheManager.afterPropertiesSet();
+    }
+
+    @Override
+    public Cache getCache(String name) {
+        if (supportedCacheNames.contains(name)) {
+            Cache cache = delegateCacheManager.getCache(name);
+            if (cache != null) {
+                return cache;
+            } else {
+                return noOpCacheManager.getCache(name);
+            }
+        } else {
+            return noOpCacheManager.getCache(name);
+        }
+    }
+
+    @Override
+    public Collection<String> getCacheNames() {
+        synchronized (supportedCacheNames) {
+            return Collections.unmodifiableSet(supportedCacheNames);
+        }
+    }
+
+    public void setDelegateCacheManager(JCacheCacheManager 
delegateCacheManager) {
+        this.delegateCacheManager = delegateCacheManager;
+    }
+
+    public void setNoOpCacheManager(NoOpCacheManager noOpCacheManager) {
+        this.noOpCacheManager = noOpCacheManager;
+    }
+
+    public void setSupportedCaches(String... cacheNames) {
+        supportedCacheNames.addAll(Arrays.asList(cacheNames));
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/TransactionBoundCacheManager.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/TransactionBoundCacheManager.java
new file mode 100644
index 000000000..5a70b4e1a
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/cache/TransactionBoundCacheManager.java
@@ -0,0 +1,52 @@
+/**
+ * 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.config.cache;
+
+import java.util.Collection;
+import lombok.RequiredArgsConstructor;
+import 
org.apache.fineract.infrastructure.core.persistence.TransactionLifecycleCallback;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+
+@RequiredArgsConstructor
+public class TransactionBoundCacheManager implements 
TransactionLifecycleCallback, CacheManager {
+
+    private final CacheManager delegate;
+
+    @Override
+    public void afterCompletion() {
+        Collection<String> cacheNames = delegate.getCacheNames();
+        cacheNames.forEach(c -> {
+            Cache cache = delegate.getCache(c);
+            if (cache != null) {
+                cache.clear();
+            }
+        });
+    }
+
+    @Override
+    public Cache getCache(String name) {
+        return delegate.getCache(name);
+    }
+
+    @Override
+    public Collection<String> getCacheNames() {
+        return delegate.getCacheNames();
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/DiagnosticsEntityManager.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/DiagnosticsEntityManager.java
new file mode 100644
index 000000000..e99dca53d
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/DiagnosticsEntityManager.java
@@ -0,0 +1,61 @@
+/**
+ * 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.diagnostics.jpa;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import javax.persistence.EntityManagerFactory;
+import org.eclipse.persistence.internal.jpa.EntityManagerImpl;
+import org.eclipse.persistence.sessions.changesets.UnitOfWorkChangeSet;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.annotation.Profile;
+import org.springframework.orm.jpa.EntityManagerFactoryUtils;
+import org.springframework.stereotype.Component;
+
+/**
+ * This class can be used for IntelliJ debugging purposes to access the 
current transaction bound EntityManager
+ * instance. <br>
+ * <br>
+ * With Alt + F8 you can run evaluations in IntelliJ and this class makes it 
easier to access the EntityManager and to
+ * see what kind of changes are pending within the Persistence Context. <br>
+ * <br>
+ * To enable this, run Fineract with the <b>diagnostics</b> profile.
+ */
+@Profile("diagnostics")
+@Component
+public class DiagnosticsEntityManager implements ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+
+    public static EntityManagerImpl getCurrentEntityManager() {
+        EntityManagerFactory emf = 
applicationContext.getBean(EntityManagerFactory.class);
+        return (EntityManagerImpl) 
EntityManagerFactoryUtils.getTransactionalEntityManager(emf);
+    }
+
+    public static UnitOfWorkChangeSet getCurrentChanges() {
+        return 
DiagnosticsEntityManager.getCurrentEntityManager().getUnitOfWork().getCurrentChanges();
+    }
+
+    @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) 
throws BeansException {
+        DiagnosticsEntityManager.applicationContext = applicationContext;
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/StatementLoggingCustomizer.java
similarity index 50%
copy from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/StatementLoggingCustomizer.java
index 24e2468cc..ca35f7ae3 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/StatementLoggingCustomizer.java
@@ -16,15 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.infrastructure.cache;
+package org.apache.fineract.infrastructure.core.diagnostics.jpa;
 
-public final class CacheApiConstants {
+import java.util.Map;
+import 
org.apache.fineract.infrastructure.core.config.jpa.EntityManagerFactoryCustomizer;
+import org.eclipse.persistence.config.PersistenceUnitProperties;
+import org.eclipse.persistence.logging.SessionLog;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.stereotype.Component;
 
-    private CacheApiConstants() {
+@Component
+@Conditional(StatementLoggingCustomizerCondition.class)
+public class StatementLoggingCustomizer implements 
EntityManagerFactoryCustomizer {
 
+    @Override
+    public Map<String, Object> additionalVendorProperties() {
+        return Map.of("eclipselink.logging.level.sql", SessionLog.FINE_LABEL, 
PersistenceUnitProperties.LOGGING_PARAMETERS,
+                Boolean.TRUE.toString());
     }
-
-    public static final String RESOURCE_NAME = "CACHE";
-    public static final String cacheTypeParameter = "cacheType";
-
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/StatementLoggingCustomizerCondition.java
similarity index 65%
copy from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/StatementLoggingCustomizerCondition.java
index 24e2468cc..e4514f1e0 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/diagnostics/jpa/StatementLoggingCustomizerCondition.java
@@ -16,15 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.infrastructure.cache;
+package org.apache.fineract.infrastructure.core.diagnostics.jpa;
 
-public final class CacheApiConstants {
+import org.apache.fineract.infrastructure.core.condition.PropertiesCondition;
+import org.apache.fineract.infrastructure.core.config.FineractProperties;
 
-    private CacheApiConstants() {
+public class StatementLoggingCustomizerCondition extends PropertiesCondition {
 
+    @Override
+    protected boolean matches(FineractProperties properties) {
+        return properties.getJpa().isStatementLoggingEnabled();
     }
-
-    public static final String RESOURCE_NAME = "CACHE";
-    public static final String cacheTypeParameter = "cacheType";
-
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/ExtendedJpaTransactionManager.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/ExtendedJpaTransactionManager.java
index 1355da65f..233fc73bb 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/ExtendedJpaTransactionManager.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/ExtendedJpaTransactionManager.java
@@ -18,6 +18,9 @@
  */
 package org.apache.fineract.infrastructure.core.persistence;
 
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
 import javax.persistence.EntityManager;
 import javax.persistence.FlushModeType;
 import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
@@ -29,6 +32,8 @@ import 
org.springframework.transaction.support.TransactionSynchronizationManager
 
 public class ExtendedJpaTransactionManager extends JpaTransactionManager {
 
+    private final List<TransactionLifecycleCallback> lifecycleCallbacks = new 
CopyOnWriteArrayList<>();
+
     public ExtendedJpaTransactionManager() {
         setValidateExistingTransaction(true);
     }
@@ -42,7 +47,7 @@ public class ExtendedJpaTransactionManager extends 
JpaTransactionManager {
                 entityManager.setFlushMode(FlushModeType.COMMIT);
             }
         }
-
+        invokeLifecycleCallbacks(TransactionLifecycleCallback::afterBegin);
     }
 
     @Override
@@ -54,6 +59,13 @@ public class ExtendedJpaTransactionManager extends 
JpaTransactionManager {
             }
         }
         super.doCommit(status);
+        invokeLifecycleCallbacks(TransactionLifecycleCallback::afterCommit);
+    }
+
+    @Override
+    protected void doCleanupAfterCompletion(Object transaction) {
+        super.doCleanupAfterCompletion(transaction);
+        
invokeLifecycleCallbacks(TransactionLifecycleCallback::afterCompletion);
     }
 
     private boolean isReadOnlyTx(Object transaction) {
@@ -68,4 +80,12 @@ public class ExtendedJpaTransactionManager extends 
JpaTransactionManager {
         }
         return null;
     }
+
+    private void 
invokeLifecycleCallbacks(Consumer<TransactionLifecycleCallback> f) {
+        lifecycleCallbacks.forEach(f::accept);
+    }
+
+    public void setLifecycleCallbacks(List<TransactionLifecycleCallback> 
lifecycleCallbacks) {
+        this.lifecycleCallbacks.addAll(lifecycleCallbacks);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/TransactionLifecycleCallback.java
similarity index 76%
copy from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
copy to 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/TransactionLifecycleCallback.java
index 24e2468cc..fa33ec8a9 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/cache/CacheApiConstants.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/persistence/TransactionLifecycleCallback.java
@@ -16,15 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.infrastructure.cache;
+package org.apache.fineract.infrastructure.core.persistence;
 
-public final class CacheApiConstants {
+public interface TransactionLifecycleCallback {
 
-    private CacheApiConstants() {
+    default void afterBegin() {}
 
-    }
-
-    public static final String RESOURCE_NAME = "CACHE";
-    public static final String cacheTypeParameter = "cacheType";
+    default void afterCommit() {}
 
+    default void afterCompletion() {}
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/ScheduledJobRunnerConfig.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/ScheduledJobRunnerConfig.java
index 7c560535d..4b1c74a0b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/ScheduledJobRunnerConfig.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/ScheduledJobRunnerConfig.java
@@ -18,7 +18,9 @@
  */
 package org.apache.fineract.infrastructure.jobs;
 
+import java.util.List;
 import 
org.apache.fineract.infrastructure.core.persistence.ExtendedJpaTransactionManager;
+import 
org.apache.fineract.infrastructure.core.persistence.TransactionLifecycleCallback;
 import 
org.apache.fineract.infrastructure.core.service.database.RoutingDataSource;
 import org.springframework.batch.core.configuration.JobRegistry;
 import org.springframework.batch.core.configuration.annotation.BatchConfigurer;
@@ -39,8 +41,10 @@ import 
org.springframework.transaction.PlatformTransactionManager;
 public class ScheduledJobRunnerConfig {
 
     @Bean
-    public PlatformTransactionManager 
transactionManager(ObjectProvider<TransactionManagerCustomizers> 
transactionManagerCustomizers) {
+    public PlatformTransactionManager 
transactionManager(ObjectProvider<TransactionManagerCustomizers> 
transactionManagerCustomizers,
+            List<TransactionLifecycleCallback> callbacks) {
         ExtendedJpaTransactionManager transactionManager = new 
ExtendedJpaTransactionManager();
+        transactionManager.setLifecycleCallbacks(callbacks);
         transactionManager.setValidateExistingTransaction(true);
         transactionManagerCustomizers.ifAvailable(customizers -> 
customizers.customize(transactionManager));
         return transactionManager;
diff --git a/fineract-provider/src/main/resources/application.properties 
b/fineract-provider/src/main/resources/application.properties
index 11677c9df..f1b158ea3 100644
--- a/fineract-provider/src/main/resources/application.properties
+++ b/fineract-provider/src/main/resources/application.properties
@@ -96,6 +96,8 @@ 
fineract.template.regex-whitelist=${FINERACT_TEMPLATE_REGEX_WHITELIST:}
 fineract.report.export.s3.bucket=${FINERACT_REPORT_EXPORT_S3_BUCKET_NAME:}
 fineract.report.export.s3.enabled=${FINERACT_REPORT_EXPORT_S3_ENABLED:false}
 
+fineract.jpa.statementLoggingEnabled=${FINERACT_STATEMENT_LOGGING_ENABLED:false}
+
 # 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}]){'\\[\\]', ''}) %clr(---){faint} 
%clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} 
%m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}
 
diff --git a/fineract-provider/src/test/resources/application-test.properties 
b/fineract-provider/src/test/resources/application-test.properties
index f33d80bea..459034b08 100644
--- a/fineract-provider/src/test/resources/application-test.properties
+++ b/fineract-provider/src/test/resources/application-test.properties
@@ -75,6 +75,8 @@ fineract.content.s3.secretKey=
 fineract.report.export.s3.bucket=${FINERACT_REPORT_EXPORT_S3_BUCKET_NAME:}
 fineract.report.export.s3.enabled=${FINERACT_REPORT_EXPORT_S3_ENABLED:false}
 
+fineract.jpa.statementLoggingEnabled=${FINERACT_STATEMENT_LOGGING_ENABLED:false}
+
 management.health.jms.enabled=false
 
 # FINERACT 1296


Reply via email to