05c132d0f59a7c8ad772d6075b3f02e5271ca1a7
[ccsdk/sli.git] /
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.ccsdk.sli.core.utils.configuration;
26
27 import com.att.eelf.configuration.EELFLogger;
28 import com.att.eelf.configuration.EELFManager;
29 import com.att.eelf.i18n.EELFResourceManager;
30 import java.io.BufferedInputStream;
31 import java.io.File;
32 import java.io.FileInputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.text.DateFormat;
36 import java.util.Date;
37 import java.util.HashMap;
38 import java.util.Optional;
39 import java.util.Properties;
40 import java.util.concurrent.locks.ReentrantReadWriteLock;
41 import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
42 import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
43 import org.apache.commons.lang3.StringUtils;
44 import org.onap.ccsdk.sli.core.utils.logging.LoggingUtils;
45 import org.onap.ccsdk.sli.core.utils.logging.Msg;
46
47 /**
48  * The configuration factory is used to obtain access to an already created and initialized
49  * singleton configuration object as well as to create and initialize the singleton if not already
50  * set up.
51  * <p>
52  * This class is responsible for the creation of the configuration object used to manage the
53  * configuration of the application. The configuration object implementation must implement the
54  * <code>Configuration</code> interface. This allows for the factory to create different
55  * specializations in the future if needed and not break any application code.
56  * </p>
57  * <p>
58  * The configuration object is basically a wrapper around a properties object. The configuration is
59  * therefore specified as a set of properties that are loaded and processed from different sources
60  * with different precedences. It is important that the configuration object always be able to
61  * supply default values for any configuration properties that must be supplied, and not rely on the
62  * user always supplying these values. This also relieves the application itself from having to
63  * interpret missing or invalid properties and applying defaults. By having all of the defaults in
64  * one place, the application code can be simpler (not having to worry about defaults or invalid
65  * properties), and the defaults can be changed much easier (they are all in one place and not
66  * distributed throughout the codebase).
67  * </p>
68  * <p>
69  * Since the configuration is managed as a property object, we can use a characteristic of the
70  * <code>Properties</code> class to our advantage. Namely, if we put a property into a
71  * <code>Properties</code> object that already exists, the <code>Properties</code> object replaces
72  * it with the new value. This does not affect any other properties that may already be defined in
73  * the properties object. This gives us the ability to initialize the properties with default values
74  * for all of the application settings, then override just those that we need to override, possibly
75  * from multiple sources and in increasing order of precedence.
76  * </p>
77  * <p>
78  * This means that properties are in effect "merged" together from multiple sources in a prescribed
79  * precedence order. In fact, the precedence order that this factory implements is defined as:
80  * </p>
81  * <ol>
82  * <li>Default values from a system resource file.</li>
83  * <li>User-supplied properties file, if any.</li>
84  * <li>Application-supplied properties, if any.</li>
85  * <li>Command-line properties (if any)</li>
86  * </ol>
87  * <p>
88  * The name and location of the properties file that is loaded can also be set, either in the
89  * defaults, overridden by the system command line via -D, or as a system environment variable.
90  * There are two properties that can be specified to define the name and path. These are:
91  * </p>
92  * <dl>
93  * <dt>org.onap.appc.bootstrap.file</dt>
94  * <dd>This property defines the name of the file that will be loaded. If not specified, the default
95  * value is "appc.properties". This can be specified in either (or both) the default properties or
96  * the command line. The command line specification will always override.</dd>
97  * <dt>org.onap.appc.bootstrap.path</dt>
98  * <dd>This is a comma-delimited (,) path of directories to be searched to locate the specified
99  * file. The first occurrence of the file is the one loaded, and no additional searching is
100  * performed. The path can be specified in either, or both, the default values and the command line
101  * specification. If specified on the command line, the value overrides the default values. If
102  * omitted, the default path is <code>$/opt/onap/appc/data/properties,${user.home},.</code></dd>
103  * </dl>
104  *
105  * @since Mar 18, 2014
106  * @version $Id$
107  */
108 public final class ConfigurationFactory {
109
110     private static final EELFLogger logger = EELFManager.getInstance().getLogger(ConfigurationFactory.class);
111
112     /**
113      * This is a string constant for the comma character. It's intended to be used a common string
114      * delimiter.
115      */
116     private static final String COMMA = ",";
117
118     /**
119      * The default Configuration object that implements the <code>Configuration</code> interface and
120      * represents our system configuration settings.
121      */
122     private static DefaultConfiguration config = null;
123
124     /**
125      * The default properties resource to be loaded
126      */
127     private static final String DEFAULT_PROPERTIES = "org/onap/appc/default.properties";
128
129     /**
130      * This collection allows for special configurations to be created and maintained, organized by
131      * some identification (such as an object reference to the StackBuilder to which they apply),
132      * and then obtained from the configuration factory when needed.
133      */
134     private static HashMap<Object, Configuration> localConfigs = new HashMap<>();
135
136     /**
137      * The reentrant shared lock used to serialize access to the properties.
138      */
139     private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
140
141     /**
142      * This is a constant array of special property names that will be copied from the configuration
143      * back to the System properties object if they are defined in the configuration AND they do not
144      * already exist in the System properties object. These are intended as a convenience for
145      * setting the AFT properties for the Discovery client where it may be difficult or impossible
146      * to set VM arguments for the container.
147      */
148     private static final String[] specialProperties =
149             {"AFT_LATITUDE", "AFT_LONGITUDE", "AFT_ENVIRONMENT", "SCLD_PLATFORM"};
150
151     private ConfigurationFactory() {}
152
153     /**
154      * This method is used to obtain the common configuration object (as well as set it up if not
155      * already).
156      *
157      * @return The configuration object implementation
158      */
159     public static Configuration getConfiguration() {
160
161         /*
162          * First, attempt to access the properties as a read lock holder
163          */
164         ReadLock readLock = lock.readLock();
165         readLock.lock();
166         try {
167
168             /*
169              * If the properties don't exist, release the read lock and acquire the write lock. Once
170              * we get the write lock, we need to re-check to see that the configuration needs to be
171              * set up (because another thread may have beat us to it). After we get a configuration
172              * set up, release the write lock and re-obtain the read lock to access the properties.
173              */
174             if (config == null) {
175                 readLock.unlock();
176                 WriteLock writeLock = lock.writeLock();
177                 writeLock.lock();
178                 try {
179                     if (config == null) {
180                         config = new DefaultConfiguration();
181                         initialize(null);
182                     }
183                 } catch (Exception e){
184                     logger.error("getConfiguration: " + LoggingUtils.formatException(e));
185                 } finally {
186                     writeLock.unlock();
187                 }
188                 readLock.lock();
189             }
190         } finally {
191             readLock.unlock();
192         }
193         return config;
194     }
195
196     /**
197      * This method will obtain the local configuration for the specified object if it exists, or
198      * will create it from the current global configuration. This allows the configuration to be
199      * tailored for a specific process or operation, and uniquely identified by some value (such as
200      * the object that represents the special use of the configuration).
201      *
202      * @param owner The owner or identification of the owner of the special configuration
203      * @return The special configuration object, or a clone of the global configuration so that it
204      *         can be altered if needed.
205      */
206     public static Configuration getConfiguration(final Object owner) {
207         DefaultConfiguration local;
208         ReadLock readLock = lock.readLock();
209         readLock.lock();
210         try {
211             local = (DefaultConfiguration) localConfigs.get(owner);
212             if (local == null) {
213                 readLock.unlock();
214                 WriteLock writeLock = lock.writeLock();
215                 writeLock.lock();
216                 local = (DefaultConfiguration) localConfigs.get(owner);
217                 if (local == null) {
218                     local = getClonedDefaultConfiguration(owner, local);
219                 }
220                 writeLock.unlock();
221             }
222             readLock.lock();
223         } finally {
224             readLock.unlock();
225         }
226         return local;
227     }
228
229     /**
230      * This method allows the caller to alter the configuration, supplying the specified
231      * configuration properties which override the application default values.
232      * <p>
233      * The configuration is re-constructed (if already constructed) or created new (if not already
234      * created) and the default properties are loaded into the configuration.
235      * </p>
236      * <p>
237      * The primary purpose of this method is to allow the application configuration properties to be
238      * reset or refreshed after the application has already been initialized. This method will lock
239      * the configuration for the duration while it is being re-built, and should not be called on a
240      * regular basis.
241      * </p>
242      *
243      * @param props The properties used to configure the application.
244      * @return Access to the configuration implementation
245      */
246     public static Configuration getConfiguration(final Properties props) {
247         WriteLock writeLock = lock.writeLock();
248         writeLock.lock();
249         try {
250             config = new DefaultConfiguration();
251             initialize(props);
252             return config;
253         } finally {
254             writeLock.unlock();
255         }
256     }
257
258     private static DefaultConfiguration getClonedDefaultConfiguration(Object owner, DefaultConfiguration local) {
259         Optional<DefaultConfiguration> global =
260                 Optional.ofNullable((DefaultConfiguration) getConfiguration());
261         try {
262             if (global.isPresent()) {
263                 local = (DefaultConfiguration) global.get().clone();
264             }
265         } catch (CloneNotSupportedException e) {
266             logger.error("getClonedDefaultConfiguration: " + LoggingUtils.formatException(e));
267         }
268         localConfigs.put(owner, local);
269         return local;
270     }
271
272     /**
273      * This method will clear the current configuration and then re-initialize it with the default
274      * values, application-specific configuration file, user-supplied properties (if any), and then
275      * command-line settings.
276      * <p>
277      * This method <strong><em>MUST</em></strong> be called holding the configuration lock!
278      * </p>
279      * <p>
280      * This method is a little special in that logging messages generated during the method must be
281      * cached and delayed until after the logging framework has been initialized. After that, the
282      * delayed logging buffer can be dumped to the log file and cleared.
283      * </p>
284      *
285      * @param props Application-supplied configuration values, if any
286      */
287     private static void initialize(final Properties props) {
288         DateFormat format = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
289         Date now = new Date();
290         logger.info(
291                 "------------------------------------------------------------------------------");
292
293         logger.info(Msg.CONFIGURATION_STARTED, format.format(now));
294
295         /*
296          * Clear any existing properties
297          */
298         config.clear();
299         logger.info(Msg.CONFIGURATION_CLEARED);
300
301         /*
302          * Load the defaults (if any are present)
303          */
304         InputStream in = Thread.currentThread().getContextClassLoader()
305                 .getResourceAsStream(DEFAULT_PROPERTIES);
306         if (in != null) {
307             logger.info(Msg.LOADING_DEFAULTS, DEFAULT_PROPERTIES);
308             try {
309                 config.setProperties(in);
310             } finally {
311                 try {
312                     in.close();
313                 } catch (IOException e) {
314                     logger.error("Cannot close inputStream: " + LoggingUtils.formatException(e));
315                 }
316             }
317             for (String key : config.getProperties().stringPropertyNames()) {
318
319                 if ((!StringUtils.containsIgnoreCase(key, "pass"))&&(!StringUtils.containsIgnoreCase(key, "secret")))
320                     logger.info(Msg.PROPERTY_VALUE, key, config.getProperty(key));
321                 else
322                     logger.info(Msg.PROPERTY_VALUE, key, "XXXX");
323             }
324         } else {
325             logger.info(Msg.NO_DEFAULTS_FOUND, DEFAULT_PROPERTIES);
326         }
327
328         /*
329          * Look for application configuration property file. By default, we will look for the file
330          * "cdp.properties" on the user home path, then on "./etc" (relative to current path), then
331          * on "../etc" (relative to current path). If we do not find any property file, then we
332          * continue. Otherwise, we load the first property file we find and then continue. In order
333          * to allow default values for the filename and paths to be searched, we first attempt to
334          * obtain these from our configuration object (which should be primed with default values
335          * and/or overridden with application-specified values). We then use the values obtained
336          * from that to get any user supplied values on the command line.
337          */
338         String filename = config.getProperty(Configuration.PROPERTY_BOOTSTRAP_FILE_NAME,
339                 Configuration.DEFAULT_BOOTSTRAP_FILE_NAME);
340         filename = System.getProperty(Configuration.PROPERTY_BOOTSTRAP_FILE_NAME, filename);
341         String env = System.getenv(Configuration.PROPERTY_BOOTSTRAP_FILE_NAME);
342         if (env != null && env.trim().length() > 0) {
343             filename = env;
344         }
345
346         String path = config.getProperty(Configuration.PROPERTY_BOOTSTRAP_FILE_PATH,
347                 Configuration.DEFAULT_BOOTSTRAP_FILE_PATH);
348         path = System.getProperty(Configuration.PROPERTY_BOOTSTRAP_FILE_PATH, path);
349         env = System.getenv(Configuration.PROPERTY_BOOTSTRAP_FILE_PATH);
350         if (env != null && env.trim().length() > 0) {
351             path = env;
352         }
353
354         logger.info(Msg.SEARCHING_CONFIGURATION_OVERRIDES, path, filename);
355
356         String[] pathElements = path.split(COMMA);
357         boolean found = false;
358         for (String pathElement : pathElements) {
359             File file = new File(pathElement, filename);
360             if (file.exists() && file.canRead() && !file.isDirectory()) {
361
362                 logger.info(Msg.LOADING_CONFIGURATION_OVERRIDES, file.getAbsolutePath());
363                 Properties fileProperties = new Properties();
364                 BufferedInputStream stream = null;
365                 try {
366                     stream = new BufferedInputStream(new FileInputStream(file));
367                     fileProperties.load(stream);
368                     for (String key : fileProperties.stringPropertyNames()) {
369                         if ((!StringUtils.containsIgnoreCase(key, "pass"))&&(!StringUtils.containsIgnoreCase(key, "secret")))
370                             logger.debug(Msg.PROPERTY_VALUE, key, fileProperties.getProperty(key));
371                         else
372                             logger.info(Msg.PROPERTY_VALUE, key, "XXXX");
373                         config.setProperty(key, fileProperties.getProperty(key));
374                     }
375                     found = true;
376                     break;
377                 } catch (IOException e) {
378                     logger.error(LoggingUtils.formatException(e));
379                 } finally {
380                     try {
381                         if (stream != null) {
382                             stream.close();
383                         }
384                     } catch (IOException e) {
385                         logger.error("Unable to close stream: " + LoggingUtils.formatException(e));
386                     }
387                 }
388             }
389         }
390
391         if (!found) {
392             logger.warn(Msg.NO_OVERRIDE_PROPERTY_FILE_LOADED, filename, path);
393         }
394
395         /*
396          * Apply any application-specified properties
397          */
398         if (props != null) {
399             logger.info(Msg.LOADING_APPLICATION_OVERRIDES);
400             for (String key : props.stringPropertyNames()) {
401                 if ((!StringUtils.containsIgnoreCase(key, "pass"))&&(!StringUtils.containsIgnoreCase(key, "secret")))
402                     logger.debug(Msg.PROPERTY_VALUE, key, props.getProperty(key));
403                 else
404                     logger.info(Msg.PROPERTY_VALUE, key, "XXXX");
405                 config.setProperty(key, props.getProperty(key));
406             }
407         } else {
408             logger.info(Msg.NO_APPLICATION_OVERRIDES);
409         }
410
411         /*
412          * Merge in the System.properties to pick-up any command line arguments (-Dkeyword=value)
413          */
414         logger.info(Msg.MERGING_SYSTEM_PROPERTIES);
415         config.setProperties(System.getProperties());
416
417         /*
418          * As a convenience, copy the "specialProperties" that are not defined in System.properties
419          * from the configuration back to the system properties object.
420          */
421         for (String key : config.getProperties().stringPropertyNames()) {
422             for (String specialProperty : specialProperties) {
423                 if (key.equals(specialProperty) && !System.getProperties().containsKey(key)) {
424                     System.setProperty(key, config.getProperty(key));
425                     logger.info(Msg.SETTING_SPECIAL_PROPERTY, key, config.getProperty(key));
426                 }
427             }
428         }
429
430         /*
431          * Initialize the resource manager by loading the requested bundles, if any are defined.
432          * Resource bundles may be specified as a comma-delimited list of names. These resource
433          * names are base names of resource bundles, do not include the language or country code, or
434          * the ".properties" extension. The actual loading of the resource bundles is done lazily
435          * when requested the first time. If the bundle does not exist, or cannot be loaded, it is
436          * ignored.
437          */
438         String resourcesList = config.getProperty(Configuration.PROPERTY_RESOURCE_BUNDLES,
439                 Configuration.DEFAULT_RESOURCE_BUNDLES);
440         String[] resources = resourcesList.split(",");
441         for (String resource : resources) {
442             logger.info(Msg.LOADING_RESOURCE_BUNDLE, resource.trim());
443             EELFResourceManager.loadMessageBundle(resource.trim());
444         }
445     }
446 }