/* * ================================================================================ * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed 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. * ============LICENSE_END========================================================= * */ package org.onap.dcae.analytics.web.spring; import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.onap.dcae.analytics.model.AnalyticsProfile; import org.onap.dcae.analytics.model.configbindingservice.ConfigBindingServiceConstants; import org.onap.dcae.analytics.model.util.function.JsonStringToMapFunction; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClient; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClientFactory; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsRequests; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration; import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsRequest; import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.env.StandardEnvironment; import org.springframework.util.ClassUtils; import org.springframework.web.context.support.StandardServletEnvironment; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * A custom spring framework environment post processor which can fetch and populate spring context with * Config Binding Service application properties. *

* Activated only when config binding service profile is active. * * @author Rajiv Singla */ public class ConfigBindingServiceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { private static final Logger logger = LoggerFactory.getLogger(ConfigBindingServiceEnvironmentPostProcessor.class); private static final String SERVLET_ENVIRONMENT_CLASS = "org.springframework.web.context.support.StandardServletEnvironment"; private static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE; private Disposable refreshConfigTask = null; private ConfigurableEnvironment env = null; private Map filterKeyMap = null; private String configServicePropertiesKey = ConfigBindingServiceConstants.CONFIG_BINDING_SERVICE_PROPERTIES_KEY; @Override public void postProcessEnvironment(final ConfigurableEnvironment environment, final SpringApplication application) { final boolean isConfigServiceProfilePresent = Arrays.stream(environment.getActiveProfiles()) .anyMatch(p -> p.equalsIgnoreCase(AnalyticsProfile.CONFIG_BINDING_SERVICE_PROFILE_NAME)); if (!isConfigServiceProfilePresent) { logger.info("Config Binding Service Profile is not active. " + "Skipping Adding config binding service properties"); return; } logger.info("Config Binding Service Profile is active. " + "Application properties will be fetched from config binding service"); env = environment; initialize(); } @Override public int getOrder() { return DEFAULT_ORDER; } public synchronized void addJsonPropertySource(final MutablePropertySources sources, final PropertySource source) { final String name = findPropertySource(sources); if (sources.contains(name)) { sources.addBefore(name, source); } else { sources.addFirst(source); } } private String findPropertySource(final MutablePropertySources sources) { if (ClassUtils.isPresent(SERVLET_ENVIRONMENT_CLASS, null) && sources.contains(StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME)) { return StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME; } return StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME; } /** * * Fetch the configuration. * */ public void initialize() { stop(); refreshConfigTask = createRefreshTask() // .subscribe(e -> logger.info("Refreshed configuration data"), throwable -> logger.error("Configuration refresh terminated due to exception", throwable), () -> logger.error("Configuration refresh terminated")); } /** * Fetch the configuration task from CBS. * */ private Flux createRefreshTask() { return readEnvironmentVariables() // .flatMap(this::createCbsClient) // .flatMapMany(this::periodicConfigurationUpdates) // .map(this::parseTcaConfig) // .onErrorResume(this::onErrorResume); } /** * periodicConfigurationUpdates. * * @param cbsClient cbsClient * @return configuration refreshed * */ public Flux periodicConfigurationUpdates(CbsClient cbsClient) { final Duration initialDelay = Duration.ZERO; final Duration refreshPeriod = Duration.ofMinutes(ConfigBindingServiceConstants.CONFIG_SERVICE_REFRESHPERIOD); final CbsRequest getConfigRequest = CbsRequests.getAll(RequestDiagnosticContext.create()); return cbsClient.updates(getConfigRequest, initialDelay, refreshPeriod); } /** * * get environment variables. * * @return environment properties. * */ public Mono readEnvironmentVariables() { logger.trace("Loading configuration from system environment variables"); CbsClientConfiguration cbsClientConfiguration = CbsClientConfiguration.fromEnvironment(); return Mono.just(cbsClientConfiguration); } /** * Stops the refreshing of the configuration. * */ public void stop() { if (refreshConfigTask != null) { refreshConfigTask.dispose(); refreshConfigTask = null; } } /** * periodicConfigurationUpdates. * * @param throwable throwable * @return Mono * */ private Mono onErrorResume(Throwable throwable) { logger.error("Could not refresh application configuration {}", throwable.toString()); return Mono.empty(); } /** * create CbsClient. * * @param cbsClientConfiguration cbs configuration * @return cbsclient * */ public Mono createCbsClient(CbsClientConfiguration cbsClientConfiguration) { return CbsClientFactory.createCbsClient(cbsClientConfiguration); } /** * Parse configuration. * * @param jsonObject the TCA service's configuration * @return this * */ public String parseTcaConfig(JsonObject jsonObject) { JsonElement jsonConfig = jsonObject.get(ConfigBindingServiceConstants.CONFIG); Optional configServiceJsonOptional = Optional.of(jsonConfig.toString()); // convert fetch config binding service json string to Map of property key and // values Map configPropertiesMap = configServiceJsonOptional .map(new JsonStringToMapFunction(configServicePropertiesKey)).orElse(Collections.emptyMap()); if (configPropertiesMap.isEmpty()) { logger.warn("No properties found in config binding service"); } else { // remove config service key prefix on spring reserved property key prefixes final Set springKeyPrefixes = ConfigBindingServiceConstants.getSpringReservedPropertiesKeyPrefixes(); final Set springKeys = springKeyPrefixes.stream() .map(springKeyPrefix -> configServicePropertiesKey + "." + springKeyPrefix) .collect(Collectors.toSet()); filterKeyMap = configPropertiesMap.entrySet().stream() .collect(Collectors.toMap((Map.Entry e) -> springKeys.stream() .anyMatch(springKey -> e.getKey().startsWith(springKey)) ? e.getKey().substring(configServicePropertiesKey.toCharArray().length + 1) : e.getKey(), Map.Entry::getValue)); filterKeyMap.forEach((key, value) -> logger .info("Adding property from config service in spring context: {} -> {}", key, value)); MutablePropertySources sources = env.getPropertySources(); addJsonPropertySource(sources, new MapPropertySource(configServicePropertiesKey, filterKeyMap)); } return configServiceJsonOptional.get(); } }