ulisesbocchio/jasypt-spring-boot

View on GitHub
jasypt-spring-boot/src/main/java/com/ulisesbocchio/jasyptspringboot/caching/RefreshScopeRefreshedEventListener.java

Summary

Maintainability
A
0 mins
Test Coverage
package com.ulisesbocchio.jasyptspringboot.caching;

import com.ulisesbocchio.jasyptspringboot.EncryptablePropertySource;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter;
import com.ulisesbocchio.jasyptspringboot.properties.JasyptEncryptorConfigurationProperties;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.util.ClassUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>RefreshScopeRefreshedEventListener class.</p>
 *
 * @author Sergio.U.Bocchio
 * @version $Id: $Id
 */
@Order(Ordered.LOWEST_PRECEDENCE - 10)
@Slf4j
public class RefreshScopeRefreshedEventListener implements ApplicationListener<ApplicationEvent>, InitializingBean {

    /** Constant <code>EVENT_CLASS_NAMES</code> */
    public static final List<String> EVENT_CLASS_NAMES = Arrays.asList(
            "org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent",
            "org.springframework.cloud.context.environment.EnvironmentChangeEvent",
            "org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent"
    );
    private final ConfigurableEnvironment environment;
    private final EncryptablePropertySourceConverter converter;
    private final List<Class<?>> eventClasses = new ArrayList<>();
    private final Map<String, Boolean> eventTriggersCache = new ConcurrentHashMap<>();
    private final JasyptEncryptorConfigurationProperties config;

    /**
     * <p>Constructor for RefreshScopeRefreshedEventListener.</p>
     *
     * @param environment a {@link org.springframework.core.env.ConfigurableEnvironment} object
     * @param converter a {@link com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter} object
     * @param config a {@link com.ulisesbocchio.jasyptspringboot.properties.JasyptEncryptorConfigurationProperties} object
     */
    public RefreshScopeRefreshedEventListener(ConfigurableEnvironment environment, EncryptablePropertySourceConverter converter, JasyptEncryptorConfigurationProperties config) {
        this.environment = environment;
        this.converter = converter;
        this.config = config;
    }

    /** {@inheritDoc} */
    @Override
    @SneakyThrows
    public void onApplicationEvent(ApplicationEvent event) {
//        log.info("APPLICATION EVENT: {}", event.getClass().getName());
        if (this.shouldTriggerRefresh(event)) {
            log.info("Refreshing cached encryptable property sources on {}", event.getClass().getSimpleName());
            refreshCachedProperties();
            decorateNewSources();
        }
    }

    private boolean shouldTriggerRefresh(ApplicationEvent event) {
        String className = event.getClass().getName();
        if (!eventTriggersCache.containsKey(className)) {
            eventTriggersCache.put(className, eventClasses.stream().anyMatch(clazz -> this.isAssignable(clazz, event)));
        }
        return eventTriggersCache.get(className);
    }

    private void decorateNewSources() {
        MutablePropertySources propSources = environment.getPropertySources();
        converter.convertPropertySources(propSources);
    }

    boolean isAssignable(Class<?> clazz, Object value) {
        return ClassUtils.isAssignableValue(clazz, value);
    }

    private void refreshCachedProperties() {
        PropertySources propertySources = environment.getPropertySources();
        propertySources.forEach(this::refreshPropertySource);
    }

    @SuppressWarnings("rawtypes")
    private void refreshPropertySource(PropertySource<?> propertySource) {
        if (propertySource instanceof CompositePropertySource) {
            CompositePropertySource cps = (CompositePropertySource) propertySource;
            cps.getPropertySources().forEach(this::refreshPropertySource);
        } else if (propertySource instanceof EncryptablePropertySource) {
            EncryptablePropertySource eps = (EncryptablePropertySource) propertySource;
            eps.refresh();
        }
    }

    private Class<?> getClassSafe(String className) {
        try {
            return ClassUtils.forName(className, null);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    /** {@inheritDoc} */
    @Override
    public void afterPropertiesSet() throws Exception {
        Stream
                .concat(EVENT_CLASS_NAMES.stream(), this.config.getRefreshedEventClasses().stream())
                .map(this::getClassSafe).filter(Objects::nonNull)
                .collect(Collectors.toCollection(() -> this.eventClasses));
    }
}