blob: aca2517447f6ef013f7df3264918ba52a616be11 [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.nlpcraft.common.config
import com.typesafe.config.{Config, ConfigFactory}
import com.typesafe.scalalogging.LazyLogging
import org.apache.nlpcraft.common.NCE
import scala.collection.JavaConverters._
/**
* Mixin for configuration factory based on https://github.com/lightbend/config.
*/
trait NCConfigurable {
import NCConfigurable._
// Accessor to the loaded config. It should reload config.
private def hocon: Config = cfg
/**
*
* @param name Full configuration property path (name).
*/
private def checkMandatory(name: String): Unit =
if (!hocon.hasPath(name))
throw new NCE(s"Mandatory configuration property not found: $name")
/**
* Gets mandatory configuration property.
*
* @param name Full configuration property path (name).
*/
def getInt(name: String): Int = {
checkMandatory(name)
hocon.getInt(name)
}
/**
* Gets mandatory configuration property.
*
* @param name Full configuration property path (name).
*/
def getBool(name: String): Boolean = {
checkMandatory(name)
hocon.getBoolean(name)
}
/**
* Gets optional configuration property.
*
* @param name Full configuration property path (name).
*/
def getBoolOpt(name: String): Option[Boolean] =
if (!hocon.hasPath(name)) None else Some(hocon.getBoolean(name))
/**
* Gets mandatory configuration property.
*
* @param name Full configuration property path (name).
*/
def getLong(name: String): Long = {
checkMandatory(name)
hocon.getLong(name)
}
/**
* Gets mandatory configuration property.
*
* @param name Full configuration property path (name).
*/
def getConfig(name: String): Config = {
checkMandatory(name)
hocon.getConfig(name)
}
/**
* Gets optional configuration property.
*
* @param name Full configuration property path (name).
*/
def getConfigOpt(name: String): Option[Config] =
if (!hocon.hasPath(name)) None else Some(hocon.getConfig(name))
/**
* Gets mandatory configuration property.
*
* @param name Full configuration property path (name).
*/
def getLongList(name: String): java.util.List[java.lang.Long] = {
checkMandatory(name)
hocon.getLongList(name)
}
/**
* Gets mandatory configuration property in `host:port` format.
*
* @param name Full configuration property path (name).
*/
def getHostPort(name: String): (String, Integer) = {
checkMandatory(name)
val ep = getString(name)
val i = ep.indexOf(':')
if (i <= 0)
throw new NCE(s"Invalid 'host:port' endpoint configuration property format [" +
s"name=$name, " +
s"endpoint=$ep" +
s"]")
try
ep.substring(0, i) → ep.substring(i + 1).toInt
catch {
case _: NumberFormatException
throw new NCE(s"Invalid 'host:port' endpoint configuration property port [" +
s"name=$name, " +
s"endpoint=$ep" +
s"]")
}
}
/**
* Gets optional configuration property in `host:port` format.
*
* @param name Full configuration property path (name).
*/
def getHostPortOpt(name: String): Option[(String, Integer)] =
if (hocon.hasPath(name)) Some(getHostPort(name)) else None
/**
* Gets optional configuration property in `host:port` format.
*
* @param name Full configuration property path (name).
* @param dfltHost Default host value.
* @param dfltPort Default port value.
*/
def getHostPortOrElse(name: String, dfltHost: String, dfltPort: Int): (String, Integer) =
getHostPortOpt(name).getOrElse(dfltHost → dfltPort)
/**
* Gets mandatory configuration property.
*
* @param name Full configuration property path (name).
*/
def getString(name: String): String = {
checkMandatory(name)
hocon.getString(name)
}
/**
* Gets mandatory configuration property.
*
* @param name Full configuration property path (name).
*/
def getObject[T](name: String, f: String ⇒ T): T = {
checkMandatory(name)
val v = hocon.getString(name)
try
f(v)
catch {
case _: Exception
throw new NCE(s"Configuration property cannot be extracted [" +
s"name=$name, " +
s"value='$v'" +
s"]")
}
}
/**
* Gets mandatory configuration property.
*
* @param name Full configuration property path (name).
*/
def getMap[K, V](name: String): Map[K, V] = {
checkMandatory(name)
try
hocon.getAnyRef(name).asInstanceOf[java.util.Map[K, V]].asScala.toMap
catch {
case e: ClassCastException
throw new NCE(s"Configuration property has unexpected type (expecting 'java.util.Map') [" +
s"name=$name" +
s"]", e)
}
}
/**
* Gets optional configuration property.
*
* @param name Full configuration property path (name).
*/
def getMapOpt[K, V](name: String): Option[Map[K, V]] =
if (!hocon.hasPath(name)) None else Some(getMap(name))
/**
* Gets optional configuration property.
*
* @param name Full configuration property path (name).
*/
def getStringOpt(name: String): Option[String] =
if (!hocon.hasPath(name)) None else Some(hocon.getString(name))
/**
* Gets optional configuration property.
*
* @param name Full configuration property path (name).
*/
def getStringOrElse(name: String, dflt: String): String =
getStringOpt(name).getOrElse(dflt)
/**
* Gets mandatory configuration property.
*
* @param name Full configuration property path (name).
*/
def getStringList(name: String): Seq[String] = {
checkMandatory(name)
hocon.getStringList(name).asScala
}
/**
* Gets optional configuration property.
*
* @param name Full configuration property path (name).
*/
def getStringListOpt(name: String): Option[Seq[String]] =
if (!hocon.hasPath(name)) None else Some(hocon.getStringList(name).asScala)
}
object NCConfigurable extends LazyLogging {
private var cfg: Config = _
/**
* Initializes system-wide configuration singleton with given parameters. All specific implementations
* of `NCConfigurable` trait will reuse this config instance.
* <p>
* Override configuration, if given, will override any other loaded or merged configuration.
* If configuration file name is provided it will be looked up in the current working directory or on the
* classpath (as class loader resource). Default configuration, if provided, will be used as a final
* fallback.
* <p>
* To override configuration from outside of JVM using environment variables:
* 1. Set system variable -Dconfig.override_with_env_vars=true
* 2. Use environment variables in a form of 'CONFIG_FORCE_x_y_z' to override configuration
* property 'x.y.z' from the file.
* <p>
* Examples:
* CONFIG_FORCE_nlpcraft_server_rest_host=localhost
* CONFIG_FORCE_nlpcraft_server_lifecycle.0=org.apache.nlpcraft.server.lifecycle.opencensus.NCStackdriverTraceExporter
* CONFIG_FORCE_nlpcraft_server_lifecycle.1=org.apache.nlpcraft.server.lifecycle.opencensus.NCStackdriverStatsExporter
*
* @param overrideCfg Optional overriding configuration.
* @param cfgFileOpt Optional file name.
* @param dfltCfg Optional default config.
* @param valFun Validation method.
*/
def initialize(
overrideCfg: Option[Config],
cfgFileOpt: Option[String],
dfltCfg: Option[Config],
valFun: ConfigBoolean): Unit = {
var tmpCfg: Config = null
require(cfgFileOpt.isDefined || dfltCfg.isDefined)
// Only default configuration is provided.
if (cfgFileOpt.isEmpty) {
logger.info(s"Using built-in default configuration.")
tmpCfg = ConfigFactory.load(dfltCfg.get)
}
else {
val name = cfgFileOpt.get
logger.info(s"Attempting to load/merge configuration from specified configuration file: $name")
tmpCfg =
if (dfltCfg.isDefined)
ConfigFactory.load(ConfigFactory.
parseFile(new java.io.File(name)).
withFallback(ConfigFactory.parseResources(name)).
withFallback(dfltCfg.get)
)
else
ConfigFactory.load(ConfigFactory.
parseFile(new java.io.File(name)).
withFallback(ConfigFactory.parseResources(name))
)
}
// Validate.
if (!valFun(tmpCfg)) {
logger.error(s"Invalid configuration.")
logger.error(s"Note that you can use environment variable to provide configuration properties - see https://nlpcraft.apache.org/server-and-probe.html.")
throw new NCE(s"No valid configuration found in: ${tmpCfg.origin().description()}")
}
else {
if (overrideCfg.isDefined)
cfg = ConfigFactory.load(overrideCfg.get).withFallback(tmpCfg)
else
cfg = ConfigFactory.load(tmpCfg)
val lines = cfg.origin().description().split(",").drop(1).distinct
logger.info(s"NLPCraft configuration successfully loaded as a merge of: ${lines.mkString("\n + ", "\n + ", "")}")
}
// Set parsed configuration into Java shim.
NCConfigurableJava.setConfig(cfg)
}
}