3e86c078858a7d9f29dee92cd9e6f696a9aa16e3
[appc.git] / appc-common / src / main / java / org / onap / appc / configuration / DefaultConfiguration.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017 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  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  * ============LICENSE_END=========================================================
23  */
24
25 package org.onap.appc.configuration;
26
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.net.URL;
30 import java.security.CodeSource;
31 import java.security.ProtectionDomain;
32 import java.security.Provider;
33 import java.security.Provider.Service;
34 import java.security.Security;
35 import java.util.Map.Entry;
36 import java.util.Properties;
37 import java.util.jar.JarFile;
38 import java.util.jar.Manifest;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41
42 import org.onap.appc.encryption.EncryptionTool;
43 import org.onap.appc.util.UnmodifiableProperties;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * This class provides the implementation of the <code>Configuration</code> interface. It is created by the
49  * ConfigurationFactory and initialized with the configuration values for the process.
50  *
51  * @version $Id$
52  * @since Mar 18, 2014
53  */
54 public final class DefaultConfiguration implements Configuration, Cloneable {
55
56     private static final Logger logger = LoggerFactory.getLogger(DefaultConfiguration.class);
57
58     /**
59      * The framework configuration properties.
60      */
61     private Properties properties = new Properties();
62
63     /**
64      * Construct the configuration object.
65      */
66     DefaultConfiguration() {
67         // do nothing
68     }
69
70     /**
71      * Clears all properties
72      */
73     public void clear() {
74         properties.clear();
75     }
76
77     /**
78      * @see java.lang.Object#clone()
79      */
80     @Override
81     protected Object clone() throws CloneNotSupportedException {
82         DefaultConfiguration clone = (DefaultConfiguration) super.clone();
83
84         clone.properties = new Properties(this.properties);
85         clone.properties.putAll(this.properties);
86
87         return clone;
88     }
89
90     /**
91      * Decrypts an encrypted value, if it is encrypted, and returns the clear text. Performs no operation on the string
92      * if it is not encrypted.
93      *
94      * @param value The value to (optionally) be decrypted
95      * @return The clear text
96      */
97     @SuppressWarnings("nls")
98     private static String decrypt(String value) {
99         if (value != null && value.startsWith(EncryptionTool.ENCRYPTED_VALUE_PREFIX)) {
100             try {
101                 return EncryptionTool.getInstance().decrypt(value);
102             } catch (Exception e) {
103                 StringBuilder out = new StringBuilder();
104                 for (Provider p : Security.getProviders()) {
105                     for (Service s : p.getServices()) {
106                         String algo = s.getAlgorithm();
107                         out.append(String.format("\n==Found Algorithm [ %s ] in provider [ %s ] and service [ %s ]",
108                                 algo, p.getName(), s.getClassName()));
109                     }
110                 }
111                 logger.debug(out.toString());
112                 logger.warn(String.format("Could not decrypt the configuration value [%s]", value), e);
113             }
114         }
115         return value;
116     }
117
118     /**
119      * Decrypts all elements in the properties object
120      */
121     private void decryptAllProperties() {
122         if (properties != null) {
123             for (Entry<Object, Object> e : properties.entrySet()) {
124                 if (e.getValue() != null) {
125                     e.setValue(decrypt(e.getValue().toString()));
126                 }
127             }
128         }
129     }
130
131     /**
132      * @see java.lang.Object#equals(java.lang.Object)
133      */
134     @Override
135     public boolean equals(Object obj) {
136
137         if (obj == null) {
138             return false;
139         }
140
141         if (this.getClass() != obj.getClass()) {
142             return false;
143         }
144
145         DefaultConfiguration other = (DefaultConfiguration) obj;
146
147         return (this.properties.size() == other.properties.size())
148                 && (this.properties.entrySet().containsAll(other.properties.entrySet()))
149                 && (other.properties.entrySet().containsAll(this.properties.entrySet()));
150
151     }
152
153     /**
154      * This method will use the properties object to expand any variables that may be present in the template provided.
155      * Variables are represented by the string "${name}", where "name" is the name of a property defined in either the
156      * current configuration object, or system properties if undefined. If the value cannot be found, the variable is
157      * removed and an empty string is used to replace the variable.
158      *
159      * @param template The template to be expanded
160      * @return The expanded template where each variable is replaced with its value
161      */
162     @SuppressWarnings("nls")
163     private String expandVariables(String template) {
164         if (template == null) {
165             return null;
166         }
167
168         // Decrypt the template if needed
169         // template = decrypt(template); DH: Do not assign values to parameters, bad form! Also, Sonar complains
170         // bitterly
171
172         StringBuilder builder = new StringBuilder(decrypt(template));
173         Pattern pattern = Pattern.compile("\\$\\{([^\\}]+)\\}");
174         Matcher matcher = pattern.matcher(builder);
175         while (matcher.find()) {
176             String variable = matcher.group(1);
177             String value = properties.getProperty(variable);
178             if (value == null) {
179                 value = System.getProperty(variable);
180             }
181             if (value == null) {
182                 value = "";
183             }
184             builder.replace(matcher.start(), matcher.end(), value);
185
186             matcher.reset();
187         }
188         return builder.toString().trim();
189     }
190
191     /**
192      * This method is called to obtain a property expressed as a boolean value (true or false). The standard rules for
193      * Boolean.parseBoolean() are used.
194      *
195      * @param key The property key
196      * @return The value of the property expressed as a boolean, or false if it does not exist.
197      */
198     @SuppressWarnings("nls")
199     @Override
200     public boolean getBooleanProperty(String key) {
201         return Boolean.valueOf(getProperty(key, "false"));
202     }
203
204     /**
205      * This method is called to obtain a property expressed as a boolean value (true or false). The standard rules for
206      * Boolean.valueOf(String) are used.
207      *
208      * @param key          The property key
209      * @param defaultValue The default value to be returned if the property does not exist
210      * @return The value of the property expressed as a boolean, or false if it does not exist.
211      * @see org.onap.appc.configuration.Configuration#getBooleanProperty(java.lang.String, boolean)
212      */
213     @Override
214     public boolean getBooleanProperty(String key, boolean defaultValue) {
215         if (isPropertyDefined(key)) {
216             return getBooleanProperty(key);
217         }
218         return defaultValue;
219     }
220
221     /**
222      * Returns the indicated property value expressed as a floating point double-precision value (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 Integer.parseInt(String, int)
274      * 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 does not exist or
294      * 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 it does not exist
309      * 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 the property does
314      * 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 modify the properties
327      * object returned will result in an exception. This allows a caller to view the current configuration as a set of
328      * 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, java.lang.String)
360      */
361     @Override
362     public String getProperty(String key, String defaultValue) {
363         if (isPropertyDefined(key)) {
364             return getProperty(key);
365         }
366
367         if (defaultValue == null) {
368             return null;
369         }
370
371         return expandVariables(defaultValue.trim());
372     }
373
374     /**
375      * @see java.lang.Object#hashCode()
376      */
377     @Override
378     public int hashCode() {
379         return properties == null ? 0 : properties.hashCode();
380     }
381
382     /**
383      * Returns true if the named property is defined, false otherwise.
384      *
385      * @param key The key of the property we are interested in
386      * @return True if the property exists.
387      */
388     @Override
389     public boolean isPropertyDefined(String key) {
390         return properties.containsKey(key);
391     }
392
393     /**
394      * Returns an indication of the validity of the boolean property. A boolean property is considered to be valid only
395      * if it has the value "true" or "false" (ignoring case).
396      *
397      * @param key The property to be checked
398      * @return True if the value is a boolean constant, or false if it does not exist or is not a correct string
399      * @see org.onap.appc.configuration.Configuration#isValidBoolean(java.lang.String)
400      */
401     @SuppressWarnings("nls")
402     @Override
403     public boolean isValidBoolean(String key) {
404         String value = getProperty(key);
405         if (value != null) {
406             value = value.toLowerCase();
407             return value.matches("true|false");
408         }
409         return false;
410     }
411
412     /**
413      * Returns an indication if the indicated property represents a valid double-precision floating point number.
414      *
415      * @param key The property to be examined
416      * @return True if the property is a valid representation of a double, or false if it does not exist or contains
417      * illegal characters.
418      * @see org.onap.appc.configuration.Configuration#isValidDouble(java.lang.String)
419      */
420     @Override
421     public boolean isValidDouble(String key) {
422         String value = getProperty(key);
423         if (value != null) {
424             try {
425                 Double.valueOf(value);
426                 return true;
427             } catch (NumberFormatException e) {
428                 return false;
429             }
430         }
431         return false;
432     }
433
434     /**
435      * Returns an indication if the property is a valid integer value or not.
436      *
437      * @param key The key of the property to check
438      * @return True if the value is a valid integer string, or false if it does not exist or contains illegal
439      * characters.
440      * @see org.onap.appc.configuration.Configuration#isValidInteger(java.lang.String)
441      */
442     @Override
443     public boolean isValidInteger(String key) {
444         String value = getProperty(key);
445         if (value != null) {
446             try {
447                 Integer.parseInt(value.trim(), 10);
448                 return true;
449             } catch (NumberFormatException e) {
450                 return false;
451             }
452         }
453         return false;
454     }
455
456     /**
457      * Determines is the specified property exists and is a valid representation of an integer long value.
458      *
459      * @param key The property to be checked
460      * @return True if the property is a valid representation of an integer long value, and false if it either does not
461      * exist or is not valid.
462      * @see org.onap.appc.configuration.Configuration#isValidLong(java.lang.String)
463      */
464     @Override
465     public boolean isValidLong(String key) {
466         String value = getProperty(key);
467         if (value != null) {
468             try {
469                 Long.parseLong(value.trim(), 10);
470                 return true;
471             } catch (NumberFormatException e) {
472                 return false;
473             }
474         }
475         return false;
476     }
477
478     /**
479      * This method allows an implementation to load configuration properties that may override default values.
480      *
481      * @param is An input stream that contains the properties to be loaded
482      */
483     public void setProperties(InputStream is) {
484         try {
485             properties.load(is);
486         } catch (IOException e) {
487             logger.warn("setProperties with inputStream got exception", e);
488         }
489     }
490
491     /**
492      * This method allows an implementation to load configuration properties that may override default values.
493      *
494      * @param props An optional Properties object to be merged into the configuration, replacing any same-named
495      *              properties.
496      * @see org.onap.appc.configuration.Configuration#setProperties(java.util.Properties)
497      */
498     @Override
499     public void setProperties(Properties props) {
500         properties.putAll(props);
501         decryptAllProperties();
502     }
503
504     /**
505      * This method allows a caller to insert a new property definition into the configuration object. This allows the
506      * application to adjust or add to the current configuration. If the property already exists, it is replaced with
507      * the new value.
508      *
509      * @param key   The key of the property to be defined
510      * @param value The value of the property to be defined
511      * @see org.onap.appc.configuration.Configuration#setProperty(java.lang.String, java.lang.String)
512      */
513     @Override
514     public void setProperty(String key, String value) {
515         properties.setProperty(key, decrypt(value));
516     }
517
518     /**
519      * @see java.lang.Object#toString()
520      */
521     @SuppressWarnings("nls")
522     @Override
523     public String toString() {
524         return String.format("Configuration: %d properties, keys:[%s]",
525                 properties.size(), properties.keySet().toString());
526     }
527
528     /**
529      * This is a helper method to read the manifest of the jar file that this class was loaded from. Note that this will
530      * only work if the code is packaged in a jar file. If it is an open deployment, such as under eclipse, this will
531      * not work and there is code added to detect that case.
532      *
533      * @return The manifest object from the jar file, or null if the code is not packaged in a jar file.
534      */
535     @SuppressWarnings({
536             "unused", "nls"
537     })
538     private Manifest getManifest() {
539         ProtectionDomain domain = getClass().getProtectionDomain();
540         CodeSource source = domain.getCodeSource();
541         URL location = source.getLocation();
542         String path = location.getPath();
543         int index = path.indexOf('!');
544         if (index != -1) {
545             path = path.substring(0, index);
546         }
547         if (path.endsWith(".jar")) {
548             try (JarFile jar = new JarFile(location.getFile())) {
549                 return jar.getManifest();
550             } catch (IOException e) {
551                 logger.error("getManifest", e);
552             }
553         }
554
555         return null;
556     }
557 }