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