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.
16 package org.onap.config.impl;
18 import static org.onap.config.ConfigurationUtils.isBlank;
21 import java.lang.reflect.Constructor;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Modifier;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
35 import java.util.concurrent.atomic.AtomicReference;
36 import java.util.function.Predicate;
37 import java.util.stream.Collectors;
38 import org.apache.commons.configuration2.ex.ConfigurationException;
39 import org.onap.config.ConfigurationUtils;
40 import org.onap.config.Constants;
41 import org.onap.config.NonConfigResource;
42 import org.onap.config.api.Config;
43 import org.onap.config.api.Hint;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
47 public class ConfigurationImpl implements org.onap.config.api.Configuration {
49 private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationImpl.class);
50 private static final String KEY_CANNOT_BE_NULL = "Key can't be null.";
51 private static final NonConfigResource NON_CONFIG_RESOURCE = new NonConfigResource();
52 private static final Map<String, AggregateConfiguration> MODULE_CONFIG_STORE = new HashMap<>();
55 if (!loadClassPathConfigurationsAndResources() || !loadAdditionalConfigurationsAndResources() || !loadTenantConfigurations()) {
56 throw new IllegalStateException("Failed to initialize configuration");
58 populateFinalConfigurationIncrementally(MODULE_CONFIG_STORE);
59 loadNodeSpecificConfigurations();
62 private static boolean loadClassPathConfigurationsAndResources() {
63 List<URL> classpathResources = ConfigurationUtils.getAllClassPathResources();
64 Predicate<URL> predicate = ConfigurationUtils::isConfig;
65 Map<Boolean, List<URL>> resources = classpathResources.stream().collect(Collectors.partitioningBy(predicate));
66 List<URL> configResources = resources.get(true);
67 List<URL> nonConfigResources = resources.get(false);
68 AtomicReference<Boolean> successFlagHolder = new AtomicReference<>(true);
69 configResources.forEach(url -> successFlagHolder.set(setUpdateModuleConfigStore(url)));
70 nonConfigResources.forEach(NON_CONFIG_RESOURCE::add);
71 return successFlagHolder.get();
74 private static boolean loadAdditionalConfigurationsAndResources() {
75 String configLocation = System.getProperty("config.location");
76 AtomicReference<Boolean> successFlagHolder = new AtomicReference<>(true);
77 if (!isBlank(configLocation)) {
78 File root = new File(configLocation);
79 Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
80 Predicate<File> filePredicate = ConfigurationUtils::isConfig;
81 Map<Boolean, List<File>> resources = filesystemResources.stream().collect(Collectors.partitioningBy(filePredicate));
82 List<File> configResources = resources.get(true);
83 List<File> nonConfigResources = resources.get(false);
84 configResources.forEach(file -> successFlagHolder.set(setUpdateModuleConfigStore(file)));
85 nonConfigResources.forEach(NON_CONFIG_RESOURCE::add);
87 return successFlagHolder.get();
90 private static boolean loadTenantConfigurations() {
91 String tenantConfigLocation = System.getProperty("tenant.config.location");
92 AtomicReference<Boolean> successFlagHolder = new AtomicReference<>(true);
93 if (!isBlank(tenantConfigLocation)) {
94 File root = new File(tenantConfigLocation);
95 Collection<File> tenantsRoot = ConfigurationUtils.getAllFiles(root, false, true);
96 Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
97 Map<Boolean, List<File>> resources = filesystemResources.stream().collect(Collectors.partitioningBy(ConfigurationUtils::isConfig));
98 Collection<File> tenantResources = resources.get(true);
99 tenantResources.forEach(configFile -> {
100 AtomicReference<String> moduleNameHolder = new AtomicReference<>(ConfigurationUtils.getNamespace(configFile));
101 Predicate<File> startsWithRootPredicate = tenantRoot -> configFile.getAbsolutePath().startsWith(tenantRoot.getAbsolutePath());
102 Collection<File> matchesTenantRoot = tenantsRoot.stream().filter(startsWithRootPredicate)
103 .collect(Collectors.toCollection(ArrayList::new));
104 AtomicReference<String[]> altResource = new AtomicReference<>();
105 matchesTenantRoot.forEach(file -> altResource.set(
106 (file.getName().toUpperCase() + Constants.TENANT_NAMESPACE_SEPARATOR + moduleNameHolder.get())
107 .split(Constants.TENANT_NAMESPACE_SEPARATOR)));
108 successFlagHolder.set(setUpdateModuleConfigStore(configFile, altResource.get()));
111 return successFlagHolder.get();
114 private static void loadNodeSpecificConfigurations() {
115 String nodeConfigLocation = System.getProperty("node.config.location");
116 if (!isBlank(nodeConfigLocation)) {
117 File root = new File(nodeConfigLocation);
118 Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
119 filesystemResources.stream().filter(ConfigurationUtils::isConfig).forEach(file -> ConfigurationRepository.lookup()
120 .populateOverrideConfiguration(ConfigurationUtils
121 .getConfigurationRepositoryKey(ConfigurationUtils.getNamespace(file).split(Constants.TENANT_NAMESPACE_SEPARATOR)), file));
125 private static void populateFinalConfigurationIncrementally(Map<String, AggregateConfiguration> configs) {
126 if (configs.get(Constants.DEFAULT_TENANT + Constants.KEY_ELEMENTS_DELIMITER + Constants.DB_NAMESPACE) != null) {
127 ConfigurationRepository.lookup()
128 .populateConfiguration(Constants.DEFAULT_TENANT + Constants.KEY_ELEMENTS_DELIMITER + Constants.DB_NAMESPACE,
129 configs.remove(Constants.DEFAULT_TENANT + Constants.KEY_ELEMENTS_DELIMITER + Constants.DB_NAMESPACE).getFinalConfiguration());
131 Set<String> modules = configs.keySet();
132 modules.forEach(m -> ConfigurationRepository.lookup().populateConfiguration(m, configs.get(m).getFinalConfiguration()));
135 private static <T> boolean setUpdateModuleConfigStore(T resource, String... namedResources) {
136 boolean success = true;
139 if (namedResources == null || namedResources.length == 0) {
140 moduleName = getConfigurationRepositoryKeyWrapper(resource);
142 moduleName = getConfigurationRepositoryKeyWrapper(namedResources);
144 moduleAddConfigWrapper(moduleName, resource);
145 } catch (Exception e) {
147 LOGGER.error("Error occurred while processing config resource {}", resource, e);
152 private static <T> String getConfigurationRepositoryKeyWrapper(T resource) throws ConfigurationException {
153 switch (resource.getClass().getSimpleName()) {
155 return ConfigurationUtils.getConfigurationRepositoryKey((URL) resource);
157 return ConfigurationUtils.getConfigurationRepositoryKey((File) resource);
159 return ConfigurationUtils.getConfigurationRepositoryKey((String[]) resource);
161 throw new ConfigurationException("Unsupported resource type.");
165 private static <T> void moduleAddConfigWrapper(String moduleName, T resource) throws ConfigurationException {
166 AggregateConfiguration moduleConfig = MODULE_CONFIG_STORE.get(moduleName);
167 if (moduleConfig == null) {
168 moduleConfig = new AggregateConfiguration();
169 MODULE_CONFIG_STORE.put(moduleName, moduleConfig);
171 switch (resource.getClass().getSimpleName()) {
173 moduleConfig.addConfig((URL) resource);
176 moduleConfig.addConfig((File) resource);
179 throw new ConfigurationException("Unsupported resource type.");
183 private static String calculateNamespace(String namespace) {
184 if (isBlank(namespace)) {
185 return Constants.DEFAULT_NAMESPACE;
187 return namespace.toUpperCase();
190 private static String calculateTenant(String tenant) {
191 if (isBlank(tenant)) {
192 return Constants.DEFAULT_TENANT;
194 return tenant.toUpperCase();
198 public <T> T get(String tenant, String namespace, String key, Class<T> clazz, Hint... hints) {
199 String[] tenantNamespaceArray;
200 if (tenant == null && namespace != null) {
201 tenantNamespaceArray = namespace.split(Constants.TENANT_NAMESPACE_SEPARATOR);
202 if (tenantNamespaceArray.length > 1) {
203 tenant = tenantNamespaceArray[0];
204 namespace = tenantNamespaceArray[1];
207 tenant = ConfigurationRepository.lookup().isValidTenant(tenant) ? tenant.toUpperCase() : Constants.DEFAULT_TENANT;
208 namespace = ConfigurationRepository.lookup().isValidNamespace(namespace) ? namespace.toUpperCase() : Constants.DEFAULT_NAMESPACE;
209 hints = hints == null || hints.length == 0 ? new Hint[]{Hint.EXTERNAL_LOOKUP, Hint.NODE_SPECIFIC} : hints;
211 returnValue = getInternal(tenant, namespace, key, clazz, hints);
212 if ((returnValue == null || ConfigurationUtils.isZeroLengthArray(clazz, returnValue)) && !Constants.DEFAULT_TENANT.equals(tenant)) {
213 returnValue = getInternal(Constants.DEFAULT_TENANT, namespace, key, clazz, hints);
215 if ((returnValue == null || ConfigurationUtils.isZeroLengthArray(clazz, returnValue)) && !Constants.DEFAULT_NAMESPACE.equals(namespace)) {
216 returnValue = getInternal(tenant, Constants.DEFAULT_NAMESPACE, key, clazz, hints);
218 if ((returnValue == null || ConfigurationUtils.isZeroLengthArray(clazz, returnValue)) && !Constants.DEFAULT_NAMESPACE.equals(namespace)
219 && !Constants.DEFAULT_TENANT.equals(tenant)) {
220 returnValue = getInternal(Constants.DEFAULT_TENANT, Constants.DEFAULT_NAMESPACE, key, clazz, hints);
222 if (returnValue == null && ConfigurationUtils.isAPrimitive(clazz)) {
223 returnValue = (T) ConfigurationUtils.getDefaultFor(clazz);
229 public <T> Map<String, T> populateMap(String tenantId, String namespace, String key, Class<T> clazz) {
230 final String calculatedTenantId = calculateTenant(tenantId);
231 final String calculatedNamespace = calculateNamespace(namespace);
232 Map<String, T> map = new HashMap<>();
233 Iterator<String> keys;
235 keys = ConfigurationRepository.lookup().getConfigurationFor(calculatedTenantId, calculatedNamespace).getKeys(key);
236 keys.forEachRemaining(k -> {
237 if (k.startsWith(key + ".")) {
238 k = k.substring(key.length() + 1);
239 String subkey = k.substring(0, k.indexOf('.'));
240 if (!map.containsKey(subkey)) {
241 map.put(subkey, get(calculatedTenantId, calculatedNamespace, key + "." + subkey, clazz));
245 } catch (Exception e) {
246 LOGGER.warn("Couldn't populate map fot tenant: {}, namespace: {}, key: {}, type: {}", tenantId, namespace, key, clazz.getSimpleName(), e);
252 public Map<Object, Object> generateMap(String tenantId, String namespace, String key) {
253 final String calculatedTenantId = calculateTenant(tenantId);
254 final String calculatedNamespace = calculateNamespace(namespace);
255 Map<Object, Object> parentMap = new HashMap<>();
256 Iterator<String> configKeys;
259 configKeys = ConfigurationRepository.lookup().getConfigurationFor(calculatedTenantId, calculatedNamespace).getKeys();
261 configKeys = ConfigurationRepository.lookup().getConfigurationFor(calculatedTenantId, calculatedNamespace).getKeys(key);
263 configKeys.forEachRemaining(subKey -> {
264 if (!isBlank(key) && !subKey.startsWith(key + ".")) {
267 parseConfigSubKeys(subKey, key, calculatedTenantId, calculatedNamespace, parentMap);
269 } catch (Exception e) {
270 LOGGER.warn("Couldn't generate map fot tenant: {}, namespace: {}, key: {}", tenantId, namespace, key, e);
275 private void parseConfigSubKeys(String subKey, String keyPrefix, String tenantId, String namespace, Map<Object, Object> targetMap) {
276 String value = getAsString(tenantId, namespace, subKey);
277 if (!isBlank(keyPrefix) && subKey.startsWith(keyPrefix + ".")) {
278 subKey = subKey.substring(keyPrefix.trim().length() + 1);
280 while (subKey.contains(".")) {
281 String subSubKey = subKey.substring(0, subKey.indexOf('.'));
282 subKey = subKey.substring(subKey.indexOf('.') + 1);
283 if (!targetMap.containsKey(subSubKey)) {
284 Map<Object, Object> subMap = new HashMap<>();
285 targetMap.put(subSubKey, subMap);
288 targetMap = (Map<Object, Object>) targetMap.get(subSubKey);
291 targetMap.put(subKey, value);
294 protected <T> T getInternal(String tenant, String namespace, String key, Class<T> clazz, Hint... hints) {
295 int processingHints = Hint.DEFAULT.value();
297 for (Hint hint : hints) {
298 processingHints = processingHints | hint.value();
301 tenant = calculateTenant(tenant);
302 namespace = calculateNamespace(namespace);
303 if (isBlank(key) && !clazz.isAnnotationPresent(Config.class)) {
304 throw new IllegalArgumentException(KEY_CANNOT_BE_NULL);
307 throw new IllegalArgumentException("clazz is null.");
309 if (ConfigurationUtils.isAPrimitive(clazz)) {
310 clazz = getWrapperClass(clazz);
313 if (ConfigurationUtils.isWrapperClass(clazz)) {
314 return getWrapperTypeValue(tenant, namespace, key, clazz, processingHints);
315 } else if (ConfigurationUtils.isAPrimitivesOrWrappersArray(clazz)) {
316 return getArrayTypeValue(tenant, namespace, key, clazz, processingHints);
317 } else if (clazz.isAnnotationPresent(Config.class)) {
318 return getAnnotatedTypeValue(tenant, namespace, clazz, isBlank(key) ? "" : (key + "."), hints);
320 throw new IllegalArgumentException("Only primitive classes, wrapper classes, corresponding array classes and any "
321 + "class decorated with @org.openecomp.config.api.Config are allowed as argument.");
323 } catch (Exception exception) {
325 .warn("Failed to get internal value fot tenant: {}, namespace: {}, key: {}, type: {}", tenant, namespace, key, clazz.getSimpleName(),
331 private <T> T getWrapperTypeValue(String tenant, String namespace, String key, Class<T> clazz, int processingHints) throws Exception {
332 Object obj = ConfigurationUtils.getProperty(ConfigurationRepository.lookup().getConfigurationFor(tenant, namespace), key, processingHints);
334 if (ConfigurationUtils.isCollection(obj.toString())) {
335 obj = ConfigurationUtils.getCollectionString(obj.toString());
337 String value = ConfigurationUtils.processVariablesIfPresent(tenant, namespace, obj.toString().split(",")[0]);
338 return (T) getTypeValue(value, clazz, processingHints);
343 private <T> T getArrayTypeValue(String tenant, String namespace, String key, Class<T> clazz, int processingHints) throws Exception {
344 Object obj = ConfigurationUtils.getProperty(ConfigurationRepository.lookup().getConfigurationFor(tenant, namespace), key, processingHints);
346 Class componentType = clazz.getComponentType();
347 if (ConfigurationUtils.isAPrimitivesArray(clazz)) {
348 componentType = getWrapperClass(componentType);
350 String collString = ConfigurationUtils.getCollectionString(obj.toString());
351 ArrayList<String> tempCollection = new ArrayList<>();
352 for (String itemValue : collString.split(",")) {
353 tempCollection.add(ConfigurationUtils.processVariablesIfPresent(tenant, namespace, itemValue));
355 Collection<T> collection = convert(ConfigurationUtils.getCollectionString(Arrays.toString(tempCollection.toArray())),
356 (Class<T>) componentType, processingHints);
357 if (ConfigurationUtils.isAPrimitivesArray(clazz)) {
358 return (T) ConfigurationUtils.getPrimitiveArray(collection, componentType);
360 return (T) collection.toArray(getZeroLengthArrayFor(getWrapperClass(componentType)));
367 private <T> T getAnnotatedTypeValue(String tenant, String namespace, Class<T> clazz, String keyPrefix, Hint... hints)
368 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
369 Config confAnnotation = clazz.getAnnotation(Config.class);
370 if (confAnnotation != null && confAnnotation.key().length() > 0 && !keyPrefix.endsWith(".")) {
371 keyPrefix += (confAnnotation.key() + ".");
373 Constructor<T> constructor = clazz.getDeclaredConstructor();
374 constructor.setAccessible(true);
375 T objToReturn = constructor.newInstance();
376 for (Field field : clazz.getDeclaredFields()) {
377 field.setAccessible(true);
378 Config fieldConfAnnotation = field.getAnnotation(Config.class);
379 Class<?> fieldType = field.getType();
380 if (fieldConfAnnotation == null) {
383 if (ConfigurationUtils.isAPrimitiveOrWrapper(fieldType) || ConfigurationUtils.isAPrimitivesOrWrappersArray(fieldType)) {
384 setPrimitiveField(field, objToReturn, tenant, namespace, keyPrefix, hints);
386 if (ConfigurationUtils.isACollection(fieldType)) {
387 setCollectionField(field, objToReturn, tenant, namespace, keyPrefix, hints);
389 if (ConfigurationUtils.isAMap(fieldType)) {
390 setMapField(field, objToReturn, tenant, namespace, keyPrefix);
396 private void setPrimitiveField(Field field, Object objToReturn, String tenant, String namespace, String keyPrefix, Hint[] hints)
397 throws IllegalAccessException {
398 String fieldConfAnnotationKey = field.getAnnotation(Config.class).key();
399 Class<?> fieldType = field.getType();
400 field.set(objToReturn, get(tenant, namespace, keyPrefix + fieldConfAnnotationKey, fieldType, hints));
403 private void setMapField(Field field, Object objToReturn, String tenant, String namespace, String keyPrefix) throws IllegalAccessException {
404 String fieldConfAnnotationKey = field.getAnnotation(Config.class).key();
405 field.set(objToReturn, generateMap(tenant, namespace, keyPrefix + fieldConfAnnotationKey));
408 private void setCollectionField(Field field, Object objToReturn, String tenant, String namespace, String keyPrefix, Hint[] hints)
409 throws IllegalAccessException, InvocationTargetException, InstantiationException {
410 String fieldConfAnnotationKey = field.getAnnotation(Config.class).key();
411 Class<?> fieldType = field.getType();
412 Object obj = get(tenant, namespace, keyPrefix + fieldConfAnnotationKey,
413 ConfigurationUtils.getArrayClass(ConfigurationUtils.getCollectionGenericType(field)), hints);
417 List<Object> list = Arrays.asList((Object[]) obj);
418 Class clazzToInstantiate;
419 if (fieldType.isInterface()) {
420 clazzToInstantiate = ConfigurationUtils.getConcreteCollection(fieldType).getClass();
421 } else if (Modifier.isAbstract(fieldType.getModifiers())) {
422 clazzToInstantiate = ConfigurationUtils.getCompatibleCollectionForAbstractDef(fieldType).getClass();
424 clazzToInstantiate = fieldType;
426 Constructor construct = getConstructorWithArguments(clazzToInstantiate, Collection.class);
427 if (construct != null) {
428 construct.setAccessible(true);
429 field.set(objToReturn, construct.newInstance(list));
431 construct = getConstructorWithArguments(clazzToInstantiate, Integer.class, Boolean.class, Collection.class);
432 if (construct != null) {
433 construct.setAccessible(true);
434 field.set(objToReturn, construct.newInstance(list.size(), true, list));
439 private Constructor getConstructorWithArguments(Class<?> clazz, Class<?>... classes) {
441 return clazz.getDeclaredConstructor(classes);
442 } catch (Exception exception) {
443 LOGGER.warn("Failed to get {} constructor.", clazz.getSimpleName(), exception);
448 private Class getWrapperClass(Class<?> clazz) {
449 if (byte.class == clazz) {
451 } else if (short.class == clazz) {
453 } else if (int.class == clazz) {
454 return Integer.class;
455 } else if (long.class == clazz) {
457 } else if (float.class == clazz) {
459 } else if (double.class == clazz) {
461 } else if (char.class == clazz) {
462 return Character.class;
463 } else if (boolean.class == clazz) {
464 return Boolean.class;
469 private <T> T getTypeValue(Object obj, Class<T> clazz, int processingHint) {
470 if (obj == null || obj.toString().trim().length() == 0) {
473 obj = obj.toString().trim();
475 if (String.class.equals(clazz)) {
476 return getValueForStringType(obj, processingHint);
478 if (Number.class.isAssignableFrom(clazz)) {
479 return getValueForNumbersType(obj, clazz);
481 if (Boolean.class.equals(clazz)) {
482 return (T) Boolean.valueOf(obj.toString());
484 if (Character.class.equals(clazz)) {
485 return (T) Character.valueOf(obj.toString().charAt(0));
490 private <T> T getValueForStringType(Object obj, int processingHint) {
491 if (obj.toString().startsWith("@") && ConfigurationUtils.isExternalLookup(processingHint)) {
492 String subString = obj.toString().substring(1).trim();
493 String contents = ConfigurationUtils.getFileContents(NON_CONFIG_RESOURCE.locate(subString));
494 if (contents == null) {
495 contents = ConfigurationUtils.getFileContents(subString);
497 if (contents != null) {
501 return (T) obj.toString();
504 private <T> T getValueForNumbersType(Object obj, Class<T> clazz) {
505 Double doubleValue = Double.valueOf(obj.toString());
506 switch (clazz.getName()) {
507 case "java.lang.Byte":
508 Byte byteVal = doubleValue.byteValue();
510 case "java.lang.Short":
511 Short shortVal = doubleValue.shortValue();
513 case "java.lang.Integer":
514 Integer intVal = doubleValue.intValue();
516 case "java.lang.Long":
517 Long longVal = doubleValue.longValue();
519 case "java.lang.Float":
520 Float floatVal = doubleValue.floatValue();
522 case "java.lang.Double":
523 return (T) doubleValue;
529 private <T> T[] getZeroLengthArrayFor(Class<T> clazz) {
530 Object objToReturn = null;
531 if (ConfigurationUtils.isAPrimitive(clazz)) {
532 objToReturn = ConfigurationUtils.getPrimitiveArray(Collections.emptyList(), clazz);
533 } else if (ConfigurationUtils.isWrapperClass(clazz)) {
534 objToReturn = ConfigurationUtils.getWrappersArray(Collections.emptyList(), clazz);
536 return (T[]) objToReturn;
539 private <T> Collection<T> convert(String commaSeparatedValues, Class<T> clazz, int processingHints) {
540 ArrayList<T> collection = new ArrayList<>();
541 for (String value : commaSeparatedValues.split(",")) {
543 T type1 = getTypeValue(value, clazz, processingHints);
545 collection.add(type1);
547 } catch (RuntimeException re) {
548 LOGGER.warn("Failed to convert {}", commaSeparatedValues, re);