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