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.dcae.analytics.web.config.SystemConfig;
34 import org.onap.dcae.analytics.web.exception.EnvironmentLoaderException;
35 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClient;
36 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClientFactory;
37 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsRequests;
38 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsRequest;
39 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.EnvProperties;
40 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.ImmutableEnvProperties;
41 import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44 import org.springframework.boot.SpringApplication;
45 import org.springframework.boot.env.EnvironmentPostProcessor;
46 import org.springframework.core.Ordered;
47 import org.springframework.core.env.ConfigurableEnvironment;
48 import org.springframework.core.env.MapPropertySource;
49 import org.springframework.core.env.MutablePropertySources;
50 import org.springframework.core.env.PropertySource;
51 import org.springframework.core.env.StandardEnvironment;
52 import org.springframework.util.ClassUtils;
53 import org.springframework.web.context.support.StandardServletEnvironment;
55 import com.google.gson.JsonElement;
56 import com.google.gson.JsonObject;
58 import reactor.core.Disposable;
59 import reactor.core.publisher.Flux;
60 import reactor.core.publisher.Mono;
63 * A custom spring framework environment post processor which can fetch and populate spring context with
64 * Config Binding Service application properties.
66 * Activated only when config binding service profile is active.
68 * @author Rajiv Singla
70 public class ConfigBindingServiceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
72 private static final Logger logger = LoggerFactory.getLogger(ConfigBindingServiceEnvironmentPostProcessor.class);
73 private static final String SERVLET_ENVIRONMENT_CLASS =
74 "org.springframework.web.context.support.StandardServletEnvironment";
76 private static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE;
78 private Disposable refreshConfigTask = null;
80 private ConfigurableEnvironment env = null;
82 private Map<String, Object> filterKeyMap = null;
84 private String configServicePropertiesKey =
85 ConfigBindingServiceConstants.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<EnvProperties> readEnvironmentVariables() {
181 logger.trace("Loading configuration from system environment variables");
182 EnvProperties envProperties;
184 envProperties = ImmutableEnvProperties.builder() //
185 .consulHost(SystemConfig.getConsulHost()) //
186 .consulPort(SystemConfig.getConsultPort()) //
187 .cbsName(SystemConfig.getConfigBindingService()) //
188 .appName(SystemConfig.getService()) //
190 } catch (EnvironmentLoaderException e) {
191 return Mono.error(e);
193 logger.trace("Evaluated environment system variables {}", envProperties);
194 return Mono.just(envProperties);
198 * Stops the refreshing of the configuration.
202 if (refreshConfigTask != null) {
203 refreshConfigTask.dispose();
204 refreshConfigTask = null;
209 * periodicConfigurationUpdates.
211 * @param throwable throwable
215 private <R> Mono<R> onErrorResume(Throwable throwable) {
216 logger.error("Could not refresh application configuration {}", throwable.toString());
223 * @param env environment properties
227 public Mono<CbsClient> createCbsClient(EnvProperties env) {
228 return CbsClientFactory.createCbsClient(env);
232 * Parse configuration.
234 * @param jsonObject the TCA service's configuration
238 public String parseTcaConfig(JsonObject jsonObject) {
240 JsonElement jsonConfig = jsonObject.get(ConfigBindingServiceConstants.CONFIG);
242 Optional<String> configServiceJsonOptional = Optional.of(jsonConfig.toString());
244 // convert fetch config binding service json string to Map of property key and
246 Map<String, Object> configPropertiesMap = configServiceJsonOptional
247 .map(new JsonStringToMapFunction(configServicePropertiesKey)).orElse(Collections.emptyMap());
249 if (configPropertiesMap.isEmpty()) {
251 logger.warn("No properties found in config binding service");
255 // remove config service key prefix on spring reserved property key prefixes
256 final Set<String> springKeyPrefixes =
257 ConfigBindingServiceConstants.SPRING_RESERVED_PROPERTIES_KEY_PREFIXES;
258 final Set<String> springKeys = springKeyPrefixes.stream()
259 .map(springKeyPrefix -> configServicePropertiesKey + "." + springKeyPrefix)
260 .collect(Collectors.toSet());
262 filterKeyMap = configPropertiesMap.entrySet().stream()
263 .collect(Collectors.toMap((Map.Entry<String, Object> e) -> springKeys.stream()
264 .anyMatch(springKey -> e.getKey().startsWith(springKey))
265 ? e.getKey().substring(configServicePropertiesKey.toCharArray().length + 1)
267 Map.Entry::getValue));
269 filterKeyMap.forEach((key, value) -> logger
270 .info("Adding property from config service in spring context: {} -> {}", key, value));
271 MutablePropertySources sources = env.getPropertySources();
272 addJsonPropertySource(sources, new MapPropertySource(configServicePropertiesKey, filterKeyMap));
275 return configServiceJsonOptional.get();