All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zodic.kubernetes.confcenter.reload.ConfigurationChangeDetector Maven / Gradle / Ivy

package org.zodic.kubernetes.confcenter.reload;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.PreDestroy;

import io.fabric8.kubernetes.client.KubernetesClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.zodiac.core.bootstrap.config.AppPropertySourceLocator;
import org.zodiac.sdk.toolkit.util.collection.CollUtil;

public abstract class ConfigurationChangeDetector {

    protected Logger log = LoggerFactory.getLogger(getClass());

    protected ConfigurableEnvironment environment;

    protected ConfigReloadInfo configReloadInfo;

    protected KubernetesClient kubernetesClient;

    protected ConfigurationUpdateStrategy strategy;

    public ConfigurationChangeDetector(ConfigurableEnvironment environment, ConfigReloadInfo configReloadInfo,
        KubernetesClient kubernetesClient, ConfigurationUpdateStrategy strategy) {
        this.environment = environment;
        this.configReloadInfo = configReloadInfo;
        this.kubernetesClient = kubernetesClient;
        this.strategy = strategy;
    }

    @PreDestroy
    public void shutdown() {
        // Ensure the kubernetes client is cleaned up from spare threads when shutting
        // down
        this.kubernetesClient.close();
    }

    public void reloadProperties() {
        this.log.info("Reloading using strategy: {}.", this.strategy.getName());
        this.strategy.reload();
    }

    /**
     * Determines if two property sources are different.
     * 
     * @param mp1
     *            map property sources 1
     * @param mp2
     *            map property sources 2
     * @return {@code true} if source has changed
     */
    protected boolean changed(MapPropertySource mp1, MapPropertySource mp2) {
        if (mp1 == mp2) {
            return false;
        }
        if (mp1 == null && mp2 != null || mp1 != null && mp2 == null) {
            return true;
        }

        Map s1 = mp1.getSource();
        Map s2 = mp2.getSource();

        return s1 == null ? s2 != null : !s1.equals(s2);
    }

    protected boolean changed(List l1, List l2) {

        if (l1.size() != l2.size()) {
            this.log.debug("The current number of Confimap PropertySources does not match the ones loaded from the Kubernetes - No reload will take place");
            return false;
        }

        for (int i = 0; i < l1.size(); i++) {
            if (changed(l1.get(i), l2.get(i))) {
                return true;
            }
        }
        return false;
    }

    /**
     * Finds one registered property source of the given type, logging a warning if multiple property sources of that
     * type are available.
     * 
     * @param 
     *            property source type
     * @param sourceClass
     *            class for which property sources will be searched for
     * @return matched property source
     */
    protected > S findPropertySource(Class sourceClass) {
        List sources = findPropertySources(sourceClass);
        if (sources.size() == 0) {
            return null;
        }
        if (sources.size() > 1) {
            this.log.warn("Found more than one property source of type {} .", sourceClass);
        }
        return sources.get(0);
    }

    /**
     * @param 
     *            property source type
     * @param sourceClass
     *            class for which property sources will be found
     * @return finds all registered property sources of the given type
     */
    protected > List findPropertySources(Class sourceClass) {
        List managedSources = new LinkedList<>();

        LinkedList> sources = toLinkedList(this.environment.getPropertySources());
        while (!sources.isEmpty()) {
            PropertySource source = sources.pop();
            if (source instanceof CompositePropertySource) {
                CompositePropertySource comp = (CompositePropertySource)source;
                sources.addAll(comp.getPropertySources());
            } else if (sourceClass.isInstance(source)) {
                managedSources.add(sourceClass.cast(source));
            }
        }

        return managedSources;
    }

    private  LinkedList toLinkedList(Iterable it) {
        LinkedList list = new LinkedList();
        for (E e : it) {
            list.add(e);
        }
        return list;
    }

    /**
     * Returns a list of MapPropertySource that correspond to the current state of the system. This only handles the
     * PropertySource objects that are returned.
     * 
     * @param propertySourceLocator
     *            Spring's property source locator
     * @param environment
     *            Spring environment
     * @return A list of MapPropertySource that correspond to the current state of the system
     */
    protected List locateMapPropertySources(AppPropertySourceLocator propertySourceLocator,
        Environment environment) {

        List result = CollUtil.list();
        PropertySource propertySource = propertySourceLocator.locatePropertySource(environment);
        if (propertySource instanceof MapPropertySource) {
            result.add((MapPropertySource)propertySource);
        } else if (propertySource instanceof CompositePropertySource) {
            result.addAll(((CompositePropertySource)propertySource).getPropertySources().stream()
                .filter(p -> p instanceof MapPropertySource).map(p -> (MapPropertySource)p)
                .collect(Collectors.toList()));
        } else {
            this.log.debug("Found property source that cannot be handled: {} .", propertySource.getClass());
        }

        return result;
    }

}