2 * ================================================================================
3 * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
4 * Copyright (c) 2019-2020 China Mobile. All rights reserved.
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 * ============LICENSE_END=========================================================
21 package org.onap.dcae.analytics.web.spring;
23 import java.time.Duration;
24 import java.util.Arrays;
25 import java.util.Collections;
27 import java.util.Optional;
29 import java.util.stream.Collectors;
31 import org.onap.dcae.analytics.model.AnalyticsProfile;
32 import org.onap.dcae.analytics.model.configbindingservice.ConfigBindingServiceConstants;
33 import org.onap.dcae.analytics.model.util.function.JsonStringToMapFunction;
34 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClient;
35 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsClientFactory;
36 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.api.CbsRequests;
37 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsClientConfiguration;
38 import org.onap.dcaegen2.services.sdk.rest.services.cbs.client.model.CbsRequest;
39 import org.onap.dcaegen2.services.sdk.rest.services.model.logging.RequestDiagnosticContext;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.springframework.boot.SpringApplication;
43 import org.springframework.boot.env.EnvironmentPostProcessor;
44 import org.springframework.core.Ordered;
45 import org.springframework.core.env.ConfigurableEnvironment;
46 import org.springframework.core.env.MapPropertySource;
47 import org.springframework.core.env.MutablePropertySources;
48 import org.springframework.core.env.PropertySource;
49 import org.springframework.core.env.StandardEnvironment;
50 import org.springframework.util.ClassUtils;
51 import org.springframework.web.context.support.StandardServletEnvironment;
53 import com.google.gson.JsonElement;
54 import com.google.gson.JsonObject;
56 import reactor.core.Disposable;
57 import reactor.core.publisher.Flux;
58 import reactor.core.publisher.Mono;
61 * A custom spring framework environment post processor which can fetch and populate spring context with
62 * Config Binding Service application properties.
64 * Activated only when config binding service profile is active.
66 * @author Rajiv Singla
68 public class ConfigBindingServiceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
70 private static final Logger logger = LoggerFactory.getLogger(ConfigBindingServiceEnvironmentPostProcessor.class);
71 private static final String SERVLET_ENVIRONMENT_CLASS =
72 "org.springframework.web.context.support.StandardServletEnvironment";
74 private static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE;
76 private Disposable refreshConfigTask = null;
78 private ConfigurableEnvironment env = null;
80 private Map<String, Object> filterKeyMap = null;
82 private String configServicePropertiesKey =
83 ConfigBindingServiceConstants.CONFIG_BINDING_SERVICE_PROPERTIES_KEY;
85 private String springConfigServicePropertiesKey =
86 ConfigBindingServiceConstants.SPRING_CONFIG_BINDING_SERVICE_PROPERTIES_KEY;
89 public void postProcessEnvironment(final ConfigurableEnvironment environment,
90 final SpringApplication application) {
92 final boolean isConfigServiceProfilePresent = Arrays.stream(environment.getActiveProfiles())
93 .anyMatch(p -> p.equalsIgnoreCase(AnalyticsProfile.CONFIG_BINDING_SERVICE_PROFILE_NAME));
95 if (!isConfigServiceProfilePresent) {
96 logger.info("Config Binding Service Profile is not active. "
97 + "Skipping Adding config binding service properties");
101 logger.info("Config Binding Service Profile is active. "
102 + "Application properties will be fetched from config binding service");
110 public int getOrder() {
111 return DEFAULT_ORDER;
114 public synchronized void addJsonPropertySource(final MutablePropertySources sources,
115 final PropertySource<?> source) {
116 final String name = findPropertySource(sources);
117 if (sources.contains(name)) {
118 sources.addBefore(name, source);
120 sources.addFirst(source);
124 private String findPropertySource(final MutablePropertySources sources) {
125 if (ClassUtils.isPresent(SERVLET_ENVIRONMENT_CLASS, null)
126 && sources.contains(StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME)) {
127 return StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME;
130 return StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME;
135 * Fetch the configuration.
138 public void initialize() {
141 refreshConfigTask = createRefreshTask() //
142 .subscribe(e -> logger.info("Refreshed configuration data"),
143 throwable -> logger.error("Configuration refresh terminated due to exception", throwable),
144 () -> logger.error("Configuration refresh terminated"));
148 * Fetch the configuration task from CBS.
151 private Flux<String> createRefreshTask() {
152 return readEnvironmentVariables() //
153 .flatMap(this::createCbsClient) //
154 .flatMapMany(this::periodicConfigurationUpdates) //
155 .map(this::parseTcaConfig) //
156 .onErrorResume(this::onErrorResume);
160 * periodicConfigurationUpdates.
162 * @param cbsClient cbsClient
163 * @return configuration refreshed
166 public Flux<JsonObject> periodicConfigurationUpdates(CbsClient cbsClient) {
167 final Duration initialDelay = Duration.ZERO;
168 final Duration refreshPeriod =
169 Duration.ofMinutes(ConfigBindingServiceConstants.CONFIG_SERVICE_REFRESHPERIOD);
170 final CbsRequest getConfigRequest = CbsRequests.getAll(RequestDiagnosticContext.create());
171 return cbsClient.updates(getConfigRequest, initialDelay, refreshPeriod);
176 * get environment variables.
178 * @return environment properties.
181 public Mono<CbsClientConfiguration> readEnvironmentVariables() {
182 logger.trace("Loading configuration from system environment variables");
183 CbsClientConfiguration cbsClientConfiguration = CbsClientConfiguration.fromEnvironment();
184 return Mono.just(cbsClientConfiguration);
188 * Stops the refreshing of the configuration.
192 if (refreshConfigTask != null) {
193 refreshConfigTask.dispose();
194 refreshConfigTask = null;
199 * periodicConfigurationUpdates.
201 * @param throwable throwable
205 private <R> Mono<R> onErrorResume(Throwable throwable) {
206 logger.error("Could not refresh application configuration {}", throwable.toString());
213 * @param cbsClientConfiguration cbs configuration
217 public Mono<CbsClient> createCbsClient(CbsClientConfiguration cbsClientConfiguration) {
218 return CbsClientFactory.createCbsClient(cbsClientConfiguration);
222 * Parse configuration.
224 * @param jsonObject the TCA service's configuration
228 public String parseTcaConfig(JsonObject jsonObject) {
230 Optional<String> configServiceJsonOptional = Optional.of(jsonObject.toString());
232 JsonElement jsonPolicyConfig = jsonObject.get(ConfigBindingServiceConstants.POLICIES);
234 String policies = null;
235 if (jsonPolicyConfig != null) {
236 policies = jsonPolicyConfig.getAsJsonObject().getAsJsonArray(ConfigBindingServiceConstants.ITEMS).get(0)
237 .getAsJsonObject().get(ConfigBindingServiceConstants.TCAPOLICY).toString();
240 // convert fetch config binding service json string to Map of property key and
242 Map<String, Object> configPropertiesMap = configServiceJsonOptional
243 .map(new JsonStringToMapFunction(configServicePropertiesKey)).orElse(Collections.emptyMap());
245 if (policies != null) {
246 configPropertiesMap.put(ConfigBindingServiceConstants.POLICY, policies);
248 if (configPropertiesMap.isEmpty()) {
249 logger.warn("No properties found in config binding service");
252 // remove config service key prefix on spring reserved property key prefixes
253 final Set<String> springKeyPrefixes =
254 ConfigBindingServiceConstants.getSpringReservedPropertiesKeyPrefixes();
255 final Set<String> springKeys = springKeyPrefixes.stream()
256 .map(springKeyPrefix -> springConfigServicePropertiesKey + "." + springKeyPrefix)
257 .collect(Collectors.toSet());
259 filterKeyMap = configPropertiesMap.entrySet().stream()
260 .collect(Collectors.toMap((Map.Entry<String, Object> e) -> springKeys.stream()
261 .anyMatch(springKey -> e.getKey().startsWith(springKey))
262 ? e.getKey().substring(springConfigServicePropertiesKey.toCharArray().length + 1)
264 Map.Entry::getValue));
266 filterKeyMap.forEach((key, value) -> logger
267 .info("Adding property from config service in spring context: {} -> {}", key, value));
268 MutablePropertySources sources = env.getPropertySources();
269 addJsonPropertySource(sources, new MapPropertySource(configServicePropertiesKey, filterKeyMap));
272 return configServiceJsonOptional.get();