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