2 * Copyright © 2016-2018 European Support Limited
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.onap.config.impl;
19 import static org.onap.config.ConfigurationUtils.isBlank;
22 import java.lang.reflect.Constructor;
23 import java.lang.reflect.Field;
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Modifier;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
36 import java.util.concurrent.atomic.AtomicReference;
37 import java.util.function.Predicate;
38 import java.util.stream.Collectors;
40 import org.apache.commons.configuration2.ex.ConfigurationException;
41 import org.onap.config.ConfigurationUtils;
42 import org.onap.config.Constants;
43 import org.onap.config.NonConfigResource;
44 import org.onap.config.api.Config;
45 import org.onap.config.api.Hint;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
49 public class ConfigurationImpl implements org.onap.config.api.Configuration {
51 private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationImpl.class);
53 private static final String KEY_CANNOT_BE_NULL = "Key can't be null.";
54 private static final NonConfigResource NON_CONFIG_RESOURCE = new NonConfigResource();
55 private static final Map<String, AggregateConfiguration> MODULE_CONFIG_STORE = new HashMap<>();
58 if (!loadClassPathConfigurationsAndResources()
59 || !loadAdditionalConfigurationsAndResources()
60 || !loadTenantConfigurations()) {
61 throw new IllegalStateException("Failed to initialize configuration");
63 populateFinalConfigurationIncrementally(MODULE_CONFIG_STORE);
64 loadNodeSpecificConfigurations();
68 public <T> T get(String tenant, String namespace, String key, Class<T> clazz, Hint... hints) {
69 String[] tenantNamespaceArray;
70 if (tenant == null && namespace != null) {
71 tenantNamespaceArray = namespace.split(Constants.TENANT_NAMESPACE_SEPARATOR);
72 if (tenantNamespaceArray.length > 1) {
73 tenant = tenantNamespaceArray[0];
74 namespace = tenantNamespaceArray[1];
78 tenant = ConfigurationRepository.lookup().isValidTenant(tenant) ? tenant.toUpperCase() : Constants.DEFAULT_TENANT;
79 namespace = ConfigurationRepository.lookup().isValidNamespace(namespace) ? namespace.toUpperCase() : Constants.DEFAULT_NAMESPACE;
80 hints = hints == null || hints.length == 0 ? new Hint[]{Hint.EXTERNAL_LOOKUP, Hint.NODE_SPECIFIC} : hints;
82 returnValue = getInternal(tenant, namespace, key, clazz, hints);
83 if ((returnValue == null || ConfigurationUtils.isZeroLengthArray(clazz, returnValue))
84 && !Constants.DEFAULT_TENANT.equals(tenant)) {
85 returnValue = getInternal(Constants.DEFAULT_TENANT, namespace, key, clazz, hints);
87 if ((returnValue == null || ConfigurationUtils.isZeroLengthArray(clazz, returnValue))
88 && !Constants.DEFAULT_NAMESPACE.equals(namespace)) {
89 returnValue = getInternal(tenant, Constants.DEFAULT_NAMESPACE, key, clazz, hints);
91 if ((returnValue == null || ConfigurationUtils.isZeroLengthArray(clazz, returnValue))
92 && !Constants.DEFAULT_NAMESPACE.equals(namespace) && !Constants.DEFAULT_TENANT.equals(tenant)) {
93 returnValue = getInternal(Constants.DEFAULT_TENANT, Constants.DEFAULT_NAMESPACE, key, clazz, hints);
95 if (returnValue == null && ConfigurationUtils.isAPrimitive(clazz)) {
96 returnValue = (T) ConfigurationUtils.getDefaultFor(clazz);
102 public <T> Map<String, T> populateMap(String tenantId, String namespace, String key, Class<T> clazz) {
103 final String calculatedTenantId = calculateTenant(tenantId);
104 final String calculatedNamespace = calculateNamespace(namespace);
105 Map<String, T> map = new HashMap<>();
106 Iterator<String> keys;
108 keys = ConfigurationRepository.lookup().getConfigurationFor(calculatedTenantId, calculatedNamespace).getKeys(key);
109 keys.forEachRemaining(k -> {
110 if (k.startsWith(key + ".")) {
111 k = k.substring(key.length() + 1);
112 String subkey = k.substring(0, k.indexOf('.'));
113 if (!map.containsKey(subkey)) {
114 map.put(subkey, get(calculatedTenantId, calculatedNamespace, key + "." + subkey, clazz));
118 } catch (Exception e) {
120 "Couldn't populate map fot tenant: {}, namespace: {}, key: {}, type: {}",
124 clazz.getSimpleName(),
132 public Map<Object, Object> generateMap(String tenantId, String namespace, String key) {
133 final String calculatedTenantId = calculateTenant(tenantId);
134 final String calculatedNamespace = calculateNamespace(namespace);
135 Map<Object, Object> parentMap = new HashMap<>();
136 Iterator<String> configKeys;
139 configKeys = ConfigurationRepository.lookup().getConfigurationFor(calculatedTenantId, calculatedNamespace).getKeys();
141 configKeys = ConfigurationRepository.lookup().getConfigurationFor(calculatedTenantId, calculatedNamespace).getKeys(key);
143 configKeys.forEachRemaining(subKey -> {
144 if (!isBlank(key) && !subKey.startsWith(key + ".")) {
147 parseConfigSubKeys(subKey, key, calculatedTenantId, calculatedNamespace, parentMap);
149 } catch (Exception e) {
151 "Couldn't generate map fot tenant: {}, namespace: {}, key: {}",
161 private static boolean loadClassPathConfigurationsAndResources() {
162 List<URL> classpathResources = ConfigurationUtils.getAllClassPathResources();
163 Predicate<URL> predicate = ConfigurationUtils::isConfig;
164 Map<Boolean, List<URL>> resources = classpathResources.stream().collect(Collectors.partitioningBy(predicate));
165 List<URL> configResources = resources.get(true);
166 List<URL> nonConfigResources = resources.get(false);
167 AtomicReference<Boolean> successFlagHolder = new AtomicReference<>(true);
169 configResources.forEach(url -> successFlagHolder.set(setUpdateModuleConfigStore(url)));
171 nonConfigResources.forEach(NON_CONFIG_RESOURCE::add);
173 return successFlagHolder.get();
176 private static boolean loadAdditionalConfigurationsAndResources() {
177 String configLocation = System.getProperty("config.location");
178 AtomicReference<Boolean> successFlagHolder = new AtomicReference<>(true);
179 if (!isBlank(configLocation)) {
180 File root = new File(configLocation);
181 Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
182 Predicate<File> filePredicate = ConfigurationUtils::isConfig;
183 Map<Boolean, List<File>> resources = filesystemResources.stream().collect(Collectors.partitioningBy(filePredicate));
184 List<File> configResources = resources.get(true);
185 List<File> nonConfigResources = resources.get(false);
187 configResources.forEach(file -> successFlagHolder.set(setUpdateModuleConfigStore(file)));
189 nonConfigResources.forEach(NON_CONFIG_RESOURCE::add);
191 return successFlagHolder.get();
194 private static boolean loadTenantConfigurations() {
195 String tenantConfigLocation = System.getProperty("tenant.config.location");
196 AtomicReference<Boolean> successFlagHolder = new AtomicReference<>(true);
197 if (!isBlank(tenantConfigLocation)) {
198 File root = new File(tenantConfigLocation);
199 Collection<File> tenantsRoot = ConfigurationUtils.getAllFiles(root, false, true);
200 Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
202 Map<Boolean, List<File>> resources = filesystemResources.stream().collect(Collectors.partitioningBy(ConfigurationUtils::isConfig));
203 Collection<File> tenantResources = resources.get(true);
205 tenantResources.forEach(configFile -> {
206 AtomicReference<String> moduleNameHolder = new AtomicReference<>(ConfigurationUtils.getNamespace(configFile));
207 Predicate<File> startsWithRootPredicate = tenantRoot -> configFile.getAbsolutePath().startsWith(tenantRoot.getAbsolutePath());
208 Collection<File> matchesTenantRoot = tenantsRoot.stream().filter(startsWithRootPredicate).collect(Collectors.toCollection(ArrayList::new));
209 AtomicReference<String[]> altResource = new AtomicReference<>();
210 matchesTenantRoot.forEach(file -> altResource.set(
211 (file.getName().toUpperCase() + Constants.TENANT_NAMESPACE_SEPARATOR + moduleNameHolder.get())
212 .split(Constants.TENANT_NAMESPACE_SEPARATOR)
215 successFlagHolder.set(setUpdateModuleConfigStore(configFile, altResource.get()));
218 return successFlagHolder.get();
221 private static void loadNodeSpecificConfigurations() {
222 String nodeConfigLocation = System.getProperty("node.config.location");
223 if (!isBlank(nodeConfigLocation)) {
224 File root = new File(nodeConfigLocation);
225 Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
226 filesystemResources.stream().filter(ConfigurationUtils::isConfig).forEach(
227 file -> ConfigurationRepository.lookup().populateOverrideConfiguration(
228 ConfigurationUtils.getConfigurationRepositoryKey(
229 ConfigurationUtils.getNamespace(file).split(Constants.TENANT_NAMESPACE_SEPARATOR)), file)
234 private static void populateFinalConfigurationIncrementally(Map<String, AggregateConfiguration> configs) {
235 if (configs.get(Constants.DEFAULT_TENANT + Constants.KEY_ELEMENTS_DELIMITER + Constants.DB_NAMESPACE) != null) {
236 ConfigurationRepository.lookup().populateConfiguration(
237 Constants.DEFAULT_TENANT + Constants.KEY_ELEMENTS_DELIMITER + Constants.DB_NAMESPACE,
238 configs.remove(Constants.DEFAULT_TENANT + Constants.KEY_ELEMENTS_DELIMITER + Constants.DB_NAMESPACE)
239 .getFinalConfiguration());
241 Set<String> modules = configs.keySet();
243 m -> ConfigurationRepository.lookup().populateConfiguration(m, configs.get(m).getFinalConfiguration())
247 private static <T> boolean setUpdateModuleConfigStore(T resource, String... namedResources) {
248 boolean success = true;
251 if (namedResources == null || namedResources.length == 0) {
252 moduleName = getConfigurationRepositoryKeyWrapper(resource);
254 moduleName = getConfigurationRepositoryKeyWrapper(namedResources);
256 moduleAddConfigWrapper(moduleName, resource);
257 } catch (Exception e) {
259 LOGGER.error("Error occurred while processing config resource {}", resource, e);
264 private static <T> String getConfigurationRepositoryKeyWrapper(T resource) throws ConfigurationException {
265 switch (resource.getClass().getSimpleName()) {
267 return ConfigurationUtils.getConfigurationRepositoryKey((URL) resource);
269 return ConfigurationUtils.getConfigurationRepositoryKey((File) resource);
271 return ConfigurationUtils.getConfigurationRepositoryKey((String[]) resource);
273 throw new ConfigurationException("Unsupported resource type.");
277 private static <T> void moduleAddConfigWrapper(String moduleName, T resource) throws ConfigurationException {
278 AggregateConfiguration moduleConfig = MODULE_CONFIG_STORE.get(moduleName);
279 if (moduleConfig == null) {
280 moduleConfig = new AggregateConfiguration();
281 MODULE_CONFIG_STORE.put(moduleName, moduleConfig);
283 switch (resource.getClass().getSimpleName()) {
285 moduleConfig.addConfig((URL) resource);
288 moduleConfig.addConfig((File) resource);
291 throw new ConfigurationException("Unsupported resource type.");
295 private void parseConfigSubKeys(String subKey, String keyPrefix, String tenantId, String namespace, Map<Object, Object> targetMap) {
296 String value = getAsString(tenantId, namespace, subKey);
297 if (!isBlank(keyPrefix) && subKey.startsWith(keyPrefix + ".")) {
298 subKey = subKey.substring(keyPrefix.trim().length() + 1);
300 while (subKey.contains(".")) {
301 String subSubKey = subKey.substring(0, subKey.indexOf('.'));
302 subKey = subKey.substring(subKey.indexOf('.') + 1);
303 if (!targetMap.containsKey(subSubKey)) {
304 Map<Object, Object> subMap = new HashMap<>();
305 targetMap.put(subSubKey, subMap);
308 targetMap = (Map<Object, Object>) targetMap.get(subSubKey);
311 targetMap.put(subKey, value);
314 protected <T> T getInternal(String tenant, String namespace, String key, Class<T> clazz, Hint... hints) {
315 int processingHints = Hint.DEFAULT.value();
317 for (Hint hint : hints) {
318 processingHints = processingHints | hint.value();
322 tenant = calculateTenant(tenant);
323 namespace = calculateNamespace(namespace);
325 if (isBlank(key) && !clazz.isAnnotationPresent(Config.class)) {
326 throw new IllegalArgumentException(KEY_CANNOT_BE_NULL);
330 throw new IllegalArgumentException("clazz is null.");
333 if (ConfigurationUtils.isAPrimitive(clazz)) {
334 clazz = getWrapperClass(clazz);
337 if (ConfigurationUtils.isWrapperClass(clazz)) {
338 return getWrapperTypeValue(tenant, namespace, key, clazz, processingHints);
339 } else if (ConfigurationUtils.isAPrimitivesOrWrappersArray(clazz)) {
340 return getArrayTypeValue(tenant, namespace, key, clazz, processingHints);
341 } else if (clazz.isAnnotationPresent(Config.class)) {
342 return getAnnotatedTypeValue(tenant, namespace, clazz, isBlank(key) ? "" : (key + "."), hints);
344 throw new IllegalArgumentException(
345 "Only primitive classes, wrapper classes, corresponding array classes and any "
346 + "class decorated with @org.openecomp.config.api.Config are allowed as argument.");
348 } catch (Exception exception) {
350 "Failed to get internal value fot tenant: {}, namespace: {}, key: {}, type: {}",
354 clazz.getSimpleName(),
361 private <T> T getWrapperTypeValue(String tenant, String namespace, String key, Class<T> clazz, int processingHints) throws Exception {
362 Object obj = ConfigurationUtils.getProperty(
363 ConfigurationRepository.lookup().getConfigurationFor(tenant, namespace), key, processingHints);
365 if (ConfigurationUtils.isCollection(obj.toString())) {
366 obj = ConfigurationUtils.getCollectionString(obj.toString());
368 String value = ConfigurationUtils.processVariablesIfPresent(
371 obj.toString().split(",")[0]
373 return (T) getTypeValue(value, clazz, processingHints);
378 private <T> T getArrayTypeValue(String tenant, String namespace, String key, Class<T> clazz, int processingHints) throws Exception {
379 Object obj = ConfigurationUtils.getProperty(
380 ConfigurationRepository.lookup().getConfigurationFor(tenant, namespace), key, processingHints);
382 Class componentType = clazz.getComponentType();
383 if (ConfigurationUtils.isAPrimitivesArray(clazz)) {
384 componentType = getWrapperClass(componentType);
386 String collString = ConfigurationUtils.getCollectionString(obj.toString());
387 ArrayList<String> tempCollection = new ArrayList<>();
388 for (String itemValue : collString.split(",")) {
389 tempCollection.add(ConfigurationUtils.processVariablesIfPresent(tenant, namespace, itemValue));
391 Collection<T> collection = convert(
392 ConfigurationUtils.getCollectionString(Arrays.toString(tempCollection.toArray())),
393 (Class<T>) componentType,
396 if (ConfigurationUtils.isAPrimitivesArray(clazz)) {
397 return (T) ConfigurationUtils.getPrimitiveArray(collection, componentType);
399 return (T) collection.toArray(getZeroLengthArrayFor(getWrapperClass(componentType)));
406 private static String calculateNamespace(String namespace) {
408 if (isBlank(namespace)) {
409 return Constants.DEFAULT_NAMESPACE;
412 return namespace.toUpperCase();
415 private static String calculateTenant(String tenant) {
417 if (isBlank(tenant)) {
418 return Constants.DEFAULT_TENANT;
421 return tenant.toUpperCase();
424 private <T> T getAnnotatedTypeValue(String tenant, String namespace, Class<T> clazz, String keyPrefix, Hint... hints)
425 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
427 Config confAnnotation = clazz.getAnnotation(Config.class);
428 if (confAnnotation != null && confAnnotation.key().length() > 0 && !keyPrefix.endsWith(".")) {
429 keyPrefix += (confAnnotation.key() + ".");
431 Constructor<T> constructor = clazz.getDeclaredConstructor();
432 constructor.setAccessible(true);
433 T objToReturn = constructor.newInstance();
434 for (Field field : clazz.getDeclaredFields()) {
435 field.setAccessible(true);
436 Config fieldConfAnnotation = field.getAnnotation(Config.class);
437 Class<?> fieldType = field.getType();
438 if (fieldConfAnnotation != null) {
439 if (ConfigurationUtils.isAPrimitiveOrWrapper(fieldType) ||
440 ConfigurationUtils.isAPrimitivesOrWrappersArray(fieldType)) {
441 setPrimitiveField(field, objToReturn, tenant, namespace, keyPrefix, hints);
443 if (ConfigurationUtils.isACollection(fieldType)) {
444 setCollectionField(field, objToReturn, tenant, namespace, keyPrefix, hints);
446 if (ConfigurationUtils.isAMap(fieldType)) {
447 setMapField(field, objToReturn, tenant, namespace, keyPrefix);
454 private void setPrimitiveField(Field field, Object objToReturn, String tenant, String namespace, String keyPrefix, Hint[] hints)
455 throws IllegalAccessException {
456 String fieldConfAnnotationKey = field.getAnnotation(Config.class).key();
457 Class<?> fieldType = field.getType();
458 field.set(objToReturn, get(tenant, namespace, keyPrefix + fieldConfAnnotationKey, fieldType, hints));
461 private void setMapField(Field field, Object objToReturn, String tenant, String namespace, String keyPrefix)
462 throws IllegalAccessException {
463 String fieldConfAnnotationKey = field.getAnnotation(Config.class).key();
464 field.set(objToReturn, generateMap(tenant, namespace, keyPrefix + fieldConfAnnotationKey));
467 private void setCollectionField(Field field, Object objToReturn, String tenant, String namespace, String keyPrefix, Hint[] hints)
468 throws IllegalAccessException, InvocationTargetException, InstantiationException {
469 String fieldConfAnnotationKey = field.getAnnotation(Config.class).key();
470 Class<?> fieldType = field.getType();
471 Object obj = get(tenant, namespace, keyPrefix + fieldConfAnnotationKey,
472 ConfigurationUtils.getArrayClass(ConfigurationUtils.getCollectionGenericType(field)),
475 List<Object> list = Arrays.asList((Object[]) obj);
476 Class clazzToInstantiate;
477 if (fieldType.isInterface()) {
478 clazzToInstantiate = ConfigurationUtils.getConcreteCollection(fieldType).getClass();
479 } else if (Modifier.isAbstract(fieldType.getModifiers())) {
481 ConfigurationUtils.getCompatibleCollectionForAbstractDef(fieldType)
484 clazzToInstantiate = fieldType;
486 Constructor construct = getConstructorWithArguments(clazzToInstantiate, Collection.class);
488 if (construct != null) {
489 construct.setAccessible(true);
490 field.set(objToReturn, construct.newInstance(list));
492 construct = getConstructorWithArguments(clazzToInstantiate, Integer.class,
493 Boolean.class, Collection.class);
494 if (construct != null) {
495 construct.setAccessible(true);
496 field.set(objToReturn, construct.newInstance(list.size(), true, list));
502 private Constructor getConstructorWithArguments(Class<?> clazz, Class<?>... classes) {
504 return clazz.getDeclaredConstructor(classes);
505 } catch (Exception exception) {
506 LOGGER.warn("Failed to get {} constructor.", clazz.getSimpleName(), exception);
511 private Class getWrapperClass(Class<?> clazz) {
512 if (byte.class == clazz) {
514 } else if (short.class == clazz) {
516 } else if (int.class == clazz) {
517 return Integer.class;
518 } else if (long.class == clazz) {
520 } else if (float.class == clazz) {
522 } else if (double.class == clazz) {
524 } else if (char.class == clazz) {
525 return Character.class;
526 } else if (boolean.class == clazz) {
527 return Boolean.class;
532 private <T> T getTypeValue(Object obj, Class<T> clazz, int processingHint) {
533 if (obj == null || obj.toString().trim().length() == 0) {
536 obj = obj.toString().trim();
538 if (String.class.equals(clazz)) {
539 return getValueForStringType(obj, processingHint);
541 if (Number.class.isAssignableFrom(clazz)) {
542 return getValueForNumbersType(obj, clazz);
544 if (Boolean.class.equals(clazz)) {
545 return (T) Boolean.valueOf(obj.toString());
547 if (Character.class.equals(clazz)) {
548 return (T) Character.valueOf(obj.toString().charAt(0));
553 private <T> T getValueForStringType(Object obj, int processingHint) {
554 if (obj.toString().startsWith("@") && ConfigurationUtils.isExternalLookup(processingHint)) {
555 String subString = obj.toString().substring(1).trim();
556 String contents = ConfigurationUtils.getFileContents(
557 NON_CONFIG_RESOURCE.locate(subString));
558 if (contents == null) {
559 contents = ConfigurationUtils.getFileContents(subString);
561 if (contents != null) {
565 return (T) obj.toString();
568 private <T> T getValueForNumbersType(Object obj, Class<T> clazz) {
569 Double doubleValue = Double.valueOf(obj.toString());
570 switch (clazz.getName()) {
571 case "java.lang.Byte":
572 Byte byteVal = doubleValue.byteValue();
574 case "java.lang.Short":
575 Short shortVal = doubleValue.shortValue();
577 case "java.lang.Integer":
578 Integer intVal = doubleValue.intValue();
580 case "java.lang.Long":
581 Long longVal = doubleValue.longValue();
583 case "java.lang.Float":
584 Float floatVal = doubleValue.floatValue();
586 case "java.lang.Double":
587 return (T) doubleValue;
593 private <T> T[] getZeroLengthArrayFor(Class<T> clazz) {
594 Object objToReturn = null;
595 if (ConfigurationUtils.isAPrimitive(clazz)) {
596 objToReturn = ConfigurationUtils.getPrimitiveArray(Collections.emptyList(), clazz);
597 } else if (ConfigurationUtils.isWrapperClass(clazz)) {
598 objToReturn = ConfigurationUtils.getWrappersArray(Collections.emptyList(), clazz);
600 return (T[]) objToReturn;
603 private <T> Collection<T> convert(String commaSeparatedValues, Class<T> clazz, int processingHints) {
604 ArrayList<T> collection = new ArrayList<>();
605 for (String value : commaSeparatedValues.split(",")) {
607 T type1 = getTypeValue(value, clazz, processingHints);
609 collection.add(type1);
611 } catch (RuntimeException re) {
612 LOGGER.warn("Failed to convert {}", commaSeparatedValues, re);