Reduce config footprint
This commit is contained in:
parent
06cc030847
commit
dbe9e073bf
11 changed files with 201 additions and 1292 deletions
|
|
@ -1,346 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Scalable Solutions AB <http://scalablesolutions.se>
|
|
||||||
*
|
|
||||||
* Based on Configgy by Robey Pointer.
|
|
||||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.config
|
|
||||||
|
|
||||||
import java.util.regex.Pattern
|
|
||||||
import scala.collection.{immutable, mutable, Map}
|
|
||||||
import scala.util.Sorting
|
|
||||||
import akka.config.string._
|
|
||||||
|
|
||||||
|
|
||||||
private[config] abstract class Cell
|
|
||||||
private[config] case class StringCell(value: String) extends Cell
|
|
||||||
private[config] case class AttributesCell(attr: Attributes) extends Cell
|
|
||||||
private[config] case class StringListCell(array: Array[String]) extends Cell
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actual implementation of ConfigMap.
|
|
||||||
* Stores items in Cell objects, and handles interpolation and key recursion.
|
|
||||||
*/
|
|
||||||
private[config] class Attributes(val config: Configuration, val name: String) extends ConfigMap {
|
|
||||||
|
|
||||||
private val cells = new mutable.HashMap[String, Cell]
|
|
||||||
var inheritFrom: Option[ConfigMap] = None
|
|
||||||
|
|
||||||
def this(config: Configuration, name: String, copyFrom: ConfigMap) = {
|
|
||||||
this(config, name)
|
|
||||||
copyFrom.copyInto(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
def keys: Iterator[String] = cells.keysIterator
|
|
||||||
|
|
||||||
def getName() = name
|
|
||||||
|
|
||||||
override def toString() = {
|
|
||||||
val buffer = new StringBuilder("{")
|
|
||||||
buffer ++= name
|
|
||||||
buffer ++= (inheritFrom match {
|
|
||||||
case Some(a: Attributes) => " (inherit=" + a.name + ")"
|
|
||||||
case None => ""
|
|
||||||
})
|
|
||||||
buffer ++= ": "
|
|
||||||
for (key <- sortedKeys) {
|
|
||||||
buffer ++= key
|
|
||||||
buffer ++= "="
|
|
||||||
buffer ++= (cells(key) match {
|
|
||||||
case StringCell(x) => "\"" + x.quoteC + "\""
|
|
||||||
case AttributesCell(x) => x.toString
|
|
||||||
case StringListCell(x) => x.mkString("[", ",", "]")
|
|
||||||
})
|
|
||||||
buffer ++= " "
|
|
||||||
}
|
|
||||||
buffer ++= "}"
|
|
||||||
buffer.toString
|
|
||||||
}
|
|
||||||
|
|
||||||
override def equals(obj: Any) = {
|
|
||||||
if (! obj.isInstanceOf[Attributes]) {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
val other = obj.asInstanceOf[Attributes]
|
|
||||||
(other.sortedKeys.toList == sortedKeys.toList) &&
|
|
||||||
(cells.keys forall (k => { cells(k) == other.cells(k) }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up a value cell for a given key. If the key is compound (ie,
|
|
||||||
* "abc.xyz"), look up the first segment, and if it refers to an inner
|
|
||||||
* Attributes object, recursively look up that cell. If it's not an
|
|
||||||
* Attributes or it doesn't exist, return None. For a non-compound key,
|
|
||||||
* return the cell if it exists, or None if it doesn't.
|
|
||||||
*/
|
|
||||||
private def lookupCell(key: String): Option[Cell] = {
|
|
||||||
val elems = key.split("\\.", 2)
|
|
||||||
if (elems.length > 1) {
|
|
||||||
cells.get(elems(0)) match {
|
|
||||||
case Some(AttributesCell(x)) => x.lookupCell(elems(1))
|
|
||||||
case None => inheritFrom match {
|
|
||||||
case Some(a: Attributes) =>
|
|
||||||
a.lookupCell(key)
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cells.get(elems(0)) match {
|
|
||||||
case x @ Some(_) => x
|
|
||||||
case None => inheritFrom match {
|
|
||||||
case Some(a: Attributes) => a.lookupCell(key)
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if a key is compound (and requires recursion), and if so,
|
|
||||||
* return the nested Attributes block and simple key that can be used to
|
|
||||||
* make a recursive call. If the key is simple, return None.
|
|
||||||
*
|
|
||||||
* If the key is compound, but nested Attributes objects don't exist
|
|
||||||
* that match the key, an attempt will be made to create the nested
|
|
||||||
* Attributes objects. If one of the key segments already refers to an
|
|
||||||
* attribute that isn't a nested Attribute object, a ConfigException
|
|
||||||
* will be thrown.
|
|
||||||
*
|
|
||||||
* For example, for the key "a.b.c", the Attributes object for "a.b"
|
|
||||||
* and the key "c" will be returned, creating the "a.b" Attributes object
|
|
||||||
* if necessary. If "a" or "a.b" exists but isn't a nested Attributes
|
|
||||||
* object, then an ConfigException will be thrown.
|
|
||||||
*/
|
|
||||||
@throws(classOf[ConfigException])
|
|
||||||
private def recurse(key: String): Option[(Attributes, String)] = {
|
|
||||||
val elems = key.split("\\.", 2)
|
|
||||||
if (elems.length > 1) {
|
|
||||||
val attr = (cells.get(elems(0)) match {
|
|
||||||
case Some(AttributesCell(x)) => x
|
|
||||||
case Some(_) => throw new ConfigException("Illegal key " + key)
|
|
||||||
case None => createNested(elems(0))
|
|
||||||
})
|
|
||||||
attr.recurse(elems(1)) match {
|
|
||||||
case ret @ Some((a, b)) => ret
|
|
||||||
case None => Some((attr, elems(1)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def replaceWith(newAttributes: Attributes): Unit = {
|
|
||||||
// stash away subnodes and reinsert them.
|
|
||||||
val subnodes = for ((key, cell @ AttributesCell(_)) <- cells.toList) yield (key, cell)
|
|
||||||
cells.clear
|
|
||||||
cells ++= newAttributes.cells
|
|
||||||
for ((key, cell) <- subnodes) {
|
|
||||||
newAttributes.cells.get(key) match {
|
|
||||||
case Some(AttributesCell(newattr)) =>
|
|
||||||
cell.attr.replaceWith(newattr)
|
|
||||||
cells(key) = cell
|
|
||||||
case None =>
|
|
||||||
cell.attr.replaceWith(new Attributes(config, ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def createNested(key: String): Attributes = {
|
|
||||||
val attr = new Attributes(config, if (name.equals("")) key else (name + "." + key))
|
|
||||||
cells(key) = new AttributesCell(attr)
|
|
||||||
attr
|
|
||||||
}
|
|
||||||
|
|
||||||
def getString(key: String): Option[String] = {
|
|
||||||
lookupCell(key) match {
|
|
||||||
case Some(StringCell(x)) => Some(x)
|
|
||||||
case Some(StringListCell(x)) => Some(x.toList.mkString("[", ",", "]"))
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getConfigMap(key: String): Option[ConfigMap] = {
|
|
||||||
lookupCell(key) match {
|
|
||||||
case Some(AttributesCell(x)) => Some(x)
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def configMap(key: String): ConfigMap = makeAttributes(key, true)
|
|
||||||
|
|
||||||
private[config] def makeAttributes(key: String): Attributes = makeAttributes(key, false)
|
|
||||||
|
|
||||||
private[config] def makeAttributes(key: String, withInherit: Boolean): Attributes = {
|
|
||||||
if (key == "") {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
recurse(key) match {
|
|
||||||
case Some((attr, name)) =>
|
|
||||||
attr.makeAttributes(name, withInherit)
|
|
||||||
case None =>
|
|
||||||
val cell = if (withInherit) lookupCell(key) else cells.get(key)
|
|
||||||
cell match {
|
|
||||||
case Some(AttributesCell(x)) => x
|
|
||||||
case Some(_) => throw new ConfigException("Illegal key " + key)
|
|
||||||
case None => createNested(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getList(key: String): Seq[String] = {
|
|
||||||
lookupCell(key) match {
|
|
||||||
case Some(StringListCell(x)) => x
|
|
||||||
case Some(StringCell(x)) => Array[String](x)
|
|
||||||
case _ => Array[String]()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def setString(key: String, value: String): Unit = {
|
|
||||||
recurse(key) match {
|
|
||||||
case Some((attr, name)) => attr.setString(name, value)
|
|
||||||
case None => cells.get(key) match {
|
|
||||||
case Some(AttributesCell(_)) => throw new ConfigException("Illegal key " + key)
|
|
||||||
case _ => cells.put(key, new StringCell(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def setList(key: String, value: Seq[String]): Unit = {
|
|
||||||
recurse(key) match {
|
|
||||||
case Some((attr, name)) => attr.setList(name, value)
|
|
||||||
case None => cells.get(key) match {
|
|
||||||
case Some(AttributesCell(_)) => throw new ConfigException("Illegal key " + key)
|
|
||||||
case _ => cells.put(key, new StringListCell(value.toArray))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def setConfigMap(key: String, value: ConfigMap): Unit = {
|
|
||||||
recurse(key) match {
|
|
||||||
case Some((attr, name)) => attr.setConfigMap(name, value)
|
|
||||||
case None =>
|
|
||||||
val subName = if (name == "") key else (name + "." + key)
|
|
||||||
cells.get(key) match {
|
|
||||||
case Some(AttributesCell(_)) =>
|
|
||||||
cells.put(key, new AttributesCell(new Attributes(config, subName, value)))
|
|
||||||
case None =>
|
|
||||||
cells.put(key, new AttributesCell(new Attributes(config, subName, value)))
|
|
||||||
case _ =>
|
|
||||||
throw new ConfigException("Illegal key " + key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def contains(key: String): Boolean = {
|
|
||||||
recurse(key) match {
|
|
||||||
case Some((attr, name)) => attr.contains(name)
|
|
||||||
case None => cells.contains(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def remove(key: String): Boolean = {
|
|
||||||
recurse(key) match {
|
|
||||||
case Some((attr, name)) => attr.remove(name)
|
|
||||||
case None => {
|
|
||||||
cells.removeKey(key) match {
|
|
||||||
case Some(_) => true
|
|
||||||
case None => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def asMap: Map[String, String] = {
|
|
||||||
var ret = immutable.Map.empty[String, String]
|
|
||||||
for ((key, value) <- cells) {
|
|
||||||
value match {
|
|
||||||
case StringCell(x) => ret = ret.update(key, x)
|
|
||||||
case StringListCell(x) => ret = ret.update(key, x.mkString("[", ",", "]"))
|
|
||||||
case AttributesCell(x) =>
|
|
||||||
for ((k, v) <- x.asMap) {
|
|
||||||
ret = ret.update(key + "." + k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
def toConfigString: String = {
|
|
||||||
toConfigList().mkString("", "\n", "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
private def toConfigList(): List[String] = {
|
|
||||||
val buffer = new mutable.ListBuffer[String]
|
|
||||||
for (key <- Sorting.stableSort(cells.keys.toList)) {
|
|
||||||
cells(key) match {
|
|
||||||
case StringCell(x) =>
|
|
||||||
buffer += (key + " = \"" + x.quoteC + "\"")
|
|
||||||
case StringListCell(x) =>
|
|
||||||
buffer += (key + " = [")
|
|
||||||
buffer ++= x.map { " \"" + _.quoteC + "\"," }
|
|
||||||
buffer += "]"
|
|
||||||
case AttributesCell(node) =>
|
|
||||||
buffer += (key + node.inheritFrom.map { " (inherit=\"" + _.asInstanceOf[Attributes].name + "\")" }.getOrElse("") + " {")
|
|
||||||
buffer ++= node.toConfigList().map { " " + _ }
|
|
||||||
buffer += "}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.toList
|
|
||||||
}
|
|
||||||
|
|
||||||
// substitute "$(...)" strings with looked-up vars
|
|
||||||
// (and find "\$" and replace them with "$")
|
|
||||||
private val INTERPOLATE_RE = """(?<!\\)\$\((\w[\w\d\._-]*)\)|\\\$""".r
|
|
||||||
|
|
||||||
protected[config] def interpolate(root: Attributes, s: String): String = {
|
|
||||||
def lookup(key: String, path: List[ConfigMap]): String = {
|
|
||||||
path match {
|
|
||||||
case Nil => ""
|
|
||||||
case attr :: xs => attr.getString(key) match {
|
|
||||||
case Some(x) => x
|
|
||||||
case None => lookup(key, xs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.regexSub(INTERPOLATE_RE) { m =>
|
|
||||||
if (m.matched == "\\$") {
|
|
||||||
"$"
|
|
||||||
} else {
|
|
||||||
lookup(m.group(1), List(this, root, EnvironmentAttributes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected[config] def interpolate(key: String, s: String): String = {
|
|
||||||
recurse(key) match {
|
|
||||||
case Some((attr, name)) => attr.interpolate(this, s)
|
|
||||||
case None => interpolate(this, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a deep copy of the Attributes tree.
|
|
||||||
def copy(): Attributes = {
|
|
||||||
copyInto(new Attributes(config, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
def copyInto[T <: ConfigMap](attr: T): T = {
|
|
||||||
inheritFrom match {
|
|
||||||
case Some(a: Attributes) => a.copyInto(attr)
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
for ((key, value) <- cells.elements) {
|
|
||||||
value match {
|
|
||||||
case StringCell(x) => attr(key) = x
|
|
||||||
case StringListCell(x) => attr(key) = x
|
|
||||||
case AttributesCell(x) => attr.setConfigMap(key, x.copy())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
attr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -52,64 +52,38 @@ object Config {
|
||||||
(envConf orElse systemConf).map("akka." + _ + ".conf").getOrElse("akka.conf")
|
(envConf orElse systemConf).map("akka." + _ + ".conf").getOrElse("akka.conf")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System.getProperty("akka.config", "") != "") {
|
try {
|
||||||
val configFile = System.getProperty("akka.config", "")
|
if (System.getProperty("akka.config", "") != "") {
|
||||||
try {
|
val configFile = System.getProperty("akka.config", "")
|
||||||
Configure.configure(configFile)
|
println("Loading config from -Dakka.config=" + configFile)
|
||||||
println("Config loaded from -Dakka.config=" + configFile)
|
Configuration.fromFile(configFile)
|
||||||
} catch {
|
} else if (getClass.getClassLoader.getResource(confName) ne null) {
|
||||||
case cause: ParseException =>
|
println("Loading config [" + confName + "] from the application classpath.")
|
||||||
val e = new ConfigurationException(
|
Configuration.fromResource(confName, getClass.getClassLoader)
|
||||||
"Config could not be loaded from -Dakka.config=" + configFile +
|
} else if (HOME.isDefined) {
|
||||||
"\n\tdue to: " + cause.toString)
|
|
||||||
EventHandler notifyListeners EventHandler.Error(e, this)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
Configure.config
|
|
||||||
} else if (getClass.getClassLoader.getResource(confName) ne null) {
|
|
||||||
try {
|
|
||||||
Configure.configureFromResource(confName, getClass.getClassLoader)
|
|
||||||
println("Config [" + confName + "] loaded from the application classpath.")
|
|
||||||
} catch {
|
|
||||||
case cause: ParseException =>
|
|
||||||
val e = new ConfigurationException(
|
|
||||||
"Can't load '" + confName + "' config file from application classpath," +
|
|
||||||
"\n\tdue to: " + cause.toString)
|
|
||||||
EventHandler notifyListeners EventHandler.Error(e, this)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
Configure.config
|
|
||||||
} else if (HOME.isDefined) {
|
|
||||||
try {
|
|
||||||
val configFile = HOME.get + "/config/" + confName
|
val configFile = HOME.get + "/config/" + confName
|
||||||
Configure.configure(configFile)
|
println("AKKA_HOME is defined as [" + HOME.get + "], loading config from [" + configFile + "].")
|
||||||
|
Configuration.fromFile(configFile)
|
||||||
|
} else {
|
||||||
println(
|
println(
|
||||||
"AKKA_HOME is defined as [" + HOME.getOrElse(throwNoAkkaHomeException) +
|
"\nCan't load '" + confName + "'." +
|
||||||
"], config loaded from [" + configFile + "].")
|
"\nOne of the three ways of locating the '" + confName + "' file needs to be defined:" +
|
||||||
} catch {
|
"\n\t1. Define the '-Dakka.config=...' system property option." +
|
||||||
case cause: ParseException =>
|
"\n\t2. Put the '" + confName + "' file on the classpath." +
|
||||||
val e = throw new ConfigurationException(
|
"\n\t3. Define 'AKKA_HOME' environment variable pointing to the root of the Akka distribution." +
|
||||||
"AKKA_HOME is defined as [" + HOME.get + "] " +
|
"\nI have no way of finding the '" + confName + "' configuration file." +
|
||||||
"\n\tbut the 'akka.conf' config file can not be found at [" + HOME.get + "/config/"+ confName + "]," +
|
"\nUsing default values everywhere.")
|
||||||
"\n\tdue to: " + cause.toString)
|
Configuration.fromString("<akka></akka>") // default empty config
|
||||||
EventHandler notifyListeners EventHandler.Error(e, this)
|
|
||||||
throw e
|
|
||||||
}
|
}
|
||||||
Configure.config
|
} catch {
|
||||||
} else {
|
case e =>
|
||||||
println(
|
EventHandler notifyListeners EventHandler.Error(e, this)
|
||||||
"\nCan't load '" + confName + "'." +
|
throw e
|
||||||
"\nOne of the three ways of locating the '" + confName + "' file needs to be defined:" +
|
|
||||||
"\n\t1. Define the '-Dakka.config=...' system property option." +
|
|
||||||
"\n\t2. Put the '" + confName + "' file on the classpath." +
|
|
||||||
"\n\t3. Define 'AKKA_HOME' environment variable pointing to the root of the Akka distribution." +
|
|
||||||
"\nI have no way of finding the '" + confName + "' configuration file." +
|
|
||||||
"\nUsing default values everywhere.")
|
|
||||||
Configuration.fromString("<akka></akka>") // default empty config
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val CONFIG_VERSION = config.getString("akka.version", VERSION)
|
val CONFIG_VERSION = config.getString("akka.version", VERSION)
|
||||||
|
|
||||||
if (VERSION != CONFIG_VERSION) throw new ConfigurationException(
|
if (VERSION != CONFIG_VERSION) throw new ConfigurationException(
|
||||||
"Akka JAR version [" + VERSION + "] is different than the provided config version [" + CONFIG_VERSION + "]")
|
"Akka JAR version [" + VERSION + "] is different than the provided config version [" + CONFIG_VERSION + "]")
|
||||||
|
|
||||||
|
|
@ -117,9 +91,4 @@ object Config {
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis
|
val startTime = System.currentTimeMillis
|
||||||
def uptime = (System.currentTimeMillis - startTime) / 1000
|
def uptime = (System.currentTimeMillis - startTime) / 1000
|
||||||
|
|
||||||
def throwNoAkkaHomeException = throw new ConfigurationException(
|
|
||||||
"Akka home is not defined. Either:" +
|
|
||||||
"\n\t1. Define 'AKKA_HOME' environment variable pointing to the root of the Akka distribution." +
|
|
||||||
"\n\t2. Add the '-Dakka.home=...' option pointing to the root of the Akka distribution.")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,369 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Scalable Solutions AB <http://scalablesolutions.se>
|
|
||||||
*
|
|
||||||
* Based on Configgy by Robey Pointer.
|
|
||||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.config
|
|
||||||
|
|
||||||
import scala.collection.Map
|
|
||||||
import scala.util.Sorting
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigException(reason: String) extends Exception(reason)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract trait for a map of string keys to strings, string lists, or (nested) ConfigMaps.
|
|
||||||
* Integers and booleans may also be stored and retrieved, but they are converted to/from
|
|
||||||
* strings in the process.
|
|
||||||
*/
|
|
||||||
trait ConfigMap {
|
|
||||||
private val TRUE = "true"
|
|
||||||
private val FALSE = "false"
|
|
||||||
|
|
||||||
|
|
||||||
// ----- required methods
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup an entry in this map, and if it exists and can be represented
|
|
||||||
* as a string, return it. Strings will be returned as-is, and string
|
|
||||||
* lists will be returned as a combined string. Nested AttributeMaps
|
|
||||||
* will return None as if there was no entry present.
|
|
||||||
*/
|
|
||||||
def getString(key: String): Option[String]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup an entry in this map, and if it exists and is a nested
|
|
||||||
* ConfigMap, return it. If the entry is a string or string list,
|
|
||||||
* it will return None as if there was no entry present.
|
|
||||||
*/
|
|
||||||
def getConfigMap(key: String): Option[ConfigMap]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup an entry in this map, and if it exists and is a nested
|
|
||||||
* ConfigMap, return it. If not, create an empty map with this name
|
|
||||||
* and return that.
|
|
||||||
*
|
|
||||||
* @throws ConfigException if the key already refers to a string or
|
|
||||||
* string list
|
|
||||||
*/
|
|
||||||
def configMap(key: String): ConfigMap
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup an entry in this map, and if it exists and can be represented
|
|
||||||
* as a string list, return it. String lists will be returned as-is, and
|
|
||||||
* strings will be returned as an array of length 1. If the entry doesn't
|
|
||||||
* exist or is a nested ConfigMap, an empty sequence is returned.
|
|
||||||
*/
|
|
||||||
def getList(key: String): Seq[String]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a key/value pair in this map. If an entry already existed with
|
|
||||||
* that key, it's replaced.
|
|
||||||
*
|
|
||||||
* @throws ConfigException if the key already refers to a nested
|
|
||||||
* ConfigMap
|
|
||||||
*/
|
|
||||||
def setString(key: String, value: String): Unit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a key/value pair in this map. If an entry already existed with
|
|
||||||
* that key, it's replaced.
|
|
||||||
*
|
|
||||||
* @throws ConfigException if the key already refers to a nested
|
|
||||||
* ConfigMap
|
|
||||||
*/
|
|
||||||
def setList(key: String, value: Seq[String]): Unit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put a nested ConfigMap inside this one. If an entry already existed with
|
|
||||||
* that key, it's replaced. The ConfigMap is deep-copied at insert-time.
|
|
||||||
*
|
|
||||||
* @throws ConfigException if the key already refers to a value that isn't
|
|
||||||
* a nested ConfigMap
|
|
||||||
*/
|
|
||||||
def setConfigMap(key: String, value: ConfigMap): Unit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this map contains the given key.
|
|
||||||
*/
|
|
||||||
def contains(key: String): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an entry with the given key, if it exists. Returns true if
|
|
||||||
* an entry was actually removed, false if not.
|
|
||||||
*/
|
|
||||||
def remove(key: String): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an iterator across the keys of this map.
|
|
||||||
*/
|
|
||||||
def keys: Iterator[String]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a new (immutable) map containing a deep copy of all the keys
|
|
||||||
* and values from this AttributeMap. Keys from nested maps will be
|
|
||||||
* compound (like `"inner.name"`).
|
|
||||||
*/
|
|
||||||
def asMap(): Map[String, String]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make a deep copy of this ConfigMap. Any inheritance chains will be
|
|
||||||
* deep-copied, but the inheritance will not be preserved: the copied
|
|
||||||
* ConfigMap stands alone as its own set of objects, reflecting the
|
|
||||||
* frozen state of any inherited ConfigMaps.
|
|
||||||
*/
|
|
||||||
def copy(): ConfigMap
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make this ConfigMap inherit default values from another ConfigMap.
|
|
||||||
* Any attributes that aren't explicitly set will fall back to the inherited
|
|
||||||
* ConfigMap on lookup.
|
|
||||||
*/
|
|
||||||
def inheritFrom_=(config: Option[ConfigMap]): Unit
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return any ConfigMap that is used as a fall back on lookups.
|
|
||||||
*/
|
|
||||||
def inheritFrom: Option[ConfigMap]
|
|
||||||
|
|
||||||
|
|
||||||
// ----- convenience methods
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested key is present, return its value. Otherwise, return
|
|
||||||
* the given default value.
|
|
||||||
*/
|
|
||||||
def getString(key: String, defaultValue: String): String = {
|
|
||||||
getString(key) match {
|
|
||||||
case Some(x) => x
|
|
||||||
case None => defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested key is present and can be converted into an int
|
|
||||||
* (via `String.toInt`), return that int. Otherwise, return `None`.
|
|
||||||
*/
|
|
||||||
def getInt(key: String): Option[Int] = {
|
|
||||||
getString(key) match {
|
|
||||||
case Some(x) => {
|
|
||||||
try {
|
|
||||||
Some(x.toInt)
|
|
||||||
} catch {
|
|
||||||
case _: NumberFormatException => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case None => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested key is present and can be converted into an int
|
|
||||||
* (via `String.toInt`), return that int. Otherwise,
|
|
||||||
* return the given default value.
|
|
||||||
*/
|
|
||||||
def getInt(key: String, defaultValue: Int): Int = {
|
|
||||||
getInt(key) match {
|
|
||||||
case Some(n) => n
|
|
||||||
case None => defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested key is present and can be converted into a long
|
|
||||||
* (via `String.toLong`), return that long. Otherwise, return `None`.
|
|
||||||
*/
|
|
||||||
def getLong(key: String): Option[Long] = {
|
|
||||||
getString(key) match {
|
|
||||||
case Some(x) => {
|
|
||||||
try {
|
|
||||||
Some(x.toLong)
|
|
||||||
} catch {
|
|
||||||
case _: NumberFormatException => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case None => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested key is present and can be converted into a long
|
|
||||||
* (via `String.toLong`), return that long. Otherwise,
|
|
||||||
* return the given default value.
|
|
||||||
*/
|
|
||||||
def getLong(key: String, defaultValue: Long): Long = {
|
|
||||||
getLong(key) match {
|
|
||||||
case Some(n) => n
|
|
||||||
case None => defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested key is present and can be converted into a double
|
|
||||||
* (via `String.toDouble`), return that double. Otherwise, return `None`.
|
|
||||||
*/
|
|
||||||
def getDouble(key: String): Option[Double] = {
|
|
||||||
getString(key) match {
|
|
||||||
case Some(x) => {
|
|
||||||
try {
|
|
||||||
Some(x.toDouble)
|
|
||||||
} catch {
|
|
||||||
case _: NumberFormatException => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case None => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested key is present and can be converted into a double
|
|
||||||
* (via `String.toDouble`), return that double. Otherwise,
|
|
||||||
* return the given default value.
|
|
||||||
*/
|
|
||||||
def getDouble(key: String, defaultValue: Double): Double = {
|
|
||||||
getDouble(key) match {
|
|
||||||
case Some(n) => n
|
|
||||||
case None => defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested key is present and can be converted into a bool
|
|
||||||
* (by being either `"true"` or `"false"`),
|
|
||||||
* return that bool. Otherwise, return `None`.
|
|
||||||
*/
|
|
||||||
def getBool(key: String): Option[Boolean] = {
|
|
||||||
getString(key) match {
|
|
||||||
case Some(x) =>
|
|
||||||
if (x != TRUE && x != FALSE) throw new ConfigException("invalid boolean value")
|
|
||||||
Some(x.equals(TRUE))
|
|
||||||
case None => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested key is present and can be converted into a bool
|
|
||||||
* (by being either `"true"` or `"false"`),
|
|
||||||
* return that bool. Otherwise, return the given default value.
|
|
||||||
*/
|
|
||||||
def getBool(key: String, defaultValue: Boolean): Boolean = {
|
|
||||||
getBool(key) match {
|
|
||||||
case Some(b) => b
|
|
||||||
case None => defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the given key to an int value, by converting it to a string
|
|
||||||
* first.
|
|
||||||
*/
|
|
||||||
def setInt(key: String, value: Int): Unit = setString(key, value.toString)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the given key to a long value, by converting it to a string
|
|
||||||
* first.
|
|
||||||
*/
|
|
||||||
def setLong(key: String, value: Long): Unit = setString(key, value.toString)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the given key to a double value, by converting it to a string
|
|
||||||
* first.
|
|
||||||
*/
|
|
||||||
def setDouble(key: String, value: Double): Unit = setString(key, value.toString)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the given key to a bool value, by converting it to a string
|
|
||||||
* first.
|
|
||||||
*/
|
|
||||||
def setBool(key: String, value: Boolean): Unit = {
|
|
||||||
setString(key, if (value) TRUE else FALSE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the keys of this map, in sorted order.
|
|
||||||
*/
|
|
||||||
def sortedKeys() = {
|
|
||||||
// :( why does this have to be done manually?
|
|
||||||
val keys = this.keys.toList.toArray
|
|
||||||
Sorting.quickSort(keys)
|
|
||||||
keys
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert this ConfigMap into a string which could be written into a config file.
|
|
||||||
*/
|
|
||||||
def toConfigString: String
|
|
||||||
|
|
||||||
def copyInto[T <: ConfigMap](configMap: T): T
|
|
||||||
|
|
||||||
def copyInto(obj: AnyRef) {
|
|
||||||
val cls = obj.getClass
|
|
||||||
//val log = Logger.get(cls)
|
|
||||||
val methods = cls.getMethods().filter { method =>
|
|
||||||
method.getName().endsWith("_$eq") && method.getParameterTypes().size == 1
|
|
||||||
}.toList
|
|
||||||
keys.foreach { key =>
|
|
||||||
val setters = methods.filter { _.getName() == key + "_$eq" }
|
|
||||||
/* if (setters.size == 0) {
|
|
||||||
log.warning("Ignoring config key '%s' which doesn't have a setter in class %s", key, cls)
|
|
||||||
}*/
|
|
||||||
setters.foreach { method =>
|
|
||||||
val expectedType = method.getParameterTypes().first.getCanonicalName
|
|
||||||
val param = expectedType match {
|
|
||||||
case "int" => getInt(key)
|
|
||||||
case "long" => getLong(key)
|
|
||||||
case "float" => getDouble(key).map { _.toFloat }
|
|
||||||
case "double" => getDouble(key)
|
|
||||||
case "boolean" => getBool(key)
|
|
||||||
case "java.lang.String" => getString(key)
|
|
||||||
case _ => None // ignore for now
|
|
||||||
}
|
|
||||||
param.map { p => method.invoke(obj, p.asInstanceOf[Object]) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the requested key is present, return its value as a string. Otherwise, throw a
|
|
||||||
* ConfigException. `toInt` and `toBoolean` may be called on the
|
|
||||||
* returned string if an int or bool is desired.
|
|
||||||
*/
|
|
||||||
def apply(key: String): String = getString(key) match {
|
|
||||||
case None => throw new ConfigException("undefined config: " + key)
|
|
||||||
case Some(v) => v
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Equivalent to `getString(key, defaultValue)`. */
|
|
||||||
def apply(key: String, defaultValue: String) = getString(key, defaultValue)
|
|
||||||
|
|
||||||
/** Equivalent to `getInt(key, defaultValue)`. */
|
|
||||||
def apply(key: String, defaultValue: Int) = getInt(key, defaultValue)
|
|
||||||
|
|
||||||
/** Equivalent to `getLong(key, defaultValue)`. */
|
|
||||||
def apply(key: String, defaultValue: Long) = getLong(key, defaultValue)
|
|
||||||
|
|
||||||
/** Equivalent to `getBool(key, defaultValue)`. */
|
|
||||||
def apply(key: String, defaultValue: Boolean) = getBool(key, defaultValue)
|
|
||||||
|
|
||||||
/** Equivalent to `setString(key, value)`. */
|
|
||||||
def update(key: String, value: String) = setString(key, value)
|
|
||||||
|
|
||||||
/** Equivalent to `setInt(key, value)`. */
|
|
||||||
def update(key: String, value: Int) = setInt(key, value)
|
|
||||||
|
|
||||||
/** Equivalent to `setLong(key, value)`. */
|
|
||||||
def update(key: String, value: Long) = setLong(key, value)
|
|
||||||
|
|
||||||
/** Equivalent to `setBool(key, value)`. */
|
|
||||||
def update(key: String, value: Boolean) = setBool(key, value)
|
|
||||||
|
|
||||||
/** Equivalent to `setList(key, value)`. */
|
|
||||||
def update(key: String, value: Seq[String]) = setList(key, value)
|
|
||||||
|
|
||||||
/** Get the name of the current config map. */
|
|
||||||
def getName(): String
|
|
||||||
}
|
|
||||||
|
|
@ -8,117 +8,68 @@
|
||||||
|
|
||||||
package akka.config
|
package akka.config
|
||||||
|
|
||||||
import scala.collection.mutable.Stack
|
import scala.collection.mutable
|
||||||
import scala.util.parsing.combinator._
|
import scala.util.parsing.combinator._
|
||||||
import scala.util.parsing.input.CharSequenceReader
|
|
||||||
import akka.config.string._
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
class ConfigParser(var prefix: String = "", map: mutable.Map[String, Any] = mutable.Map.empty[String, Any], importer: Importer) extends RegexParsers {
|
||||||
* An exception thrown when parsing a config file, if there was an error
|
val sections = mutable.Stack[String]()
|
||||||
* during parsing. The `reason` string will contain the parsing
|
|
||||||
* error details.
|
|
||||||
*/
|
|
||||||
class ParseException(reason: String, cause: Throwable) extends Exception(reason, cause) {
|
|
||||||
def this(reason: String) = this(reason, null)
|
|
||||||
def this(cause: Throwable) = this(null, cause)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
def createPrefix = {
|
||||||
|
prefix = if (sections.isEmpty) "" else sections.toList.reverse.mkString("", ".", ".")
|
||||||
|
}
|
||||||
|
|
||||||
private[config] class ConfigParser(var attr: Attributes, val importer: Importer) extends RegexParsers {
|
override val whiteSpace = """(\s+|#[^\n]*\n)+""".r
|
||||||
|
|
||||||
val sections = new Stack[String]
|
|
||||||
var prefix = ""
|
|
||||||
|
|
||||||
// Stack reversed iteration order from 2.7 to 2.8!!
|
|
||||||
def sectionsString = sections.toList.reverse.mkString(".")
|
|
||||||
|
|
||||||
// tokens
|
// tokens
|
||||||
override val whiteSpace = """(\s+|#[^\n]*\n)+""".r
|
|
||||||
val numberToken: Parser[String] = """-?\d+(\.\d+)?""".r
|
val numberToken: Parser[String] = """-?\d+(\.\d+)?""".r
|
||||||
val stringToken: Parser[String] = ("\"" + """([^\\\"]|\\[^ux]|\\\n|\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2})*""" + "\"").r
|
val stringToken: Parser[String] = ("\"" + """([^\\\"]|\\[^ux]|\\\n|\\u[0-9a-fA-F]{4}|\\x[0-9a-fA-F]{2})*""" + "\"").r
|
||||||
|
val booleanToken: Parser[String] = "(true|on|false|off)".r
|
||||||
val identToken: Parser[String] = """([\da-zA-Z_][-\w]*)(\.[a-zA-Z_][-\w]*)*""".r
|
val identToken: Parser[String] = """([\da-zA-Z_][-\w]*)(\.[a-zA-Z_][-\w]*)*""".r
|
||||||
val assignToken: Parser[String] = """=|\?=""".r
|
val assignToken: Parser[String] = "=".r
|
||||||
val tagNameToken: Parser[String] = """[a-zA-Z][-\w]*""".r
|
val sectionToken: Parser[String] = """[a-zA-Z][-\w]*""".r
|
||||||
|
|
||||||
|
// values
|
||||||
|
|
||||||
def root = rep(includeFile | includeOptFile | assignment | toggle | sectionOpen | sectionClose |
|
def value: Parser[Any] = number | string | list | boolean
|
||||||
sectionOpenBrace | sectionCloseBrace)
|
def number = numberToken
|
||||||
|
def string = stringToken ^^ { s => s.substring(1, s.length - 1) }
|
||||||
|
def list = "[" ~> repsep(string | numberToken, opt(",")) <~ (opt(",") ~ "]")
|
||||||
|
def boolean = booleanToken
|
||||||
|
|
||||||
|
// parser
|
||||||
|
|
||||||
|
def root = rep(includeFile | assignment | sectionOpen | sectionClose)
|
||||||
|
|
||||||
def includeFile = "include" ~> string ^^ {
|
def includeFile = "include" ~> string ^^ {
|
||||||
case filename: String =>
|
case filename: String =>
|
||||||
new ConfigParser(attr.makeAttributes(sectionsString), importer) parse importer.importFile(filename)
|
new ConfigParser(prefix, map, importer) parse importer.importFile(filename)
|
||||||
}
|
|
||||||
|
|
||||||
def includeOptFile = "include?" ~> string ^^ {
|
|
||||||
case filename: String =>
|
|
||||||
new ConfigParser(attr.makeAttributes(sections.mkString(".")), importer) parse importer.importFile(filename, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def assignment = identToken ~ assignToken ~ value ^^ {
|
def assignment = identToken ~ assignToken ~ value ^^ {
|
||||||
case k ~ a ~ v => if (a match {
|
case k ~ a ~ v => map(prefix + k) = v
|
||||||
case "=" => true
|
|
||||||
case "?=" => ! attr.contains(prefix + k)
|
|
||||||
}) v match {
|
|
||||||
case x: Long => attr(prefix + k) = x
|
|
||||||
case x: String => attr(prefix + k) = x
|
|
||||||
case x: Array[String] => attr(prefix + k) = x
|
|
||||||
case x: Boolean => attr(prefix + k) = x
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def toggle = identToken ~ trueFalse ^^ { case k ~ v => attr(prefix + k) = v }
|
def sectionOpen = sectionToken <~ "{" ^^ { name =>
|
||||||
|
|
||||||
def sectionOpen = "<" ~> tagNameToken ~ rep(tagAttribute) <~ ">" ^^ {
|
|
||||||
case name ~ attrList => openBlock(name, attrList)
|
|
||||||
}
|
|
||||||
def tagAttribute = opt(whiteSpace) ~> (tagNameToken <~ "=") ~ string ^^ { case k ~ v => (k, v) }
|
|
||||||
def sectionClose = "</" ~> tagNameToken <~ ">" ^^ { name => closeBlock(Some(name)) }
|
|
||||||
|
|
||||||
def sectionOpenBrace = tagNameToken ~ opt("(" ~> rep(tagAttribute) <~ ")") <~ "{" ^^ {
|
|
||||||
case name ~ attrListOption => openBlock(name, attrListOption.getOrElse(Nil))
|
|
||||||
}
|
|
||||||
def sectionCloseBrace = "}" ^^ { x => closeBlock(None) }
|
|
||||||
|
|
||||||
private def openBlock(name: String, attrList: List[(String, String)]) = {
|
|
||||||
val parent = if (sections.size > 0) attr.makeAttributes(sectionsString) else attr
|
|
||||||
sections push name
|
sections push name
|
||||||
prefix = sectionsString + "."
|
createPrefix
|
||||||
val newBlock = attr.makeAttributes(sectionsString)
|
|
||||||
for ((k, v) <- attrList) k match {
|
|
||||||
case "inherit" =>
|
|
||||||
newBlock.inheritFrom = Some(if (parent.getConfigMap(v).isDefined) parent.makeAttributes(v) else attr.makeAttributes(v))
|
|
||||||
case _ =>
|
|
||||||
throw new ParseException("Unknown block modifier")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def closeBlock(name: Option[String]) = {
|
def sectionClose = "}" ^^ { _ =>
|
||||||
if (sections.isEmpty) {
|
if (sections.isEmpty) {
|
||||||
failure("dangling close tag")
|
failure("dangling close tag")
|
||||||
} else {
|
} else {
|
||||||
val last = sections.pop
|
sections.pop
|
||||||
if (name.isDefined && last != name.get) {
|
createPrefix
|
||||||
failure("got closing tag for " + name.get + ", expected " + last)
|
|
||||||
} else {
|
|
||||||
prefix = if (sections.isEmpty) "" else sectionsString + "."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def parse(in: String): Map[String, Any] = {
|
||||||
def value: Parser[Any] = number | string | stringList | trueFalse
|
|
||||||
def number = numberToken ^^ { x => if (x.contains('.')) x else x.toLong }
|
|
||||||
def string = stringToken ^^ { s => attr.interpolate(prefix, s.substring(1, s.length - 1).unquoteC) }
|
|
||||||
def stringList = "[" ~> repsep(string | numberToken, opt(",")) <~ (opt(",") ~ "]") ^^ { list => list.toArray }
|
|
||||||
def trueFalse: Parser[Boolean] = ("(true|on)".r ^^ { x => true }) | ("(false|off)".r ^^ { x => false })
|
|
||||||
|
|
||||||
|
|
||||||
def parse(in: String): Unit = {
|
|
||||||
parseAll(root, in) match {
|
parseAll(root, in) match {
|
||||||
case Success(result, _) => result
|
case Success(result, _) => map.toMap
|
||||||
case x @ Failure(msg, z) => throw new ParseException(x.toString)
|
case x @ Failure(msg, _) => throw new ConfigurationException(x.toString)
|
||||||
case x @ Error(msg, _) => throw new ParseException(x.toString)
|
case x @ Error(msg, _) => throw new ConfigurationException(x.toString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,148 +9,135 @@
|
||||||
package akka.config
|
package akka.config
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import scala.collection.{Map, Set}
|
|
||||||
import scala.collection.{immutable, mutable}
|
|
||||||
|
|
||||||
class Configuration extends ConfigMap {
|
|
||||||
private var root = new Attributes(this, "")
|
|
||||||
private var reloadAction: Option[() => Unit] = None
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Importer for resolving "include" lines when loading config files.
|
|
||||||
* By default, it's a FilesystemImporter based on the current working
|
|
||||||
* directory.
|
|
||||||
*/
|
|
||||||
var importer: Importer = new FilesystemImporter(new File(".").getCanonicalPath)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read config data from a string and use it to populate this object.
|
|
||||||
*/
|
|
||||||
def load(data: String) {
|
|
||||||
reloadAction = Some(() => configure(data))
|
|
||||||
reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read config data from a file and use it to populate this object.
|
|
||||||
*/
|
|
||||||
def loadFile(filename: String) {
|
|
||||||
reloadAction = Some(() => configure(importer.importFile(filename)))
|
|
||||||
reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read config data from a file and use it to populate this object.
|
|
||||||
*/
|
|
||||||
def loadFile(path: String, filename: String) {
|
|
||||||
importer = new FilesystemImporter(path)
|
|
||||||
loadFile(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloads the configuration from whatever source it was previously loaded
|
|
||||||
* from, undoing any in-memory changes. This is a no-op if the configuration
|
|
||||||
* data has not be loaded from a source (file or string).
|
|
||||||
*/
|
|
||||||
def reload() {
|
|
||||||
reloadAction.foreach(_())
|
|
||||||
}
|
|
||||||
|
|
||||||
private def configure(data: String) {
|
|
||||||
val newRoot = new Attributes(this, "")
|
|
||||||
new ConfigParser(newRoot, importer) parse data
|
|
||||||
root.replaceWith(newRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
override def toString = root.toString
|
|
||||||
|
|
||||||
// ----- implement AttributeMap by wrapping our root object:
|
|
||||||
|
|
||||||
def getString(key: String): Option[String] = root.getString(key)
|
|
||||||
def getConfigMap(key: String): Option[ConfigMap] = root.getConfigMap(key)
|
|
||||||
def configMap(key: String): ConfigMap = root.configMap(key)
|
|
||||||
def getList(key: String): Seq[String] = root.getList(key)
|
|
||||||
def setString(key: String, value: String): Unit = root.setString(key, value)
|
|
||||||
def setList(key: String, value: Seq[String]): Unit = root.setList(key, value)
|
|
||||||
def setConfigMap(key: String, value: ConfigMap): Unit = root.setConfigMap(key, value)
|
|
||||||
def contains(key: String): Boolean = root.contains(key)
|
|
||||||
def remove(key: String): Boolean = root.remove(key)
|
|
||||||
def keys: Iterator[String] = root.keys
|
|
||||||
def asMap(): Map[String, String] = root.asMap()
|
|
||||||
def toConfigString = root.toConfigString
|
|
||||||
def copy(): ConfigMap = root.copy()
|
|
||||||
def copyInto[T <: ConfigMap](m: T): T = root.copyInto(m)
|
|
||||||
def inheritFrom = root.inheritFrom
|
|
||||||
def inheritFrom_=(config: Option[ConfigMap]) = root.inheritFrom=(config)
|
|
||||||
def getName(): String = root.name
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
object Configuration {
|
object Configuration {
|
||||||
/**
|
val DefaultPath = new File(".").getCanonicalPath
|
||||||
* Create a configuration object from a config file of the given path
|
val DefaultImporter = new FilesystemImporter(DefaultPath)
|
||||||
* and filename. The filename must be relative to the path. The path is
|
|
||||||
* used to resolve filenames given in "include" lines.
|
def load(data: String, importer: Importer = DefaultImporter): Configuration = {
|
||||||
*/
|
val parser = new ConfigParser(importer = importer)
|
||||||
def fromFile(path: String, filename: String): Configuration = {
|
new Configuration(parser parse data)
|
||||||
val config = new Configuration
|
}
|
||||||
config.loadFile(path, filename)
|
|
||||||
config
|
def fromFile(filename: String, importer: Importer): Configuration = {
|
||||||
|
load(importer.importFile(filename), importer)
|
||||||
|
}
|
||||||
|
|
||||||
|
def fromFile(path: String, filename: String): Configuration = {
|
||||||
|
val importer = new FilesystemImporter(path)
|
||||||
|
fromFile(filename, importer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a Configuration object from a config file of the given filename.
|
|
||||||
* The base folder will be extracted from the filename and used as a base
|
|
||||||
* path for resolving filenames given in "include" lines.
|
|
||||||
*/
|
|
||||||
def fromFile(filename: String): Configuration = {
|
def fromFile(filename: String): Configuration = {
|
||||||
val n = filename.lastIndexOf('/')
|
val n = filename.lastIndexOf('/')
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
fromFile(new File(".").getCanonicalPath, filename)
|
fromFile(DefaultPath, filename)
|
||||||
} else {
|
} else {
|
||||||
fromFile(filename.substring(0, n), filename.substring(n + 1))
|
fromFile(filename.substring(0, n), filename.substring(n + 1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
def fromResource(filename: String): Configuration = {
|
||||||
* Create a Configuration object from the given named resource inside this jar
|
fromResource(filename, ClassLoader.getSystemClassLoader)
|
||||||
* file, using the system class loader. "include" lines will also operate
|
|
||||||
* on resource paths.
|
|
||||||
*/
|
|
||||||
def fromResource(name: String): Configuration = {
|
|
||||||
fromResource(name, ClassLoader.getSystemClassLoader)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
def fromResource(filename: String, classLoader: ClassLoader): Configuration = {
|
||||||
* Create a Configuration object from the given named resource inside this jar
|
val importer = new ResourceImporter(classLoader)
|
||||||
* file, using a specific class loader. "include" lines will also operate
|
fromFile(filename, importer)
|
||||||
* on resource paths.
|
|
||||||
*/
|
|
||||||
def fromResource(name: String, classLoader: ClassLoader): Configuration = {
|
|
||||||
val config = new Configuration
|
|
||||||
config.importer = new ResourceImporter(classLoader)
|
|
||||||
config.loadFile(name)
|
|
||||||
config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
def fromMap(map: Map[String, Any]) = {
|
||||||
* Create a Configuration object from a map of String keys and String values.
|
new Configuration(map)
|
||||||
*/
|
|
||||||
def fromMap(m: Map[String, String]) = {
|
|
||||||
val config = new Configuration
|
|
||||||
for ((k, v) <- m.elements) {
|
|
||||||
config(k) = v
|
|
||||||
}
|
|
||||||
config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a Configuration object from a string containing a config file's contents.
|
|
||||||
*/
|
|
||||||
def fromString(data: String): Configuration = {
|
def fromString(data: String): Configuration = {
|
||||||
val config = new Configuration
|
load(data)
|
||||||
config.load(data)
|
}
|
||||||
config
|
}
|
||||||
|
|
||||||
|
class Configuration(val map: Map[String, Any]) {
|
||||||
|
private val trueValues = Set("true", "on")
|
||||||
|
private val falseValues = Set("false", "off")
|
||||||
|
|
||||||
|
def contains(key: String): Boolean = map contains key
|
||||||
|
|
||||||
|
def keys: Iterable[String] = map.keys
|
||||||
|
|
||||||
|
def getString(key: String): Option[String] = map.get(key).map(_.toString)
|
||||||
|
|
||||||
|
def getString(key: String, defaultValue: String): String = getString(key).getOrElse(defaultValue)
|
||||||
|
|
||||||
|
def getList(key: String): Seq[String] = map(key).asInstanceOf[Seq[String]]
|
||||||
|
|
||||||
|
def getInt(key: String): Option[Int] = {
|
||||||
|
try {
|
||||||
|
Some(map(key).toString.toInt)
|
||||||
|
} catch {
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getInt(key: String, defaultValue: Int): Int = getInt(key).getOrElse(defaultValue)
|
||||||
|
|
||||||
|
def getLong(key: String): Option[Long] = {
|
||||||
|
try {
|
||||||
|
Some(map(key).toString.toLong)
|
||||||
|
} catch {
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getLong(key: String, defaultValue: Long): Long = getLong(key).getOrElse(defaultValue)
|
||||||
|
|
||||||
|
def getFloat(key: String): Option[Float] = {
|
||||||
|
try {
|
||||||
|
Some(map(key).toString.toFloat)
|
||||||
|
} catch {
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getFloat(key: String, defaultValue: Float): Float = getFloat(key).getOrElse(defaultValue)
|
||||||
|
|
||||||
|
def getDouble(key: String): Option[Double] = {
|
||||||
|
try {
|
||||||
|
Some(map(key).toString.toDouble)
|
||||||
|
} catch {
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getDouble(key: String, defaultValue: Double): Double = getDouble(key).getOrElse(defaultValue)
|
||||||
|
|
||||||
|
def getBoolean(key: String): Option[Boolean] = {
|
||||||
|
getString(key) flatMap { s =>
|
||||||
|
val isTrue = trueValues.contains(s)
|
||||||
|
if (!isTrue && !falseValues.contains(s)) None
|
||||||
|
else Some(isTrue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getBoolean(key: String, defaultValue: Boolean): Boolean = getBool(key).getOrElse(defaultValue)
|
||||||
|
|
||||||
|
def getBool(key: String): Option[Boolean] = getBoolean(key)
|
||||||
|
|
||||||
|
def getBool(key: String, defaultValue: Boolean): Boolean = getBoolean(key, defaultValue)
|
||||||
|
|
||||||
|
def apply(key: String): String = getString(key) match {
|
||||||
|
case None => throw new ConfigurationException("undefined config: " + key)
|
||||||
|
case Some(v) => v
|
||||||
|
}
|
||||||
|
|
||||||
|
def apply(key: String, defaultValue: String) = getString(key, defaultValue)
|
||||||
|
def apply(key: String, defaultValue: Int) = getInt(key, defaultValue)
|
||||||
|
def apply(key: String, defaultValue: Long) = getLong(key, defaultValue)
|
||||||
|
def apply(key: String, defaultValue: Boolean) = getBool(key, defaultValue)
|
||||||
|
|
||||||
|
def getSection(name: String): Option[Configuration] = {
|
||||||
|
val l = name.length + 1
|
||||||
|
val m = map.collect { case (k, v) if k.startsWith(name) => (k.substring(l), v) }
|
||||||
|
if (m.isEmpty) None
|
||||||
|
else Some(new Configuration(m))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Scalable Solutions AB <http://scalablesolutions.se>
|
|
||||||
*
|
|
||||||
* Based on Configgy by Robey Pointer.
|
|
||||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.config
|
|
||||||
|
|
||||||
import scala.util.matching.Regex
|
|
||||||
|
|
||||||
|
|
||||||
final class ConfigurationString(wrapped: String) {
|
|
||||||
/**
|
|
||||||
* For every section of a string that matches a regular expression, call
|
|
||||||
* a function to determine a replacement (as in python's
|
|
||||||
* `re.sub`). The function will be passed the Matcher object
|
|
||||||
* corresponding to the substring that matches the pattern, and that
|
|
||||||
* substring will be replaced by the function's result.
|
|
||||||
*
|
|
||||||
* For example, this call:
|
|
||||||
*
|
|
||||||
* "ohio".regexSub("""h.""".r) { m => "n" }
|
|
||||||
*
|
|
||||||
* will return the string `"ono"`.
|
|
||||||
*
|
|
||||||
* The matches are found using `Matcher.find()` and so
|
|
||||||
* will obey all the normal java rules (the matches will not overlap,
|
|
||||||
* etc).
|
|
||||||
*
|
|
||||||
* @param re the regex pattern to replace
|
|
||||||
* @param replace a function that takes Regex.MatchData objects and
|
|
||||||
* returns a string to substitute
|
|
||||||
* @return the resulting string with replacements made
|
|
||||||
*/
|
|
||||||
def regexSub(re: Regex)(replace: (Regex.MatchData => String)): String = {
|
|
||||||
var offset = 0
|
|
||||||
var out = new StringBuilder
|
|
||||||
|
|
||||||
for (m <- re.findAllIn(wrapped).matchData) {
|
|
||||||
if (m.start > offset) {
|
|
||||||
out.append(wrapped.substring(offset, m.start))
|
|
||||||
}
|
|
||||||
|
|
||||||
out.append(replace(m))
|
|
||||||
offset = m.end
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offset < wrapped.length) {
|
|
||||||
out.append(wrapped.substring(offset))
|
|
||||||
}
|
|
||||||
out.toString
|
|
||||||
}
|
|
||||||
|
|
||||||
private val QUOTE_RE = "[\u0000-\u001f\u007f-\uffff\\\\\"]".r
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quote a string so that unprintable chars (in ASCII) are represented by
|
|
||||||
* C-style backslash expressions. For example, a raw linefeed will be
|
|
||||||
* translated into <code>"\n"</code>. Control codes (anything below 0x20)
|
|
||||||
* and unprintables (anything above 0x7E) are turned into either
|
|
||||||
* <code>"\xHH"</code> or <code>"\\uHHHH"</code> expressions, depending on
|
|
||||||
* their range. Embedded backslashes and double-quotes are also quoted.
|
|
||||||
*
|
|
||||||
* @return a quoted string, suitable for ASCII display
|
|
||||||
*/
|
|
||||||
def quoteC(): String = {
|
|
||||||
regexSub(QUOTE_RE) { m =>
|
|
||||||
m.matched.charAt(0) match {
|
|
||||||
case '\r' => "\\r"
|
|
||||||
case '\n' => "\\n"
|
|
||||||
case '\t' => "\\t"
|
|
||||||
case '"' => "\\\""
|
|
||||||
case '\\' => "\\\\"
|
|
||||||
case c =>
|
|
||||||
if (c <= 255) {
|
|
||||||
"\\x%02x".format(c.asInstanceOf[Int])
|
|
||||||
} else {
|
|
||||||
"\\u%04x" format c.asInstanceOf[Int]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we intentionally don't unquote "\$" here, so it can be used to escape interpolation later.
|
|
||||||
private val UNQUOTE_RE = """\\(u[\dA-Fa-f]{4}|x[\dA-Fa-f]{2}|[/rnt\"\\])""".r
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unquote an ASCII string that has been quoted in a style like
|
|
||||||
* {@link #quoteC} and convert it into a standard unicode string.
|
|
||||||
* <code>"\\uHHHH"</code> and <code>"\xHH"</code> expressions are unpacked
|
|
||||||
* into unicode characters, as well as <code>"\r"</code>, <code>"\n"<code>,
|
|
||||||
* <code>"\t"</code>, <code>"\\"<code>, and <code>'\"'</code>.
|
|
||||||
*
|
|
||||||
* @return an unquoted unicode string
|
|
||||||
*/
|
|
||||||
def unquoteC() = {
|
|
||||||
regexSub(UNQUOTE_RE) { m =>
|
|
||||||
val ch = m.group(1).charAt(0) match {
|
|
||||||
// holy crap! this is terrible:
|
|
||||||
case 'u' => Character.valueOf(Integer.valueOf(m.group(1).substring(1), 16).asInstanceOf[Int].toChar)
|
|
||||||
case 'x' => Character.valueOf(Integer.valueOf(m.group(1).substring(1), 16).asInstanceOf[Int].toChar)
|
|
||||||
case 'r' => '\r'
|
|
||||||
case 'n' => '\n'
|
|
||||||
case 't' => '\t'
|
|
||||||
case x => x
|
|
||||||
}
|
|
||||||
ch.toString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object string {
|
|
||||||
implicit def stringToConfigurationString(s: String): ConfigurationString = new ConfigurationString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Scalable Solutions AB <http://scalablesolutions.se>
|
|
||||||
*
|
|
||||||
* Based on Configgy by Robey Pointer.
|
|
||||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.config
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
object Configure {
|
|
||||||
private var _config: Configuration = null
|
|
||||||
|
|
||||||
def config = _config
|
|
||||||
|
|
||||||
def config_=(c: Configuration) {
|
|
||||||
_config = c
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure(path: String, filename: String): Unit = {
|
|
||||||
config = Configuration.fromFile(path, filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure(filename: String): Unit = {
|
|
||||||
config = Configuration.fromFile(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
def configureFromResource(name: String) = {
|
|
||||||
config = Configuration.fromResource(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
def configureFromResource(name: String, classLoader: ClassLoader) = {
|
|
||||||
config = Configuration.fromResource(name, classLoader)
|
|
||||||
}
|
|
||||||
|
|
||||||
def configureFromString(data: String) = {
|
|
||||||
config = Configuration.fromString(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2011 Scalable Solutions AB <http://scalablesolutions.se>
|
|
||||||
*
|
|
||||||
* Based on Configgy by Robey Pointer.
|
|
||||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.config
|
|
||||||
|
|
||||||
import java.net.InetAddress
|
|
||||||
import scala.collection.{immutable, mutable}
|
|
||||||
import scala.collection.JavaConversions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A ConfigMap that wraps the system environment. This is used as a
|
|
||||||
* fallback when looking up "$(...)" substitutions in config files.
|
|
||||||
*/
|
|
||||||
private[config] object EnvironmentAttributes extends ConfigMap {
|
|
||||||
|
|
||||||
private val env = immutable.Map.empty[String, String] ++ (JavaConversions.asMap(System.getenv()).elements)
|
|
||||||
|
|
||||||
// deal with java.util.Properties extending
|
|
||||||
// java.util.Hashtable[Object, Object] and not
|
|
||||||
// java.util.Hashtable[String, String]
|
|
||||||
private def getSystemProperties(): mutable.HashMap[String,String] = {
|
|
||||||
val map = new mutable.HashMap[String, String]
|
|
||||||
for (entry <- JavaConversions.asMap(System.getProperties()).elements) {
|
|
||||||
entry match {
|
|
||||||
case (k: String, v: String) => map.put(k, v)
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
def getName() = ""
|
|
||||||
|
|
||||||
def getString(key: String): Option[String] = {
|
|
||||||
getSystemProperties().get(key).orElse(env.get(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
def getConfigMap(key: String): Option[ConfigMap] = None
|
|
||||||
def configMap(key: String): ConfigMap = error("not implemented")
|
|
||||||
|
|
||||||
def getList(key: String): Seq[String] = getString(key) match {
|
|
||||||
case None => Array[String]()
|
|
||||||
case Some(x) => Array[String](x)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setString(key: String, value: String): Unit = error("read-only attributes")
|
|
||||||
def setList(key: String, value: Seq[String]): Unit = error("read-only attributes")
|
|
||||||
def setConfigMap(key: String, value: ConfigMap): Unit = error("read-only attributes")
|
|
||||||
|
|
||||||
def contains(key: String): Boolean = {
|
|
||||||
env.contains(key) || getSystemProperties().contains(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
def remove(key: String): Boolean = error("read-only attributes")
|
|
||||||
def keys: Iterator[String] = (getSystemProperties().keySet ++ env.keySet).elements
|
|
||||||
def asMap(): Map[String, String] = error("not implemented")
|
|
||||||
def toConfigString = error("not implemented")
|
|
||||||
def copy(): ConfigMap = this
|
|
||||||
def copyInto[T <: ConfigMap](m: T) = m
|
|
||||||
def inheritFrom: Option[ConfigMap] = None
|
|
||||||
def inheritFrom_=(config: Option[ConfigMap]) = error("not implemented")
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
val addr = InetAddress.getLocalHost
|
|
||||||
val ip = addr.getHostAddress
|
|
||||||
val dns = addr.getHostName
|
|
||||||
|
|
||||||
if (ip ne null) {
|
|
||||||
env("HOSTIP") = ip
|
|
||||||
}
|
|
||||||
if (dns ne null) {
|
|
||||||
env("HOSTNAME") = dns
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
case _ => // pass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,49 +16,28 @@ import java.io.{BufferedReader, File, FileInputStream, InputStream, InputStreamR
|
||||||
* parsing. This is used to handle `include` directives in config files.
|
* parsing. This is used to handle `include` directives in config files.
|
||||||
*/
|
*/
|
||||||
trait Importer {
|
trait Importer {
|
||||||
/**
|
|
||||||
* Imports a requested file and returns the string contents of that file,
|
|
||||||
* if the file exists, and empty string if it does not exist and `required`
|
|
||||||
* is false.
|
|
||||||
*
|
|
||||||
* If the file couldn't be imported, throws a `ParseException`.
|
|
||||||
*/
|
|
||||||
@throws(classOf[ParseException])
|
|
||||||
def importFile(filename: String, required: Boolean): String
|
|
||||||
|
|
||||||
/**
|
def importFile(filename: String): String
|
||||||
* Imports a requested file and returns the string contents of that file.
|
|
||||||
* If the file couldn't be imported, throws a `ParseException`.
|
|
||||||
*/
|
|
||||||
@throws(classOf[ParseException])
|
|
||||||
def importFile(filename: String): String = importFile(filename, true)
|
|
||||||
|
|
||||||
private val BUFFER_SIZE = 8192
|
private val BUFFER_SIZE = 8192
|
||||||
|
|
||||||
/**
|
|
||||||
* Exhaustively reads an InputStream and converts it into a String (using
|
|
||||||
* UTF-8 encoding). This is meant as a helper function for custom Importer
|
|
||||||
* classes.
|
|
||||||
*
|
|
||||||
* No exceptions are caught!
|
|
||||||
*/
|
|
||||||
protected def streamToString(in: InputStream): String = {
|
protected def streamToString(in: InputStream): String = {
|
||||||
val reader = new BufferedReader(new InputStreamReader(in, "UTF-8"))
|
|
||||||
val buffer = new Array[Char](BUFFER_SIZE)
|
|
||||||
val out = new StringBuilder
|
|
||||||
var n = 0
|
|
||||||
while (n >= 0) {
|
|
||||||
n = reader.read(buffer, 0, buffer.length)
|
|
||||||
if (n >= 0) {
|
|
||||||
out.append(buffer, 0, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
|
val reader = new BufferedReader(new InputStreamReader(in, "UTF-8"))
|
||||||
|
val buffer = new Array[Char](BUFFER_SIZE)
|
||||||
|
val sb = new StringBuilder
|
||||||
|
var n = 0
|
||||||
|
while (n >= 0) {
|
||||||
|
n = reader.read(buffer, 0, buffer.length)
|
||||||
|
if (n >= 0) {
|
||||||
|
sb.appendAll(buffer, 0, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
in.close()
|
in.close()
|
||||||
|
sb.toString
|
||||||
} catch {
|
} catch {
|
||||||
case _ =>
|
case x => throw new ConfigurationException(x.toString)
|
||||||
}
|
}
|
||||||
out.toString
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,21 +46,11 @@ trait Importer {
|
||||||
* An Importer that looks for imported config files in the filesystem.
|
* An Importer that looks for imported config files in the filesystem.
|
||||||
* This is the default importer.
|
* This is the default importer.
|
||||||
*/
|
*/
|
||||||
class FilesystemImporter(val baseFolder: String) extends Importer {
|
class FilesystemImporter(val baseDir: String) extends Importer {
|
||||||
def importFile(filename: String, required: Boolean): String = {
|
def importFile(filename: String): String = {
|
||||||
var f = new File(filename)
|
val f = new File(filename)
|
||||||
if (! f.isAbsolute) {
|
val file = if (f.isAbsolute) f else new File(baseDir, filename)
|
||||||
f = new File(baseFolder, filename)
|
streamToString(new FileInputStream(file))
|
||||||
}
|
|
||||||
if (!required && !f.exists) {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
streamToString(new FileInputStream(f))
|
|
||||||
} catch {
|
|
||||||
case x => throw new ParseException(x.toString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,19 +60,8 @@ class FilesystemImporter(val baseFolder: String) extends Importer {
|
||||||
* of the system class loader (usually the jar used to launch this app).
|
* of the system class loader (usually the jar used to launch this app).
|
||||||
*/
|
*/
|
||||||
class ResourceImporter(classLoader: ClassLoader) extends Importer {
|
class ResourceImporter(classLoader: ClassLoader) extends Importer {
|
||||||
def importFile(filename: String, required: Boolean): String = {
|
def importFile(filename: String): String = {
|
||||||
try {
|
val stream = classLoader.getResourceAsStream(filename)
|
||||||
val stream = classLoader.getResourceAsStream(filename)
|
streamToString(stream)
|
||||||
if (stream eq null) {
|
|
||||||
if (required) {
|
|
||||||
throw new ParseException("Can't find resource: " + filename)
|
|
||||||
}
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
streamToString(stream)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
case x => throw new ParseException(x.toString)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import akka.actor.newUuid
|
||||||
import akka.config.Config._
|
import akka.config.Config._
|
||||||
import akka.util.{Duration,ReflectiveAccess}
|
import akka.util.{Duration,ReflectiveAccess}
|
||||||
|
|
||||||
import akka.config.ConfigMap
|
import akka.config.Configuration
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ object Dispatchers {
|
||||||
val MAILBOX_TYPE: MailboxType = if (MAILBOX_CAPACITY < 0) UnboundedMailbox() else BoundedMailbox()
|
val MAILBOX_TYPE: MailboxType = if (MAILBOX_CAPACITY < 0) UnboundedMailbox() else BoundedMailbox()
|
||||||
|
|
||||||
lazy val defaultGlobalDispatcher = {
|
lazy val defaultGlobalDispatcher = {
|
||||||
config.getConfigMap("akka.actor.default-dispatcher").flatMap(from).getOrElse(globalExecutorBasedEventDrivenDispatcher)
|
config.getSection("akka.actor.default-dispatcher").flatMap(from).getOrElse(globalExecutorBasedEventDrivenDispatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
object globalExecutorBasedEventDrivenDispatcher extends ExecutorBasedEventDrivenDispatcher("global", THROUGHPUT, THROUGHPUT_DEADLINE_TIME_MILLIS, MAILBOX_TYPE)
|
object globalExecutorBasedEventDrivenDispatcher extends ExecutorBasedEventDrivenDispatcher("global", THROUGHPUT, THROUGHPUT_DEADLINE_TIME_MILLIS, MAILBOX_TYPE)
|
||||||
|
|
@ -155,7 +155,7 @@ object Dispatchers {
|
||||||
* or else use the supplied default dispatcher
|
* or else use the supplied default dispatcher
|
||||||
*/
|
*/
|
||||||
def fromConfig(key: String, default: => MessageDispatcher = defaultGlobalDispatcher): MessageDispatcher =
|
def fromConfig(key: String, default: => MessageDispatcher = defaultGlobalDispatcher): MessageDispatcher =
|
||||||
config getConfigMap key flatMap from getOrElse default
|
config getSection key flatMap from getOrElse default
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Creates of obtains a dispatcher from a ConfigMap according to the format below
|
* Creates of obtains a dispatcher from a ConfigMap according to the format below
|
||||||
|
|
@ -180,7 +180,7 @@ object Dispatchers {
|
||||||
* Throws: IllegalArgumentException if the value of "type" is not valid
|
* Throws: IllegalArgumentException if the value of "type" is not valid
|
||||||
* IllegalArgumentException if it cannot
|
* IllegalArgumentException if it cannot
|
||||||
*/
|
*/
|
||||||
def from(cfg: ConfigMap): Option[MessageDispatcher] = {
|
def from(cfg: Configuration): Option[MessageDispatcher] = {
|
||||||
cfg.getString("type") map {
|
cfg.getString("type") map {
|
||||||
case "ExecutorBasedEventDriven" => new ExecutorBasedEventDrivenDispatcherConfigurator()
|
case "ExecutorBasedEventDriven" => new ExecutorBasedEventDrivenDispatcherConfigurator()
|
||||||
case "ExecutorBasedEventDrivenWorkStealing" => new ExecutorBasedEventDrivenWorkStealingDispatcherConfigurator()
|
case "ExecutorBasedEventDrivenWorkStealing" => new ExecutorBasedEventDrivenWorkStealingDispatcherConfigurator()
|
||||||
|
|
@ -203,11 +203,11 @@ object Dispatchers {
|
||||||
}
|
}
|
||||||
|
|
||||||
object GlobalExecutorBasedEventDrivenDispatcherConfigurator extends MessageDispatcherConfigurator {
|
object GlobalExecutorBasedEventDrivenDispatcherConfigurator extends MessageDispatcherConfigurator {
|
||||||
def configure(config: ConfigMap): MessageDispatcher = Dispatchers.globalExecutorBasedEventDrivenDispatcher
|
def configure(config: Configuration): MessageDispatcher = Dispatchers.globalExecutorBasedEventDrivenDispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExecutorBasedEventDrivenDispatcherConfigurator extends MessageDispatcherConfigurator {
|
class ExecutorBasedEventDrivenDispatcherConfigurator extends MessageDispatcherConfigurator {
|
||||||
def configure(config: ConfigMap): MessageDispatcher = {
|
def configure(config: Configuration): MessageDispatcher = {
|
||||||
configureThreadPool(config, threadPoolConfig => new ExecutorBasedEventDrivenDispatcher(
|
configureThreadPool(config, threadPoolConfig => new ExecutorBasedEventDrivenDispatcher(
|
||||||
config.getString("name", newUuid.toString),
|
config.getString("name", newUuid.toString),
|
||||||
config.getInt("throughput", Dispatchers.THROUGHPUT),
|
config.getInt("throughput", Dispatchers.THROUGHPUT),
|
||||||
|
|
@ -218,7 +218,7 @@ class ExecutorBasedEventDrivenDispatcherConfigurator extends MessageDispatcherCo
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExecutorBasedEventDrivenWorkStealingDispatcherConfigurator extends MessageDispatcherConfigurator {
|
class ExecutorBasedEventDrivenWorkStealingDispatcherConfigurator extends MessageDispatcherConfigurator {
|
||||||
def configure(config: ConfigMap): MessageDispatcher = {
|
def configure(config: Configuration): MessageDispatcher = {
|
||||||
configureThreadPool(config, threadPoolConfig => new ExecutorBasedEventDrivenWorkStealingDispatcher(
|
configureThreadPool(config, threadPoolConfig => new ExecutorBasedEventDrivenWorkStealingDispatcher(
|
||||||
config.getString("name", newUuid.toString),
|
config.getString("name", newUuid.toString),
|
||||||
config.getInt("throughput", Dispatchers.THROUGHPUT),
|
config.getInt("throughput", Dispatchers.THROUGHPUT),
|
||||||
|
|
@ -226,4 +226,4 @@ class ExecutorBasedEventDrivenWorkStealingDispatcherConfigurator extends Message
|
||||||
mailboxType(config),
|
mailboxType(config),
|
||||||
threadPoolConfig)).build
|
threadPoolConfig)).build
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ package akka.dispatch
|
||||||
|
|
||||||
import java.util.concurrent._
|
import java.util.concurrent._
|
||||||
import atomic. {AtomicInteger, AtomicBoolean, AtomicReference, AtomicLong}
|
import atomic. {AtomicInteger, AtomicBoolean, AtomicReference, AtomicLong}
|
||||||
import akka.config.ConfigMap
|
import akka.config.Configuration
|
||||||
import akka.config.Config.TIME_UNIT
|
import akka.config.Config.TIME_UNIT
|
||||||
import akka.util.{Duration, Switch, ReentrantGuard, HashCode, ReflectiveAccess}
|
import akka.util.{Duration, Switch, ReentrantGuard, HashCode, ReflectiveAccess}
|
||||||
import java.util.concurrent.ThreadPoolExecutor.{AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy}
|
import java.util.concurrent.ThreadPoolExecutor.{AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy}
|
||||||
|
|
@ -35,7 +35,7 @@ final case class FutureInvocation(future: CompletableFuture[Any], function: () =
|
||||||
def run = future complete (try {
|
def run = future complete (try {
|
||||||
Right(function.apply)
|
Right(function.apply)
|
||||||
} catch {
|
} catch {
|
||||||
case e: Exception =>
|
case e: Exception =>
|
||||||
EventHandler notifyListeners EventHandler.Error(e, this)
|
EventHandler notifyListeners EventHandler.Error(e, this)
|
||||||
Left(e)
|
Left(e)
|
||||||
})
|
})
|
||||||
|
|
@ -209,16 +209,16 @@ trait MessageDispatcher {
|
||||||
* Trait to be used for hooking in new dispatchers into Dispatchers.fromConfig
|
* Trait to be used for hooking in new dispatchers into Dispatchers.fromConfig
|
||||||
*/
|
*/
|
||||||
abstract class MessageDispatcherConfigurator {
|
abstract class MessageDispatcherConfigurator {
|
||||||
def configure(config: ConfigMap): MessageDispatcher
|
def configure(config: Configuration): MessageDispatcher
|
||||||
|
|
||||||
def mailboxType(config: ConfigMap): MailboxType = {
|
def mailboxType(config: Configuration): MailboxType = {
|
||||||
val capacity = config.getInt("mailbox-capacity", Dispatchers.MAILBOX_CAPACITY)
|
val capacity = config.getInt("mailbox-capacity", Dispatchers.MAILBOX_CAPACITY)
|
||||||
// FIXME how do we read in isBlocking for mailbox? Now set to 'false'.
|
// FIXME how do we read in isBlocking for mailbox? Now set to 'false'.
|
||||||
if (capacity < 0) UnboundedMailbox()
|
if (capacity < 0) UnboundedMailbox()
|
||||||
else BoundedMailbox(false, capacity, Duration(config.getInt("mailbox-push-timeout-time", Dispatchers.MAILBOX_PUSH_TIME_OUT.toMillis.toInt), TIME_UNIT))
|
else BoundedMailbox(false, capacity, Duration(config.getInt("mailbox-push-timeout-time", Dispatchers.MAILBOX_PUSH_TIME_OUT.toMillis.toInt), TIME_UNIT))
|
||||||
}
|
}
|
||||||
|
|
||||||
def configureThreadPool(config: ConfigMap, createDispatcher: => (ThreadPoolConfig) => MessageDispatcher): ThreadPoolConfigDispatcherBuilder = {
|
def configureThreadPool(config: Configuration, createDispatcher: => (ThreadPoolConfig) => MessageDispatcher): ThreadPoolConfigDispatcherBuilder = {
|
||||||
import ThreadPoolConfigDispatcherBuilder.conf_?
|
import ThreadPoolConfigDispatcherBuilder.conf_?
|
||||||
|
|
||||||
//Apply the following options to the config if they are present in the config
|
//Apply the following options to the config if they are present in the config
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue