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