Add configgy sources under akka package
This commit is contained in:
parent
283a5b4c83
commit
9dc3bbc849
15 changed files with 2188 additions and 8 deletions
|
|
@ -5,8 +5,8 @@
|
|||
package akka.config
|
||||
|
||||
import akka.AkkaException
|
||||
import akka.actor.{EventHandler}
|
||||
import net.lag.configgy.{Config => CConfig, Configgy, ParseException}
|
||||
import akka.actor.EventHandler
|
||||
import akka.configgy.{Config => CConfig, Configgy, ParseException}
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
import java.lang.reflect.Method
|
||||
|
|
|
|||
434
akka-actor/src/main/scala/akka/configgy/Attributes.scala
Normal file
434
akka-actor/src/main/scala/akka/configgy/Attributes.scala
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
import java.util.regex.Pattern
|
||||
import javax.{management => jmx}
|
||||
import scala.collection.{immutable, mutable, Map}
|
||||
import scala.util.Sorting
|
||||
import extensions._
|
||||
|
||||
|
||||
private[configgy] abstract class Cell
|
||||
private[configgy] case class StringCell(value: String) extends Cell
|
||||
private[configgy] case class AttributesCell(attr: Attributes) extends Cell
|
||||
private[configgy] case class StringListCell(array: Array[String]) extends Cell
|
||||
|
||||
|
||||
/**
|
||||
* Actual implementation of ConfigMap.
|
||||
* Stores items in Cell objects, and handles interpolation and key recursion.
|
||||
*/
|
||||
private[configgy] class Attributes(val config: Config, val name: String) extends ConfigMap {
|
||||
|
||||
private val cells = new mutable.HashMap[String, Cell]
|
||||
private var monitored = false
|
||||
var inheritFrom: Option[ConfigMap] = None
|
||||
|
||||
def this(config: Config, 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))
|
||||
if (monitored) {
|
||||
attr.setMonitored
|
||||
}
|
||||
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[configgy] def makeAttributes(key: String): Attributes = makeAttributes(key, false)
|
||||
|
||||
private[configgy] 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 = {
|
||||
if (monitored) {
|
||||
config.deepSet(name, key, value)
|
||||
return
|
||||
}
|
||||
|
||||
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 = {
|
||||
if (monitored) {
|
||||
config.deepSet(name, key, value)
|
||||
return
|
||||
}
|
||||
|
||||
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 = {
|
||||
if (monitored) {
|
||||
config.deepSet(name, key, value)
|
||||
return
|
||||
}
|
||||
|
||||
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 = {
|
||||
if (monitored) {
|
||||
return config.deepRemove(name, key)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
def subscribe(subscriber: Subscriber) = {
|
||||
config.subscribe(name, subscriber)
|
||||
}
|
||||
|
||||
// substitute "$(...)" strings with looked-up vars
|
||||
// (and find "\$" and replace them with "$")
|
||||
private val INTERPOLATE_RE = """(?<!\\)\$\((\w[\w\d\._-]*)\)|\\\$""".r
|
||||
|
||||
protected[configgy] 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[configgy] def interpolate(key: String, s: String): String = {
|
||||
recurse(key) match {
|
||||
case Some((attr, name)) => attr.interpolate(this, s)
|
||||
case None => interpolate(this, s)
|
||||
}
|
||||
}
|
||||
|
||||
/* set this node as part of a monitored config tree. once this is set,
|
||||
* all modification requests go through the root Config, so validation
|
||||
* will happen.
|
||||
*/
|
||||
protected[configgy] def setMonitored: Unit = {
|
||||
if (monitored) {
|
||||
return
|
||||
}
|
||||
|
||||
monitored = true
|
||||
for (cell <- cells.values) {
|
||||
cell match {
|
||||
case AttributesCell(x) => x.setMonitored
|
||||
case _ => // pass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected[configgy] def isMonitored = monitored
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
def asJmxAttributes(): Array[jmx.MBeanAttributeInfo] = {
|
||||
cells.map { case (key, value) =>
|
||||
value match {
|
||||
case StringCell(_) =>
|
||||
new jmx.MBeanAttributeInfo(key, "java.lang.String", "", true, true, false)
|
||||
case StringListCell(_) =>
|
||||
new jmx.MBeanAttributeInfo(key, "java.util.List", "", true, true, false)
|
||||
case AttributesCell(_) =>
|
||||
null
|
||||
}
|
||||
}.filter { x => x ne null }.toList.toArray
|
||||
}
|
||||
|
||||
def asJmxDisplay(key: String): AnyRef = {
|
||||
cells.get(key) match {
|
||||
case Some(StringCell(x)) => x
|
||||
case Some(StringListCell(x)) => java.util.Arrays.asList(x: _*)
|
||||
case x => null
|
||||
}
|
||||
}
|
||||
|
||||
def getJmxNodes(prefix: String, name: String): List[(String, JmxWrapper)] = {
|
||||
(prefix + ":type=Config,name=" + (if (name == "") "(root)" else name), new JmxWrapper(this)) :: cells.flatMap { item =>
|
||||
val (key, value) = item
|
||||
value match {
|
||||
case AttributesCell(x) =>
|
||||
x.getJmxNodes(prefix, if (name == "") key else (name + "." + key))
|
||||
case _ => Nil
|
||||
}
|
||||
}.toList
|
||||
}
|
||||
}
|
||||
410
akka-actor/src/main/scala/akka/configgy/Config.scala
Normal file
410
akka-actor/src/main/scala/akka/configgy/Config.scala
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
import java.io.File
|
||||
import java.lang.management.ManagementFactory
|
||||
import javax.{management => jmx}
|
||||
import scala.collection.{Map, Set}
|
||||
import scala.collection.{immutable, mutable}
|
||||
import extensions._
|
||||
|
||||
|
||||
private abstract class Phase
|
||||
private case object VALIDATE_PHASE extends Phase
|
||||
private case object COMMIT_PHASE extends Phase
|
||||
|
||||
|
||||
private class SubscriptionNode {
|
||||
var subscribers = new mutable.HashSet[Subscriber]
|
||||
var map = new mutable.HashMap[String, SubscriptionNode]
|
||||
|
||||
def get(name: String): SubscriptionNode = {
|
||||
map.get(name) match {
|
||||
case Some(x) => x
|
||||
case None =>
|
||||
val node = new SubscriptionNode
|
||||
map(name) = node
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
override def toString() = {
|
||||
val out = new StringBuilder("%d" format subscribers.size)
|
||||
if (map.size > 0) {
|
||||
out.append(" { ")
|
||||
for (key <- map.keys) {
|
||||
out.append(key)
|
||||
out.append("=")
|
||||
out.append(map(key).toString)
|
||||
out.append(" ")
|
||||
}
|
||||
out.append("}")
|
||||
}
|
||||
out.toString
|
||||
}
|
||||
|
||||
@throws(classOf[ValidationException])
|
||||
def validate(key: List[String], current: Option[ConfigMap], replacement: Option[ConfigMap], phase: Phase): Unit = {
|
||||
if ((current == None) && (replacement == None)) {
|
||||
// someone has subscribed to a nonexistent node... ignore.
|
||||
return
|
||||
}
|
||||
|
||||
// first, call all subscribers for this node.
|
||||
for (subscriber <- subscribers) {
|
||||
phase match {
|
||||
case VALIDATE_PHASE => subscriber.validate(current, replacement)
|
||||
case COMMIT_PHASE => subscriber.commit(current, replacement)
|
||||
}
|
||||
}
|
||||
|
||||
/* if we're walking a key, lookup the next segment's subscribers and
|
||||
* continue the validate/commit. if the key is exhausted, call
|
||||
* subscribers for ALL nodes below this one.
|
||||
*/
|
||||
var nextNodes: Iterator[(String, SubscriptionNode)] = null
|
||||
key match {
|
||||
case Nil => nextNodes = map.elements
|
||||
case segment :: _ => {
|
||||
map.get(segment) match {
|
||||
case None => return // done!
|
||||
case Some(node) => nextNodes = Iterator.single((segment, node))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((segment, node) <- nextNodes) {
|
||||
val subCurrent = current match {
|
||||
case None => None
|
||||
case Some(x) => x.getConfigMap(segment)
|
||||
}
|
||||
val subReplacement = replacement match {
|
||||
case None => None
|
||||
case Some(x) => x.getConfigMap(segment)
|
||||
}
|
||||
node.validate(if (key == Nil) Nil else key.tail, subCurrent, subReplacement, phase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An attribute map of key/value pairs and subscriptions, where values may
|
||||
* be other attribute maps. Config objects represent the "root" of a nested
|
||||
* set of attribute maps, and control the flow of subscriptions and events
|
||||
* for subscribers.
|
||||
*/
|
||||
class Config extends ConfigMap {
|
||||
private var root = new Attributes(this, "")
|
||||
private val subscribers = new SubscriptionNode
|
||||
private val subscriberKeys = new mutable.HashMap[Int, (SubscriptionNode, Subscriber)]
|
||||
private var nextKey = 1
|
||||
|
||||
private var jmxNodes: List[String] = Nil
|
||||
private var jmxPackageName: String = ""
|
||||
private var jmxSubscriptionKey: Option[SubscriptionKey] = None
|
||||
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
|
||||
|
||||
if (root.isMonitored) {
|
||||
// throws exception if validation fails:
|
||||
List(VALIDATE_PHASE, COMMIT_PHASE) foreach (p => subscribers.validate(Nil, Some(root), Some(newRoot), p))
|
||||
}
|
||||
|
||||
if (root.isMonitored) newRoot.setMonitored
|
||||
root.replaceWith(newRoot)
|
||||
}
|
||||
|
||||
override def toString = root.toString
|
||||
|
||||
|
||||
// ----- subscriptions
|
||||
|
||||
private[configgy] def subscribe(key: String, subscriber: Subscriber): SubscriptionKey = synchronized {
|
||||
root.setMonitored
|
||||
var subkey = nextKey
|
||||
nextKey += 1
|
||||
var node = subscribers
|
||||
if (key ne null) {
|
||||
for (segment <- key.split("\\.")) {
|
||||
node = node.get(segment)
|
||||
}
|
||||
}
|
||||
node.subscribers += subscriber
|
||||
subscriberKeys += Pair(subkey, (node, subscriber))
|
||||
new SubscriptionKey(this, subkey)
|
||||
}
|
||||
|
||||
private[configgy] def subscribe(key: String)(f: (Option[ConfigMap]) => Unit): SubscriptionKey = {
|
||||
subscribe(key, new Subscriber {
|
||||
def validate(current: Option[ConfigMap], replacement: Option[ConfigMap]): Unit = { }
|
||||
def commit(current: Option[ConfigMap], replacement: Option[ConfigMap]): Unit = {
|
||||
f(replacement)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def subscribe(subscriber: Subscriber) = subscribe(null.asInstanceOf[String], subscriber)
|
||||
|
||||
override def subscribe(f: (Option[ConfigMap]) => Unit): SubscriptionKey = subscribe(null.asInstanceOf[String])(f)
|
||||
|
||||
private[configgy] def unsubscribe(subkey: SubscriptionKey) = synchronized {
|
||||
subscriberKeys.get(subkey.id) match {
|
||||
case None => false
|
||||
case Some((node, sub)) => {
|
||||
node.subscribers -= sub
|
||||
subscriberKeys -= subkey.id
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a formatted string of all the subscribers, useful for debugging.
|
||||
*/
|
||||
def debugSubscribers() = synchronized {
|
||||
"subs=" + subscribers.toString
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-register this object from JMX. Any existing JMX nodes for this config object will vanish.
|
||||
*/
|
||||
def unregisterWithJmx() = {
|
||||
val mbs = ManagementFactory.getPlatformMBeanServer()
|
||||
for (name <- jmxNodes) mbs.unregisterMBean(new jmx.ObjectName(name))
|
||||
jmxNodes = Nil
|
||||
for (key <- jmxSubscriptionKey) unsubscribe(key)
|
||||
jmxSubscriptionKey = None
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this object as a tree of JMX nodes that can be used to view and modify the config.
|
||||
* This has the effect of subscribing to the root node, in order to reflect changes to the
|
||||
* config object in JMX.
|
||||
*
|
||||
* @param packageName the name (usually your app's package name) that config objects should
|
||||
* appear inside
|
||||
*/
|
||||
def registerWithJmx(packageName: String): Unit = {
|
||||
val mbs = ManagementFactory.getPlatformMBeanServer()
|
||||
val nodes = root.getJmxNodes(packageName, "")
|
||||
val nodeNames = nodes.map { case (name, bean) => name }
|
||||
// register any new nodes
|
||||
nodes.filter { name => !(jmxNodes contains name) }.foreach { case (name, bean) =>
|
||||
try {
|
||||
mbs.registerMBean(bean, new jmx.ObjectName(name))
|
||||
} catch {
|
||||
case x: jmx.InstanceAlreadyExistsException =>
|
||||
// happens in unit tests.
|
||||
}
|
||||
}
|
||||
// unregister nodes that vanished
|
||||
(jmxNodes -- nodeNames).foreach { name => mbs.unregisterMBean(new jmx.ObjectName(name)) }
|
||||
|
||||
jmxNodes = nodeNames
|
||||
jmxPackageName = packageName
|
||||
if (jmxSubscriptionKey == None) {
|
||||
jmxSubscriptionKey = Some(subscribe { _ => registerWithJmx(packageName) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----- modifications that happen within monitored Attributes nodes
|
||||
|
||||
@throws(classOf[ValidationException])
|
||||
private def deepChange(name: String, key: String, operation: (ConfigMap, String) => Boolean): Boolean = synchronized {
|
||||
val fullKey = if (name == "") (key) else (name + "." + key)
|
||||
val newRoot = root.copy
|
||||
val keyList = fullKey.split("\\.").toList
|
||||
|
||||
if (! operation(newRoot, fullKey)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// throws exception if validation fails:
|
||||
subscribers.validate(keyList, Some(root), Some(newRoot), VALIDATE_PHASE)
|
||||
subscribers.validate(keyList, Some(root), Some(newRoot), COMMIT_PHASE)
|
||||
|
||||
if (root.isMonitored) newRoot.setMonitored
|
||||
root.replaceWith(newRoot)
|
||||
true
|
||||
}
|
||||
|
||||
private[configgy] def deepSet(name: String, key: String, value: String) = {
|
||||
deepChange(name, key, { (newRoot, fullKey) => newRoot(fullKey) = value; true })
|
||||
}
|
||||
|
||||
private[configgy] def deepSet(name: String, key: String, value: Seq[String]) = {
|
||||
deepChange(name, key, { (newRoot, fullKey) => newRoot(fullKey) = value; true })
|
||||
}
|
||||
|
||||
private[configgy] def deepSet(name: String, key: String, value: ConfigMap) = {
|
||||
deepChange(name, key, { (newRoot, fullKey) => newRoot.setConfigMap(fullKey, value); true })
|
||||
}
|
||||
|
||||
private[configgy] def deepRemove(name: String, key: String): Boolean = {
|
||||
deepChange(name, key, { (newRoot, fullKey) => newRoot.remove(fullKey) })
|
||||
}
|
||||
|
||||
|
||||
// ----- 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 Config {
|
||||
/**
|
||||
* Create a config object from a config file of the given path
|
||||
* and filename. The filename must be relative to the path. The path is
|
||||
* used to resolve filenames given in "include" lines.
|
||||
*/
|
||||
def fromFile(path: String, filename: String): Config = {
|
||||
val config = new Config
|
||||
try {
|
||||
config.loadFile(path, filename)
|
||||
} catch {
|
||||
case e: Throwable =>
|
||||
//Logger.get.critical(e, "Failed to load config file '%s/%s'", path, filename)
|
||||
throw e
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Config 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): Config = {
|
||||
val n = filename.lastIndexOf('/')
|
||||
if (n < 0) {
|
||||
fromFile(new File(".").getCanonicalPath, filename)
|
||||
} else {
|
||||
fromFile(filename.substring(0, n), filename.substring(n + 1))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Config object from the given named resource inside this jar
|
||||
* file, using the system class loader. "include" lines will also operate
|
||||
* on resource paths.
|
||||
*/
|
||||
def fromResource(name: String): Config = {
|
||||
fromResource(name, ClassLoader.getSystemClassLoader)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Config object from a string containing a config file's contents.
|
||||
*/
|
||||
def fromString(data: String): Config = {
|
||||
val config = new Config
|
||||
config.load(data)
|
||||
config
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Config object from the given named resource inside this jar
|
||||
* file, using a specific class loader. "include" lines will also operate
|
||||
* on resource paths.
|
||||
*/
|
||||
def fromResource(name: String, classLoader: ClassLoader): Config = {
|
||||
val config = new Config
|
||||
try {
|
||||
config.importer = new ResourceImporter(classLoader)
|
||||
config.loadFile(name)
|
||||
} catch {
|
||||
case e: Throwable =>
|
||||
//Logger.get.critical(e, "Failed to load config resource '%s'", name)
|
||||
throw e
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Config object from a map of String keys and String values.
|
||||
*/
|
||||
def fromMap(m: Map[String, String]) = {
|
||||
val config = new Config
|
||||
for ((k, v) <- m.elements) {
|
||||
config(k) = v
|
||||
}
|
||||
config
|
||||
}
|
||||
}
|
||||
402
akka-actor/src/main/scala/akka/configgy/ConfigMap.scala
Normal file
402
akka-actor/src/main/scala/akka/configgy/ConfigMap.scala
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
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]
|
||||
|
||||
/**
|
||||
* Subscribe to changes on this map. Any changes (including deletions)
|
||||
* that occur on this node will be sent through the subscriber to
|
||||
* validate and possibly commit. See {@link Subscriber} for details
|
||||
* on the validate/commit process.
|
||||
*
|
||||
* @return a key which can be used to cancel the subscription
|
||||
*/
|
||||
def subscribe(subscriber: Subscriber): SubscriptionKey
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to changes on this ConfigMap, but don't bother with
|
||||
* validating. Whenever this ConfigMap changes, a new copy will be
|
||||
* passed to the given function.
|
||||
*/
|
||||
def subscribe(f: (Option[ConfigMap]) => Unit): SubscriptionKey = {
|
||||
subscribe(new Subscriber {
|
||||
def validate(current: Option[ConfigMap], replacement: Option[ConfigMap]): Unit = { }
|
||||
def commit(current: Option[ConfigMap], replacement: Option[ConfigMap]): Unit = {
|
||||
f(replacement)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this ConfigMap into a string which could be written into a config
|
||||
* file and parsed by configgy.
|
||||
*/
|
||||
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
|
||||
}
|
||||
132
akka-actor/src/main/scala/akka/configgy/ConfigParser.scala
Normal file
132
akka-actor/src/main/scala/akka/configgy/ConfigParser.scala
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
import scala.collection.mutable.Stack
|
||||
import scala.util.parsing.combinator._
|
||||
import scala.util.parsing.input.CharSequenceReader
|
||||
import extensions._
|
||||
|
||||
|
||||
/**
|
||||
* An exception thrown when parsing a config file, if there was an error
|
||||
* 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)
|
||||
}
|
||||
|
||||
|
||||
private[configgy] class ConfigParser(var attr: Attributes, val importer: Importer) extends RegexParsers {
|
||||
|
||||
val sections = new Stack[String]
|
||||
var prefix = ""
|
||||
|
||||
// Stack reversed iteration order from 2.7 to 2.8!!
|
||||
def sectionsString = sections.toList.reverse.mkString(".")
|
||||
|
||||
// tokens
|
||||
override val whiteSpace = """(\s+|#[^\n]*\n)+""".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 identToken: Parser[String] = """([\da-zA-Z_][-\w]*)(\.[a-zA-Z_][-\w]*)*""".r
|
||||
val assignToken: Parser[String] = """=|\?=""".r
|
||||
val tagNameToken: Parser[String] = """[a-zA-Z][-\w]*""".r
|
||||
|
||||
|
||||
def root = rep(includeFile | includeOptFile | assignment | toggle | sectionOpen | sectionClose |
|
||||
sectionOpenBrace | sectionCloseBrace)
|
||||
|
||||
def includeFile = "include" ~> string ^^ {
|
||||
case filename: String =>
|
||||
new ConfigParser(attr.makeAttributes(sectionsString), 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 ^^ {
|
||||
case k ~ a ~ v => if (a match {
|
||||
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 = "<" ~> 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
|
||||
prefix = sectionsString + "."
|
||||
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]) = {
|
||||
if (sections.isEmpty) {
|
||||
failure("dangling close tag")
|
||||
} else {
|
||||
val last = sections.pop
|
||||
if (name.isDefined && last != name.get) {
|
||||
failure("got closing tag for " + name.get + ", expected " + last)
|
||||
} else {
|
||||
prefix = if (sections.isEmpty) "" else sectionsString + "."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
case Success(result, _) => result
|
||||
case x @ Failure(msg, z) => throw new ParseException(x.toString)
|
||||
case x @ Error(msg, _) => throw new ParseException(x.toString)
|
||||
}
|
||||
}
|
||||
}
|
||||
98
akka-actor/src/main/scala/akka/configgy/Configgy.scala
Normal file
98
akka-actor/src/main/scala/akka/configgy/Configgy.scala
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
import java.io.File
|
||||
|
||||
|
||||
/**
|
||||
* Main API entry point into the configgy library.
|
||||
*/
|
||||
object Configgy {
|
||||
private var _config: Config = null
|
||||
|
||||
/**
|
||||
* The base Config object for this server. This will only be defined
|
||||
* after calling one of `configure` or `configureFromResource`.
|
||||
*/
|
||||
def config = _config
|
||||
|
||||
/**
|
||||
* Sets the base Config object for this server. You might want to
|
||||
* call one of the configure methods instead of this, but if those
|
||||
* don't work for your needs, use this as a fallback.
|
||||
*/
|
||||
def config_=(c: Config) {
|
||||
_config = c
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the server by loading a config file from the given path
|
||||
* and filename. The filename must be relative to the path. The path is
|
||||
* used to resolve filenames given in "include" lines.
|
||||
*/
|
||||
def configure(path: String, filename: String): Unit = {
|
||||
config = Config.fromFile(path, filename)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the server by loading a config file from 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 configure(filename: String): Unit = {
|
||||
val n = filename.lastIndexOf('/')
|
||||
if (n < 0) {
|
||||
configure(new File(".").getCanonicalPath, filename)
|
||||
} else {
|
||||
configure(filename.substring(0, n), filename.substring(n + 1))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the previously-loaded config file from disk. Any changes will
|
||||
* take effect immediately. **All** subscribers will be called to
|
||||
* verify and commit the change (even if their nodes didn't actually
|
||||
* change).
|
||||
*/
|
||||
def reload() = _config.reload()
|
||||
|
||||
/**
|
||||
* Configure the server by loading a config file from the given named
|
||||
* resource inside this jar file. "include" lines will also operate
|
||||
* on resource paths.
|
||||
*/
|
||||
def configureFromResource(name: String) = {
|
||||
config = Config.fromResource(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the server by loading a config file from the given named
|
||||
* resource inside this jar file, using a specific class loader.
|
||||
* "include" lines will also operate on resource paths.
|
||||
*/
|
||||
def configureFromResource(name: String, classLoader: ClassLoader) = {
|
||||
config = Config.fromResource(name, classLoader)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the server by loading config data from given string.
|
||||
*/
|
||||
def configureFromString(data: String) = {
|
||||
config = Config.fromString(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
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[configgy] 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 subscribe(subscriber: Subscriber): SubscriptionKey = 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
|
||||
}
|
||||
}
|
||||
117
akka-actor/src/main/scala/akka/configgy/Importer.scala
Normal file
117
akka-actor/src/main/scala/akka/configgy/Importer.scala
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
import java.io.{BufferedReader, File, FileInputStream, InputStream, InputStreamReader}
|
||||
|
||||
|
||||
/**
|
||||
* An interface for finding config files and reading them into strings for
|
||||
* parsing. This is used to handle `include` directives in config files.
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
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 {
|
||||
in.close()
|
||||
} catch {
|
||||
case _ =>
|
||||
}
|
||||
out.toString
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An Importer that looks for imported config files in the filesystem.
|
||||
* This is the default importer.
|
||||
*/
|
||||
class FilesystemImporter(val baseFolder: String) extends Importer {
|
||||
def importFile(filename: String, required: Boolean): String = {
|
||||
var f = new File(filename)
|
||||
if (! f.isAbsolute) {
|
||||
f = new File(baseFolder, filename)
|
||||
}
|
||||
if (!required && !f.exists) {
|
||||
""
|
||||
} else {
|
||||
try {
|
||||
streamToString(new FileInputStream(f))
|
||||
} catch {
|
||||
case x => throw new ParseException(x.toString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An Importer that looks for imported config files in the java resources
|
||||
* of the system class loader (usually the jar used to launch this app).
|
||||
*/
|
||||
class ResourceImporter(classLoader: ClassLoader) extends Importer {
|
||||
def importFile(filename: String, required: Boolean): String = {
|
||||
try {
|
||||
val stream = classLoader.getResourceAsStream(filename)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
116
akka-actor/src/main/scala/akka/configgy/JmxWrapper.scala
Normal file
116
akka-actor/src/main/scala/akka/configgy/JmxWrapper.scala
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
import javax.{management => jmx}
|
||||
import scala.collection.JavaConversions
|
||||
|
||||
class JmxWrapper(node: Attributes) extends jmx.DynamicMBean {
|
||||
|
||||
//private val log = Logger.get
|
||||
|
||||
val operations: Array[jmx.MBeanOperationInfo] = Array(
|
||||
new jmx.MBeanOperationInfo("set", "set a string value",
|
||||
Array(
|
||||
new jmx.MBeanParameterInfo("key", "java.lang.String", "config key"),
|
||||
new jmx.MBeanParameterInfo("value", "java.lang.String", "string value")
|
||||
), "void", jmx.MBeanOperationInfo.ACTION),
|
||||
new jmx.MBeanOperationInfo("remove", "remove a value",
|
||||
Array(
|
||||
new jmx.MBeanParameterInfo("key", "java.lang.String", "config key")
|
||||
), "void", jmx.MBeanOperationInfo.ACTION),
|
||||
new jmx.MBeanOperationInfo("add_list", "append a value to a list",
|
||||
Array(
|
||||
new jmx.MBeanParameterInfo("key", "java.lang.String", "config key"),
|
||||
new jmx.MBeanParameterInfo("value", "java.lang.String", "value")
|
||||
), "void", jmx.MBeanOperationInfo.ACTION),
|
||||
new jmx.MBeanOperationInfo("remove_list", "remove a value to a list",
|
||||
Array(
|
||||
new jmx.MBeanParameterInfo("key", "java.lang.String", "config key"),
|
||||
new jmx.MBeanParameterInfo("value", "java.lang.String", "value")
|
||||
), "void", jmx.MBeanOperationInfo.ACTION)
|
||||
)
|
||||
|
||||
def getMBeanInfo() = {
|
||||
new jmx.MBeanInfo("akka.configgy.ConfigMap", "configuration node", node.asJmxAttributes(),
|
||||
null, operations, null, new jmx.ImmutableDescriptor("immutableInfo=false"))
|
||||
}
|
||||
|
||||
def getAttribute(name: String): AnyRef = node.asJmxDisplay(name)
|
||||
|
||||
def getAttributes(names: Array[String]): jmx.AttributeList = {
|
||||
val rv = new jmx.AttributeList
|
||||
for (name <- names) rv.add(new jmx.Attribute(name, getAttribute(name)))
|
||||
rv
|
||||
}
|
||||
|
||||
def invoke(actionName: String, params: Array[Object], signature: Array[String]): AnyRef = {
|
||||
actionName match {
|
||||
case "set" =>
|
||||
params match {
|
||||
case Array(name: String, value: String) =>
|
||||
try {
|
||||
node.setString(name, value)
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
//log.warning("exception: %s", e.getMessage)
|
||||
throw e
|
||||
}
|
||||
case _ =>
|
||||
throw new jmx.MBeanException(new Exception("bad signature " + params.toList.toString))
|
||||
}
|
||||
case "remove" =>
|
||||
params match {
|
||||
case Array(name: String) =>
|
||||
node.remove(name)
|
||||
case _ =>
|
||||
throw new jmx.MBeanException(new Exception("bad signature " + params.toList.toString))
|
||||
}
|
||||
case "add_list" =>
|
||||
params match {
|
||||
case Array(name: String, value: String) =>
|
||||
node.setList(name, node.getList(name).toList ++ List(value))
|
||||
case _ =>
|
||||
throw new jmx.MBeanException(new Exception("bad signature " + params.toList.toString))
|
||||
}
|
||||
case "remove_list" =>
|
||||
params match {
|
||||
case Array(name: String, value: String) =>
|
||||
node.setList(name, node.getList(name).toList - value)
|
||||
case _ =>
|
||||
throw new jmx.MBeanException(new Exception("bad signature " + params.toList.toString))
|
||||
}
|
||||
case _ =>
|
||||
throw new jmx.MBeanException(new Exception("no such method"))
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
def setAttribute(attr: jmx.Attribute): Unit = {
|
||||
attr.getValue() match {
|
||||
case s: String =>
|
||||
node.setString(attr.getName(), s)
|
||||
case _ =>
|
||||
throw new jmx.InvalidAttributeValueException()
|
||||
}
|
||||
}
|
||||
|
||||
def setAttributes(attrs: jmx.AttributeList): jmx.AttributeList = {
|
||||
for (attr <- JavaConversions.asBuffer(attrs.asList)) setAttribute(attr)
|
||||
attrs
|
||||
}
|
||||
}
|
||||
143
akka-actor/src/main/scala/akka/configgy/RuntimeEnvironment.scala
Normal file
143
akka-actor/src/main/scala/akka/configgy/RuntimeEnvironment.scala
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
import java.io.File
|
||||
import java.util.Properties
|
||||
import scala.collection.mutable
|
||||
import extensions._
|
||||
|
||||
|
||||
/**
|
||||
* Use information in a local `build.properties` file to determine runtime
|
||||
* environment info like the package name, version, and installation path.
|
||||
* This can be used to automatically load config files from a `config/` path
|
||||
* relative to the executable jar.
|
||||
*
|
||||
* An example of how to generate a `build.properties` file is included in
|
||||
* configgy's ant files, and also in the "scala-build" github project here:
|
||||
* <http://github.com/robey/scala-build/tree/master>
|
||||
*
|
||||
* You have to pass in a class from your package in order to identify the
|
||||
* location of the `build.properties` file.
|
||||
*/
|
||||
class RuntimeEnvironment(cls: Class[_]) {
|
||||
// load build info, if present.
|
||||
private var buildProperties = new Properties
|
||||
try {
|
||||
buildProperties.load(cls.getResource("build.properties").openStream)
|
||||
} catch {
|
||||
case _ =>
|
||||
}
|
||||
|
||||
val jarName = buildProperties.getProperty("name", "unknown")
|
||||
val jarVersion = buildProperties.getProperty("version", "0.0")
|
||||
val jarBuild = buildProperties.getProperty("build_name", "unknown")
|
||||
val jarBuildRevision = buildProperties.getProperty("build_revision", "unknown")
|
||||
val stageName = System.getProperty("stage", "production")
|
||||
val savedOverrides = new mutable.HashMap[String, String]
|
||||
|
||||
|
||||
/**
|
||||
* Return the path this jar was executed from. Depends on the presence of
|
||||
* a valid `build.properties` file. Will return `None` if it couldn't
|
||||
* figure out the environment.
|
||||
*/
|
||||
lazy val jarPath: Option[String] = {
|
||||
val paths = System.getProperty("java.class.path").split(System.getProperty("path.separator"))
|
||||
findCandidateJar(paths, jarName, jarVersion).flatMap { path =>
|
||||
val parent = new File(path).getParentFile
|
||||
if (parent == null) None else Some(parent.getCanonicalPath)
|
||||
}
|
||||
}
|
||||
|
||||
def findCandidateJar(paths: Seq[String], name: String, version: String): Option[String] = {
|
||||
val pattern = ("(.*?)" + name + "(?:_[\\d.]+)?-" + version + "\\.jar$").r
|
||||
paths.find { path =>
|
||||
pattern.findFirstIn(path).isDefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Config filename, as determined from this jar's runtime path, possibly
|
||||
* overridden by a command-line option.
|
||||
*/
|
||||
var configFilename: String = jarPath match {
|
||||
case Some(path) => path + "/config/" + stageName + ".conf"
|
||||
case None => "/etc/" + jarName + ".conf"
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform baseline command-line argument parsing. Responds to `--help`,
|
||||
* `--version`, and `-f` (which overrides the config filename).
|
||||
*/
|
||||
def parseArgs(args: List[String]): Unit = {
|
||||
args match {
|
||||
case "-f" :: filename :: xs =>
|
||||
configFilename = filename
|
||||
parseArgs(xs)
|
||||
case "-D" :: keyval :: xs =>
|
||||
keyval.split("=", 2).toList match {
|
||||
case key :: value :: Nil =>
|
||||
savedOverrides(key) = value
|
||||
parseArgs(xs)
|
||||
case _ =>
|
||||
println("Unknown -D option (must be '-D key=value'): " + keyval)
|
||||
help
|
||||
}
|
||||
case "--help" :: xs =>
|
||||
help
|
||||
case "--version" :: xs =>
|
||||
println("%s %s (%s)".format(jarName, jarVersion, jarBuild))
|
||||
case Nil =>
|
||||
case unknown :: _ =>
|
||||
println("Unknown command-line option: " + unknown)
|
||||
help
|
||||
}
|
||||
}
|
||||
|
||||
private def help = {
|
||||
println
|
||||
println("%s %s (%s)".format(jarName, jarVersion, jarBuild))
|
||||
println("options:")
|
||||
println(" -f <filename>")
|
||||
println(" load config file (default: %s)".format(configFilename))
|
||||
println
|
||||
System.exit(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse any command-line arguments (using `parseArgs`) and then load the
|
||||
* config file as determined by `configFilename` into the default config
|
||||
* block.
|
||||
*/
|
||||
def load(args: Array[String]) = {
|
||||
savedOverrides.clear()
|
||||
val choppedArgs = args.flatMap { arg =>
|
||||
if (arg.length > 2 && arg.startsWith("-D")) {
|
||||
List("-D", arg.substring(2))
|
||||
} else {
|
||||
List(arg)
|
||||
}
|
||||
}
|
||||
parseArgs(choppedArgs.toList)
|
||||
Configgy.configure(configFilename)
|
||||
for ((key, value) <- savedOverrides) {
|
||||
Configgy.config(key) = value
|
||||
}
|
||||
}
|
||||
}
|
||||
84
akka-actor/src/main/scala/akka/configgy/Subscriber.scala
Normal file
84
akka-actor/src/main/scala/akka/configgy/Subscriber.scala
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
|
||||
/**
|
||||
* Interface for receiving notifications of changes to a {@link Config}
|
||||
* object. When subscribed to an {@link AttributeMap} node, these methods
|
||||
* will be called to validate, and optionally commit, any changes that would
|
||||
* affect that node.
|
||||
*
|
||||
* <p> Changes happen in two phases: First, any affected subscribers are asked
|
||||
* to validate the change by having their {@link #validate} method called.
|
||||
* If any <code>validate</code> method throws a <code>ValidationException</code>,
|
||||
* the change is aborted, and the exception is thrown to the code that tried
|
||||
* to make that change. If all of the <code>validate</code> methods return
|
||||
* successfully, the {@link #commit} methods will be called to confirm that
|
||||
* the change has been validated and is being committed.
|
||||
*/
|
||||
trait Subscriber {
|
||||
/**
|
||||
* Validate a potential change to the subscribed config node. It the node
|
||||
* didn't exist prior to this potential change, <code>current</code> will
|
||||
* be <code>None</code>. Similarly, if the node is being removed by this
|
||||
* change, <code>replacement</code> will be <code>None</code>. Never will
|
||||
* both parameters be <code>None</code>.
|
||||
*
|
||||
* <p> To reject the change, throw <code>ValidationException</code>. A
|
||||
* normal return validates the config change and potentially permits it to
|
||||
* be committed.
|
||||
*
|
||||
* @param current the current config node, if it exists
|
||||
* @param replacement the new config node, if it will exist
|
||||
*/
|
||||
@throws(classOf[ValidationException])
|
||||
def validate(current: Option[ConfigMap], replacement: Option[ConfigMap]): Unit
|
||||
|
||||
/**
|
||||
* Commit this change to the subscribed config node. If this method is
|
||||
* called, a prior call to <code>validate</code> with these parameters
|
||||
* succeeded for all subscribers, and the change is now active. As with
|
||||
* <code>validate</code>, either <code>current</code> or
|
||||
* <code>replacement</code> (but not both) may be <code>None</code>.
|
||||
*
|
||||
* @param current the current (now previous) config node, if it existed
|
||||
* @param replacement the new (now current) config node, if it exists
|
||||
*/
|
||||
def commit(current: Option[ConfigMap], replacement: Option[ConfigMap]): Unit
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Key returned by a call to <code>AttributeMap.subscribe</code> which may
|
||||
* be used to unsubscribe from config change events.
|
||||
*/
|
||||
class SubscriptionKey private[configgy](val config: Config, private[configgy] val id: Int) {
|
||||
/**
|
||||
* Remove the subscription referenced by this key. After unsubscribing,
|
||||
* no more validate/commit events will be sent to this subscriber.
|
||||
*/
|
||||
def unsubscribe() = config.unsubscribe(this)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Exception thrown by <code>Subscriber.validate</code> when a config change
|
||||
* must be rejected. If returned by a modification to a {@link Config} or
|
||||
* {@link AttributeMap} node, the modification has failed.
|
||||
*/
|
||||
class ValidationException(reason: String) extends Exception(reason)
|
||||
156
akka-actor/src/main/scala/akka/configgy/extensions.scala
Normal file
156
akka-actor/src/main/scala/akka/configgy/extensions.scala
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright 2009 Robey Pointer <robeypointer@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package akka.configgy
|
||||
|
||||
import scala.util.matching.Regex
|
||||
|
||||
|
||||
final class ConfiggyString(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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a string of hex digits into a byte array. This does the exact
|
||||
* opposite of `Array[Byte]#hexlify`.
|
||||
*/
|
||||
def unhexlify(): Array[Byte] = {
|
||||
val buffer = new Array[Byte](wrapped.length / 2)
|
||||
for (i <- 0.until(wrapped.length, 2)) {
|
||||
buffer(i/2) = Integer.parseInt(wrapped.substring(i, i+2), 16).toByte
|
||||
}
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class ConfiggyByteArray(wrapped: Array[Byte]) {
|
||||
/**
|
||||
* Turn an Array[Byte] into a string of hex digits.
|
||||
*/
|
||||
def hexlify(): String = {
|
||||
val out = new StringBuffer
|
||||
for (b <- wrapped) {
|
||||
val s = (b.toInt & 0xff).toHexString
|
||||
if (s.length < 2) {
|
||||
out append '0'
|
||||
}
|
||||
out append s
|
||||
}
|
||||
out.toString
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object extensions {
|
||||
implicit def stringToConfiggyString(s: String): ConfiggyString = new ConfiggyString(s)
|
||||
implicit def byteArrayToConfiggyByteArray(b: Array[Byte]): ConfiggyByteArray = new ConfiggyByteArray(b)
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import akka.actor.newUuid
|
|||
import akka.config.Config._
|
||||
import akka.util.{Duration}
|
||||
|
||||
import net.lag.configgy.ConfigMap
|
||||
import akka.configgy.ConfigMap
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor.{AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy}
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import java.util.concurrent.{CountDownLatch, TimeUnit}
|
|||
import org.scalatest.junit.JUnitSuite
|
||||
import org.junit.Test
|
||||
|
||||
import net.lag.configgy.Config
|
||||
import akka.configgy.Config
|
||||
import scala.reflect.{Manifest}
|
||||
import akka.dispatch._
|
||||
|
||||
|
|
|
|||
|
|
@ -133,8 +133,6 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) {
|
|||
|
||||
lazy val commons_io = "commons-io" % "commons-io" % "2.0.1" % "compile" //ApacheV2
|
||||
|
||||
lazy val configgy = "net.lag" % "configgy" % "2.0.2-nologgy" % "compile" //ApacheV2
|
||||
|
||||
lazy val javax_servlet_30 = "org.glassfish" % "javax.servlet" % JAVAX_SERVLET_VERSION % "provided" //CDDL v1
|
||||
|
||||
lazy val jetty = "org.eclipse.jetty" % "jetty-server" % JETTY_VERSION % "compile" //Eclipse license
|
||||
|
|
@ -297,8 +295,6 @@ class AkkaParentProject(info: ProjectInfo) extends DefaultProject(info) {
|
|||
// -------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
class AkkaActorProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) {
|
||||
val configgy = Dependencies.configgy
|
||||
|
||||
// testing
|
||||
val junit = Dependencies.junit
|
||||
val scalatest = Dependencies.scalatest
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue