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