blob: 91d5e6577335278f8a4eb071b58eb993f9792fd1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.openwhisk.common
import scala.util.Try
/**
* A set of properties which define a configuration.
*
* This class tries to populate the properties in the following order. Each step may overwrite
* properties defined earlier.
*
* <ol>
* <li> first it uses default values specified in the requiredProperties map if any.
* <li> first it looks in the system environment. If the system environment defines a value for FOO_BAR, this
* will be used as the value for properties foo.bar or FoO.BaR. By convention, we usually define variables as
* all lowercase (foo.bar)
* <li> next it reads the properties from the propertiesFile. In this case it looks for an exact match for "foo.bar" in the
* file
* </ol>
*
* After loading the properties, this validates that all required properties are defined.
*
* @param requiredProperties a Map whose keys define properties that must be bound to
* a value, and whose values are default values. A null value in the Map means there is
* no default value specified.
* @param optionalProperties a Set of optional properties which may or not be defined.
* @param env an optional environment to read from (defaults to sys.env).
*/
class Config(requiredProperties: Map[String, String], optionalProperties: Set[String] = Set.empty)(
env: Map[String, String] = sys.env)(implicit logging: Logging) {
private val settings = getProperties().toMap.filter {
case (k, v) =>
requiredProperties.contains(k) ||
(optionalProperties.contains(k) && v != null)
}
lazy val isValid: Boolean = Config.validateProperties(requiredProperties, settings)
/**
* Gets value for key if it exists else the empty string.
* The value of the override key will instead be returned if its value is present in the map.
*
* @param key to lookup
* @param overrideKey the property whose value will be returned if the map contains the override key.
* @return value for the key or the empty string if the key does not have a value/does not exist
*/
def apply(key: String, overrideKey: String = ""): String = {
Try(settings(overrideKey)).orElse(Try(settings(key))).getOrElse("")
}
/**
* Returns the value of a given key.
*
* @param key the property that has to be returned.
*/
def getProperty(key: String): String = {
this(key)
}
/**
* Returns the value of a given key parsed as a double.
* If parsing fails, return the default value.
*
* @param key the property that has to be returned.
*/
def getAsDouble(key: String, defaultValue: Double): Double = {
Try { getProperty(key).toDouble } getOrElse { defaultValue }
}
/**
* Returns the value of a given key parsed as an integer.
* If parsing fails, return the default value.
*
* @param key the property that has to be returned.
*/
def getAsInt(key: String, defaultValue: Int): Int = {
Try { getProperty(key).toInt } getOrElse { defaultValue }
}
/**
* Returns the value of a given key parsed as a boolean.
* If parsing fails, return the default value.
*
* @param key the property that has to be returned.
*/
def getAsBoolean(key: String, defaultValue: Boolean): Boolean = {
Try(getProperty(key).toBoolean).getOrElse(defaultValue)
}
/**
* Converts the set of property to a string for debugging.
*/
def mkString: String = settings.mkString("\n")
/**
* Loads the properties from the environment into a mutable map.
*
* @return a pair which is the Map defining the properties, and a boolean indicating whether validation succeeded.
*/
protected def getProperties(): scala.collection.mutable.Map[String, String] = {
val required = scala.collection.mutable.Map[String, String]() ++= requiredProperties
Config.readPropertiesFromSystemAndEnv(required, env)
// for optional value, assign them a default from the required properties list
// to prevent loss of a default value on a required property that may not otherwise be defined
val optional = scala.collection.mutable.Map[String, String]() ++= optionalProperties.map { k =>
k -> required.getOrElse(k, null)
}
Config.readPropertiesFromSystemAndEnv(optional, env)
required ++ optional
}
}
/**
* Singleton object which provides global methods to manage configuration.
*/
object Config {
val prefix = "whisk-config."
/**
* Reads a Map of key-value pairs from the environment -- store them in the
* mutable properties object.
*/
def readPropertiesFromSystemAndEnv(properties: scala.collection.mutable.Map[String, String],
env: Map[String, String])(implicit logging: Logging) = {
readPropertiesFromSystem(properties)
for (p <- properties.keys) {
val envp = p.replace('.', '_').toUpperCase
val envv = env.get(envp)
if (envv.isDefined) {
logging.info(this, s"environment set value for $p")
properties += p -> envv.get.trim
}
}
}
def readPropertiesFromSystem(properties: scala.collection.mutable.Map[String, String])(implicit logging: Logging) = {
for (p <- properties.keys) {
val sysv = Option(System.getProperty(prefix + p))
if (sysv.isDefined) {
logging.info(this, s"system set value for $p")
properties += p -> sysv.get.trim
}
}
}
/**
* Checks that the properties object defines all the required properties.
*
* @param required a key-value map where the keys are required properties
* @param properties a set of properties to check
*/
def validateProperties(required: Map[String, String], properties: Map[String, String])(
implicit logging: Logging): Boolean = {
required.keys.forall { key =>
val value = properties(key)
if (value == null) logging.error(this, s"required property $key still not set")
value != null
}
}
}