c41e4168377b54f5db8bdec19e1cd301813045b8
[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.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;
51
52 import com.google.gson.JsonElement;
53 import com.google.gson.JsonObject;
54
55 import reactor.core.Disposable;
56 import reactor.core.publisher.Flux;
57 import reactor.core.publisher.Mono;
58
59 /**
60  * A custom spring framework environment post processor which can fetch and populate spring context with
61  * Config Binding Service application properties.
62  * <p>
63  * Activated only when config binding service profile is active.
64  *
65  * @author Rajiv Singla
66  */
67 public class ConfigBindingServiceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
68
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";
72
73     private static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE;
74
75     private Disposable refreshConfigTask = null;
76
77     private ConfigurableEnvironment env = null;
78
79     private Map<String, Object> filterKeyMap = null;
80
81     private String configServicePropertiesKey =
82             ConfigBindingServiceConstants.CONFIG_BINDING_SERVICE_PROPERTIES_KEY;
83
84     @Override
85     public void postProcessEnvironment(final ConfigurableEnvironment environment,
86             final SpringApplication application) {
87
88         final boolean isConfigServiceProfilePresent = Arrays.stream(environment.getActiveProfiles())
89                 .anyMatch(p -> p.equalsIgnoreCase(AnalyticsProfile.CONFIG_BINDING_SERVICE_PROFILE_NAME));
90
91         if (!isConfigServiceProfilePresent) {
92             logger.info("Config Binding Service Profile is not active. "
93                     + "Skipping Adding config binding service properties");
94             return;
95         }
96
97         logger.info("Config Binding Service Profile is active. "
98                 + "Application properties will be fetched from config binding service");
99
100         env = environment;
101         initialize();
102
103     }
104
105     @Override
106     public int getOrder() {
107         return DEFAULT_ORDER;
108     }
109
110     public synchronized void addJsonPropertySource(final MutablePropertySources sources,
111             final PropertySource<?> source) {
112         final String name = findPropertySource(sources);
113         if (sources.contains(name)) {
114             sources.addBefore(name, source);
115         } else {
116             sources.addFirst(source);
117         }
118     }
119
120     private String findPropertySource(final MutablePropertySources sources) {
121         if (ClassUtils.isPresent(SERVLET_ENVIRONMENT_CLASS, null)
122                 && sources.contains(StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME)) {
123             return StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME;
124
125         }
126         return StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME;
127     }
128
129     /**
130      *
131      * Fetch the configuration.
132      *
133      */
134     public void initialize() {
135         stop();
136
137         refreshConfigTask = createRefreshTask() //
138                 .subscribe(e -> logger.info("Refreshed configuration data"),
139                         throwable -> logger.error("Configuration refresh terminated due to exception", throwable),
140                         () -> logger.error("Configuration refresh terminated"));
141     }
142
143     /**
144      * Fetch the configuration task from CBS.
145      *
146      */
147     private Flux<String> createRefreshTask() {
148         return readEnvironmentVariables() //
149                 .flatMap(this::createCbsClient) //
150                 .flatMapMany(this::periodicConfigurationUpdates) //
151                 .map(this::parseTcaConfig) //
152                 .onErrorResume(this::onErrorResume);
153     }
154
155     /**
156      * periodicConfigurationUpdates.
157      *
158      * @param cbsClient cbsClient
159      * @return configuration refreshed
160      *
161      */
162     public Flux<JsonObject> periodicConfigurationUpdates(CbsClient cbsClient) {
163         final Duration initialDelay = Duration.ZERO;
164         final Duration refreshPeriod =
165                  Duration.ofMinutes(ConfigBindingServiceConstants.CONFIG_SERVICE_REFRESHPERIOD);
166         final CbsRequest getConfigRequest = CbsRequests.getAll(RequestDiagnosticContext.create());
167         return cbsClient.updates(getConfigRequest, initialDelay, refreshPeriod);
168     }
169
170     /**
171      *
172      * get environment variables.
173      *
174      * @return environment properties.
175      *
176      */
177     public Mono<CbsClientConfiguration> readEnvironmentVariables() {
178         logger.trace("Loading configuration from system environment variables");
179         CbsClientConfiguration cbsClientConfiguration = CbsClientConfiguration.fromEnvironment();
180         return Mono.just(cbsClientConfiguration);
181     }
182
183     /**
184      * Stops the refreshing of the configuration.
185      *
186      */
187     public void stop() {
188         if (refreshConfigTask != null) {
189             refreshConfigTask.dispose();
190             refreshConfigTask = null;
191         }
192     }
193
194     /**
195      * periodicConfigurationUpdates.
196      *
197      * @param throwable throwable
198      * @return Mono
199      *
200      */
201     private <R> Mono<R> onErrorResume(Throwable throwable) {
202         logger.error("Could not refresh application configuration {}", throwable.toString());
203         return Mono.empty();
204     }
205
206     /**
207      * create CbsClient.
208      *
209      * @param cbsClientConfiguration cbs configuration
210      * @return cbsclient
211      *
212      */
213     public Mono<CbsClient> createCbsClient(CbsClientConfiguration cbsClientConfiguration) {
214         return CbsClientFactory.createCbsClient(cbsClientConfiguration);
215     }
216
217     /**
218      * Parse configuration.
219      *
220      * @param jsonObject the TCA service's configuration
221      * @return this
222      *
223      */
224     public String parseTcaConfig(JsonObject jsonObject) {
225
226         Optional<String> configServiceJsonOptional;
227         JsonElement jsonConfig = jsonObject.get(ConfigBindingServiceConstants.CONFIG);
228
229         if (jsonConfig.getAsJsonObject().get(ConfigBindingServiceConstants.CONFIG) != null) {
230             configServiceJsonOptional = Optional.of(jsonConfig.toString());
231         } else {
232             configServiceJsonOptional = Optional.of(jsonObject.toString());
233         }
234
235         // convert fetch config binding service json string to Map of property key and
236         // values
237         Map<String, Object> configPropertiesMap = configServiceJsonOptional
238                 .map(new JsonStringToMapFunction(configServicePropertiesKey)).orElse(Collections.emptyMap());
239
240         if (configPropertiesMap.isEmpty()) {
241
242             logger.warn("No properties found in config binding service");
243
244         } else {
245
246             // remove config service key prefix on spring reserved property key prefixes
247             final Set<String> springKeyPrefixes =
248                     ConfigBindingServiceConstants.getSpringReservedPropertiesKeyPrefixes();
249             final Set<String> springKeys = springKeyPrefixes.stream()
250                     .map(springKeyPrefix -> configServicePropertiesKey + "." + springKeyPrefix)
251                     .collect(Collectors.toSet());
252
253             filterKeyMap = configPropertiesMap.entrySet().stream()
254                     .collect(Collectors.toMap((Map.Entry<String, Object> e) -> springKeys.stream()
255                             .anyMatch(springKey -> e.getKey().startsWith(springKey))
256                                     ? e.getKey().substring(configServicePropertiesKey.toCharArray().length + 1)
257                                     : e.getKey(),
258                             Map.Entry::getValue));
259
260             filterKeyMap.forEach((key, value) -> logger
261                     .info("Adding property from config service in spring context: {} -> {}", key, value));
262             MutablePropertySources sources = env.getPropertySources();
263             addJsonPropertySource(sources, new MapPropertySource(configServicePropertiesKey, filterKeyMap));
264
265         }
266         return configServiceJsonOptional.get();
267     }
268
269 }