fix jacoco.exec file name
[appc.git] / appc-core / appc-common-bundle / java / org / onap / appc / configuration / DefaultConfiguration.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Copyright (C) 2017 Amdocs
8  * =============================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  * 
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  * 
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * 
21  * ============LICENSE_END=========================================================
22  */
23
24 package org.onap.appc.configuration;
25
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.net.URL;
29 import java.security.CodeSource;
30 import java.security.ProtectionDomain;
31 import java.security.Provider;
32 import java.security.Provider.Service;
33 import java.security.Security;
34 import java.util.Map.Entry;
35 import java.util.Properties;
36 import java.util.jar.JarFile;
37 import java.util.jar.Manifest;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
40 import org.onap.appc.encryption.EncryptionTool;
41 import org.onap.appc.util.UnmodifiableProperties;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * This class provides the implementation of the <code>Configuration</code> interface. It is created
47  * by the ConfigurationFactory and initialized with the configuration values for the process.
48  *
49  * @since Mar 18, 2014
50  */
51 public final class DefaultConfiguration implements Configuration, Cloneable {
52
53     private static final Logger logger = LoggerFactory.getLogger(DefaultConfiguration.class);
54
55     /**
56      * The framework configuration properties.
57      */
58     private Properties properties = new Properties();
59
60     /**
61      * Construct the configuration object.
62      */
63     DefaultConfiguration() {}
64
65     /**
66      * Clears all properties
67      */
68     public void clear() {
69         properties.clear();
70     }
71
72     /**
73      * @see java.lang.Object#clone()
74      */
75     @Override
76     protected Object clone() throws CloneNotSupportedException {
77         DefaultConfiguration clone = (DefaultConfiguration) super.clone();
78
79         clone.properties = new Properties(this.properties);
80         clone.properties.putAll(this.properties);
81
82         return clone;
83     }
84
85     /**
86      * Decrypts an encrypted value, if it is encrypted, and returns the clear text. Performs no
87      * operation on the string if it is not encrypted.
88      *
89      * @param value The value to (optionally) be decrypted
90      * @return The clear text
91      */
92     @SuppressWarnings("nls")
93     private static String decrypt(String value) {
94         if (value != null && value.startsWith(EncryptionTool.ENCRYPTED_VALUE_PREFIX)) {
95             try {
96                 return EncryptionTool.getInstance().decrypt(value);
97             } catch (Exception e) {
98                 StringBuilder out = new StringBuilder();
99                 for (Provider p : Security.getProviders()) {
100                     for (Service s : p.getServices()) {
101                         String algo = s.getAlgorithm();
102                         out.append(String.format(
103                                 "\n==Found Algorithm [ %s ] in provider [ %s ] and service [ %s ]",
104                                 algo, p.getName(), s.getClassName()));
105                     }
106                 }
107                 logger.debug(out.toString());
108                 logger.warn(String.format("Could not decrypt the configuration value [%s]", value),
109                         e);
110             }
111         }
112         return value;
113     }
114
115     /**
116      * Decrypts all elements in the properties object
117      */
118     private void decryptAllProperties() {
119         if (properties != null) {
120             for (Entry<Object, Object> e : properties.entrySet()) {
121                 if (e.getValue() != null) {
122                     e.setValue(decrypt(e.getValue().toString()));
123                 }
124             }
125         }
126     }
127
128     /**
129      * @see java.lang.Object#equals(java.lang.Object)
130      */
131     @Override
132     public boolean equals(Object obj) {
133
134         if (obj == null) {
135             return false;
136         }
137
138         if (this.getClass() != obj.getClass()) {
139             return false;
140         }
141
142         DefaultConfiguration other = (DefaultConfiguration) obj;
143
144         return (this.properties.size() == other.properties.size())
145                 && (this.properties.entrySet().containsAll(other.properties.entrySet()))
146                 && (other.properties.entrySet().containsAll(this.properties.entrySet()));
147
148     }
149
150     /**
151      * This method will use the properties object to expand any variables that may be present in the
152      * template provided. Variables are represented by the string "${name}", where "name" is the
153      * name of a property defined in either the current configuration object, or system properties
154      * if undefined. If the value cannot be found, the variable is removed and an empty string is
155      * used to replace the variable.
156      *
157      * @param template The template to be expanded
158      * @return The expanded template where each variable is replaced with its value
159      */
160     @SuppressWarnings("nls")
161     private String expandVariables(String template) {
162         if (template == null) {
163             return null;
164         }
165
166         // Decrypt the template if needed
167         // template = decrypt(template); DH: Do not assign values to parameters, bad form! Also,
168         // Sonar complains
169         // bitterly
170
171         StringBuilder builder = new StringBuilder(decrypt(template));
172         Pattern pattern = Pattern.compile("\\$\\{([^\\}]+)\\}");
173         Matcher matcher = pattern.matcher(builder);
174         while (matcher.find()) {
175             String variable = matcher.group(1);
176             String value = properties.getProperty(variable);
177             if (value == null) {
178                 value = System.getProperty(variable);
179             }
180             if (value == null) {
181                 value = "";
182             }
183             builder.replace(matcher.start(), matcher.end(), value);
184
185             matcher.reset();
186         }
187         return builder.toString().trim();
188     }
189
190     /**
191      * This method is called to obtain a property expressed as a boolean value (true or false). The
192      * standard rules for Boolean.parseBoolean() are used.
193      *
194      * @param key The property key
195      * @return The value of the property expressed as a boolean, or false if it does not exist.
196      */
197     @SuppressWarnings("nls")
198     @Override
199     public boolean getBooleanProperty(String key) {
200         return Boolean.valueOf(getProperty(key, "false"));
201     }
202
203     /**
204      * This method is called to obtain a property expressed as a boolean value (true or false). The
205      * standard rules for Boolean.valueOf(String) are used.
206      *
207      * @param key The property key
208      * @param defaultValue The default value to be returned if the property does not exist
209      * @return The value of the property expressed as a boolean, or false if it does not exist.
210      * @see org.onap.appc.configuration.Configuration#getBooleanProperty(java.lang.String, boolean)
211      */
212     @Override
213     public boolean getBooleanProperty(String key, boolean defaultValue) {
214         if (isPropertyDefined(key)) {
215             return getBooleanProperty(key);
216         }
217         return defaultValue;
218     }
219
220     /**
221      * Returns the indicated property value expressed as a floating point double-precision value
222      * (double).
223      *
224      * @param key The property to retrieve
225      * @return The value of the property, or 0.0 if not found
226      * @see org.onap.appc.configuration.Configuration#getDoubleProperty(java.lang.String)
227      */
228     @SuppressWarnings("nls")
229     @Override
230     public double getDoubleProperty(String key) {
231         try {
232             return Double.valueOf(getProperty(key, "0.0"));
233         } catch (NumberFormatException e) {
234             return 0.0;
235         }
236     }
237
238     /**
239      * This method is called to obtain a property as a string value
240      *
241      * @param key The key of the property
242      * @param defaultValue The default value to be returned if the property does not exist
243      * @return The string value, or null if it does not exist.
244      * @see org.onap.appc.configuration.Configuration#getDoubleProperty(java.lang.String, double)
245      */
246     @Override
247     public double getDoubleProperty(String key, double defaultValue) {
248         if (isPropertyDefined(key)) {
249             return getDoubleProperty(key);
250         }
251         return defaultValue;
252     }
253
254     /**
255      * Returns the property indicated expressed as an integer. The standard rules for
256      * {@link Integer#parseInt(String, int)} using a radix of 10 are used.
257      *
258      * @param key The property name to retrieve.
259      * @return The value of the property, or 0 if it does not exist or is invalid.
260      * @see org.onap.appc.configuration.Configuration#getIntegerProperty(java.lang.String)
261      */
262     @SuppressWarnings("nls")
263     @Override
264     public int getIntegerProperty(String key) {
265         try {
266             return Integer.parseInt(getProperty(key, "0"), 10);
267         } catch (NumberFormatException e) {
268             return 0;
269         }
270     }
271
272     /**
273      * Returns the property indicated expressed as an integer. The standard rules for
274      * Integer.parseInt(String, int) using a radix of 10 are used.
275      *
276      * @param key The property name to retrieve.
277      * @param defaultValue The default value to be returned if the property does not exist
278      * @return The value of the property, or 0 if it does not exist or is invalid.
279      * @see org.onap.appc.configuration.Configuration#getIntegerProperty(java.lang.String, int)
280      */
281     @Override
282     public int getIntegerProperty(String key, int defaultValue) {
283         if (isPropertyDefined(key)) {
284             return getIntegerProperty(key);
285         }
286         return defaultValue;
287     }
288
289     /**
290      * Returns the specified property as a long integer value, if it exists, or zero if it does not.
291      *
292      * @param key The key of the property desired.
293      * @return The value of the property expressed as an integer long value, or zero if the property
294      *         does not exist or is not a valid integer long.
295      * @see org.onap.appc.configuration.Configuration#getLongProperty(java.lang.String)
296      */
297     @SuppressWarnings("nls")
298     @Override
299     public long getLongProperty(String key) {
300         try {
301             return Long.parseLong(getProperty(key, "0"), 10);
302         } catch (NumberFormatException e) {
303             return 0;
304         }
305     }
306
307     /**
308      * Returns the specified property as a long integer value, if it exists, or the default value if
309      * it does not exist or is invalid.
310      *
311      * @param key The key of the property desired.
312      * @param defaultValue the value to be returned if the property is not valid or does not exist.
313      * @return The value of the property expressed as an integer long value, or the default value if
314      *         the property does not exist or is not a valid integer long.
315      * @see org.onap.appc.configuration.Configuration#getLongProperty(java.lang.String, long)
316      */
317     @Override
318     public long getLongProperty(String key, long defaultValue) {
319         if (isPropertyDefined(key)) {
320             return getLongProperty(key);
321         }
322         return defaultValue;
323     }
324
325     /**
326      * This method can be called to retrieve a properties object that is immutable. Any attempt to
327      * modify the properties object returned will result in an exception. This allows a caller to
328      * view the current configuration as a set of properties.
329      *
330      * @return An unmodifiable properties object.
331      * @see org.onap.appc.configuration.Configuration#getProperties()
332      */
333     @Override
334     public Properties getProperties() {
335         return new UnmodifiableProperties(properties);
336     }
337
338     /**
339      * This method is called to obtain a property as a string value
340      *
341      * @param key The key of the property
342      * @return The string value, or null if it does not exist.
343      */
344     @Override
345     public String getProperty(String key) {
346         String value = properties.getProperty(key);
347         if (value == null) {
348             return null;
349         }
350         return expandVariables(value.trim());
351     }
352
353     /**
354      * This method is called to obtain a property as a string value
355      *
356      * @param key The key of the property
357      * @param defaultValue The default value to be returned if the property does not exist
358      * @return The string value, or null if it does not exist.
359      * @see org.onap.appc.configuration.Configuration#getProperty(java.lang.String,
360      *      java.lang.String)
361      */
362     @Override
363     public String getProperty(String key, String defaultValue) {
364         if (isPropertyDefined(key)) {
365             return getProperty(key);
366         }
367
368         if (defaultValue == null) {
369             return null;
370         }
371
372         return expandVariables(defaultValue.trim());
373     }
374
375     /**
376      * @see java.lang.Object#hashCode()
377      */
378     @Override
379     public int hashCode() {
380         return properties == null ? 0 : properties.hashCode();
381     }
382
383     /**
384      * Returns true if the named property is defined, false otherwise.
385      *
386      * @param key The key of the property we are interested in
387      * @return True if the property exists.
388      */
389     @Override
390     public boolean isPropertyDefined(String key) {
391         return properties.containsKey(key);
392     }
393
394     /**
395      * Returns an indication of the validity of the boolean property. A boolean property is
396      * considered to be valid only if it has the value "true" or "false" (ignoring case).
397      *
398      * @param key The property to be checked
399      * @return True if the value is a boolean constant, or false if it does not exist or is not a
400      *         correct string
401      * @see org.onap.appc.configuration.Configuration#isValidBoolean(java.lang.String)
402      */
403     @SuppressWarnings("nls")
404     @Override
405     public boolean isValidBoolean(String key) {
406         String value = getProperty(key);
407         if (value != null) {
408             value = value.toLowerCase();
409             return value.matches("true|false");
410         }
411         return false;
412     }
413
414     /**
415      * Returns an indication if the indicated property represents a valid double-precision floating
416      * point number.
417      *
418      * @param key The property to be examined
419      * @return True if the property is a valid representation of a double, or false if it does not
420      *         exist or contains illegal characters.
421      * @see org.onap.appc.configuration.Configuration#isValidDouble(java.lang.String)
422      */
423     @Override
424     public boolean isValidDouble(String key) {
425         String value = getProperty(key);
426         if (value != null) {
427             try {
428                 Double.valueOf(value);
429                 return true;
430             } catch (NumberFormatException e) {
431                 return false;
432             }
433         }
434         return false;
435     }
436
437     /**
438      * Returns an indication if the property is a valid integer value or not.
439      *
440      * @param key The key of the property to check
441      * @return True if the value is a valid integer string, or false if it does not exist or
442      *         contains illegal characters.
443      * @see org.onap.appc.configuration.Configuration#isValidInteger(java.lang.String)
444      */
445     @Override
446     public boolean isValidInteger(String key) {
447         String value = getProperty(key);
448         if (value != null) {
449             try {
450                 Integer.parseInt(value.trim(), 10);
451                 return true;
452             } catch (NumberFormatException e) {
453                 return false;
454             }
455         }
456         return false;
457     }
458
459     /**
460      * Determines is the specified property exists and is a valid representation of an integer long
461      * value.
462      *
463      * @param key The property to be checked
464      * @return True if the property is a valid representation of an integer long value, and false if
465      *         it either does not exist or is not valid.
466      * @see org.onap.appc.configuration.Configuration#isValidLong(java.lang.String)
467      */
468     @Override
469     public boolean isValidLong(String key) {
470         String value = getProperty(key);
471         if (value != null) {
472             try {
473                 Long.parseLong(value.trim(), 10);
474                 return true;
475             } catch (NumberFormatException e) {
476                 return false;
477             }
478         }
479         return false;
480     }
481
482     /**
483      * This method allows an implementation to load configuration properties that may override
484      * default values.
485      *
486      * @param is An input stream that contains the properties to be loaded
487      */
488     public void setProperties(InputStream is) {
489         try {
490             properties.load(is);
491         } catch (IOException e) {
492             logger.warn("setProperties with inputStream got exception", e);
493         }
494     }
495
496     /**
497      * This method allows an implementation to load configuration properties that may override
498      * default values.
499      *
500      * @param props An optional Properties object to be merged into the configuration, replacing any
501      *        same-named properties.
502      * @see org.onap.appc.configuration.Configuration#setProperties(java.util.Properties)
503      */
504     @Override
505     public void setProperties(Properties props) {
506         properties.putAll(props);
507         decryptAllProperties();
508     }
509
510     /**
511      * This method allows a caller to insert a new property definition into the configuration
512      * object. This allows the application to adjust or add to the current configuration. If the
513      * property already exists, it is replaced with the new value.
514      *
515      * @param key The key of the property to be defined
516      * @param value The value of the property to be defined
517      * @see org.onap.appc.configuration.Configuration#setProperty(java.lang.String,
518      *      java.lang.String)
519      */
520     @Override
521     public void setProperty(String key, String value) {
522         properties.setProperty(key, decrypt(value));
523     }
524
525     /**
526      * @see java.lang.Object#toString()
527      */
528     @SuppressWarnings("nls")
529     @Override
530     public String toString() {
531         return String.format("Configuration: %d properties, keys:[%s]", properties.size(),
532                 properties.keySet().toString());
533     }
534
535     /**
536      * This is a helper method to read the manifest of the jar file that this class was loaded from.
537      * Note that this will only work if the code is packaged in a jar file. If it is an open
538      * deployment, such as under eclipse, this will not work and there is code added to detect that
539      * case.
540      *
541      * @return The manifest object from the jar file, or null if the code is not packaged in a jar
542      *         file.
543      */
544     @SuppressWarnings({"unused", "nls"})
545     private Manifest getManifest() {
546         ProtectionDomain domain = getClass().getProtectionDomain();
547         CodeSource source = domain.getCodeSource();
548         URL location = source.getLocation();
549         String path = location.getPath();
550         int index = path.indexOf('!');
551         if (index != -1) {
552             path = path.substring(0, index);
553         }
554         if (path.endsWith(".jar")) {
555             try (JarFile jar = new JarFile(location.getFile())) {
556                 return jar.getManifest();
557             } catch (IOException e) {
558                 logger.error("getManifest", e);
559             }
560         }
561
562         return null;
563     }
564 }