
com.icthh.xm.commons.config.client.config.InitRefreshableConfigurationBeanPostProcessor Maven / Gradle / Ivy
package com.icthh.xm.commons.config.client.config;
import com.icthh.xm.commons.config.client.api.ConfigService;
import com.icthh.xm.commons.config.client.api.ConfigurationChangedListener;
import com.icthh.xm.commons.config.client.api.RefreshableConfiguration;
import com.icthh.xm.commons.config.domain.Configuration;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.AntPathMatcher;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.length;
@Slf4j
public class InitRefreshableConfigurationBeanPostProcessor implements BeanPostProcessor {
public static final String LOG_CONFIG_EMPTY = "";
private static final String CONFIG_PATH = "/config/tenants/{tenantName}/**";
private static final String COMMONS = "commons";
private final ConfigService configService;
private final Map refreshableConfigurations = new HashMap<>();
private volatile Map configMap;
private final Set includedTenants;
private final AntPathMatcher matcher = new AntPathMatcher();
public InitRefreshableConfigurationBeanPostProcessor(ConfigService configService,
XmConfigProperties xmConfigProperties) {
this.configService = configService;
this.includedTenants = xmConfigProperties.getIncludeTenantUppercase();
addLepCommons();
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof RefreshableConfiguration) {
refreshableConfigurations.put(beanName, (RefreshableConfiguration) bean);
log.info("refreshable configuration bean added: {} = {}", beanName, bean.getClass());
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (refreshableConfigurations.containsKey(beanName)) {
initBean(refreshableConfigurations.get(beanName), getConfig());
}
return bean;
}
private Map getConfig() {
if (configMap == null) {
configMap = configService.getConfigurationMap(null);
}
return configMap;
}
private void initBean(RefreshableConfiguration refreshableConfiguration, Map configMap) {
List initedPaths = initConfigPaths(refreshableConfiguration, configMap);
refreshFinished(refreshableConfiguration, initedPaths);
log.info("refreshable configuration bean [{}] initialized by configMap with {} entries",
getBeanName(refreshableConfiguration), configMap.size());
configService.addConfigurationChangedListener(new ConfigurationChangedListener() {
@Override
public void onConfigurationChanged(Configuration configuration) {
onEntryChange(refreshableConfiguration, configuration);
}
@Override
public void refreshFinished(Collection paths) {
List listenPaths = paths.stream()
.filter(s -> isTenantIncluded(s))
.filter(refreshableConfiguration::isListeningConfiguration)
.collect(Collectors.toList());
if (!listenPaths.isEmpty()) {
InitRefreshableConfigurationBeanPostProcessor.this.refreshFinished(refreshableConfiguration,
listenPaths);
}
}
});
refreshableConfiguration.refreshableConfigurationInited();
}
public List initConfigPaths(final RefreshableConfiguration refreshableConfiguration,
final Map configMap) {
return configMap
.entrySet()
.stream()
.filter(e -> isTenantIncluded(e.getKey()))
.filter(e -> refreshableConfiguration.isListeningConfiguration(e.getKey()))
.peek(e -> printLog(getBeanName(refreshableConfiguration), e))
.peek(e -> refreshableConfiguration.onInit(e.getKey(), e.getValue().getContent()))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
private static void printLog(final String beanName,
final Map.Entry e) {
log.info("Process config init event: [key = {}, size = {}, newHash = {}] in bean: [{}]",
e.getKey(),
length(e.getValue().getContent()),
getValueHash(e.getValue().getContent()),
beanName);
}
private boolean isTenantIncluded(String configKey) {
if (!includedTenants.isEmpty()) {
if (matcher.match(CONFIG_PATH, configKey)) {
String tenant = matcher.extractUriTemplateVariables(CONFIG_PATH, configKey).get("tenantName");
return includedTenants.contains(tenant);
}
return false;
}
return true;
}
private void addLepCommons() {
if (!includedTenants.isEmpty()) {
includedTenants.add(COMMONS); // tenant level commons are always included.
}
}
private void refreshFinished(RefreshableConfiguration refreshableConfiguration, Collection paths) {
try {
refreshableConfiguration.refreshFinished(paths);
} catch (Exception e) {
log.error("Error during refresh finished", e);
}
}
private void onEntryChange(RefreshableConfiguration refreshableConfiguration, Configuration configuration) {
String configContent = configuration.getContent();
if (isTenantIncluded(configuration.getPath())) {
if (refreshableConfiguration.isListeningConfiguration(configuration.getPath())) {
refreshableConfiguration.onRefresh(configuration.getPath(), configContent);
log.info(
"Process config update event: [path = {}, size = {}, hash = {}] in bean: [{}]",
configuration.getPath(),
length(configContent),
getValueHash(configContent),
getBeanName(refreshableConfiguration));
} else {
log.debug("Ignored config update event: [path = {}, configSize = {} in bean [{}]",
configuration.getPath(),
length(configContent),
getBeanName(refreshableConfiguration));
}
}
}
private static String getBeanName(final RefreshableConfiguration refreshableConfiguration) {
return refreshableConfiguration.getClass().getSimpleName();
}
private static String getValueHash(final String configContent) {
return StringUtils.isEmpty(configContent) ? LOG_CONFIG_EMPTY :
DigestUtils.md5Hex(configContent);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy