d13d5f7b924c400c5c4755b3b78cc681fe38132a
[sdc.git] / common / onap-common-configuration-management / onap-configuration-management-core / src / main / java / org / onap / config / impl / ConfigurationImpl.java
1 /*
2  * Copyright © 2016-2018 European Support Limited
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package org.onap.config.impl;
18
19 import static org.onap.config.ConfigurationUtils.isBlank;
20
21 import java.io.File;
22 import java.lang.reflect.Constructor;
23 import java.lang.reflect.Field;
24 import java.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Modifier;
26 import java.net.URL;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.function.Predicate;
36 import org.apache.commons.configuration2.ex.ConfigurationException;
37 import org.onap.config.ConfigurationUtils;
38 import org.onap.config.Constants;
39 import org.onap.config.NonConfigResource;
40 import org.onap.config.api.Config;
41 import org.onap.config.api.Hint;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 public class ConfigurationImpl implements org.onap.config.api.Configuration {
46
47     private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationImpl.class);
48
49     private static final String KEY_CANNOT_BE_NULL = "Key can't be null.";
50
51     private static final NonConfigResource NON_CONFIG_RESOURCE = new NonConfigResource();
52
53     static {
54
55         try {
56             init();
57         } catch (ConfigurationException e) {
58             throw new IllegalStateException("Failed to initialize configuration", e);
59         }
60     }
61
62     private static void init() throws ConfigurationException {
63
64         Map<String, AggregateConfiguration> moduleConfigStore = new HashMap<>();
65         List<URL> classpathResources = ConfigurationUtils.getAllClassPathResources();
66         Predicate<URL> predicate = ConfigurationUtils::isConfig;
67         for (URL url : classpathResources) {
68             if (predicate.test(url)) {
69                 String moduleName = ConfigurationUtils.getConfigurationRepositoryKey(url);
70                 AggregateConfiguration moduleConfig = moduleConfigStore.get(moduleName);
71                 if (moduleConfig == null) {
72                     moduleConfig = new AggregateConfiguration();
73                     moduleConfigStore.put(moduleName, moduleConfig);
74                 }
75                 moduleConfig.addConfig(url);
76             } else {
77                 NON_CONFIG_RESOURCE.add(url);
78             }
79         }
80         String configLocation = System.getProperty("config.location");
81         if (!isBlank(configLocation)) {
82             File root = new File(configLocation);
83             Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
84             Predicate<File> filePredicate = ConfigurationUtils::isConfig;
85             for (File file : filesystemResources) {
86                 if (filePredicate.test(file)) {
87                     String moduleName = ConfigurationUtils.getConfigurationRepositoryKey(file);
88                     AggregateConfiguration moduleConfig = moduleConfigStore.get(moduleName);
89                     if (moduleConfig == null) {
90                         moduleConfig = new AggregateConfiguration();
91                         moduleConfigStore.put(moduleName, moduleConfig);
92                     }
93                     moduleConfig.addConfig(file);
94                 } else {
95                     NON_CONFIG_RESOURCE.add(file);
96                 }
97             }
98         }
99         String tenantConfigLocation = System.getProperty("tenant.config.location");
100         if (!isBlank(tenantConfigLocation)) {
101             File root = new File(tenantConfigLocation);
102             Collection<File> tenantsRoot = ConfigurationUtils.getAllFiles(root, false, true);
103             Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
104             Predicate<File> filePredicate = ConfigurationUtils::isConfig;
105             for (File file : filesystemResources) {
106                 if (filePredicate.test(file)) {
107                     String moduleName = ConfigurationUtils.getNamespace(file);
108                     for (File tenantFileRoot : tenantsRoot) {
109                         if (file.getAbsolutePath().startsWith(tenantFileRoot.getAbsolutePath())) {
110                             moduleName = ConfigurationUtils.getConfigurationRepositoryKey(
111                                     (tenantFileRoot.getName().toUpperCase() + Constants.TENANT_NAMESPACE_SEPARATOR
112                                              + moduleName).split(Constants.TENANT_NAMESPACE_SEPARATOR));
113                         }
114                     }
115                     AggregateConfiguration moduleConfig = moduleConfigStore.get(moduleName);
116                     if (moduleConfig == null) {
117                         moduleConfig = new AggregateConfiguration();
118                         moduleConfigStore.put(moduleName, moduleConfig);
119                     }
120                     moduleConfig.addConfig(file);
121                 }
122             }
123         }
124
125         populateFinalConfigurationIncrementally(moduleConfigStore);
126         String nodeConfigLocation = System.getProperty("node.config.location");
127         if (!isBlank(nodeConfigLocation)) {
128             File root = new File(nodeConfigLocation);
129             Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
130             Predicate<File> filePredicate = ConfigurationUtils::isConfig;
131             for (File file : filesystemResources) {
132                 if (filePredicate.test(file)) {
133                     ConfigurationRepository.lookup().populateOverrideConfiguration(
134                             ConfigurationUtils.getConfigurationRepositoryKey(
135                                     ConfigurationUtils.getNamespace(file).split(Constants.TENANT_NAMESPACE_SEPARATOR)),
136                             file);
137                 }
138             }
139         }
140     }
141
142     private static void populateFinalConfigurationIncrementally(Map<String, AggregateConfiguration> configs) {
143
144         if (configs.get(Constants.DEFAULT_TENANT + Constants.KEY_ELEMENTS_DELIMITER + Constants.DB_NAMESPACE) != null) {
145             ConfigurationRepository.lookup().populateConfiguration(
146                     Constants.DEFAULT_TENANT + Constants.KEY_ELEMENTS_DELIMITER + Constants.DB_NAMESPACE,
147                     configs.remove(Constants.DEFAULT_TENANT + Constants.KEY_ELEMENTS_DELIMITER + Constants.DB_NAMESPACE)
148                             .getFinalConfiguration());
149         }
150
151         Set<String> modules = configs.keySet();
152         for (String module : modules) {
153             ConfigurationRepository.lookup().populateConfiguration(module, configs.get(module).getFinalConfiguration());
154         }
155     }
156
157     @Override
158     public <T> T get(String tenant, String namespace, String key, Class<T> clazz, Hint... hints) {
159
160         String[] tenantNamespaceArray;
161         if (tenant == null && namespace != null) {
162             tenantNamespaceArray = namespace.split(Constants.TENANT_NAMESPACE_SEPARATOR);
163             if (tenantNamespaceArray.length > 1) {
164                 tenant = tenantNamespaceArray[0];
165                 namespace = tenantNamespaceArray[1];
166             }
167         }
168
169         tenant = ConfigurationRepository.lookup().isValidTenant(tenant) ? tenant.toUpperCase()
170                          : Constants.DEFAULT_TENANT;
171         namespace = ConfigurationRepository.lookup().isValidNamespace(namespace) ? namespace.toUpperCase()
172                             : Constants.DEFAULT_NAMESPACE;
173         T returnValue;
174         returnValue = (T) getInternal(tenant, namespace, key, clazz.isPrimitive() ? getWrapperClass(clazz) : clazz,
175                 hints == null || hints.length == 0 ? new Hint[] {Hint.EXTERNAL_LOOKUP, Hint.NODE_SPECIFIC} : hints);
176         if ((returnValue == null || ConfigurationUtils.isZeroLengthArray(clazz, returnValue))
177                     && !Constants.DEFAULT_TENANT.equals(tenant)) {
178             returnValue = (T) getInternal(Constants.DEFAULT_TENANT, namespace, key,
179                     clazz.isPrimitive() ? getWrapperClass(clazz) : clazz,
180                     hints == null || hints.length == 0 ? new Hint[] {Hint.EXTERNAL_LOOKUP, Hint.NODE_SPECIFIC} : hints);
181         }
182         if ((returnValue == null || ConfigurationUtils.isZeroLengthArray(clazz, returnValue))
183                     && !Constants.DEFAULT_NAMESPACE.equals(namespace)) {
184             returnValue = (T) getInternal(tenant, Constants.DEFAULT_NAMESPACE, key,
185                     clazz.isPrimitive() ? getWrapperClass(clazz) : clazz,
186                     hints == null || hints.length == 0 ? new Hint[] {Hint.EXTERNAL_LOOKUP, Hint.NODE_SPECIFIC} : hints);
187         }
188         if ((returnValue == null || ConfigurationUtils.isZeroLengthArray(clazz, returnValue))
189                     && !Constants.DEFAULT_NAMESPACE.equals(namespace) && !Constants.DEFAULT_TENANT.equals(tenant)) {
190             returnValue = (T) getInternal(Constants.DEFAULT_TENANT, Constants.DEFAULT_NAMESPACE, key,
191                     clazz.isPrimitive() ? getWrapperClass(clazz) : clazz,
192                     hints == null || hints.length == 0 ? new Hint[] {Hint.EXTERNAL_LOOKUP, Hint.NODE_SPECIFIC} : hints);
193         }
194         if (returnValue == null && clazz.isPrimitive()) {
195             return (T) ConfigurationUtils.getDefaultFor(clazz);
196         } else {
197             return returnValue;
198         }
199     }
200
201     @Override
202     public <T> Map<String, T> populateMap(String tenantId, String namespace, String key, Class<T> clazz) {
203
204         tenantId = calculateTenant(tenantId);
205         namespace = calculateNamespace(namespace);
206         Map<String, T> map = new HashMap<>();
207         Iterator<String> keys;
208         try {
209             keys = ConfigurationRepository.lookup().getConfigurationFor(tenantId, namespace).getKeys(key);
210             while (keys.hasNext()) {
211                 String k = keys.next();
212                 if (k.startsWith(key + ".")) {
213                     k = k.substring(key.length() + 1);
214                     String subkey = k.substring(0, k.indexOf('.'));
215                     if (!map.containsKey(subkey)) {
216                         map.put(subkey, get(tenantId, namespace, key + "." + subkey, clazz));
217                     }
218                 }
219             }
220         } catch (Exception e) {
221             LOGGER.warn(
222                     "Couldn't populate map fot tenant: {}, namespace: {}, key: {}, type: {}",
223                     tenantId,
224                     namespace,
225                     key,
226                     clazz.getSimpleName(),
227                     e
228             );
229         }
230         return map;
231     }
232
233     @Override
234     public Map generateMap(String tenantId, String namespace, String key) {
235
236         tenantId = calculateTenant(tenantId);
237         namespace = calculateNamespace(namespace);
238
239         Map map;
240         Map parentMap = new HashMap<>();
241         Iterator<String> keys;
242         try {
243             if (isBlank(key)) {
244                 keys = ConfigurationRepository.lookup().getConfigurationFor(tenantId, namespace).getKeys();
245             } else {
246                 keys = ConfigurationRepository.lookup().getConfigurationFor(tenantId, namespace).getKeys(key);
247             }
248             while (keys.hasNext()) {
249                 map = parentMap;
250                 String k = keys.next();
251
252                 if (!isBlank(key) && !k.startsWith(key + ".")) {
253                     continue;
254                 }
255                 String value = getAsString(tenantId, namespace, k);
256                 if (!isBlank(key) && k.startsWith(key + ".")) {
257                     k = k.substring(key.trim().length() + 1);
258                 }
259
260                 while (k.contains(".")) {
261                     if (k.contains(".")) {
262                         String subkey = k.substring(0, k.indexOf('.'));
263                         k = k.substring(k.indexOf('.') + 1);
264                         if (!map.containsKey(subkey)) {
265                             Map tmp = new HashMap();
266                             map.put(subkey, tmp);
267                             map = tmp;
268                         } else {
269                             map = (Map) map.get(subkey);
270                         }
271                     }
272                 }
273                 map.put(k, value);
274             }
275         } catch (Exception e) {
276             LOGGER.warn(
277                     "Couldn't generate map fot tenant: {}, namespace: {}, key: {}",
278                     tenantId,
279                     namespace,
280                     key,
281                     e
282             );
283         }
284         return parentMap;
285     }
286
287     protected <T> T getInternal(String tenant, String namespace, String key, Class<T> clazz, Hint... hints) {
288         int processingHints = Hint.DEFAULT.value();
289         if (hints != null) {
290             for (Hint hint : hints) {
291                 processingHints = processingHints | hint.value();
292             }
293         }
294
295         tenant = calculateTenant(tenant);
296         namespace = calculateNamespace(namespace);
297
298         if (isBlank(key) && !clazz.isAnnotationPresent(Config.class)) {
299             throw new IllegalArgumentException(KEY_CANNOT_BE_NULL);
300         }
301
302         if (clazz == null) {
303             throw new IllegalArgumentException("clazz is null.");
304         }
305
306         if (clazz.isPrimitive()) {
307             clazz = getWrapperClass(clazz);
308         }
309         try {
310             if (ConfigurationUtils.isWrapperClass(clazz) || clazz.isPrimitive()) {
311                 Object obj = ConfigurationUtils.getProperty(
312                         ConfigurationRepository.lookup().getConfigurationFor(tenant, namespace), key, processingHints);
313                 if (obj != null) {
314                     if (ConfigurationUtils.isCollection(obj.toString())) {
315                         obj = ConfigurationUtils.getCollectionString(obj.toString());
316                     }
317                     String value = obj.toString().split(",")[0];
318                     value = ConfigurationUtils.processVariablesIfPresent(tenant, namespace, value);
319                     return (T) getValue(value, clazz.isPrimitive() ? getWrapperClass(clazz) : clazz, processingHints);
320                 } else {
321                     return null;
322                 }
323             } else if (clazz.isArray() && (clazz.getComponentType().isPrimitive() || ConfigurationUtils.isWrapperClass(
324                     clazz.getComponentType()))) {
325                 Object obj = ConfigurationUtils.getProperty(
326                         ConfigurationRepository.lookup().getConfigurationFor(tenant, namespace), key, processingHints);
327                 if (obj != null) {
328                     Class componentClass = clazz.getComponentType();
329                     if (clazz.getComponentType().isPrimitive()) {
330                         componentClass = getWrapperClass(clazz.getComponentType());
331                     }
332                     String collString = ConfigurationUtils.getCollectionString(obj.toString());
333                     ArrayList<String> tempCollection = new ArrayList<>();
334                     for (String itemValue : collString.split(",")) {
335                         tempCollection.add(ConfigurationUtils.processVariablesIfPresent(tenant, namespace, itemValue));
336                     }
337                     Collection<T> collection =
338                             convert(ConfigurationUtils.getCollectionString(Arrays.toString(tempCollection.toArray())),
339                                     componentClass, processingHints);
340                     if (clazz.getComponentType().isPrimitive()) {
341                         return (T) ConfigurationUtils.getPrimitiveArray(collection, clazz.getComponentType());
342                     } else {
343                         return (T) collection.toArray(getZeroLengthArrayFor(getWrapperClass(clazz.getComponentType())));
344                     }
345                 } else {
346                     return null;
347                 }
348             } else if (clazz.isAnnotationPresent(Config.class)) {
349                 return read(tenant, namespace, clazz, isBlank(key) ? "" : (key + "."), hints);
350             } else {
351                 throw new IllegalArgumentException(
352                         "Only primitive classes, wrapper classes, corresponding array classes and any "
353                                 + "class decorated with @org.openecomp.config.api.Config are allowed as argument.");
354             }
355         } catch (Exception exception) {
356             LOGGER.warn(
357                     "Failed to get internal value fot tenant: {}, namespace: {}, key: {}, type: {}",
358                     tenant,
359                     namespace,
360                     key,
361                     clazz.getSimpleName(),
362                     exception
363             );
364         }
365         return null;
366     }
367
368     private static String calculateNamespace(String namespace) {
369
370         if (isBlank(namespace)) {
371             return Constants.DEFAULT_NAMESPACE;
372         }
373
374         return namespace.toUpperCase();
375     }
376
377     private static String calculateTenant(String tenant) {
378
379         if (isBlank(tenant)) {
380             return Constants.DEFAULT_TENANT;
381         }
382
383         return tenant.toUpperCase();
384     }
385
386     private <T> T read(String tenant, String namespace, Class<T> clazz, String keyPrefix, Hint... hints)
387             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
388
389         Config confAnnotation = clazz.getAnnotation(Config.class);
390         if (confAnnotation != null && confAnnotation.key().length() > 0 && !keyPrefix.endsWith(".")) {
391             keyPrefix += (confAnnotation.key() + ".");
392         }
393         Constructor<T> constructor = clazz.getDeclaredConstructor();
394         constructor.setAccessible(true);
395         T objToReturn = constructor.newInstance();
396         for (Field field : clazz.getDeclaredFields()) {
397             field.setAccessible(true);
398             Config fieldConfAnnotation = field.getAnnotation(Config.class);
399             if (fieldConfAnnotation != null) {
400                 if (field.getType().isPrimitive() || ConfigurationUtils.isWrapperClass(field.getType()) || (
401                         field.getType().isArray() && (field.getType().getComponentType().isPrimitive()
402                                                               || ConfigurationUtils.isWrapperClass(
403                                 field.getType().getComponentType())))
404                             || field.getType().getAnnotation(Config.class) != null) {
405                     field.set(objToReturn,
406                             get(tenant, namespace, keyPrefix + fieldConfAnnotation.key(), field.getType(), hints));
407                 } else if (Collection.class.isAssignableFrom(field.getType())) {
408                     Object obj = get(tenant, namespace, keyPrefix + fieldConfAnnotation.key(),
409                             ConfigurationUtils.getArrayClass(ConfigurationUtils.getCollectionGenericType(field)),
410                             hints);
411                     if (obj != null) {
412                         List list = Arrays.asList((Object[]) obj);
413                         Class clazzToInstantiate;
414                         if (field.getType().isInterface()) {
415                             clazzToInstantiate = ConfigurationUtils.getConcreteCollection(field.getType()).getClass();
416                         } else if (Modifier.isAbstract(field.getType().getModifiers())) {
417                             clazzToInstantiate =
418                                     ConfigurationUtils.getCompatibleCollectionForAbstractDef(field.getType())
419                                             .getClass();
420                         } else {
421                             clazzToInstantiate = field.getType();
422                         }
423                         Constructor construct = getConstructorWithArguments(clazzToInstantiate, Collection.class);
424
425                         if (construct != null) {
426                             construct.setAccessible(true);
427                             field.set(objToReturn, construct.newInstance(list));
428                         } else {
429                             construct = getConstructorWithArguments(clazzToInstantiate, Integer.class,
430                                     Boolean.class, Collection.class);
431                             if (construct != null) {
432                                 construct.setAccessible(true);
433                                 field.set(objToReturn, construct.newInstance(list.size(), true, list));
434                             }
435                         }
436                     }
437                 } else if (Map.class.isAssignableFrom(field.getType())) {
438                     field.set(objToReturn, generateMap(tenant, namespace, keyPrefix + fieldConfAnnotation.key()));
439                 }
440             }
441         }
442         return objToReturn;
443     }
444
445     private Constructor getConstructorWithArguments(Class clazz, Class... classes) {
446         try {
447             return clazz.getDeclaredConstructor(classes);
448         } catch (Exception exception) {
449             LOGGER.warn("Failed to get {} constructor.", clazz.getSimpleName(), exception);
450             return null;
451         }
452     }
453
454     private Class getWrapperClass(Class clazz) {
455         if (byte.class == clazz) {
456             return Byte.class;
457         } else if (short.class == clazz) {
458             return Short.class;
459         } else if (int.class == clazz) {
460             return Integer.class;
461         } else if (long.class == clazz) {
462             return Long.class;
463         } else if (float.class == clazz) {
464             return Float.class;
465         } else if (double.class == clazz) {
466             return Double.class;
467         } else if (char.class == clazz) {
468             return Character.class;
469         } else if (boolean.class == clazz) {
470             return Boolean.class;
471         }
472         return clazz;
473     }
474
475     private <T> T getValue(Object obj, Class<T> clazz, int processingHint) {
476
477         if (obj == null || obj.toString().trim().length() == 0) {
478             return null;
479         } else {
480             obj = obj.toString().trim();
481         }
482
483         if (String.class.equals(clazz)) {
484             if (obj.toString().startsWith("@") && ConfigurationUtils.isExternalLookup(processingHint)) {
485                 String contents = ConfigurationUtils.getFileContents(
486                         NON_CONFIG_RESOURCE.locate(obj.toString().substring(1).trim()));
487                 if (contents == null) {
488                     contents = ConfigurationUtils.getFileContents(obj.toString().substring(1).trim());
489                 }
490                 if (contents != null) {
491                     obj = contents;
492                 }
493             }
494             return (T) obj.toString();
495         } else if (Number.class.isAssignableFrom(clazz)) {
496             Double doubleValue = Double.valueOf(obj.toString());
497             switch (clazz.getName()) {
498                 case "java.lang.Byte":
499                     Byte byteVal = doubleValue.byteValue();
500                     return (T) byteVal;
501                 case "java.lang.Short":
502                     Short shortVal = doubleValue.shortValue();
503                     return (T) shortVal;
504                 case "java.lang.Integer":
505                     Integer intVal = doubleValue.intValue();
506                     return (T) intVal;
507                 case "java.lang.Long":
508                     Long longVal = doubleValue.longValue();
509                     return (T) longVal;
510                 case "java.lang.Float":
511                     Float floatVal = doubleValue.floatValue();
512                     return (T) floatVal;
513                 case "java.lang.Double":
514                     return (T) doubleValue;
515                 default:
516             }
517         } else if (Boolean.class.equals(clazz)) {
518             return (T) Boolean.valueOf(obj.toString());
519         } else if (Character.class.equals(clazz)) {
520             return (T) Character.valueOf(obj.toString().charAt(0));
521         }
522         return null;
523     }
524
525     private <T> T[] getZeroLengthArrayFor(Class<T> clazz) {
526         Object obj = null;
527         if (clazz == int.class) {
528             obj = new int[] {};
529         } else if (clazz == byte.class) {
530             obj = new byte[] {};
531         } else if (clazz == short.class) {
532             obj = new short[] {};
533         } else if (clazz == long.class) {
534             obj = new long[] {};
535         } else if (clazz == float.class) {
536             obj = new float[] {};
537         } else if (clazz == double.class) {
538             obj = new double[] {};
539         } else if (clazz == boolean.class) {
540             obj = new boolean[] {};
541         } else if (clazz == char.class) {
542             obj = new char[] {};
543         } else if (clazz == Byte.class) {
544             obj = new Byte[] {};
545         } else if (clazz == Short.class) {
546             obj = new Short[] {};
547         } else if (clazz == Integer.class) {
548             obj = new Integer[] {};
549         } else if (clazz == Long.class) {
550             obj = new Long[] {};
551         } else if (clazz == Float.class) {
552             obj = new Float[] {};
553         } else if (clazz == Double.class) {
554             obj = new Double[] {};
555         } else if (clazz == Boolean.class) {
556             obj = new Boolean[] {};
557         } else if (clazz == Character.class) {
558             obj = new Character[] {};
559         } else if (clazz == String.class) {
560             obj = new String[] {};
561         }
562         return (T[]) obj;
563     }
564
565     private <T> Collection<T> convert(String commaSeparatedValues, Class<T> clazz, int processingHints) {
566         ArrayList<T> collection = new ArrayList<>();
567         for (String value : commaSeparatedValues.split(",")) {
568             try {
569                 T type1 = getValue(value, clazz, processingHints);
570                 if (type1 != null) {
571                     collection.add(type1);
572                 }
573             } catch (RuntimeException re) {
574                 LOGGER.warn("Failed to convert {}", commaSeparatedValues, re);
575             }
576         }
577         return collection;
578     }
579 }