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