Handled not thread-safe fields in configuration
[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 java.io.File;
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.Field;
22 import java.lang.reflect.Modifier;
23 import java.net.URL;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.function.Predicate;
33 import org.onap.config.ConfigurationUtils;
34 import org.onap.config.Constants;
35 import org.onap.config.NonConfigResource;
36 import org.onap.config.api.Config;
37 import org.onap.config.api.Hint;
38
39 public class ConfigurationImpl implements org.onap.config.api.Configuration {
40
41     private static final String KEY_CANNOT_BE_NULL = "Key can't be null.";
42
43     private static final ThreadLocal<String> TENANT = ThreadLocal.withInitial(() -> Constants.DEFAULT_TENANT);
44
45     private static final Object LOCK = new Object();
46
47     private static boolean instantiated = false;
48
49     public ConfigurationImpl() throws Exception {
50
51         synchronized (LOCK) {
52             init();
53         }
54     }
55
56     private void init() throws Exception {
57
58         if (instantiated || !CliConfigurationImpl.class.isAssignableFrom(this.getClass())) {
59             throw new RuntimeException("Illegal access to configuration.");
60         }
61
62         Map<String, AggregateConfiguration> moduleConfigStore = new HashMap<>();
63         List<URL> classpathResources = ConfigurationUtils.getAllClassPathResources();
64         Predicate<URL> predicate = ConfigurationUtils::isConfig;
65         for (URL url : classpathResources) {
66             if (predicate.test(url)) {
67                 String moduleName = ConfigurationUtils.getConfigurationRepositoryKey(url);
68                 AggregateConfiguration moduleConfig = moduleConfigStore.get(moduleName);
69                 if (moduleConfig == null) {
70                     moduleConfig = new AggregateConfiguration();
71                     moduleConfigStore.put(moduleName, moduleConfig);
72                 }
73                 moduleConfig.addConfig(url);
74             } else {
75                 NonConfigResource.add(url);
76             }
77         }
78         String configLocation = System.getProperty("config.location");
79         if (configLocation != null && configLocation.trim().length() > 0) {
80             File root = new File(configLocation);
81             Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
82             Predicate<File> filePredicate = ConfigurationUtils::isConfig;
83             for (File file : filesystemResources) {
84                 if (filePredicate.test(file)) {
85                     String moduleName = ConfigurationUtils.getConfigurationRepositoryKey(file);
86                     AggregateConfiguration moduleConfig = moduleConfigStore.get(moduleName);
87                     if (moduleConfig == null) {
88                         moduleConfig = new AggregateConfiguration();
89                         moduleConfigStore.put(moduleName, moduleConfig);
90                     }
91                     moduleConfig.addConfig(file);
92                 } else {
93                     NonConfigResource.add(file);
94                 }
95             }
96         }
97         String tenantConfigLocation = System.getProperty("tenant.config.location");
98         if (tenantConfigLocation != null && tenantConfigLocation.trim().length() > 0) {
99             File root = new File(tenantConfigLocation);
100             Collection<File> tenantsRoot = ConfigurationUtils.getAllFiles(root, false, true);
101             Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
102             Predicate<File> filePredicate = ConfigurationUtils::isConfig;
103             for (File file : filesystemResources) {
104                 if (filePredicate.test(file)) {
105                     String moduleName = ConfigurationUtils.getNamespace(file);
106                     for (File tenantFileRoot : tenantsRoot) {
107                         if (file.getAbsolutePath().startsWith(tenantFileRoot.getAbsolutePath())) {
108                             moduleName = ConfigurationUtils.getConfigurationRepositoryKey(
109                                     (tenantFileRoot.getName().toUpperCase() + Constants.TENANT_NAMESPACE_SEPARATOR
110                                              + moduleName).split(Constants.TENANT_NAMESPACE_SEPARATOR));
111                         }
112                     }
113                     AggregateConfiguration moduleConfig = moduleConfigStore.get(moduleName);
114                     if (moduleConfig == null) {
115                         moduleConfig = new AggregateConfiguration();
116                         moduleConfigStore.put(moduleName, moduleConfig);
117                     }
118                     moduleConfig.addConfig(file);
119                 }
120             }
121         }
122
123         populateFinalConfigurationIncrementally(moduleConfigStore);
124         String nodeConfigLocation = System.getProperty("node.config.location");
125         if (nodeConfigLocation != null && nodeConfigLocation.trim().length() > 0) {
126             File root = new File(nodeConfigLocation);
127             Collection<File> filesystemResources = ConfigurationUtils.getAllFiles(root, true, false);
128             Predicate<File> filePredicate = ConfigurationUtils::isConfig;
129             for (File file : filesystemResources) {
130                 if (filePredicate.test(file)) {
131                     ConfigurationRepository.lookup().populateOverrideConfiguration(
132                             ConfigurationUtils.getConfigurationRepositoryKey(
133                                     ConfigurationUtils.getNamespace(file).split(Constants.TENANT_NAMESPACE_SEPARATOR)),
134                             file);
135                 }
136             }
137         }
138
139         instantiated = true;
140     }
141
142     private 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         if (tenantId == null || tenantId.trim().length() == 0) {
205             tenantId = TENANT.get();
206         } else {
207             tenantId = tenantId.toUpperCase();
208         }
209         if (namespace == null || namespace.trim().length() == 0) {
210             namespace = Constants.DEFAULT_NAMESPACE;
211         } else {
212             namespace = namespace.toUpperCase();
213         }
214         Map<String, T> map = new HashMap<>();
215         Iterator<String> keys;
216         try {
217             keys = ConfigurationRepository.lookup().getConfigurationFor(tenantId, namespace).getKeys(key);
218             while (keys.hasNext()) {
219                 String k = keys.next();
220                 if (k.startsWith(key + ".")) {
221                     k = k.substring(key.length() + 1);
222                     String subkey = k.substring(0, k.indexOf("."));
223                     if (!map.containsKey(subkey)) {
224                         map.put(subkey, get(tenantId, namespace, key + "." + subkey, clazz));
225                     }
226                 }
227             }
228         } catch (Exception e) {
229             e.printStackTrace();
230         }
231         return map;
232     }
233
234     @Override
235     public Map generateMap(String tenantId, String namespace, String key) {
236
237         if (tenantId == null || tenantId.trim().length() == 0) {
238             tenantId = TENANT.get();
239         } else {
240             tenantId = tenantId.toUpperCase();
241         }
242         if (namespace == null || namespace.trim().length() == 0) {
243             namespace = Constants.DEFAULT_NAMESPACE;
244         } else {
245             namespace = namespace.toUpperCase();
246         }
247         Map map;
248         Map parentMap = new HashMap<>();
249         Iterator<String> keys;
250         try {
251             if (key == null || key.trim().length() == 0) {
252                 keys = ConfigurationRepository.lookup().getConfigurationFor(tenantId, namespace).getKeys();
253             } else {
254                 keys = ConfigurationRepository.lookup().getConfigurationFor(tenantId, namespace).getKeys(key);
255             }
256             while (keys.hasNext()) {
257                 map = parentMap;
258                 String k = keys.next();
259
260                 if (key != null && key.trim().length() != 0 && !k.startsWith(key + ".")) {
261                     continue;
262                 }
263                 String value = getAsString(tenantId, namespace, k);
264                 if (key != null && key.trim().length() != 0 && k.startsWith(key + ".")) {
265                     k = k.substring(key.trim().length() + 1);
266                 }
267
268                 while (k.contains(".")) {
269                     if (k.contains(".")) {
270                         String subkey = k.substring(0, k.indexOf("."));
271                         k = k.substring(k.indexOf(".") + 1);
272                         if (!map.containsKey(subkey)) {
273                             map.put(subkey, map = new HashMap<>());
274                         } else {
275                             map = (Map) map.get(subkey);
276                         }
277                     }
278                 }
279                 map.put(k, value);
280             }
281         } catch (Exception e) {
282             e.printStackTrace();
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         if (tenant == null || tenant.trim().length() == 0) {
296             tenant = TENANT.get();
297         } else {
298             tenant = tenant.toUpperCase();
299         }
300         if (namespace == null || namespace.trim().length() == 0) {
301             namespace = Constants.DEFAULT_NAMESPACE;
302         } else {
303             namespace = namespace.toUpperCase();
304         }
305         if ((key == null || key.trim().length() == 0) && !clazz.isAnnotationPresent(Config.class)) {
306             throw new IllegalArgumentException(KEY_CANNOT_BE_NULL);
307         }
308         if (clazz == null) {
309             throw new IllegalArgumentException("clazz is null.");
310         }
311         if (clazz.isPrimitive()) {
312             clazz = getWrapperClass(clazz);
313         }
314         try {
315             if (ConfigurationUtils.isWrapperClass(clazz) || clazz.isPrimitive()) {
316                 Object obj = ConfigurationUtils.getProperty(
317                         ConfigurationRepository.lookup().getConfigurationFor(tenant, namespace), key, processingHints);
318                 if (obj != null) {
319                     if (ConfigurationUtils.isCollection(obj.toString())) {
320                         obj = ConfigurationUtils.getCollectionString(obj.toString());
321                     }
322                     String value = obj.toString().split(",")[0];
323                     value = ConfigurationUtils.processVariablesIfPresent(tenant, namespace, value);
324                     return (T) getValue(value, clazz.isPrimitive() ? getWrapperClass(clazz) : clazz, processingHints);
325                 } else {
326                     return null;
327                 }
328             } else if (clazz.isArray() && (clazz.getComponentType().isPrimitive() || ConfigurationUtils.isWrapperClass(
329                     clazz.getComponentType()))) {
330                 Object obj = ConfigurationUtils.getProperty(
331                         ConfigurationRepository.lookup().getConfigurationFor(tenant, namespace), key, processingHints);
332                 if (obj != null) {
333                     Class componentClass = clazz.getComponentType();
334                     if (clazz.getComponentType().isPrimitive()) {
335                         componentClass = getWrapperClass(clazz.getComponentType());
336                     }
337                     String collString = ConfigurationUtils.getCollectionString(obj.toString());
338                     ArrayList<String> tempCollection = new ArrayList<>();
339                     for (String itemValue : collString.split(",")) {
340                         tempCollection.add(ConfigurationUtils.processVariablesIfPresent(tenant, namespace, itemValue));
341                     }
342                     Collection<T> collection =
343                             convert(ConfigurationUtils.getCollectionString(Arrays.toString(tempCollection.toArray())),
344                                     componentClass, processingHints);
345                     if (clazz.getComponentType().isPrimitive()) {
346                         return (T) ConfigurationUtils.getPrimitiveArray(collection, clazz.getComponentType());
347                     } else {
348                         return (T) collection.toArray(getZeroLengthArrayFor(getWrapperClass(clazz.getComponentType())));
349                     }
350                 } else {
351                     return null;
352                 }
353             } else if (clazz.isAnnotationPresent(Config.class)) {
354                 return read(tenant, namespace, clazz, (key == null || key.trim().length() == 0) ? "" : (key + "."),
355                         hints);
356             } else {
357                 throw new IllegalArgumentException(
358                         "Only primitive classes, wrapper classes, corresponding array classes and any "
359                                 + "class decorated with @org.openecomp.config.api.Config are allowed as argument.");
360             }
361         } catch (Exception exception) {
362             exception.printStackTrace();
363         }
364         return null;
365     }
366
367     private <T> T read(String tenant, String namespace, Class<T> clazz, String keyPrefix, Hint... hints)
368             throws Exception {
369         Config confAnnotation = clazz.getAnnotation(Config.class);
370         if (confAnnotation != null && confAnnotation.key().length() > 0 && !keyPrefix.endsWith(".")) {
371             keyPrefix += (confAnnotation.key() + ".");
372         }
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             if (fieldConfAnnotation != null) {
380                 if (field.getType().isPrimitive() || ConfigurationUtils.isWrapperClass(field.getType()) || (
381                         field.getType().isArray() && (field.getType().getComponentType().isPrimitive()
382                                                               || ConfigurationUtils.isWrapperClass(
383                                 field.getType().getComponentType())))
384                             || field.getType().getAnnotation(Config.class) != null) {
385                     field.set(objToReturn,
386                             get(tenant, namespace, keyPrefix + fieldConfAnnotation.key(), field.getType(), hints));
387                 } else if (Collection.class.isAssignableFrom(field.getType())) {
388                     Object obj = get(tenant, namespace, keyPrefix + fieldConfAnnotation.key(),
389                             ConfigurationUtils.getArrayClass(ConfigurationUtils.getCollectionGenericType(field)),
390                             hints);
391                     if (obj != null) {
392                         List list = Arrays.asList((Object[]) obj);
393                         Class clazzToInstantiate;
394                         if (field.getType().isInterface()) {
395                             clazzToInstantiate = ConfigurationUtils.getConcreteCollection(field.getType()).getClass();
396                         } else if (Modifier.isAbstract(field.getType().getModifiers())) {
397                             clazzToInstantiate =
398                                     ConfigurationUtils.getCompatibleCollectionForAbstractDef(field.getType())
399                                             .getClass();
400                         } else {
401                             clazzToInstantiate = field.getType();
402                         }
403                         Constructor construct = getConstructorWithArguments(clazzToInstantiate, Collection.class);
404                         if (construct != null) {
405                             construct.setAccessible(true);
406                             field.set(objToReturn, construct.newInstance(list));
407                         } else if ((construct = getConstructorWithArguments(clazzToInstantiate, Integer.class,
408                                 Boolean.class, Collection.class)) != null) {
409                             construct.setAccessible(true);
410                             field.set(objToReturn, construct.newInstance(list.size(), true, list));
411                         }
412                     }
413                 } else if (Map.class.isAssignableFrom(field.getType())) {
414                     field.set(objToReturn, generateMap(tenant, namespace, keyPrefix + fieldConfAnnotation.key()));
415                 }
416             }
417         }
418         return objToReturn;
419     }
420
421     private Constructor getConstructorWithArguments(Class clazz, Class... classes) {
422         try {
423             return clazz.getDeclaredConstructor(classes);
424         } catch (Exception exception) {
425             return null;
426         }
427     }
428
429     private Class getWrapperClass(Class clazz) {
430         if (byte.class == clazz) {
431             return Byte.class;
432         } else if (short.class == clazz) {
433             return Short.class;
434         } else if (int.class == clazz) {
435             return Integer.class;
436         } else if (long.class == clazz) {
437             return Long.class;
438         } else if (float.class == clazz) {
439             return Float.class;
440         } else if (double.class == clazz) {
441             return Double.class;
442         } else if (char.class == clazz) {
443             return Character.class;
444         } else if (boolean.class == clazz) {
445             return Boolean.class;
446         }
447         return clazz;
448     }
449
450     private <T> T getValue(Object obj, Class<T> clazz, int processingHint) {
451         if (obj == null || obj.toString().trim().length() == 0) {
452             return null;
453         } else {
454             obj = obj.toString().trim();
455         }
456         if (String.class.equals(clazz)) {
457             if (obj.toString().startsWith("@") && ConfigurationUtils.isExternalLookup(processingHint)) {
458                 String contents = ConfigurationUtils.getFileContents(
459                         NonConfigResource.locate(obj.toString().substring(1).trim()));
460                 if (contents == null) {
461                     contents = ConfigurationUtils.getFileContents(obj.toString().substring(1).trim());
462                 }
463                 if (contents != null) {
464                     obj = contents;
465                 }
466             }
467             return (T) obj.toString();
468         } else if (Number.class.isAssignableFrom(clazz)) {
469             Double doubleValue = Double.valueOf(obj.toString());
470             switch (clazz.getName()) {
471                 case "java.lang.Byte":
472                     Byte byteVal = doubleValue.byteValue();
473                     return (T) byteVal;
474                 case "java.lang.Short":
475                     Short shortVal = doubleValue.shortValue();
476                     return (T) shortVal;
477                 case "java.lang.Integer":
478                     Integer intVal = doubleValue.intValue();
479                     return (T) intVal;
480                 case "java.lang.Long":
481                     Long longVal = doubleValue.longValue();
482                     return (T) longVal;
483                 case "java.lang.Float":
484                     Float floatVal = doubleValue.floatValue();
485                     return (T) floatVal;
486                 case "java.lang.Double":
487                     return (T) doubleValue;
488                 default:
489             }
490         } else if (Boolean.class.equals(clazz)) {
491             return (T) Boolean.valueOf(obj.toString());
492         } else if (Character.class.equals(clazz)) {
493             return (T) Character.valueOf(obj.toString().charAt(0));
494         }
495         return null;
496     }
497
498     private <T> T[] getZeroLengthArrayFor(Class<T> clazz) {
499         Object obj = null;
500         if (clazz == int.class) {
501             obj = new int[] {};
502         } else if (clazz == byte.class) {
503             obj = new byte[] {};
504         } else if (clazz == short.class) {
505             obj = new short[] {};
506         } else if (clazz == long.class) {
507             obj = new long[] {};
508         } else if (clazz == float.class) {
509             obj = new float[] {};
510         } else if (clazz == double.class) {
511             obj = new double[] {};
512         } else if (clazz == boolean.class) {
513             obj = new boolean[] {};
514         } else if (clazz == char.class) {
515             obj = new char[] {};
516         } else if (clazz == Byte.class) {
517             obj = new Byte[] {};
518         } else if (clazz == Short.class) {
519             obj = new Short[] {};
520         } else if (clazz == Integer.class) {
521             obj = new Integer[] {};
522         } else if (clazz == Long.class) {
523             obj = new Long[] {};
524         } else if (clazz == Float.class) {
525             obj = new Float[] {};
526         } else if (clazz == Double.class) {
527             obj = new Double[] {};
528         } else if (clazz == Boolean.class) {
529             obj = new Boolean[] {};
530         } else if (clazz == Character.class) {
531             obj = new Character[] {};
532         } else if (clazz == String.class) {
533             obj = new String[] {};
534         }
535         return (T[]) obj;
536     }
537
538     private <T> Collection<T> convert(String commaSaperatedValues, Class<T> clazz, int processingHints) {
539         ArrayList<T> collection = new ArrayList<>();
540         for (String value : commaSaperatedValues.split(",")) {
541             try {
542                 T type1 = getValue(value, clazz, processingHints);
543                 if (type1 != null) {
544                     collection.add(type1);
545                 }
546             } catch (RuntimeException re) {
547                 // do nothing
548             }
549         }
550         return collection;
551     }
552 }