FINERACT-1724: Making global configuration caching transaction bound

This commit is contained in:
Arnold Galovics
2023-03-14 13:42:51 +01:00
committed by Arnold Gálovics
parent 651052c44b
commit e9e31a8190
17 changed files with 399 additions and 81 deletions
@@ -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";
}
@@ -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()) {
@@ -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 {
@RequiredArgsConstructor
@Slf4j
public class RuntimeDelegatingCacheManager implements CacheManager, InitializingBean {
private static final Logger LOG = LoggerFactory.getLogger(RuntimeDelegatingCacheManager.class);
private final CacheManager cacheManager;
private final CacheManager noOpCacheManager = new NoOpCacheManager();
@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);
if (currentCacheManager.getCacheNames().size() == 0) {
log.error("No caches configured for activated CacheManager {}", currentCacheManager);
}
break;
case MULTI_NODE:
if (!distributedCacheEnabled) {
changes.put(CacheApiConstants.cacheTypeParameter, toCacheType.getValue());
}
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();
}
}
}
@@ -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
@@ -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);
}
}
@@ -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;
}
}
@@ -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))
@@ -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));
}
}
@@ -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();
}
}
@@ -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;
}
}
@@ -0,0 +1,37 @@
/**
* 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 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;
@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());
}
}
@@ -0,0 +1,30 @@
/**
* 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 org.apache.fineract.infrastructure.core.condition.PropertiesCondition;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
public class StatementLoggingCustomizerCondition extends PropertiesCondition {
@Override
protected boolean matches(FineractProperties properties) {
return properties.getJpa().isStatementLoggingEnabled();
}
}
@@ -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);
}
}
@@ -0,0 +1,28 @@
/**
* 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.persistence;
public interface TransactionLifecycleCallback {
default void afterBegin() {}
default void afterCommit() {}
default void afterCompletion() {}
}
@@ -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;
@@ -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}}
@@ -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