5fa82d6d57152fd161a256e76f6d3658a5244891
[dcaegen2/analytics/tca-gen2.git] / dcae-analytics / dcae-analytics-web / src / main / java / org / onap / dcae / analytics / web / spring / ConfigBindingServiceEnvironmentPostProcessor.java
1 /*
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
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
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=========================================================
17  *
18  */
19
20 package org.onap.dcae.analytics.web.spring;
21
22 import java.time.Duration;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.stream.Collectors;
29
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;
54
55 import com.google.gson.JsonElement;
56 import com.google.gson.JsonObject;
57
58 import reactor.core.Disposable;
59 import reactor.core.publisher.Flux;
60 import reactor.core.publisher.Mono;
61
62 /**
63  * A custom spring framework environment post processor which can fetch and populate spring context with
64  * Config Binding Service application properties.
65  * <p>
66  * Activated only when config binding service profile is active.
67  *
68  * @author Rajiv Singla
69  */
70 public class ConfigBindingServiceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
71
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";
75
76     private static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE;
77
78     private Disposable refreshConfigTask = null;
79
80     private ConfigurableEnvironment env = null;
81
82     private Map<String, Object> filterKeyMap = null;
83
84     private String configServicePropertiesKey =
85             ConfigBindingServiceConstants.CONFIG_BINDING_SERVICE_PROPERTIES_KEY;
86
87     @Override
88     public void postProcessEnvironment(final ConfigurableEnvironment environment,
89             final SpringApplication application) {
90
91         final boolean isConfigServiceProfilePresent = Arrays.stream(environment.getActiveProfiles())
92                 .anyMatch(p -> p.equalsIgnoreCase(AnalyticsProfile.CONFIG_BINDING_SERVICE_PROFILE_NAME));
93
94         if (!isConfigServiceProfilePresent) {
95             logger.info("Config Binding Service Profile is not active. "
96                     + "Skipping Adding config binding service properties");
97             return;
98         }
99
100         logger.info("Config Binding Service Profile is active. "
101                 + "Application properties will be fetched from config binding service");
102
103         env = environment;
104         initialize();
105
106     }
107
108     @Override
109     public int getOrder() {
110         return DEFAULT_ORDER;
111     }
112
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);
118         } else {
119             sources.addFirst(source);
120         }
121     }
122
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;
127
128         }
129         return StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME;
130     }
131
132     /**
133      *
134      * Fetch the configuration.
135      *
136      */
137     public void initialize() {
138         stop();
139
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"));
144     }
145
146     /**
147      * Fetch the configuration task from CBS.
148      *
149      */
150     private Flux<String> createRefreshTask() {
151         return readEnvironmentVariables() //
152                 .flatMap(this::createCbsClient) //
153                 .flatMapMany(this::periodicConfigurationUpdates) //
154                 .map(this::parseTcaConfig) //
155                 .onErrorResume(this::onErrorResume);
156     }
157
158     /**
159      * periodicConfigurationUpdates.
160      *
161      * @param cbsClient cbsClient
162      * @return configuration refreshed
163      *
164      */
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);
171     }
172
173     /**
174      *
175      * get environment variables.
176      *
177      * @return environment properties.
178      *
179      */
180     public Mono<EnvProperties> readEnvironmentVariables() {
181         logger.trace("Loading configuration from system environment variables");
182         EnvProperties envProperties;
183         try {
184             envProperties = ImmutableEnvProperties.builder() //
185                     .consulHost(SystemConfig.getConsulHost()) //
186                     .consulPort(SystemConfig.getConsultPort()) //
187                     .cbsName(SystemConfig.getConfigBindingService()) //
188                     .appName(SystemConfig.getService()) //
189                     .build();
190         } catch (EnvironmentLoaderException e) {
191             return Mono.error(e);
192         }
193         logger.trace("Evaluated environment system variables {}", envProperties);
194         return Mono.just(envProperties);
195     }
196
197     /**
198      * Stops the refreshing of the configuration.
199      *
200      */
201     public void stop() {
202         if (refreshConfigTask != null) {
203             refreshConfigTask.dispose();
204             refreshConfigTask = null;
205         }
206     }
207
208     /**
209      * periodicConfigurationUpdates.
210      *
211      * @param throwable throwable
212      * @return Mono
213      *
214      */
215     private <R> Mono<R> onErrorResume(Throwable throwable) {
216         logger.error("Could not refresh application configuration {}", throwable.toString());
217         return Mono.empty();
218     }
219
220     /**
221      * create CbsClient.
222      *
223      * @param env environment properties
224      * @return cbsclient
225      *
226      */
227     public Mono<CbsClient> createCbsClient(EnvProperties env) {
228         return CbsClientFactory.createCbsClient(env);
229     }
230
231     /**
232      * Parse configuration.
233      *
234      * @param jsonObject the TCA service's configuration
235      * @return this
236      *
237      */
238     public String parseTcaConfig(JsonObject jsonObject) {
239
240         JsonElement jsonConfig = jsonObject.get(ConfigBindingServiceConstants.CONFIG);
241
242         Optional<String> configServiceJsonOptional = Optional.of(jsonConfig.toString());
243
244         // convert fetch config binding service json string to Map of property key and
245         // values
246         Map<String, Object> configPropertiesMap = configServiceJsonOptional
247                 .map(new JsonStringToMapFunction(configServicePropertiesKey)).orElse(Collections.emptyMap());
248
249         if (configPropertiesMap.isEmpty()) {
250
251             logger.warn("No properties found in config binding service");
252
253         } else {
254
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());
261
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)
266                                     : e.getKey(),
267                             Map.Entry::getValue));
268
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));
273
274         }
275         return configServiceJsonOptional.get();
276     }
277
278 }