diff options
Diffstat (limited to 'appc-common/src/main/java/org/openecomp/appc/configuration/ConfigurationFactory.java')
-rw-r--r-- | appc-common/src/main/java/org/openecomp/appc/configuration/ConfigurationFactory.java | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/appc-common/src/main/java/org/openecomp/appc/configuration/ConfigurationFactory.java b/appc-common/src/main/java/org/openecomp/appc/configuration/ConfigurationFactory.java new file mode 100644 index 000000000..bbfd90de1 --- /dev/null +++ b/appc-common/src/main/java/org/openecomp/appc/configuration/ConfigurationFactory.java @@ -0,0 +1,416 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.configuration; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.DateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Properties; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +import org.openecomp.appc.i18n.Msg; +import com.att.eelf.configuration.EELFLogger; +import com.att.eelf.configuration.EELFManager; +import com.att.eelf.i18n.EELFResourceManager; + +/** + * The configuration factory is used to obtain access to an already created and initialized singleton configuration + * object as well as to create and initialize the singleton if not already set up. + * <p> + * This class is responsible for the creation of the configuration object used to manage the configuration of the + * application. The configuration object implementation must implement the <code>Configuration</code> interface. This + * allows for the factory to create different specializations in the future if needed and not break any application + * code. + * </p> + * <p> + * The configuration object is basically a wrapper around a properties object. The configuration is therefore specified + * as a set of properties that are loaded and processed from different sources with different precedences. It is + * important that the configuration object always be able to supply default values for any configuration properties that + * must be supplied, and not rely on the user always supplying these values. This also relieves the application itself + * from having to interpret missing or invalid properties and applying defaults. By having all of the defaults in one + * place, the application code can be simpler (not having to worry about defaults or invalid properties), and the + * defaults can be changed much easier (they are all in one place and not distributed throughout the codebase). + * </p> + * <p> + * Since the configuration is managed as a property object, we can use a characteristic of the <code>Properties</code> + * class to our advantage. Namely, if we put a property into a <code>Properties</code> object that already exists, the + * <code>Properties</code> object replaces it with the new value. This does not affect any other properties that may + * already be defined in the properties object. This gives us the ability to initialize the properties with default + * values for all of the application settings, then override just those that we need to override, possibly from multiple + * sources and in increasing order of precedence. + * </p> + * <p> + * This means that properties are in effect "merged" together from multiple sources in a prescribed precedence order. In + * fact, the precedence order that this factory implements is defined as: + * </p> + * <ol> + * <li>Default values from a system resource file.</li> + * <li>User-supplied properties file, if any.</li> + * <li>Application-supplied properties, if any.</li> + * <li>Command-line properties (if any)</li> + * </ol> + * <p> + * The name and location of the properties file that is loaded can also be set, either in the defaults, overridden by + * the system command line via -D, or as a system environment variable. There are two properties that can be specified + * to define the name and path. These are: + * </p> + * <dl> + * <dt>org.openecomp.appc.bootstrap.file</dt> + * <dd>This property defines the name of the file that will be loaded. If not specified, the default value is + * "appc.properties". This can be specified in either (or both) the default properties or the command line. The command + * line specification will always override.</dd> + * <dt>org.openecomp.appc.bootstrap.path</dt> + * <dd>This is a comma-delimited (,) path of directories to be searched to locate the specified file. The first + * occurrence of the file is the one loaded, and no additional searching is performed. The path can be specified in + * either, or both, the default values and the command line specification. If specified on the command line, the value + * overrides the default values. If omitted, the default path is <code>$/opt/openecomp/appc/data/properties,${user.home},.</code></dd> + * </dl> + * + * @since Mar 18, 2014 + * @version $Id$ + */ +public final class ConfigurationFactory { + + private static final EELFLogger logger = EELFManager.getInstance().getApplicationLogger(); + + /** + * This is a string constant for the comma character. It's intended to be used a common string delimiter. + */ + private static final String COMMA = ","; + + /** + * The default Configuration object that implements the <code>Configuration</code> interface and represents our + * system configuration settings. + */ + private static DefaultConfiguration config = null; + + /** + * The default properties resource to be loaded + */ + private static final String DEFAULT_PROPERTIES = "org/openecomp/appc/default.properties"; + + /** + * This collection allows for special configurations to be created and maintained, organized by some identification + * (such as an object reference to the StackBuilder to which they apply), and then obtained from the configuration + * factory when needed. + */ + private static HashMap<Object, Configuration> localConfigs = new HashMap<Object, Configuration>(); + + /** + * The reentrant shared lock used to serialize access to the properties. + */ + private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + /** + * This is a constant array of special property names that will be copied from the configuration back to the System + * properties object if they are defined in the configuration AND they do not already exist in the System properties + * object. These are intended as a convenience for setting the AFT properties for the Discovery client where it may + * be difficult or impossible to set VM arguments for the container. + */ + private static final String[] specialProperties = { + "AFT_LATITUDE", "AFT_LONGITUDE", "AFT_ENVIRONMENT", "SCLD_PLATFORM" + }; + + private ConfigurationFactory() { + } + + /** + * This method is used to obtain the common configuration object (as well as set it up if not already). + * + * @return The configuration object implementation + */ + public static Configuration getConfiguration() { + + /* + * First, attempt to access the properties as a read lock holder + */ + ReadLock readLock = lock.readLock(); + readLock.lock(); + try { + + /* + * If the properties don't exist, release the read lock and acquire the write lock. Once we get the write + * lock, we need to re-check to see that the configuration needs to be set up (because another thread may + * have beat us to it). After we get a configuration set up, release the write lock and re-obtain the read + * lock to access the properties. + */ + if (config == null) { + readLock.unlock(); + WriteLock writeLock = lock.writeLock(); + writeLock.lock(); + try { + if (config == null) { + config = new DefaultConfiguration(); + initialize(null); + } + } catch (Exception t) { + logger.error("getConfiguration", t); + } finally { + writeLock.unlock(); + } + readLock.lock(); + } + return config; + } finally { + readLock.unlock(); + } + } + + /** + * This method will obtain the local configuration for the specified object if it exists, or will create it from the + * current global configuration. This allows the configuration to be tailored for a specific process or operation, + * and uniquely identified by some value (such as the object that represents the special use of the configuration). + * + * @param owner + * The owner or identification of the owner of the special configuration + * @return The special configuration object, or a clone of the global configuration so that it can be altered if + * needed. + */ + public static Configuration getConfiguration(final Object owner) { + ReadLock readLock = lock.readLock(); + readLock.lock(); + try { + DefaultConfiguration local = (DefaultConfiguration) localConfigs.get(owner); + if (local == null) { + readLock.unlock(); + WriteLock writeLock = lock.writeLock(); + writeLock.lock(); + try { + local = (DefaultConfiguration) localConfigs.get(owner); + if (local == null) { + DefaultConfiguration global = (DefaultConfiguration) getConfiguration(); + try { + local = (DefaultConfiguration) global.clone(); + } catch (CloneNotSupportedException e) { + logger.error("getConfiguration", e); + } + localConfigs.put(owner, local); + } + } finally { + writeLock.unlock(); + } + readLock.lock(); + } + return local; + } finally { + readLock.unlock(); + } + } + + /** + * This method allows the caller to alter the configuration, supplying the specified configuration properties which + * override the application default values. + * <p> + * The configuration is re-constructed (if already constructed) or created new (if not already created) and the + * default properties are loaded into the configuration. + * </p> + * <p> + * The primary purpose of this method is to allow the application configuration properties to be reset or refreshed + * after the application has already been initialized. This method will lock the configuration for the duration + * while it is being re-built, and should not be called on a regular basis. + * </p> + * + * @param props + * The properties used to configure the application. + * @return Access to the configuration implementation + */ + public static Configuration getConfiguration(final Properties props) { + WriteLock writeLock = lock.writeLock(); + writeLock.lock(); + try { + config = new DefaultConfiguration(); + initialize(props); + return config; + } finally { + writeLock.unlock(); + } + } + + /** + * This method will clear the current configuration and then re-initialize it with the default values, + * application-specific configuration file, user-supplied properties (if any), and then command-line settings. + * <p> + * This method <strong><em>MUST</em></strong> be called holding the configuration lock! + * </p> + * <p> + * This method is a little special in that logging messages generated during the method must be cached and delayed + * until after the logging framework has been initialized. After that, the delayed logging buffer can be dumped to + * the log file and cleared. + * </p> + * + * @param props + * Application-supplied configuration values, if any + */ + private static void initialize(final Properties props) { + DateFormat format = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); + Date now = new Date(); + logger.info("------------------------------------------------------------------------------"); + + logger.info(Msg.CONFIGURATION_STARTED, format.format(now)); + + /* + * Clear any existing properties + */ + config.clear(); + logger.info(Msg.CONFIGURATION_CLEARED); + + /* + * Load the defaults (if any are present) + */ + InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(DEFAULT_PROPERTIES); + if (in != null) { + logger.info(Msg.LOADING_DEFAULTS, DEFAULT_PROPERTIES); + try { + config.setProperties(in); + } finally { + try { + in.close(); + } catch (IOException e) { + // not much we can do since logger may not be configured yet + e.printStackTrace(System.out); + } + } + for (String key : config.getProperties().stringPropertyNames()) { + logger.info(Msg.PROPERTY_VALUE, key, config.getProperty(key)); + } + } else { + logger.info(Msg.NO_DEFAULTS_FOUND, DEFAULT_PROPERTIES); + } + + /* + * Look for application configuration property file. By default, we will look for the file "cdp.properties" on + * the user home path, then on "./etc" (relative to current path), then on "../etc" (relative to current path). + * If we do not find any property file, then we continue. Otherwise, we load the first property file we find and + * then continue. In order to allow default values for the filename and paths to be searched, we first attempt + * to obtain these from our configuration object (which should be primed with default values and/or overridden + * with application-specified values). We then use the values obtained from that to get any user supplied values + * on the command line. + */ + String filename = + config.getProperty(Configuration.PROPERTY_BOOTSTRAP_FILE_NAME, Configuration.DEFAULT_BOOTSTRAP_FILE_NAME); + filename = System.getProperty(Configuration.PROPERTY_BOOTSTRAP_FILE_NAME, filename); + String env = System.getenv(Configuration.PROPERTY_BOOTSTRAP_FILE_NAME); + if (env != null && env.trim().length() > 0) { + filename = env; + } + + String path = + config.getProperty(Configuration.PROPERTY_BOOTSTRAP_FILE_PATH, Configuration.DEFAULT_BOOTSTRAP_FILE_PATH); + path = System.getProperty(Configuration.PROPERTY_BOOTSTRAP_FILE_PATH, path); + env = System.getenv(Configuration.PROPERTY_BOOTSTRAP_FILE_PATH); + if (env != null && env.trim().length() > 0) { + path = env; + } + + logger.info(Msg.SEARCHING_CONFIGURATION_OVERRIDES, path, filename); + + String[] pathElements = path.split(COMMA); + boolean found = false; + for (String pathElement : pathElements) { + File file = new File(pathElement, filename); + if (file.exists() && file.canRead() && !file.isDirectory()) { + + logger.info(Msg.LOADING_CONFIGURATION_OVERRIDES, file.getAbsolutePath()); + Properties fileProperties = new Properties(); + BufferedInputStream stream = null; + try { + stream = new BufferedInputStream(new FileInputStream(file)); + fileProperties.load(stream); + for (String key : fileProperties.stringPropertyNames()) { + logger.debug(Msg.PROPERTY_VALUE, key, fileProperties.getProperty(key)); + config.setProperty(key, fileProperties.getProperty(key)); + } + found = true; + break; + } catch (IOException e) { + logger.error(EELFResourceManager.format(e)); + } finally { + try { + stream.close(); + } catch (IOException e) { + // not much we can do since logger may not be configured + // yet + e.printStackTrace(System.out); + } + } + } + } + + if (!found) { + logger.warn(Msg.NO_OVERRIDE_PROPERTY_FILE_LOADED, filename, path); + } + + /* + * Apply any application-specified properties + */ + if (props != null) { + logger.info(Msg.LOADING_APPLICATION_OVERRIDES); + for (String key : props.stringPropertyNames()) { + logger.debug(Msg.PROPERTY_VALUE, key, props.getProperty(key)); + config.setProperty(key, props.getProperty(key)); + } + } else { + logger.info(Msg.NO_APPLICATION_OVERRIDES); + } + + /* + * Merge in the System.properties to pick-up any command line arguments (-Dkeyword=value) + */ + logger.info(Msg.MERGING_SYSTEM_PROPERTIES); + config.setProperties(System.getProperties()); + + /* + * As a convenience, copy the "specialProperties" that are not defined in System.properties from the + * configuration back to the system properties object. + */ + for (String key : config.getProperties().stringPropertyNames()) { + for (String specialProperty : specialProperties) { + if (key.equals(specialProperty) && !System.getProperties().containsKey(key)) { + System.setProperty(key, config.getProperty(key)); + logger.info(Msg.SETTING_SPECIAL_PROPERTY, key, config.getProperty(key)); + } + } + } + + /* + * Initialize the resource manager by loading the requested bundles, if any are defined. Resource bundles may be + * specified as a comma-delimited list of names. These resource names are base names of resource bundles, do not + * include the language or country code, or the ".properties" extension. The actual loading of the resource + * bundles is done lazily when requested the first time. If the bundle does not exist, or cannot be loaded, it + * is ignored. + */ + String resourcesList = + config.getProperty(Configuration.PROPERTY_RESOURCE_BUNDLES, Configuration.DEFAULT_RESOURCE_BUNDLES); + String[] resources = resourcesList.split(","); + for (String resource : resources) { + logger.info(Msg.LOADING_RESOURCE_BUNDLE, resource.trim()); + EELFResourceManager.loadMessageBundle(resource.trim()); + } + } +} |