2  * ================================================================================
 
   3  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
 
   4  * ================================================================================
 
   5  * Licensed under the Apache License, Version 2.0 (the "License");
 
   6  * you may not use this file except in compliance with the License.
 
   7  * You may obtain a copy of the License at
 
   9  *      http://www.apache.org/licenses/LICENSE-2.0
 
  11  * Unless required by applicable law or agreed to in writing, software
 
  12  * distributed under the License is distributed on an "AS IS" BASIS,
 
  13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  14  * See the License for the specific language governing permissions and
 
  15  * limitations under the License.
 
  16  * ============LICENSE_END=========================================================
 
  20 package org.onap.dcae.analytics.web.spring;
 
  22 import java.time.Duration;
 
  23 import java.util.Arrays;
 
  24 import java.util.Collections;
 
  26 import java.util.Optional;
 
  28 import java.util.stream.Collectors;
 
  30 import org.onap.dcae.analytics.model.AnalyticsProfile;
 
  31 import org.onap.dcae.analytics.model.configbindingservice.ConfigBindingServiceConstants;
 
  32 import org.onap.dcae.analytics.model.util.function.JsonStringToMapFunction;
 
  33 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClient;
 
  34 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClientFactory;
 
  35 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsRequests;
 
  36 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration;
 
  37 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsRequest;
 
  38 import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext;
 
  39 import org.slf4j.Logger;
 
  40 import org.slf4j.LoggerFactory;
 
  41 import org.springframework.boot.SpringApplication;
 
  42 import org.springframework.boot.env.EnvironmentPostProcessor;
 
  43 import org.springframework.core.Ordered;
 
  44 import org.springframework.core.env.ConfigurableEnvironment;
 
  45 import org.springframework.core.env.MapPropertySource;
 
  46 import org.springframework.core.env.MutablePropertySources;
 
  47 import org.springframework.core.env.PropertySource;
 
  48 import org.springframework.core.env.StandardEnvironment;
 
  49 import org.springframework.util.ClassUtils;
 
  50 import org.springframework.web.context.support.StandardServletEnvironment;
 
  52 import com.google.gson.JsonElement;
 
  53 import com.google.gson.JsonObject;
 
  55 import reactor.core.Disposable;
 
  56 import reactor.core.publisher.Flux;
 
  57 import reactor.core.publisher.Mono;
 
  60  * A custom spring framework environment post processor which can fetch and populate spring context with
 
  61  * Config Binding Service application properties.
 
  63  * Activated only when config binding service profile is active.
 
  65  * @author Rajiv Singla
 
  67 public class ConfigBindingServiceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
 
  69     private static final Logger logger = LoggerFactory.getLogger(ConfigBindingServiceEnvironmentPostProcessor.class);
 
  70     private static final String SERVLET_ENVIRONMENT_CLASS =
 
  71             "org.springframework.web.context.support.StandardServletEnvironment";
 
  73     private static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE;
 
  75     private Disposable refreshConfigTask = null;
 
  77     private ConfigurableEnvironment env = null;
 
  79     private Map<String, Object> filterKeyMap = null;
 
  81     private String configServicePropertiesKey =
 
  82             ConfigBindingServiceConstants.CONFIG_BINDING_SERVICE_PROPERTIES_KEY;
 
  84     private String springConfigServicePropertiesKey =
 
  85             ConfigBindingServiceConstants.SPRING_CONFIG_BINDING_SERVICE_PROPERTIES_KEY;
 
  88     public void postProcessEnvironment(final ConfigurableEnvironment environment,
 
  89             final SpringApplication application) {
 
  91         final boolean isConfigServiceProfilePresent = Arrays.stream(environment.getActiveProfiles())
 
  92                 .anyMatch(p -> p.equalsIgnoreCase(AnalyticsProfile.CONFIG_BINDING_SERVICE_PROFILE_NAME));
 
  94         if (!isConfigServiceProfilePresent) {
 
  95             logger.info("Config Binding Service Profile is not active. "
 
  96                     + "Skipping Adding config binding service properties");
 
 100         logger.info("Config Binding Service Profile is active. "
 
 101                 + "Application properties will be fetched from config binding service");
 
 109     public int getOrder() {
 
 110         return DEFAULT_ORDER;
 
 113     public synchronized void addJsonPropertySource(final MutablePropertySources sources,
 
 114             final PropertySource<?> source) {
 
 115         final String name = findPropertySource(sources);
 
 116         if (sources.contains(name)) {
 
 117             sources.addBefore(name, source);
 
 119             sources.addFirst(source);
 
 123     private String findPropertySource(final MutablePropertySources sources) {
 
 124         if (ClassUtils.isPresent(SERVLET_ENVIRONMENT_CLASS, null)
 
 125                 && sources.contains(StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME)) {
 
 126             return StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME;
 
 129         return StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME;
 
 134      * Fetch the configuration.
 
 137     public void initialize() {
 
 140         refreshConfigTask = createRefreshTask() //
 
 141                 .subscribe(e -> logger.info("Refreshed configuration data"),
 
 142                         throwable -> logger.error("Configuration refresh terminated due to exception", throwable),
 
 143                         () -> logger.error("Configuration refresh terminated"));
 
 147      * Fetch the configuration task from CBS.
 
 150     private Flux<String> createRefreshTask() {
 
 151         return readEnvironmentVariables() //
 
 152                 .flatMap(this::createCbsClient) //
 
 153                 .flatMapMany(this::periodicConfigurationUpdates) //
 
 154                 .map(this::parseTcaConfig) //
 
 155                 .onErrorResume(this::onErrorResume);
 
 159      * periodicConfigurationUpdates.
 
 161      * @param cbsClient cbsClient
 
 162      * @return configuration refreshed
 
 165     public Flux<JsonObject> periodicConfigurationUpdates(CbsClient cbsClient) {
 
 166         final Duration initialDelay = Duration.ZERO;
 
 167         final Duration refreshPeriod =
 
 168                  Duration.ofMinutes(ConfigBindingServiceConstants.CONFIG_SERVICE_REFRESHPERIOD);
 
 169         final CbsRequest getConfigRequest = CbsRequests.getAll(RequestDiagnosticContext.create());
 
 170         return cbsClient.updates(getConfigRequest, initialDelay, refreshPeriod);
 
 175      * get environment variables.
 
 177      * @return environment properties.
 
 180     public Mono<CbsClientConfiguration> readEnvironmentVariables() {
 
 181         logger.trace("Loading configuration from system environment variables");
 
 182         CbsClientConfiguration cbsClientConfiguration = CbsClientConfiguration.fromEnvironment();
 
 183         return Mono.just(cbsClientConfiguration);
 
 187      * Stops the refreshing of the configuration.
 
 191         if (refreshConfigTask != null) {
 
 192             refreshConfigTask.dispose();
 
 193             refreshConfigTask = null;
 
 198      * periodicConfigurationUpdates.
 
 200      * @param throwable throwable
 
 204     private <R> Mono<R> onErrorResume(Throwable throwable) {
 
 205         logger.error("Could not refresh application configuration {}", throwable.toString());
 
 212      * @param cbsClientConfiguration cbs configuration
 
 216     public Mono<CbsClient> createCbsClient(CbsClientConfiguration cbsClientConfiguration) {
 
 217         return CbsClientFactory.createCbsClient(cbsClientConfiguration);
 
 221      * Parse configuration.
 
 223      * @param jsonObject the TCA service's configuration
 
 227     public String parseTcaConfig(JsonObject jsonObject) {
 
 229         Optional<String> configServiceJsonOptional;
 
 230         JsonElement jsonConfig = jsonObject.get(ConfigBindingServiceConstants.CONFIG);
 
 232         String policies = null;
 
 233         if (jsonConfig.getAsJsonObject().get(ConfigBindingServiceConstants.CONFIG) != null) {
 
 234             configServiceJsonOptional = Optional.of(jsonConfig.toString());
 
 235             policies = jsonConfig.getAsJsonObject().get(ConfigBindingServiceConstants.POLICIES)
 
 236                                  .getAsJsonObject().getAsJsonArray(ConfigBindingServiceConstants.ITEMS).get(0)
 
 237                                  .getAsJsonObject().get(ConfigBindingServiceConstants.CONFIG)
 
 238                                  .getAsJsonObject().get(ConfigBindingServiceConstants.TCAPOLICY).toString();
 
 240             configServiceJsonOptional = Optional.of(jsonObject.toString());
 
 243         // convert fetch config binding service json string to Map of property key and
 
 245         Map<String, Object> configPropertiesMap = configServiceJsonOptional
 
 246                 .map(new JsonStringToMapFunction(configServicePropertiesKey)).orElse(Collections.emptyMap());
 
 247         if (policies != null) {
 
 248             configPropertiesMap.put(ConfigBindingServiceConstants.CONFIG_POLICIES, policies);
 
 250         if (configPropertiesMap.isEmpty()) {
 
 252             logger.warn("No properties found in config binding service");
 
 256             // remove config service key prefix on spring reserved property key prefixes
 
 257             final Set<String> springKeyPrefixes =
 
 258                     ConfigBindingServiceConstants.getSpringReservedPropertiesKeyPrefixes();
 
 259             final Set<String> springKeys = springKeyPrefixes.stream()
 
 260                     .map(springKeyPrefix -> springConfigServicePropertiesKey + "." + springKeyPrefix)
 
 261                     .collect(Collectors.toSet());
 
 263             filterKeyMap = configPropertiesMap.entrySet().stream()
 
 264                     .collect(Collectors.toMap((Map.Entry<String, Object> e) -> springKeys.stream()
 
 265                             .anyMatch(springKey -> e.getKey().startsWith(springKey))
 
 266                                     ? e.getKey().substring(springConfigServicePropertiesKey.toCharArray().length + 1)
 
 268                             Map.Entry::getValue));
 
 270             filterKeyMap.forEach((key, value) -> logger
 
 271                     .info("Adding property from config service in spring context: {} -> {}", key, value));
 
 272             MutablePropertySources sources = env.getPropertySources();
 
 273             addJsonPropertySource(sources, new MapPropertySource(configServicePropertiesKey, filterKeyMap));
 
 276         return configServiceJsonOptional.get();